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.8"
```
## Usage
### Servers
A typical server can be built with `hyperlocal::server::UnixServerExt`.
```rust
use std::{error::Error, fs, path::Path};
use hyper::{
service::{make_service_fn, service_fn},
Body, Response, Server,
};
use hyperlocal::UnixServerExt;
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`
```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 using `Hyper`'s native `Client` interface.
Configure your `Hyper` client using `hyper::Client::builder()`.
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`.
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.
```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
hyperlocal-0.8.0/examples/client.rs 0100644 0001751 0000164 00000000773 13772450027 0015521 0 ustar 0000000 0000000 use hyper::{body::HttpBody, Client};
use hyperlocal::{UnixClientExt, 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::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(())
}
hyperlocal-0.8.0/examples/server.rs 0100644 0001751 0000164 00000001245 13772450027 0015544 0 ustar 0000000 0000000 use hyper::{
service::{make_service_fn, service_fn},
Body, Response, Server,
};
use hyperlocal::UnixServerExt;
use std::{error::Error, fs, path::Path};
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(())
}
hyperlocal-0.8.0/rustfmt.toml 0100644 0001751 0000164 00000000533 13772450027 0014452 0 ustar 0000000 0000000 # 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#format_code_in_doc_comments
format_code_in_doc_comments = true hyperlocal-0.8.0/src/client.rs 0100644 0001751 0000164 00000007473 13772450027 0014476 0 ustar 0000000 0000000 use futures_util::future::BoxFuture;
use hex::FromHex;
use hyper::{
client::connect::{Connected, Connection},
service::Service,
Body, Client, Uri,
};
use pin_project::pin_project;
use std::{
io,
path::{Path, PathBuf},
pin::Pin,
task::{Context, Poll},
};
use tokio::io::ReadBuf;
#[pin_project]
#[derive(Debug)]
pub struct UnixStream {
#[pin]
unix_stream: tokio::net::UnixStream,
}
impl UnixStream {
async fn connect
(path: P) -> std::io::Result
where
P: AsRef,
{
let unix_stream = tokio::net::UnixStream::connect(path).await?;
Ok(Self { unix_stream })
}
}
impl tokio::io::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)
}
}
impl tokio::io::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)
}
}
/// the `[UnixConnector]` can be used to construct a `[hyper::Client]` which can
/// speak to a unix domain socket.
///
/// # Example
/// ```
/// use hyper::{Client, Body};
/// use hyperlocal::UnixConnector;
///
/// let connector = UnixConnector;
/// let client: Client = Client::builder().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 = std::io::Error;
type Future = BoxFuture<'static, Result>;
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",
))
}
}
/// Extention 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 hyper::Client;
/// use hyperlocal::UnixClientExt;
///
/// let client = Client::unix();
/// ```
fn unix() -> Client {
Client::builder().build(UnixConnector)
}
}
impl UnixClientExt for Client {}
hyperlocal-0.8.0/src/lib.rs 0100644 0001751 0000164 00000002020 13772450027 0013745 0 ustar 0000000 0000000 #![deny(
missing_debug_implementations,
unreachable_pub,
rust_2018_idioms,
missing_docs
)]
//! `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.
//!
//! # 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;
hyperlocal-0.8.0/src/server.rs 0100644 0001751 0000164 00000005332 13772450027 0014516 0 ustar 0000000 0000000 use std::{io, path::Path};
use hyper::server::{Builder, Server};
use conn::SocketIncoming;
pub(crate) mod conn {
use futures_util::ready;
use hyper::server::accept::Accept;
use pin_project::pin_project;
use std::{
io,
path::Path,
pin::Pin,
task::{Context, Poll},
};
use tokio::net::{UnixListener, UnixStream};
/// A stream of connections from binding to a socket.
#[pin_project]
#[derive(Debug)]
pub struct SocketIncoming {
listener: UnixListener,
}
impl SocketIncoming {
/// Creates a new `SocketIncoming` binding to provided socket path.
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