File: hyper.rs

package info (click to toggle)
rust-matchit 0.8.6-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 308 kB
  • sloc: makefile: 2
file content (106 lines) | stat: -rw-r--r-- 3,389 bytes parent folder | download
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);
            }
        });
    }
}