usher 0.3.0

Parameterized routing for generic resources in Rust
Documentation
use std::net::SocketAddr;
use std::sync::Arc;

use http_body_util::Full;
use hyper::body::{Bytes, Incoming};
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::{Error, Request, Response, StatusCode};
use hyper_util::rt::TokioIo;
use tokio::net::TcpListener;
use usher::capture::{find_capture, Captures};
use usher::http::HttpRouter;
use usher::prelude::*;

/// Represents a boxed function which receives a request/params and returns a response.
type Callee = Box<dyn Fn(Request<Incoming>, Captures) -> Response<Full<Bytes>> + Send + Sync>;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    // Create our address to bind to, localhost:3000
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));

    // We create a TcpListener and bind it to 127.0.0.1:3000
    let listener = TcpListener::bind(addr).await?;

    // Just like in a normal Router, we provide our parsers at startup.
    let mut router: HttpRouter<Callee> =
        HttpRouter::new(vec![Box::new(DynamicParser), Box::new(StaticParser)]);

    // This will echo the provided name back to the caller.
    router.get(
        "/:name",
        Box::new(|req, params| {
            let path = req.uri().path();
            let name = find_capture(path, &params, "name").unwrap();

            Response::new(format!("Hello, {}!\n", name).into())
        }),
    );

    // Wrap inside an Arc to avoid large clones.
    let router = Arc::new(router);

    loop {
        // Accept the next incoming connection.
        let (stream, _) = listener.accept().await?;
        let io = TokioIo::new(stream);

        // Construct our Hyper server.
        let service = service_fn(|req: Request<Incoming>| {
            // We need a "clone" of the router.
            let router = router.clone();

            async move {
                // First we need to extract the method and path to use for the
                // actual handler lookup as it uses a combination of both values.
                let method = req.method();
                let path = req.uri().path();

                // Then we delegate to a hander when possible.
                let response = match router.handler(method, path) {
                    // In this case, invoke the handler and pass through the
                    // request instance and the captures associated with it.
                    //
                    // In this case we pass through the captures as they're
                    // generated by default, but this might be where you wish
                    // to turn them into something like a `HashMap` for access.
                    Some((handler, captures)) => handler(req, captures),

                    // If no handler matches, we generate a 404 response to
                    // state so back to the caller. This happens when either
                    // the HTTP method or the path is wrong during matching.
                    None => {
                        let mut response = Response::new(Full::new(Bytes::new()));
                        *response.status_mut() = StatusCode::NOT_FOUND;
                        response
                    }
                };

                // pass it back to the service_fn
                Ok::<_, Error>(response)
            }
        });

        if let Err(err) = http1::Builder::new().serve_connection(io, service).await {
            println!("Error serving connection: {:?}", err);
        }
    }
}