"]
description = "Hyper bindings for Unix domain sockets"
homepage = "https://github.com/softprops/hyperlocal"
repository = "https://github.com/softprops/hyperlocal"
keywords = ["hyper", "unix", "sockets", "http"]
license = "MIT"
readme = "README.md"
edition = "2021"
[dependencies]
hex = "0.4"
http-body-util = { version = "0.1", optional = true }
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 }
pin-project-lite = "0.2"
[dev-dependencies]
thiserror = "1.0"
tokio = { version = "1.35", features = [
"io-std",
"io-util",
"macros",
"rt-multi-thread",
] }
[features]
default = ["client", "server"]
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-util/tokio"]
[[example]]
name = "client"
required-features = ["client"]
[[example]]
name = "server"
required-features = ["server"]
[[test]]
name = "server_client"
required-features = ["client", "server"]
hyperlocal-0.9.1/LICENSE 0000644 0000000 0000000 00000002044 10461020230 0013002 0 ustar 0000000 0000000 Copyright (c) 2015-2020 Doug Tangren
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
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. hyperlocal-0.9.1/README.md 0000644 0000000 0000000 00000006420 10461020230 0013256 0 ustar 0000000 0000000 u
🔌 ✨
hyperlocal
Hyper client and server bindings for Unix domain sockets
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.
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
Add the following to your `Cargo.toml` file
```toml
[dependencies]
hyperlocal = "0.9"
```
## Usage
### Servers
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()`.
`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`
```sh
$ curl --unix-socket /tmp/hyperlocal.sock localhost
It's a Unix system. I know this.
```
### Clients
`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 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.
---
Doug Tangren (softprops) 2015-2020
hyperlocal-0.9.1/examples/client.rs 0000644 0000000 0000000 00000001323 10461020230 0015436 0 ustar 0000000 0000000 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 _};
#[tokio::main]
async fn main() -> Result<(), Box> {
let url = Uri::new("/tmp/hyperlocal.sock", "/").into();
let client: Client> = Client::unix();
let mut response = client.get(url).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(())
}
hyperlocal-0.9.1/examples/server.rs 0000644 0000000 0000000 00000001473 10461020230 0015474 0 ustar 0000000 0000000 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/
#[tokio::main]
async fn main() -> Result<(), Box> {
let path = Path::new("/tmp/hyperlocal.sock");
if path.exists() {
fs::remove_file(path)?;
}
let listener = UnixListener::bind(path)?;
println!("Listening for connections at {}.", path.display());
listener
.serve(|| {
println!("Accepted connection.");
|_request| async {
let body = PHRASE.to_string();
Ok::<_, hyper::Error>(Response::new(body))
}
})
.await?;
Ok(())
}
hyperlocal-0.9.1/rustfmt.toml 0000644 0000000 0000000 00000000556 10461020230 0014404 0 ustar 0000000 0000000 # 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 hyperlocal-0.9.1/src/client.rs 0000644 0000000 0000000 00000013252 10461020230 0014413 0 ustar 0000000 0000000 use hex::FromHex;
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::{AsyncRead, AsyncWrite, ReadBuf};
use tower_service::Service;
pin_project! {
/// Wrapper around [`tokio::net::UnixStream`].
#[derive(Debug)]
pub struct UnixStream {
#[pin]
unix_stream: tokio::net::UnixStream,
}
}
impl UnixStream {
async fn connect(path: impl AsRef) -> io::Result {
let unix_stream = tokio::net::UnixStream::connect(path).await?;
Ok(Self { unix_stream })
}
}
impl AsyncWrite 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)
}
fn poll_write_vectored(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
bufs: &[io::IoSlice<'_>],
) -> Poll> {
self.project().unix_stream.poll_write_vectored(cx, bufs)
}
fn is_write_vectored(&self) -> bool {
self.unix_stream.is_write_vectored()
}
}
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<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll> {
self.project().unix_stream.poll_read(cx, buf)
}
}
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 http_body_util::Full;
/// use hyper::body::Bytes;
/// use hyper_util::{client::legacy::Client, rt::TokioExecutor};
/// use hyperlocal::UnixConnector;
///
/// let connector = UnixConnector;
/// let client: Client> =
/// Client::builder(TokioExecutor::new()).build(connector);
/// ```
///
/// # Note
/// If you don't need access to the low-level `[hyper::Client]` builder
/// interface, consider using the `[UnixClientExt]` trait instead.
#[derive(Clone, Copy, Debug, Default)]
pub struct UnixConnector;
impl Unpin for UnixConnector {}
impl Service for UnixConnector {
type Response = UnixStream;
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)?;
UnixStream::connect(path).await
};
Box::pin(fut)
}
fn poll_ready(
&mut self,
_cx: &mut Context<'_>,
) -> Poll> {
Poll::Ready(Ok(()))
}
}
impl Connection for UnixStream {
fn connected(&self) -> Connected {
Connected::new()
}
}
fn parse_socket_path(uri: &Uri) -> Result {
if uri.scheme_str() != Some("unix") {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"invalid URL, scheme must be unix",
));
}
if let Some(host) = uri.host() {
let bytes = Vec::from_hex(host).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidInput,
"invalid URL, host must be a hex-encoded path",
)
})?;
Ok(PathBuf::from(String::from_utf8_lossy(&bytes).into_owned()))
} else {
Err(io::Error::new(
io::ErrorKind::InvalidInput,
"invalid URL, host must be present",
))
}
}
/// Extension trait for constructing a hyper HTTP client over a Unix domain
/// socket.
pub trait UnixClientExt {
/// Construct a client which speaks HTTP over a Unix domain socket
///
/// # Example
/// ```
/// use http_body_util::Full;
/// use hyper::body::Bytes;
/// use hyper_util::client::legacy::Client;
/// use hyperlocal::{UnixClientExt, UnixConnector};
///
/// let client: Client> = Client::unix();
/// ```
#[must_use]
fn unix() -> Client
where
B::Data: Send,
{
Client::builder(TokioExecutor::new()).build(UnixConnector)
}
}
impl UnixClientExt for Client {}
hyperlocal-0.9.1/src/lib.rs 0000644 0000000 0000000 00000001505 10461020230 0013701 0 ustar 0000000 0000000 #![deny(
missing_debug_implementations,
unreachable_pub,
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/).
//!
//! 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, UnixStream};
#[cfg(feature = "server")]
mod server;
#[cfg(feature = "server")]
pub use server::UnixListenerExt;
mod uri;
pub use uri::Uri;
hyperlocal-0.9.1/src/server.rs 0000644 0000000 0000000 00000005144 10461020230 0014444 0 ustar 0000000 0000000 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