From 3f3a2e4eafaa346dec37d25bf079e74ac2e0ff5b Mon Sep 17 00:00:00 2001 From: onalante-msft <89409054+onalante-msft@users.noreply.github.com> Date: Thu, 6 Jan 2022 18:17:14 -0800 Subject: [PATCH 01/22] Switch from pin-project to pin-project-lite --- Cargo.toml | 2 +- src/client.rs | 13 +++++++------ src/server.rs | 13 +++++++------ 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a96a7f1..3d270c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ edition = "2018" hex = "0.4" hyper = { version = "0.14", features = ["server", "client", "http1", "runtime"] } tokio = { version = "1.0", features = ["rt-multi-thread", "net"] } -pin-project = "1.0" +pin-project-lite = "0.2" futures-util = "0.3" [dev-dependencies] diff --git a/src/client.rs b/src/client.rs index a54e762..40eca0d 100644 --- a/src/client.rs +++ b/src/client.rs @@ -5,7 +5,7 @@ use hyper::{ service::Service, Body, Client, Uri, }; -use pin_project::pin_project; +use pin_project_lite::pin_project; use std::{ io, path::{Path, PathBuf}, @@ -14,11 +14,12 @@ use std::{ }; use tokio::io::ReadBuf; -#[pin_project] -#[derive(Debug)] -pub struct UnixStream { - #[pin] - unix_stream: tokio::net::UnixStream, +pin_project! { + #[derive(Debug)] + pub struct UnixStream { + #[pin] + unix_stream: tokio::net::UnixStream, + } } impl UnixStream { diff --git a/src/server.rs b/src/server.rs index 0bbc549..f5b9324 100644 --- a/src/server.rs +++ b/src/server.rs @@ -7,7 +7,7 @@ use conn::SocketIncoming; pub(crate) mod conn { use futures_util::ready; use hyper::server::accept::Accept; - use pin_project::pin_project; + use pin_project_lite::pin_project; use std::{ io, path::Path, @@ -16,11 +16,12 @@ pub(crate) mod conn { }; use tokio::net::{UnixListener, UnixStream}; - /// A stream of connections from binding to a socket. - #[pin_project] - #[derive(Debug)] - pub struct SocketIncoming { - listener: UnixListener, + pin_project! { + /// A stream of connections from binding to a socket. + #[derive(Debug)] + pub struct SocketIncoming { + listener: UnixListener, + } } impl SocketIncoming { From f6b4ccd10d0c12457693f3b4ec41f37834af8a89 Mon Sep 17 00:00:00 2001 From: onalante-msft <89409054+onalante-msft@users.noreply.github.com> Date: Thu, 6 Jan 2022 18:18:19 -0800 Subject: [PATCH 02/22] Match hyperlocal features with hyper features --- Cargo.toml | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3d270c5..7a62b56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,16 +11,34 @@ readme = "README.md" edition = "2018" [dependencies] +futures-util = "0.3" hex = "0.4" -hyper = { version = "0.14", features = ["server", "client", "http1", "runtime"] } -tokio = { version = "1.0", features = ["rt-multi-thread", "net"] } +hyper = "0.14" +tokio = { version = "1.0", features = ["net"] } pin-project-lite = "0.2" -futures-util = "0.3" [dev-dependencies] -tokio = { version = "1.0", features = ["rt-multi-thread", "net", "macros", "io-std", "io-util"] } +tokio = { version = "1.0", features = ["io-std", "io-util", "macros", "rt-multi-thread"] } [features] -client = [] -server = [] -default = ["client", "server"] +default = [] +client = [ + "hyper/client", + "hyper/http1", +] +server = [ + "hyper/http1", + "hyper/server" +] + +[[example]] +name = "client" +required-features = ["client", "hyper/runtime"] + +[[example]] +name = "server" +required-features = ["server", "hyper/runtime"] + +[[test]] +name = "server_client" +required-features = ["client", "server", "hyper/runtime"] From 4618d6e26b8ab5017e03f6bf2086925fe11e13e2 Mon Sep 17 00:00:00 2001 From: onalante-msft <89409054+onalante-msft@users.noreply.github.com> Date: Thu, 6 Jan 2022 18:29:02 -0800 Subject: [PATCH 03/22] Remove unused futures-util features Cf. softprops/hyperlocal#52. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7a62b56..2dd260b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ readme = "README.md" edition = "2018" [dependencies] -futures-util = "0.3" +futures-util = { version = "0.3", default-features = false, features = ["alloc"] } hex = "0.4" hyper = "0.14" tokio = { version = "1.0", features = ["net"] } From c95ce37e81f1919b57ee47755732475577598d1f Mon Sep 17 00:00:00 2001 From: onalante-msft <89409054+onalante-msft@users.noreply.github.com> Date: Thu, 6 Jan 2022 18:43:17 -0800 Subject: [PATCH 04/22] Remove futures-util dependency --- Cargo.toml | 1 - src/client.rs | 4 ++-- src/server.rs | 5 ++--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2dd260b..fc2c070 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,6 @@ readme = "README.md" edition = "2018" [dependencies] -futures-util = { version = "0.3", default-features = false, features = ["alloc"] } hex = "0.4" hyper = "0.14" tokio = { version = "1.0", features = ["net"] } diff --git a/src/client.rs b/src/client.rs index 40eca0d..796cbb8 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,4 +1,3 @@ -use futures_util::future::BoxFuture; use hex::FromHex; use hyper::{ client::connect::{Connected, Connection}, @@ -8,6 +7,7 @@ use hyper::{ use pin_project_lite::pin_project; use std::{ io, + future::Future, path::{Path, PathBuf}, pin::Pin, task::{Context, Poll}, @@ -81,7 +81,7 @@ impl Unpin for UnixConnector {} impl Service for UnixConnector { type Response = UnixStream; type Error = std::io::Error; - type Future = BoxFuture<'static, Result>; + type Future = Pin> + Send + 'static>>; fn call(&mut self, req: Uri) -> Self::Future { let fut = async move { let path = parse_socket_path(req)?; diff --git a/src/server.rs b/src/server.rs index f5b9324..6c9630a 100644 --- a/src/server.rs +++ b/src/server.rs @@ -5,7 +5,6 @@ use hyper::server::{Builder, Server}; use conn::SocketIncoming; pub(crate) mod conn { - use futures_util::ready; use hyper::server::accept::Accept; use pin_project_lite::pin_project; use std::{ @@ -51,8 +50,8 @@ pub(crate) mod conn { self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll>> { - let conn = ready!(self.listener.poll_accept(cx))?.0; - Poll::Ready(Some(Ok(conn))) + self.listener.poll_accept(cx)? + .map(|(conn, _)| Some(Ok(conn))) } } From f185a485f7c7a160f859ce990c2b1b63ec9346c3 Mon Sep 17 00:00:00 2001 From: onalante-msft <89409054+onalante-msft@users.noreply.github.com> Date: Sun, 9 Jan 2022 18:54:54 -0800 Subject: [PATCH 05/22] Add and fix clippy lints and formatting errors --- src/client.rs | 22 +++++++++++++--------- src/lib.rs | 1 + src/server.rs | 7 ++++++- src/uri.rs | 3 +++ 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/client.rs b/src/client.rs index 796cbb8..fe96edb 100644 --- a/src/client.rs +++ b/src/client.rs @@ -6,8 +6,8 @@ use hyper::{ }; use pin_project_lite::pin_project; use std::{ - io, future::Future, + io, path::{Path, PathBuf}, pin::Pin, task::{Context, Poll}, @@ -23,10 +23,7 @@ pin_project! { } impl UnixStream { - async fn connect

(path: P) -> std::io::Result - where - P: AsRef, - { + async fn connect(path: impl AsRef) -> io::Result { let unix_stream = tokio::net::UnixStream::connect(path).await?; Ok(Self { unix_stream }) } @@ -40,9 +37,11 @@ impl tokio::io::AsyncWrite for UnixStream { ) -> Poll> { self.project().unix_stream.poll_write(cx, buf) } + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().unix_stream.poll_flush(cx) } + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().unix_stream.poll_shutdown(cx) } @@ -80,16 +79,20 @@ impl Unpin for UnixConnector {} impl Service for UnixConnector { type Response = UnixStream; - type Error = std::io::Error; - type Future = Pin> + Send + 'static>>; + type Error = io::Error; + #[allow(clippy::type_complexity)] + type Future = + Pin> + Send + 'static>>; + fn call(&mut self, req: Uri) -> Self::Future { let fut = async move { - let path = parse_socket_path(req)?; + let path = parse_socket_path(&req)?; UnixStream::connect(path).await }; Box::pin(fut) } + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } @@ -101,7 +104,7 @@ impl Connection for UnixStream { } } -fn parse_socket_path(uri: Uri) -> Result { +fn parse_socket_path(uri: &Uri) -> Result { if uri.scheme_str() != Some("unix") { return Err(io::Error::new( io::ErrorKind::InvalidInput, @@ -138,6 +141,7 @@ pub trait UnixClientExt { /// /// let client = Client::unix(); /// ``` + #[must_use] fn unix() -> Client { Client::builder().build(UnixConnector) } diff --git a/src/lib.rs b/src/lib.rs index 48c41b4..0a64652 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ rust_2018_idioms, missing_docs )] +#![warn(clippy::all, clippy::pedantic)] //! `hyperlocal` provides [Hyper](http://github.com/hyperium/hyper) bindings //! for [Unix domain sockets](https://github.com/tokio-rs/tokio/tree/master/tokio-net/src/uds/). diff --git a/src/server.rs b/src/server.rs index 6c9630a..c477e17 100644 --- a/src/server.rs +++ b/src/server.rs @@ -25,6 +25,9 @@ pub(crate) mod conn { impl SocketIncoming { /// Creates a new `SocketIncoming` binding to provided socket path. + /// + /// # Errors + /// Refer to [`tokio::net::Listener::bind`](https://docs.rs/tokio/1.15.0/tokio/net/struct.UnixListener.html#method.bind). pub fn bind(path: impl AsRef) -> Result { let listener = UnixListener::bind(path)?; @@ -50,7 +53,8 @@ pub(crate) mod conn { self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll>> { - self.listener.poll_accept(cx)? + self.listener + .poll_accept(cx)? .map(|(conn, _)| Some(Ok(conn))) } } @@ -84,6 +88,7 @@ pub(crate) mod conn { /// ``` pub trait UnixServerExt { /// Convenience method for constructing a Server listening on a Unix socket. + #[allow(clippy::missing_errors_doc)] fn bind_unix(path: impl AsRef) -> Result, io::Error>; } diff --git a/src/uri.rs b/src/uri.rs index 51cc9f8..23fb167 100644 --- a/src/uri.rs +++ b/src/uri.rs @@ -19,6 +19,9 @@ pub struct Uri { impl Uri { /// Create a new `[Uri]` from a socket address and a path + /// + /// # Panics + /// Will panic if path is not absolute and/or a malformed path string. pub fn new(socket: impl AsRef, path: &str) -> Self { let host = hex::encode(socket.as_ref().to_string_lossy().as_bytes()); let host_str = format!("unix://{}:0{}", host, path); From b29beff5a3e2aa6346893343a6c2fb4bf06ed1ca Mon Sep 17 00:00:00 2001 From: onalante-msft <89409054+onalante-msft@users.noreply.github.com> Date: Sun, 9 Jan 2022 19:09:24 -0800 Subject: [PATCH 06/22] Update GitHub workflows to use all features --- .github/workflows/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b8344cb..726ec61 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -85,7 +85,7 @@ jobs: with: rust-version: ${{ matrix.rust }} - name: Test - run: cargo test + run: cargo test --all-features publish-docs: if: github.ref == 'refs/heads/main' @@ -97,7 +97,7 @@ jobs: - uses: actions/checkout@v2 - name: Generate Docs run: | - cargo doc --no-deps + cargo doc --no-deps --all-features echo "" > target/doc/index.html - name: Publish uses: peaceiris/actions-gh-pages@v3 @@ -118,4 +118,4 @@ jobs: shell: bash run: cargo publish --token ${{ env.CRATES_TOKEN }} env: - CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }} \ No newline at end of file + CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }} From b91ded316893c8d566985a38fd27173e75c0d511 Mon Sep 17 00:00:00 2001 From: Fletcher Nichol Date: Wed, 20 Oct 2021 21:54:59 -0600 Subject: [PATCH 07/22] export `UnixStream` for clients Resolves #50 Signed-off-by: Fletcher Nichol --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 0a64652..b7aae49 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,7 @@ #[cfg(feature = "client")] mod client; #[cfg(feature = "client")] -pub use client::{UnixClientExt, UnixConnector}; +pub use client::{UnixClientExt, UnixConnector, UnixStream}; #[cfg(feature = "server")] mod server; From cee7961ad7441478d0f5c98b7045e8acff0d7210 Mon Sep 17 00:00:00 2001 From: onalante-msft <89409054+onalante-msft@users.noreply.github.com> Date: Sat, 13 Aug 2022 08:47:42 -0700 Subject: [PATCH 08/22] Add rustdoc for UnixStream --- src/client.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client.rs b/src/client.rs index fe96edb..5c68de4 100644 --- a/src/client.rs +++ b/src/client.rs @@ -15,6 +15,7 @@ use std::{ use tokio::io::ReadBuf; pin_project! { + /// Wrapper around [`tokio::net::UnixStream`]. #[derive(Debug)] pub struct UnixStream { #[pin] From fe34a47be743d33d634a7655842f8b850e47d2c8 Mon Sep 17 00:00:00 2001 From: David Raifaizen Date: Sat, 28 Oct 2023 10:32:00 -0400 Subject: [PATCH 09/22] Adding support for UnixStream to take advantage of vectored writes when applicable. If one checks UnixStream source code, they will see that is_write_vectored is equal to true. --- src/client.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/client.rs b/src/client.rs index fe96edb..f79ba86 100644 --- a/src/client.rs +++ b/src/client.rs @@ -45,6 +45,18 @@ impl tokio::io::AsyncWrite for UnixStream { fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().unix_stream.poll_shutdown(cx) } + + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[IoSlice<'_>], + ) -> Poll> { + self.project().unix_stream.poll_write_vectored(cx) + } + + fn is_write_vectored(&self) -> bool { + self.project().unix_stream.is_write_vectored() + } } impl tokio::io::AsyncRead for UnixStream { From 22f53f9a1133613797d9f02798c851eab90a0867 Mon Sep 17 00:00:00 2001 From: Joshua Potts <8704475+iamjpotts@users.noreply.github.com> Date: Sat, 6 Jan 2024 19:53:29 -0600 Subject: [PATCH 10/22] Upgrade hyper to 1.1 --- CHANGELOG.md | 4 ++ Cargo.toml | 30 ++++++++----- README.md | 67 +++++---------------------- examples/client.rs | 17 ++++--- examples/server.rs | 39 +++++++++++----- src/client.rs | 76 ++++++++++++++++++++++++------- src/lib.rs | 21 +-------- src/server.rs | 100 ----------------------------------------- src/uri.rs | 2 +- tests/server_client.rs | 59 ++++++++++++++---------- 10 files changed, 170 insertions(+), 245 deletions(-) delete mode 100644 src/server.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index cdb58a4..3b8020b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.9.0 + +* upgrade to hyper 1.0 + # 0.8.0 * upgrade to tokio 1.0 and hyper 0.14 [#44](https://github.com/softprops/hyperlocal/pull/44) diff --git a/Cargo.toml b/Cargo.toml index fc2c070..687cd45 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hyperlocal" -version = "0.8.0" +version = "0.9.0-alpha" authors = ["softprops "] description = "Hyper bindings for Unix domain sockets" homepage = "https://github.com/softprops/hyperlocal" @@ -8,36 +8,46 @@ repository = "https://github.com/softprops/hyperlocal" keywords = ["hyper", "unix", "sockets", "http"] license = "MIT" readme = "README.md" -edition = "2018" +edition = "2021" [dependencies] hex = "0.4" -hyper = "0.14" -tokio = { version = "1.0", features = ["net"] } +http-body-util = { version = "0.1", optional = true } +hyper = "1.1" +hyper-util = { version = "0.1.2", optional = true } +tokio = { version = "1.35", default-features = false, features = ["net"] } +tower-service = { version = "0.3", optional = true } pin-project-lite = "0.2" [dev-dependencies] -tokio = { version = "1.0", features = ["io-std", "io-util", "macros", "rt-multi-thread"] } +thiserror = "1.0" +tokio = { version = "1.35", features = ["io-std", "io-util", "macros", "rt-multi-thread"] } [features] -default = [] +default = ["client"] client = [ + "http-body-util", "hyper/client", "hyper/http1", + "hyper-util/client-legacy", + "hyper-util/http1", + "hyper-util/tokio", + "tower-service" ] server = [ "hyper/http1", - "hyper/server" + "hyper/server", + "hyper-util/tokio", ] [[example]] name = "client" -required-features = ["client", "hyper/runtime"] +required-features = ["client"] [[example]] name = "server" -required-features = ["server", "hyper/runtime"] +required-features = ["server"] [[test]] name = "server_client" -required-features = ["client", "server", "hyper/runtime"] +required-features = ["client", "server"] diff --git a/README.md b/README.md index ecbd5b1..f304581 100644 --- a/README.md +++ b/README.md @@ -47,47 +47,20 @@ Add the following to your `Cargo.toml` file ```toml [dependencies] -hyperlocal = "0.8" +hyperlocal = "0.9" ``` ## Usage ### Servers -A typical server can be built with `hyperlocal::server::UnixServerExt`. +A typical server can be built by creating a `tokio::net::UnixListener` and accepting connections in a loop using +`hyper::service::service_fn` to create a request/response processing function, and connecting the `UnixStream` to it +using `hyper::server::conn::http1::Builder::new().serve_connection()`. -```rust -use std::{error::Error, fs, path::Path}; -use hyper::{ - service::{make_service_fn, service_fn}, - Body, Response, Server, -}; -use hyperlocal::UnixServerExt; +An example is at [examples/server.rs](./examples/server.rs). -const PHRASE: &str = "It's a Unix system. I know this."; - -#[tokio::main] -async fn main() -> Result<(), Box> { - let path = Path::new("/tmp/hyperlocal.sock"); - - if path.exists() { - fs::remove_file(path)?; - } - - let make_service = make_service_fn(|_| async { - Ok::<_, hyper::Error>(service_fn(|_req| async { - Ok::<_, hyper::Error>(Response::new(Body::from(PHRASE))) - })) - }); - - Server::bind_unix(path)?.serve(make_service).await?; - - Ok(()) -} - -``` - -To test that your server is working you can use an out of the box tool like `curl` +To test that your server is working you can use an out-of-the-box tool like `curl` ```sh @@ -98,9 +71,10 @@ It's a Unix system. I know this. ### Clients -`hyperlocal` also provides bindings for writing unix domain socket based HTTP clients using `Hyper`'s native `Client` interface. +`hyperlocal` also provides bindings for writing unix domain socket based HTTP clients the `Client` interface from the +`hyper-utils` crate. -Configure your `Hyper` client using `hyper::Client::builder()`. +An example is at [examples/client.rs](./examples/client.rs). Hyper's client interface makes it easy to send typical HTTP methods like `GET`, `POST`, `DELETE` with factory methods, `get`, `post`, `delete`, etc. These require an argument that can be tranformed into a `hyper::Uri`. @@ -109,27 +83,6 @@ Since Unix domain sockets aren't represented with hostnames that resolve to ip a your standard over the counter URL string won't do. Instead, use a `hyperlocal::Uri`, which represents both file path to the domain socket and the resource URI path and query string. -```rust -use std::error::Error; -use hyper::{body::HttpBody, Client}; -use hyperlocal::{UnixClientExt, Uri}; -use tokio::io::{self, AsyncWriteExt as _}; - -#[tokio::main] -async fn main() -> Result<(), Box> { - let url = Uri::new("/tmp/hyperlocal.sock", "/").into(); - - let client = Client::unix(); - - let mut response = client.get(url).await?; - - while let Some(next) = response.data().await { - let chunk = next?; - io::stdout().write_all(&chunk).await?; - } - - Ok(()) -} -``` +--- Doug Tangren (softprops) 2015-2020 diff --git a/examples/client.rs b/examples/client.rs index b429464..701cd1f 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -1,5 +1,7 @@ -use hyper::{body::HttpBody, Client}; -use hyperlocal::{UnixClientExt, Uri}; +use http_body_util::{BodyExt, Full}; +use hyper::body::Bytes; +use hyper_util::client::legacy::Client; +use hyperlocal::{UnixClientExt, UnixConnector, Uri}; use std::error::Error; use tokio::io::{self, AsyncWriteExt as _}; @@ -7,13 +9,16 @@ use tokio::io::{self, AsyncWriteExt as _}; async fn main() -> Result<(), Box> { let url = Uri::new("/tmp/hyperlocal.sock", "/").into(); - let client = Client::unix(); + let client: Client> = Client::unix(); let mut response = client.get(url).await?; - while let Some(next) = response.data().await { - let chunk = next?; - io::stdout().write_all(&chunk).await?; + while let Some(frame_result) = response.frame().await { + let frame = frame_result?; + + if let Some(segment) = frame.data_ref() { + io::stdout().write_all(segment.iter().as_slice()).await?; + } } Ok(()) diff --git a/examples/server.rs b/examples/server.rs index 4792ad8..920df50 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -1,12 +1,11 @@ -use hyper::{ - service::{make_service_fn, service_fn}, - Body, Response, Server, -}; -use hyperlocal::UnixServerExt; +use hyper::{service::service_fn, Response}; +use hyper_util::rt::TokioIo; use std::{error::Error, fs, path::Path}; +use tokio::net::UnixListener; const PHRASE: &str = "It's a Unix system. I know this."; +// Adapted from https://hyper.rs/guides/1/server/hello-world/ #[tokio::main] async fn main() -> Result<(), Box> { let path = Path::new("/tmp/hyperlocal.sock"); @@ -15,13 +14,29 @@ async fn main() -> Result<(), Box> { fs::remove_file(path)?; } - let make_service = make_service_fn(|_| async { - Ok::<_, hyper::Error>(service_fn(|_req| async { - Ok::<_, hyper::Error>(Response::new(Body::from(PHRASE))) - })) - }); + let listener = UnixListener::bind(path)?; - Server::bind_unix(path)?.serve(make_service).await?; + loop { + let (stream, _) = listener.accept().await?; + let io = TokioIo::new(stream); - Ok(()) + tokio::task::spawn(async move { + let svc_fn = service_fn(|_req| async { + let body = PHRASE.to_string(); + Ok::<_, hyper::Error>(Response::new(body)) + }); + + match hyper::server::conn::http1::Builder::new() + .serve_connection(io, svc_fn) + .await + { + Ok(()) => { + println!("Accepted connection."); + } + Err(err) => { + eprintln!("Failed to accept connection: {err:?}"); + } + }; + }); + } } diff --git a/src/client.rs b/src/client.rs index fe96edb..b99f7ce 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,18 +1,23 @@ use hex::FromHex; -use hyper::{ - client::connect::{Connected, Connection}, - service::Service, - Body, Client, Uri, +use hyper::{body::Body, rt::ReadBufCursor, Uri}; +use hyper_util::{ + client::legacy::{ + connect::{Connected, Connection}, + Client, + }, + rt::{TokioExecutor, TokioIo}, }; use pin_project_lite::pin_project; use std::{ future::Future, io, + io::Error, path::{Path, PathBuf}, pin::Pin, task::{Context, Poll}, }; -use tokio::io::ReadBuf; +use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; +use tower_service::Service; pin_project! { #[derive(Debug)] @@ -29,7 +34,7 @@ impl UnixStream { } } -impl tokio::io::AsyncWrite for UnixStream { +impl AsyncWrite for UnixStream { fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -47,7 +52,25 @@ impl tokio::io::AsyncWrite for UnixStream { } } -impl tokio::io::AsyncRead for UnixStream { +impl hyper::rt::Write for UnixStream { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + self.project().unix_stream.poll_write(cx, buf) + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.project().unix_stream.poll_flush(cx) + } + + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.project().unix_stream.poll_shutdown(cx) + } +} + +impl AsyncRead for UnixStream { fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -57,16 +80,30 @@ impl tokio::io::AsyncRead for UnixStream { } } +impl hyper::rt::Read for UnixStream { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: ReadBufCursor<'_>, + ) -> Poll> { + let mut t = TokioIo::new(self.project().unix_stream); + Pin::new(&mut t).poll_read(cx, buf) + } +} + /// the `[UnixConnector]` can be used to construct a `[hyper::Client]` which can /// speak to a unix domain socket. /// /// # Example /// ``` -/// use hyper::{Client, Body}; +/// use http_body_util::Full; +/// use hyper::body::Bytes; +/// use hyper_util::client::legacy::Client; +/// use hyper_util::rt::TokioExecutor; /// use hyperlocal::UnixConnector; /// /// let connector = UnixConnector; -/// let client: Client = Client::builder().build(connector); +/// let client: Client> = Client::builder(TokioExecutor::new()).build(connector); /// ``` /// /// # Note @@ -129,22 +166,27 @@ fn parse_socket_path(uri: &Uri) -> Result { } } -/// Extention trait for constructing a hyper HTTP client over a Unix domain +/// Extension trait for constructing a hyper HTTP client over a Unix domain /// socket. -pub trait UnixClientExt { +pub trait UnixClientExt { /// Construct a client which speaks HTTP over a Unix domain socket /// /// # Example /// ``` - /// use hyper::Client; - /// use hyperlocal::UnixClientExt; + /// use http_body_util::Full; + /// use hyper::body::Bytes; + /// use hyper_util::client::legacy::Client; + /// use hyperlocal::{UnixClientExt, UnixConnector}; /// - /// let client = Client::unix(); + /// let client: Client> = Client::unix(); /// ``` #[must_use] - fn unix() -> Client { - Client::builder().build(UnixConnector) + fn unix() -> Client + where + B::Data: Send, + { + Client::builder(TokioExecutor::new()).build(UnixConnector) } } -impl UnixClientExt for Client {} +impl UnixClientExt for Client {} diff --git a/src/lib.rs b/src/lib.rs index 0a64652..d8e0fb7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,35 +9,18 @@ //! `hyperlocal` provides [Hyper](http://github.com/hyperium/hyper) bindings //! for [Unix domain sockets](https://github.com/tokio-rs/tokio/tree/master/tokio-net/src/uds/). //! -//! See the [`UnixClientExt`] docs for -//! how to configure clients. -//! -//! See the -//! [`UnixServerExt`] docs for how to -//! configure servers. -//! -//! The [`UnixConnector`] can be used in the [`hyper::Client`] builder -//! interface, if required. +//! See the examples for how to configure a client or a server. //! //! # Features //! //! - Client- enables the client extension trait and connector. *Enabled by //! default*. -//! -//! - Server- enables the server extension trait. *Enabled by default*. #[cfg(feature = "client")] mod client; #[cfg(feature = "client")] pub use client::{UnixClientExt, UnixConnector}; -#[cfg(feature = "server")] -mod server; -#[cfg(feature = "server")] -pub use server::UnixServerExt; - mod uri; -pub use uri::Uri; -#[cfg(feature = "server")] -pub use crate::server::conn::SocketIncoming; +pub use uri::Uri; diff --git a/src/server.rs b/src/server.rs deleted file mode 100644 index c477e17..0000000 --- a/src/server.rs +++ /dev/null @@ -1,100 +0,0 @@ -use std::{io, path::Path}; - -use hyper::server::{Builder, Server}; - -use conn::SocketIncoming; - -pub(crate) mod conn { - use hyper::server::accept::Accept; - use pin_project_lite::pin_project; - use std::{ - io, - path::Path, - pin::Pin, - task::{Context, Poll}, - }; - use tokio::net::{UnixListener, UnixStream}; - - pin_project! { - /// A stream of connections from binding to a socket. - #[derive(Debug)] - pub struct SocketIncoming { - listener: UnixListener, - } - } - - impl SocketIncoming { - /// Creates a new `SocketIncoming` binding to provided socket path. - /// - /// # Errors - /// Refer to [`tokio::net::Listener::bind`](https://docs.rs/tokio/1.15.0/tokio/net/struct.UnixListener.html#method.bind). - pub fn bind(path: impl AsRef) -> Result { - let listener = UnixListener::bind(path)?; - - Ok(Self { listener }) - } - - /// Creates a new `SocketIncoming` from Tokio's `UnixListener` - /// - /// ```rust,ignore - /// let socket = SocketIncoming::from_listener(unix_listener); - /// let server = Server::builder(socket).serve(service); - /// ``` - pub fn from_listener(listener: UnixListener) -> Self { - Self { listener } - } - } - - impl Accept for SocketIncoming { - type Conn = UnixStream; - type Error = io::Error; - - fn poll_accept( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll>> { - self.listener - .poll_accept(cx)? - .map(|(conn, _)| Some(Ok(conn))) - } - } - - impl From for SocketIncoming { - fn from(listener: UnixListener) -> Self { - Self::from_listener(listener) - } - } -} - -/// Extension trait for provisioning a hyper HTTP server over a Unix domain -/// socket. -/// -/// # Example -/// -/// ```rust -/// use hyper::{Server, Body, Response, service::{make_service_fn, service_fn}}; -/// use hyperlocal::UnixServerExt; -/// -/// # async { -/// let make_service = make_service_fn(|_| async { -/// Ok::<_, hyper::Error>(service_fn(|_req| async { -/// Ok::<_, hyper::Error>(Response::new(Body::from("It works!"))) -/// })) -/// }); -/// -/// Server::bind_unix("/tmp/hyperlocal.sock")?.serve(make_service).await?; -/// # Ok::<_, Box>(()) -/// # }; -/// ``` -pub trait UnixServerExt { - /// Convenience method for constructing a Server listening on a Unix socket. - #[allow(clippy::missing_errors_doc)] - fn bind_unix(path: impl AsRef) -> Result, io::Error>; -} - -impl UnixServerExt for Server { - fn bind_unix(path: impl AsRef) -> Result, io::Error> { - let incoming = SocketIncoming::bind(path)?; - Ok(Server::builder(incoming)) - } -} diff --git a/src/uri.rs b/src/uri.rs index 23fb167..1b8739f 100644 --- a/src/uri.rs +++ b/src/uri.rs @@ -24,7 +24,7 @@ impl Uri { /// Will panic if path is not absolute and/or a malformed path string. pub fn new(socket: impl AsRef, path: &str) -> Self { let host = hex::encode(socket.as_ref().to_string_lossy().as_bytes()); - let host_str = format!("unix://{}:0{}", host, path); + let host_str = format!("unix://{host}:0{path}"); let hyper_uri: HyperUri = host_str.parse().unwrap(); Self { hyper_uri } diff --git a/tests/server_client.rs b/tests/server_client.rs index 7930a3d..02941e0 100644 --- a/tests/server_client.rs +++ b/tests/server_client.rs @@ -1,11 +1,21 @@ +use http_body_util::{BodyExt, Full}; use std::{error::Error, fs, path::Path}; -use hyper::{ - body::HttpBody, - service::{make_service_fn, service_fn}, - Body, Client, Response, Server, -}; -use hyperlocal::{UnixClientExt, UnixServerExt, Uri}; +use hyper::{body::Bytes, service::service_fn, Response}; +use hyper_util::{client::legacy::Client, rt::TokioIo}; +use hyperlocal::{UnixClientExt, UnixConnector, Uri}; +use tokio::net::UnixListener; + +const PHRASE: &str = "It works!"; + +#[derive(Debug, thiserror::Error)] +enum ListenerError { + #[error("Failed to accept connection: {0}")] + Accepting(std::io::Error), + + #[error("Failed to serve connection: {0}")] + Serving(hyper::Error), +} #[tokio::test] async fn test_server_client() -> Result<(), Box> { @@ -15,37 +25,40 @@ async fn test_server_client() -> Result<(), Box> { fs::remove_file(path)?; } - let make_service = make_service_fn(|_| async { - Ok::<_, hyper::Error>(service_fn(|_req| async { - Ok::<_, hyper::Error>(Response::new(Body::from("It works!"))) - })) - }); + let svc_fn = + service_fn(|_req| async { Ok::<_, hyper::Error>(Response::new(PHRASE.to_string())) }); - let (tx, rx) = tokio::sync::oneshot::channel::<()>(); + let listener = UnixListener::bind(path)?; - let server = Server::bind_unix("/tmp/hyperlocal.sock")? - .serve(make_service) - .with_graceful_shutdown(async { rx.await.unwrap() }); + let _server_task = tokio::spawn(async move { + let (stream, _) = listener.accept().await.map_err(ListenerError::Accepting)?; - tokio::spawn(async move { server.await.unwrap() }); + let io = TokioIo::new(stream); - let client = Client::unix(); + hyper::server::conn::http1::Builder::new() + .serve_connection(io, svc_fn) + .await + .map_err(ListenerError::Serving) + }); + + let client: Client> = Client::unix(); let url = Uri::new(path, "/").into(); let mut response = client.get(url).await?; let mut bytes = Vec::default(); - while let Some(next) = response.data().await { - let chunk = next?; - bytes.extend(chunk); + while let Some(frame_result) = response.frame().await { + let frame = frame_result?; + + if let Some(segment) = frame.data_ref() { + bytes.extend(segment.iter().as_slice()); + } } let string = String::from_utf8(bytes)?; - tx.send(()).unwrap(); - - assert_eq!(string, "It works!"); + assert_eq!(PHRASE, string); Ok(()) } From 70c0b8e4007b96ed392be7561b21add719d1dbce Mon Sep 17 00:00:00 2001 From: softprops Date: Fri, 8 Mar 2024 16:58:15 -0500 Subject: [PATCH 11/22] release prep --- CHANGELOG.md | 46 +++++++++++++++++++++++----------------------- Cargo.toml | 2 +- README.md | 11 +++++------ 3 files changed, 29 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b8020b..8fcc533 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,43 +1,43 @@ # 0.9.0 -* upgrade to hyper 1.0 +- upgrade to hyper 1.1 [#65](https://github.com/softprops/hyperlocal/pull/65) via [iamjpotts](https://github.com/iamjpotts) # 0.8.0 -* upgrade to tokio 1.0 and hyper 0.14 [#44](https://github.com/softprops/hyperlocal/pull/44) -* port ci from Travis CI to GitHub Actions -* `main` is the new default GitHub branch +- upgrade to tokio 1.0 and hyper 0.14 [#44](https://github.com/softprops/hyperlocal/pull/44) +- port ci from Travis CI to GitHub Actions +- `main` is the new default GitHub branch # 0.7.0 -* reimplement server for `std::future` (`async`/`await`) -* upgrade to tokio 0.2 -* add `SocketIncoming` interface +- reimplement server for `std::future` (`async`/`await`) +- upgrade to tokio 0.2 +- add `SocketIncoming` interface # 0.6.0 -* upgrade to hyper 0.13 -* upgrade hex to 0.3 [#15](https://github.com/softprops/hyperlocal/pull/15) -* move from tokio-core to tokio 0.1 [#16](https://github.com/softprops/hyperlocal/pull/16) -* don't explicitly block on unix socket connection [#18](https://github.com/softprops/hyperlocal/pull/18) -* provide a more flexible set of Server interfaces and to align more closely with those of hyper's default server bindings [#19](https://github.com/softprops/hyperlocal/pull/19) +- upgrade to hyper 0.13 +- upgrade hex to 0.3 [#15](https://github.com/softprops/hyperlocal/pull/15) +- move from tokio-core to tokio 0.1 [#16](https://github.com/softprops/hyperlocal/pull/16) +- don't explicitly block on unix socket connection [#18](https://github.com/softprops/hyperlocal/pull/18) +- provide a more flexible set of Server interfaces and to align more closely with those of hyper's default server bindings [#19](https://github.com/softprops/hyperlocal/pull/19) You'll want to use `hyperlocal::server::Server` where you would have used `hyperlocal::server::Http` in the past and use `hyperlocal::server::Http` for a lower level interfaces that give you more control over "driving" your server. # 0.5.0 -* upgrade to hyper 0.12 [#11](https://github.com/softprops/hyperlocal/pull/11) -* expose the [SocketAddr](https://doc.rust-lang.org/std/os/unix/net/struct.SocketAddr.html) servers listen on with `Server#local_addr` +- upgrade to hyper 0.12 [#11](https://github.com/softprops/hyperlocal/pull/11) +- expose the [SocketAddr](https://doc.rust-lang.org/std/os/unix/net/struct.SocketAddr.html) servers listen on with `Server#local_addr` # 0.4.1 -* implement Clone for `UnixConnector` [@letmutx](https://github.com/softprops/hyperlocal/pull/7) +- implement Clone for `UnixConnector` [@letmutx](https://github.com/softprops/hyperlocal/pull/7) # 0.4.0 -* refactor for async hyper -* `hyperlocal::DomainUrl` is now `hyperlocal::Uri` the semantics are the same but the name now matches hyper's new name can can be lifted into hypers type +- refactor for async hyper +- `hyperlocal::DomainUrl` is now `hyperlocal::Uri` the semantics are the same but the name now matches hyper's new name can can be lifted into hypers type ```rust let uri: hyper:Uri = @@ -46,18 +46,18 @@ let uri: hyper:Uri = "/foo/bar?baz=boom" ).into(); ``` -* `hyperlocal::UnitSocketConnector` is now just `hyperlocal::UnixConnector` to be more inline with the naming conventions behind`hyper::HttpConnector` and `hyper_tls::HttpsConnector` -* `hyperlocal::UnixSocketServer` is now `hyperlocal::server::Http` to be more inline with hyper naming conventions + +- `hyperlocal::UnitSocketConnector` is now just `hyperlocal::UnixConnector` to be more inline with the naming conventions behind`hyper::HttpConnector` and `hyper_tls::HttpsConnector` +- `hyperlocal::UnixSocketServer` is now `hyperlocal::server::Http` to be more inline with hyper naming conventions # 0.3.0 -* enable using unix_socket from stdlib. [#4](https://github.com/softprops/hyperlocal/pull/4) -* upgrade to hyper 0.10 +- enable using unix_socket from stdlib. [#4](https://github.com/softprops/hyperlocal/pull/4) +- upgrade to hyper 0.10 # 0.2.0 -* upgraded to hyper 0.9 and transitively url 1.0 - +- upgraded to hyper 0.9 and transitively url 1.0 # 0.1.0 diff --git a/Cargo.toml b/Cargo.toml index 687cd45..33e65e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hyperlocal" -version = "0.9.0-alpha" +version = "0.9.0" authors = ["softprops "] description = "Hyper bindings for Unix domain sockets" homepage = "https://github.com/softprops/hyperlocal" diff --git a/README.md b/README.md index f304581..7c9e709 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ -

- 🔌 ✨ +u
+🔌 ✨ +

@@ -40,7 +41,6 @@ want to limit access to the current host, in which case, opening and exposing tc not needed. Examples of Unix daemons that provide this kind of host local interface include [Docker](https://docs.docker.com/engine/misc/), a process container manager. - ## Installation Add the following to your `Cargo.toml` file @@ -58,11 +58,10 @@ A typical server can be built by creating a `tokio::net::UnixListener` and accep `hyper::service::service_fn` to create a request/response processing function, and connecting the `UnixStream` to it using `hyper::server::conn::http1::Builder::new().serve_connection()`. -An example is at [examples/server.rs](./examples/server.rs). +An example is at [examples/server.rs](./examples/server.rs), runnable via `cargo run --example server` To test that your server is working you can use an out-of-the-box tool like `curl` - ```sh $ curl --unix-socket /tmp/hyperlocal.sock localhost @@ -74,7 +73,7 @@ It's a Unix system. I know this. `hyperlocal` also provides bindings for writing unix domain socket based HTTP clients the `Client` interface from the `hyper-utils` crate. -An example is at [examples/client.rs](./examples/client.rs). +An example is at [examples/client.rs](./examples/client.rs), runnable via `cargo run --features="server" --example client` Hyper's client interface makes it easy to send typical HTTP methods like `GET`, `POST`, `DELETE` with factory methods, `get`, `post`, `delete`, etc. These require an argument that can be tranformed into a `hyper::Uri`. From 0596ef93c22badec396ecd41d84f81dcd2fec89b Mon Sep 17 00:00:00 2001 From: Joshua Potts <8704475+iamjpotts@users.noreply.github.com> Date: Tue, 19 Mar 2024 00:34:00 -0400 Subject: [PATCH 12/22] Examples: Add additional logging of accepted connections --- examples/server.rs | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/examples/server.rs b/examples/server.rs index 920df50..13cbb9f 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -1,9 +1,9 @@ use hyper::{service::service_fn, Response}; use hyper_util::rt::TokioIo; -use std::{error::Error, fs, path::Path}; +use std::{error::Error, fs, io::ErrorKind, path::Path}; use tokio::net::UnixListener; -const PHRASE: &str = "It's a Unix system. I know this."; +const PHRASE: &str = "It's a Unix system. I know this.\n"; // Adapted from https://hyper.rs/guides/1/server/hello-world/ #[tokio::main] @@ -16,16 +16,25 @@ async fn main() -> Result<(), Box> { let listener = UnixListener::bind(path)?; + println!("Listening for connections at {}.", path.display()); + loop { let (stream, _) = listener.accept().await?; let io = TokioIo::new(stream); + println!("Accepting connection."); + tokio::task::spawn(async move { let svc_fn = service_fn(|_req| async { let body = PHRASE.to_string(); Ok::<_, hyper::Error>(Response::new(body)) }); + // On linux, serve_connection will return right away with Result::Ok. + // + // On OSX, serve_connection will block until the client disconnects, + // and return Result::Err(hyper::Error) with a source (inner/cause) + // socket error indicating the client connection is no longer open. match hyper::server::conn::http1::Builder::new() .serve_connection(io, svc_fn) .await @@ -34,7 +43,17 @@ async fn main() -> Result<(), Box> { println!("Accepted connection."); } Err(err) => { - eprintln!("Failed to accept connection: {err:?}"); + let source: Option<&std::io::Error> = + err.source().and_then(|s| s.downcast_ref()); + + match source { + Some(io_err) if io_err.kind() == ErrorKind::NotConnected => { + println!("Client disconnected."); + } + _ => { + eprintln!("Failed to accept connection: {err:?}"); + } + } } }; }); From bf06e28c994418bce4ac5970af9bb13a370f66c5 Mon Sep 17 00:00:00 2001 From: Joshua Potts <8704475+iamjpotts@users.noreply.github.com> Date: Sun, 7 Apr 2024 15:45:06 -0500 Subject: [PATCH 13/22] Disable keep-alive on server example --- examples/server.rs | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/examples/server.rs b/examples/server.rs index 13cbb9f..e68c53c 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -1,6 +1,6 @@ use hyper::{service::service_fn, Response}; use hyper_util::rt::TokioIo; -use std::{error::Error, fs, io::ErrorKind, path::Path}; +use std::{error::Error, fs, path::Path}; use tokio::net::UnixListener; const PHRASE: &str = "It's a Unix system. I know this.\n"; @@ -30,12 +30,10 @@ async fn main() -> Result<(), Box> { Ok::<_, hyper::Error>(Response::new(body)) }); - // On linux, serve_connection will return right away with Result::Ok. - // - // On OSX, serve_connection will block until the client disconnects, - // and return Result::Err(hyper::Error) with a source (inner/cause) - // socket error indicating the client connection is no longer open. match hyper::server::conn::http1::Builder::new() + // On OSX, disabling keep alive prevents serve_connection from + // blocking and later returning an Err derived from E_NOTCONN. + .keep_alive(false) .serve_connection(io, svc_fn) .await { @@ -43,17 +41,7 @@ async fn main() -> Result<(), Box> { println!("Accepted connection."); } Err(err) => { - let source: Option<&std::io::Error> = - err.source().and_then(|s| s.downcast_ref()); - - match source { - Some(io_err) if io_err.kind() == ErrorKind::NotConnected => { - println!("Client disconnected."); - } - _ => { - eprintln!("Failed to accept connection: {err:?}"); - } - } + eprintln!("Failed to accept connection: {err:?}"); } }; }); From 6409d894e7058ea1afea9676cc5ffc21e21f7faa Mon Sep 17 00:00:00 2001 From: softprops Date: Sun, 5 May 2024 12:57:39 -0400 Subject: [PATCH 14/22] delete dup rustfmt config file and resolve rustfmt depr warnings. fixes #71 --- .rustfmt.toml | 5 ----- rustfmt.toml | 8 ++++---- src/client.rs | 36 +++++++++++++++++++++++++++--------- src/uri.rs | 7 +++++-- 4 files changed, 36 insertions(+), 20 deletions(-) delete mode 100644 .rustfmt.toml diff --git a/.rustfmt.toml b/.rustfmt.toml deleted file mode 100644 index b4d1e94..0000000 --- a/.rustfmt.toml +++ /dev/null @@ -1,5 +0,0 @@ -version = "Two" -use_field_init_shorthand = true -merge_imports = true -wrap_comments = true -use_try_shorthand = true \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml index 0f780d3..608658f 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,6 +1,6 @@ -# https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#fn_args_layout -fn_args_layout = "Vertical" -# https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#merge_imports -merge_imports = true +# https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#fn_params_layout +fn_params_layout = "Vertical" +# https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#imports_granularity +imports_granularity = "Crate" # https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#format_code_in_doc_comments format_code_in_doc_comments = true \ No newline at end of file diff --git a/src/client.rs b/src/client.rs index b99f7ce..b5b1052 100644 --- a/src/client.rs +++ b/src/client.rs @@ -43,11 +43,17 @@ impl AsyncWrite for UnixStream { self.project().unix_stream.poll_write(cx, buf) } - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + fn poll_flush( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { self.project().unix_stream.poll_flush(cx) } - fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + fn poll_shutdown( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { self.project().unix_stream.poll_shutdown(cx) } } @@ -61,11 +67,17 @@ impl hyper::rt::Write for UnixStream { self.project().unix_stream.poll_write(cx, buf) } - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + fn poll_flush( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { self.project().unix_stream.poll_flush(cx) } - fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + fn poll_shutdown( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { self.project().unix_stream.poll_shutdown(cx) } } @@ -98,12 +110,12 @@ impl hyper::rt::Read for UnixStream { /// ``` /// use http_body_util::Full; /// use hyper::body::Bytes; -/// use hyper_util::client::legacy::Client; -/// use hyper_util::rt::TokioExecutor; +/// use hyper_util::{client::legacy::Client, rt::TokioExecutor}; /// use hyperlocal::UnixConnector; /// /// let connector = UnixConnector; -/// let client: Client> = Client::builder(TokioExecutor::new()).build(connector); +/// let client: Client> = +/// Client::builder(TokioExecutor::new()).build(connector); /// ``` /// /// # Note @@ -121,7 +133,10 @@ impl Service for UnixConnector { type Future = Pin> + Send + 'static>>; - fn call(&mut self, req: Uri) -> Self::Future { + fn call( + &mut self, + req: Uri, + ) -> Self::Future { let fut = async move { let path = parse_socket_path(&req)?; UnixStream::connect(path).await @@ -130,7 +145,10 @@ impl Service for UnixConnector { Box::pin(fut) } - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { Poll::Ready(Ok(())) } } diff --git a/src/uri.rs b/src/uri.rs index 1b8739f..9c5d23e 100644 --- a/src/uri.rs +++ b/src/uri.rs @@ -7,8 +7,8 @@ use std::path::Path; /// /// # Example /// ``` -/// use hyperlocal::Uri; /// use hyper::Uri as HyperUri; +/// use hyperlocal::Uri; /// /// let uri: HyperUri = Uri::new("/tmp/hyperlocal.sock", "/").into(); /// ``` @@ -22,7 +22,10 @@ impl Uri { /// /// # Panics /// Will panic if path is not absolute and/or a malformed path string. - pub fn new(socket: impl AsRef, path: &str) -> Self { + pub fn new( + socket: impl AsRef, + path: &str, + ) -> Self { let host = hex::encode(socket.as_ref().to_string_lossy().as_bytes()); let host_str = format!("unix://{host}:0{path}"); let hyper_uri: HyperUri = host_str.parse().unwrap(); From 464dd26be8ed89e0758bf2506340b3ff1e0c0eaf Mon Sep 17 00:00:00 2001 From: Joshua Potts <8704475+iamjpotts@users.noreply.github.com> Date: Sun, 9 Jun 2024 23:10:57 -0400 Subject: [PATCH 15/22] Update readme to reflect lack of server extension trait after upgrade to hyper 1.x --- README.md | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 7c9e709..6ad813c 100644 --- a/README.md +++ b/README.md @@ -34,11 +34,10 @@ u
Hyper is a rock solid [Rust](https://www.rust-lang.org/) HTTP client and server toolkit. [Unix domain sockets](https://en.wikipedia.org/wiki/Unix_domain_socket) provide a mechanism for host-local interprocess communication. `hyperlocal` builds on and complements Hyper's -interfaces for building Unix domain socket HTTP clients and servers. +interfaces for building Unix domain socket HTTP clients. -This is useful for exposing simple HTTP interfaces for your Unix daemons in cases where you -want to limit access to the current host, in which case, opening and exposing tcp ports is -not needed. Examples of Unix daemons that provide this kind of host local interface include +This is useful for accessing HTTP interfaces exposed via a Unix daemons. +Examples of Unix daemons that provide this kind of host local interface include [Docker](https://docs.docker.com/engine/misc/), a process container manager. ## Installation @@ -68,12 +67,14 @@ $ curl --unix-socket /tmp/hyperlocal.sock localhost It's a Unix system. I know this. ``` +Note that `hyperlocal` is not required to build a server, though `hyper` and `tokio` are both used in the example. + ### Clients -`hyperlocal` also provides bindings for writing unix domain socket based HTTP clients the `Client` interface from the +`hyperlocal` provides bindings for writing unix domain socket based HTTP clients the `Client` interface from the `hyper-utils` crate. -An example is at [examples/client.rs](./examples/client.rs), runnable via `cargo run --features="server" --example client` +An example is at [examples/client.rs](./examples/client.rs), runnable via `cargo run --example client` Hyper's client interface makes it easy to send typical HTTP methods like `GET`, `POST`, `DELETE` with factory methods, `get`, `post`, `delete`, etc. These require an argument that can be tranformed into a `hyper::Uri`. @@ -82,6 +83,21 @@ Since Unix domain sockets aren't represented with hostnames that resolve to ip a your standard over the counter URL string won't do. Instead, use a `hyperlocal::Uri`, which represents both file path to the domain socket and the resource URI path and query string. +## Recent Releases of `hyperlocal` + +### 0.9 + +Supports `hyper 1.x` by providing a `tower` service `UnixConnector` and an +extension method `hyper_util::client::legacy::Client::unix()` to create a +client. + +The server extension method `bind_unix` was removed since there is no longer +an equivalent to `hyper::Server`. + +### 0.8 + +Supports `hyper 0.14` and provided extensions to both hyper's `Client` and `Server` via traits. + --- Doug Tangren (softprops) 2015-2020 From 8e2979e69651dff8dd5eeb2017f9f8e55ea20e63 Mon Sep 17 00:00:00 2001 From: Joshua Potts <8704475+iamjpotts@users.noreply.github.com> Date: Mon, 10 Jun 2024 00:44:04 -0400 Subject: [PATCH 16/22] Add server extension trait revised for hyper 1.x --- Cargo.toml | 4 +-- README.md | 30 +++++------------ examples/server.rs | 40 ++++++++--------------- src/lib.rs | 7 ++++ src/server.rs | 80 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 110 insertions(+), 51 deletions(-) create mode 100644 src/server.rs diff --git a/Cargo.toml b/Cargo.toml index 33e65e8..17e3175 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ edition = "2021" [dependencies] hex = "0.4" http-body-util = { version = "0.1", optional = true } -hyper = "1.1" +hyper = "1.3" hyper-util = { version = "0.1.2", optional = true } tokio = { version = "1.35", default-features = false, features = ["net"] } tower-service = { version = "0.3", optional = true } @@ -24,7 +24,7 @@ thiserror = "1.0" tokio = { version = "1.35", features = ["io-std", "io-util", "macros", "rt-multi-thread"] } [features] -default = ["client"] +default = ["client", "server"] client = [ "http-body-util", "hyper/client", diff --git a/README.md b/README.md index 6ad813c..05f299a 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,11 @@ u
Hyper is a rock solid [Rust](https://www.rust-lang.org/) HTTP client and server toolkit. [Unix domain sockets](https://en.wikipedia.org/wiki/Unix_domain_socket) provide a mechanism for host-local interprocess communication. `hyperlocal` builds on and complements Hyper's -interfaces for building Unix domain socket HTTP clients. +interfaces for building Unix domain socket HTTP clients and servers. -This is useful for accessing HTTP interfaces exposed via a Unix daemons. -Examples of Unix daemons that provide this kind of host local interface include +This is useful for exposing simple HTTP interfaces for your Unix daemons in cases where you +want to limit access to the current host, in which case, opening and exposing tcp ports is +not needed. Examples of Unix daemons that provide this kind of host local interface include [Docker](https://docs.docker.com/engine/misc/), a process container manager. ## Installation @@ -57,6 +58,8 @@ A typical server can be built by creating a `tokio::net::UnixListener` and accep `hyper::service::service_fn` to create a request/response processing function, and connecting the `UnixStream` to it using `hyper::server::conn::http1::Builder::new().serve_connection()`. +`hyperlocal` provides an extension trait `UnixListenerExt` with an implementation of this. + An example is at [examples/server.rs](./examples/server.rs), runnable via `cargo run --example server` To test that your server is working you can use an out-of-the-box tool like `curl` @@ -67,37 +70,20 @@ $ curl --unix-socket /tmp/hyperlocal.sock localhost It's a Unix system. I know this. ``` -Note that `hyperlocal` is not required to build a server, though `hyper` and `tokio` are both used in the example. - ### Clients -`hyperlocal` provides bindings for writing unix domain socket based HTTP clients the `Client` interface from the +`hyperlocal` also provides bindings for writing unix domain socket based HTTP clients the `Client` interface from the `hyper-utils` crate. An example is at [examples/client.rs](./examples/client.rs), runnable via `cargo run --example client` Hyper's client interface makes it easy to send typical HTTP methods like `GET`, `POST`, `DELETE` with factory -methods, `get`, `post`, `delete`, etc. These require an argument that can be tranformed into a `hyper::Uri`. +methods, `get`, `post`, `delete`, etc. These require an argument that can be transformed into a `hyper::Uri`. Since Unix domain sockets aren't represented with hostnames that resolve to ip addresses coupled with network ports, your standard over the counter URL string won't do. Instead, use a `hyperlocal::Uri`, which represents both file path to the domain socket and the resource URI path and query string. -## Recent Releases of `hyperlocal` - -### 0.9 - -Supports `hyper 1.x` by providing a `tower` service `UnixConnector` and an -extension method `hyper_util::client::legacy::Client::unix()` to create a -client. - -The server extension method `bind_unix` was removed since there is no longer -an equivalent to `hyper::Server`. - -### 0.8 - -Supports `hyper 0.14` and provided extensions to both hyper's `Client` and `Server` via traits. - --- Doug Tangren (softprops) 2015-2020 diff --git a/examples/server.rs b/examples/server.rs index e68c53c..e936e7b 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -1,8 +1,10 @@ -use hyper::{service::service_fn, Response}; -use hyper_util::rt::TokioIo; use std::{error::Error, fs, path::Path}; + +use hyper::Response; use tokio::net::UnixListener; +use hyperlocal::UnixListenerExt; + const PHRASE: &str = "It's a Unix system. I know this.\n"; // Adapted from https://hyper.rs/guides/1/server/hello-world/ @@ -18,32 +20,16 @@ async fn main() -> Result<(), Box> { println!("Listening for connections at {}.", path.display()); - loop { - let (stream, _) = listener.accept().await?; - let io = TokioIo::new(stream); - - println!("Accepting connection."); + listener + .serve(|| { + println!("Accepted connection."); - tokio::task::spawn(async move { - let svc_fn = service_fn(|_req| async { + |_request| async { let body = PHRASE.to_string(); Ok::<_, hyper::Error>(Response::new(body)) - }); - - match hyper::server::conn::http1::Builder::new() - // On OSX, disabling keep alive prevents serve_connection from - // blocking and later returning an Err derived from E_NOTCONN. - .keep_alive(false) - .serve_connection(io, svc_fn) - .await - { - Ok(()) => { - println!("Accepted connection."); - } - Err(err) => { - eprintln!("Failed to accept connection: {err:?}"); - } - }; - }); - } + } + }) + .await?; + + Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index d8e0fb7..84236c9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,12 +15,19 @@ //! //! - Client- enables the client extension trait and connector. *Enabled by //! default*. +//! +//! - Server- enables the server extension trait. *Enabled by default*. #[cfg(feature = "client")] mod client; #[cfg(feature = "client")] pub use client::{UnixClientExt, UnixConnector}; +#[cfg(feature = "server")] +mod server; +#[cfg(feature = "server")] +pub use server::UnixListenerExt; + mod uri; pub use uri::Uri; diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..68c3241 --- /dev/null +++ b/src/server.rs @@ -0,0 +1,80 @@ +use hyper::{ + body::{Body, Incoming}, + service::service_fn, + Request, Response, +}; +use hyper_util::rt::TokioIo; +use std::future::Future; +use tokio::net::UnixListener; + +/// Extension trait for provisioning a hyper HTTP server over a Unix domain +/// socket. +/// +/// # Example +/// +/// ```rust +/// use hyper::Response; +/// use hyperlocal::UnixListenerExt; +/// use tokio::net::UnixListener; +/// +/// let future = async move { +/// let listener = UnixListener::bind("/tmp/hyperlocal.sock").expect("parsed unix path"); +/// +/// listener +/// .serve(|| { +/// |_request| async { +/// Ok::<_, hyper::Error>(Response::new("Hello, world.".to_string())) +/// } +/// }) +/// .await +/// .expect("failed to serve a connection") +/// }; +/// ``` +pub trait UnixListenerExt { + /// Indefinitely accept and respond to connections. + /// + /// Pass a function which will generate the function which responds to + /// all requests for an individual connection. + fn serve( + self, + f: MakeResponseFn, + ) -> impl Future>> + where + MakeResponseFn: Fn() -> ResponseFn, + ResponseFn: Fn(Request) -> ResponseFuture, + ResponseFuture: Future, E>>, + B: Body + 'static, + ::Error: std::error::Error + Send + Sync, + E: std::error::Error + Send + Sync + 'static; +} + +impl UnixListenerExt for UnixListener { + fn serve( + self, + f: MakeServiceFn, + ) -> impl Future>> + where + MakeServiceFn: Fn() -> ResponseFn, + ResponseFn: Fn(Request) -> ResponseFuture, + ResponseFuture: Future, E>>, + B: Body + 'static, + ::Error: std::error::Error + Send + Sync, + E: std::error::Error + Send + Sync + 'static, + { + async move { + loop { + let (stream, _) = self.accept().await?; + let io = TokioIo::new(stream); + + let svc_fn = service_fn(f()); + + hyper::server::conn::http1::Builder::new() + // On OSX, disabling keep alive prevents serve_connection from + // blocking and later returning an Err derived from E_NOTCONN. + .keep_alive(false) + .serve_connection(io, svc_fn) + .await?; + } + } + } +} From 6d3e2a69738fc542f963b13506cb8dbddf62297f Mon Sep 17 00:00:00 2001 From: softprops Date: Mon, 22 Jul 2024 10:27:44 -0400 Subject: [PATCH 17/22] update changelog --- CHANGELOG.md | 4 ++++ Cargo.toml | 17 +++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fcc533..a78b93d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.9.1 + +- Adding support for UnixStream to take advantage of vectored writes [#63](https://github.com/softprops/hyperlocal/pull/63) via [@craftytrickster](https://github.com/craftytrickster) + # 0.9.0 - upgrade to hyper 1.1 [#65](https://github.com/softprops/hyperlocal/pull/65) via [iamjpotts](https://github.com/iamjpotts) diff --git a/Cargo.toml b/Cargo.toml index 17e3175..27badc8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hyperlocal" -version = "0.9.0" +version = "0.9.1" authors = ["softprops "] description = "Hyper bindings for Unix domain sockets" homepage = "https://github.com/softprops/hyperlocal" @@ -21,7 +21,12 @@ pin-project-lite = "0.2" [dev-dependencies] thiserror = "1.0" -tokio = { version = "1.35", features = ["io-std", "io-util", "macros", "rt-multi-thread"] } +tokio = { version = "1.35", features = [ + "io-std", + "io-util", + "macros", + "rt-multi-thread", +] } [features] default = ["client", "server"] @@ -32,13 +37,9 @@ client = [ "hyper-util/client-legacy", "hyper-util/http1", "hyper-util/tokio", - "tower-service" -] -server = [ - "hyper/http1", - "hyper/server", - "hyper-util/tokio", + "tower-service", ] +server = ["hyper/http1", "hyper/server", "hyper-util/tokio"] [[example]] name = "client" From b8b75dcc48c1d4608ed9d3dfe9204b09ba960254 Mon Sep 17 00:00:00 2001 From: softprops Date: Mon, 22 Jul 2024 10:32:38 -0400 Subject: [PATCH 18/22] update changelog --- CHANGELOG.md | 6 +++++- Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a78b93d..2228e9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ +# 0.9.2 + +- Export UnixStream for clients [#60](https://github.com/softprops/hyperlocal/pull/60) via [@onalante-msft](https://github.com/onalante-msft) + # 0.9.1 -- Adding support for UnixStream to take advantage of vectored writes [#63](https://github.com/softprops/hyperlocal/pull/63) via [@craftytrickster](https://github.com/craftytrickster) +- Adding support for UnixStream to take advantage of vectored writes [#63](https://github.com/softprops/hyperlocal/pull/63) via [@craftytrickster](https://github.com/craftytrickster) # 0.9.0 diff --git a/Cargo.toml b/Cargo.toml index 27badc8..8ec527f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hyperlocal" -version = "0.9.1" +version = "0.9.2" authors = ["softprops "] description = "Hyper bindings for Unix domain sockets" homepage = "https://github.com/softprops/hyperlocal" From d4b04aedfcc43b4215bbbaf29163d85b7a7f4c81 Mon Sep 17 00:00:00 2001 From: softprops Date: Mon, 22 Jul 2024 10:39:28 -0400 Subject: [PATCH 19/22] fix broken vectorization pull --- src/client.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client.rs b/src/client.rs index 1725758..f7bdba5 100644 --- a/src/client.rs +++ b/src/client.rs @@ -61,13 +61,13 @@ impl AsyncWrite for UnixStream { fn poll_write_vectored( self: Pin<&mut Self>, cx: &mut Context<'_>, - bufs: &[IoSlice<'_>], + bufs: &[io::IoSlice<'_>], ) -> Poll> { - self.project().unix_stream.poll_write_vectored(cx) + self.project().unix_stream.poll_write_vectored(cx, bufs) } fn is_write_vectored(&self) -> bool { - self.project().unix_stream.is_write_vectored() + self.unix_stream.is_write_vectored() } } From 720d1c9a6f6acb311919dfa0e3ed92101b66c1ba Mon Sep 17 00:00:00 2001 From: softprops Date: Mon, 22 Jul 2024 10:41:21 -0400 Subject: [PATCH 20/22] reset version because previous workflow run failed to faulty pull --- CHANGELOG.md | 5 +---- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2228e9f..1799f67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,6 @@ -# 0.9.2 - -- Export UnixStream for clients [#60](https://github.com/softprops/hyperlocal/pull/60) via [@onalante-msft](https://github.com/onalante-msft) - # 0.9.1 +- Export UnixStream for clients [#60](https://github.com/softprops/hyperlocal/pull/60) via [@onalante-msft](https://github.com/onalante-msft) - Adding support for UnixStream to take advantage of vectored writes [#63](https://github.com/softprops/hyperlocal/pull/63) via [@craftytrickster](https://github.com/craftytrickster) # 0.9.0 diff --git a/Cargo.toml b/Cargo.toml index 8ec527f..27badc8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hyperlocal" -version = "0.9.2" +version = "0.9.1" authors = ["softprops "] description = "Hyper bindings for Unix domain sockets" homepage = "https://github.com/softprops/hyperlocal" From e0323e6d03e566f3a431864ce1f8a21bf8d2c196 Mon Sep 17 00:00:00 2001 From: softprops Date: Mon, 22 Jul 2024 10:46:35 -0400 Subject: [PATCH 21/22] fix readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 05f299a..78ec8e3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -u
+
🔌 ✨
From aa7bb1244502a7976672fa68ddd3fd5fa23563c9 Mon Sep 17 00:00:00 2001 From: softprops Date: Mon, 22 Jul 2024 10:48:29 -0400 Subject: [PATCH 22/22] maint --- LICENSE | 4 ++-- README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/LICENSE b/LICENSE index 244be72..934a79e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2015-2020 Doug Tangren +Copyright (c) 2015-2024 Doug Tangren Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -17,4 +17,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 78ec8e3..4da6d3d 100644 --- a/README.md +++ b/README.md @@ -86,4 +86,4 @@ socket and the resource URI path and query string. --- -Doug Tangren (softprops) 2015-2020 +Doug Tangren (softprops) 2015-2024