1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
|
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use http_body_util::Full;
use hyper::body::{Bytes, Incoming};
use hyper::server::conn::http1::Builder as ConnectionBuilder;
use hyper::{Method, Request, Response, StatusCode};
use hyper_util::rt::TokioIo;
use tokio::net::TcpListener;
use tower::service_fn;
use tower::util::BoxCloneService;
use tower::Service as _;
type Body = Full<Bytes>;
// GET /
async fn index(_req: Request<Incoming>) -> hyper::Result<Response<Body>> {
Ok(Response::new(Body::from("Hello, world!")))
}
// GET /blog
async fn blog(_req: Request<Incoming>) -> hyper::Result<Response<Body>> {
Ok(Response::new(Body::from("...")))
}
// 404 handler
async fn not_found(_req: Request<Incoming>) -> hyper::Result<Response<Body>> {
Ok(Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Body::default())
.unwrap())
}
// We can use `BoxCloneService` to erase the type of each handler service.
//
// We still need a `Mutex` around each service because `BoxCloneService` doesn't
// require the service to implement `Sync`.
type Service = Mutex<BoxCloneService<Request<Incoming>, Response<Body>, hyper::Error>>;
// We use a `HashMap` to hold a `Router` for each HTTP method. This allows us
// to register the same route for multiple methods.
type Router = HashMap<Method, matchit::Router<Service>>;
async fn route(router: Arc<Router>, req: Request<Incoming>) -> hyper::Result<Response<Body>> {
// find the subrouter for this request method
let Some(router) = router.get(req.method()) else {
// if there are no routes for this method, respond with 405 Method Not Allowed
return Ok(Response::builder()
.status(StatusCode::METHOD_NOT_ALLOWED)
.body(Body::default())
.unwrap());
};
// find the service for this request path
let Ok(found) = router.at(req.uri().path()) else {
// if we there is no matching service, call the 404 handler
return not_found(req).await;
};
// lock the service for a very short time, just to clone the service
let mut service = found.value.lock().unwrap().clone();
service.call(req).await
}
#[tokio::main]
async fn main() {
// Create a router and register our routes.
let mut router = Router::new();
// GET / => `index`
router
.entry(Method::GET)
.or_default()
.insert("/", BoxCloneService::new(service_fn(index)).into())
.unwrap();
// GET /blog => `blog`
router
.entry(Method::GET)
.or_default()
.insert("/blog", BoxCloneService::new(service_fn(blog)).into())
.unwrap();
let listener = TcpListener::bind(("127.0.0.1", 3000)).await.unwrap();
// boilerplate for the hyper service
let router = Arc::new(router);
loop {
let router = router.clone();
let (tcp, _) = listener.accept().await.unwrap();
tokio::task::spawn(async move {
if let Err(err) = ConnectionBuilder::new()
.serve_connection(
TokioIo::new(tcp),
hyper::service::service_fn(|request| async {
route(router.clone(), request).await
}),
)
.await
{
println!("Error serving connection: {:?}", err);
}
});
}
}
|