Skip to content

warlock-labs/hyper-server

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

hyper-server

License Crates.io Docs CI codecov

A high-performance, modular server implementation built on hyper, designed to work seamlessly with axum, tonic, tower, and other tower-compatible frameworks.

Features

  • HTTP/1 and HTTP/2 support
  • TLS/HTTPS through rustls
  • High performance leveraging hyper 1.0
  • Modular architecture based on tokio::net::TcpListener, tokio-rustls::Acceptor, and hyper::server::conn::auto
  • Flexible integration with tower the ecosystem, supporting various backends:
    • axum
    • tonic
    • Any hyper::service::Service, tower::Service, or tower::Layer composition

Installation

Add this to your Cargo.toml:

[dependencies]
hyper-server = "0.7.0"

Usage

Here's an example of how to use hyper-server with a simple tower lambda service via TCP/TLS/HTTP2 transport:

use std::convert::Infallible;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;

use bytes::Bytes;
use http_body_util::Full;
use hyper::{Request, Response};
use hyper::body::Incoming;
use hyper_util::rt::TokioExecutor;
use hyper_util::server::conn::auto::Builder as HttpConnectionBuilder;
use hyper_util::service::TowerToHyperService;
use rustls::ServerConfig;
use tokio::net::TcpListener;
use tokio_stream::wrappers::TcpListenerStream;
use tower::{Layer, ServiceBuilder};
use tracing::{trace, debug, info};

use hyper_server::{load_certs, load_private_key, serve_http_with_shutdown};

// Define a simple service that responds with "Hello, World!"
async fn hello(_: Request<Incoming>) -> Result<Response<Full<Bytes>>, Infallible> {
    Ok(Response::new(Full::new(Bytes::from("Hello, World!"))))
}

// Define a Custom middleware to add a header to all responses, for example
struct AddHeaderLayer;

impl<S> Layer<S> for AddHeaderLayer {
    type Service = AddHeaderService<S>;

    fn layer(&self, service: S) -> Self::Service {
        AddHeaderService { inner: service }
    }
}

#[derive(Clone)]
struct AddHeaderService<S> {
    inner: S,
}

impl<S, B> tower::Service<Request<B>> for AddHeaderService<S>
where
    S: tower::Service<Request<B>, Response=Response<Full<Bytes>>>,
    S::Future: Send + 'static,
{
    type Response = S::Response;
    type Error = S::Error;
    type Future = std::pin::Pin<
        Box<dyn std::future::Future<Output=Result<Self::Response, Self::Error>> + Send>,
    >;

    fn poll_ready(
        &mut self,
        cx: &mut std::task::Context<'_>,
    ) -> std::task::Poll<Result<(), Self::Error>> {
        self.inner.poll_ready(cx)
    }

    fn call(&mut self, req: Request<B>) -> Self::Future {
        trace!("Adding custom header to response");
        let future = self.inner.call(req);
        Box::pin(async move {
            let mut resp = future.await?;
            resp.headers_mut()
                .insert("X-Custom-Header", "Hello from middleware!".parse().unwrap());
            Ok(resp)
        })
    }
}

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let addr = SocketAddr::from(([127, 0, 0, 1], 8443));
    // 1. Set up the TCP listener
    let listener = TcpListener::bind(addr).await?;
    info!("Listening on https://{}", addr);
    let incoming = TcpListenerStream::new(listener);

    // 2. Create the HTTP connection builder
    let builder = HttpConnectionBuilder::new(TokioExecutor::new());

    // 3. Set up the Tower service with middleware
    let svc = tower::service_fn(hello);
    let svc = ServiceBuilder::new()
        .layer(AddHeaderLayer) // Custom middleware
        .service(svc);

    // 4. Convert the Tower service to a Hyper service
    let svc = TowerToHyperService::new(svc);

    // 5. Set up TLS config
    let certs = load_certs("examples/sample.pem")?;
    let key = load_private_key("examples/sample.rsa")?;

    let mut config = ServerConfig::builder()
        .with_no_client_auth()
        .with_single_cert(certs, key)
        .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
    config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec(), b"http/1.0".to_vec()];
    let tls_config = Arc::new(config);

    // 6. Set up graceful shutdown
    let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel::<()>();

    // Spawn a task to send the shutdown signal after 1 second
    tokio::spawn(async move {
        tokio::time::sleep(Duration::from_secs(1)).await;
        let _ = shutdown_tx.send(());
        debug!("Shutdown signal sent");
    });

    // 7. Start the server
    info!("Starting HTTPS server...");
    serve_http_with_shutdown(
        svc,
        incoming,
        builder,
        Some(tls_config),
        Some(async {
            shutdown_rx.await.ok();
            info!("Shutdown signal received, starting graceful shutdown");
        }),
    )
        .await?;

    info!("Server has shut down");
    // Et voilà!
    // A flexible, high-performance server with custom services, middleware, 
    // http, tls, tcp, and graceful shutdown
    Ok(())
}

For more advanced usage and examples, please refer to, or contribute to, the examples directory.

Architecture

hyper-server provides a layered, composable architecture:

  1. TCP Listening: tokio::net::TcpListener
  2. TLS (optional): rustls::TlsAcceptor
  3. HTTP: hyper_util::server::conn::auto
  4. Service: hyper::service::Service
  5. Middleware: tower::Service
  6. Application: Your choice of tower-compatible framework (axum, tonic, etc.) or custom service implementation

This structure allows for easy customization and extension at each layer. You can integrate your own implementations at any level of the stack, providing maximum flexibility for your specific use case.

Security

hyper-server takes security seriously. We use rustls for TLS support, which provides modern, secure defaults. However, please ensure that you configure your server appropriately for your use case, especially when deploying in production environments.

If you discover any security-related issues, please email team@warlock.xyz instead of using the issue tracker.

API

For detailed API documentation, please refer to the API docs on docs.rs.

Minimum Supported Rust Version

hyper-server's MSRV is 1.80.

Contributing

We welcome contributions to hyper-server! Our contributing guidelines are inspired by the Rule of St. Benedict, emphasizing humility, listening, and community. Before contributing, please familiarize yourself with these principles at The Rule of St. Benedict.

Key points for contributors:

  • Listen first, speak second (Chapter 6)
  • Be humble in your contributions (Chapter 7)
  • Work diligently and carefully (Chapter 48)
  • Treat all code and ideas with respect (Chapter 72)

License

This project is licensed under the MIT License — see the LICENSE file for details.