Skip to main content

homepage/
homepage.rs

1use std::net::SocketAddr;
2use std::sync::atomic::{AtomicU64, Ordering};
3use std::time::Instant;
4
5use rustio_core::auth::{authenticate, require_admin, require_auth};
6use rustio_core::defaults::with_defaults;
7use rustio_core::{resolve, text, Error, Next, Request, Response, Router, Server};
8
9#[derive(Debug)]
10struct RequestId(u64);
11
12static REQUEST_COUNTER: AtomicU64 = AtomicU64::new(0);
13
14async fn request_id(mut req: Request, next: Next) -> Result<Response, Error> {
15    let id = REQUEST_COUNTER.fetch_add(1, Ordering::Relaxed);
16    req.ctx_mut().insert(RequestId(id));
17    let mut resp = resolve(next.run(req).await);
18    if let Ok(header) = format!("req-{id}").parse() {
19        resp.headers_mut().insert("x-request-id", header);
20    }
21    Ok(resp)
22}
23
24async fn logger(req: Request, next: Next) -> Result<Response, Error> {
25    let method = req.method().clone();
26    let path = req.uri().path().to_owned();
27    let id = req.ctx().get::<RequestId>().map(|r| r.0);
28    let user = rustio_core::auth::identity(req.ctx()).map(|i| i.user_id.clone());
29    let started = Instant::now();
30    let result = next.run(req).await;
31    let status = match &result {
32        Ok(resp) => resp.status().as_u16(),
33        Err(err) => err.status(),
34    };
35    let id_display = id.map(|i| format!("req-{i}")).unwrap_or_else(|| "-".into());
36    let user_display = user.unwrap_or_else(|| "-".into());
37    eprintln!(
38        "[{:>3}] {:>4} {} id={} user={} ({:?})",
39        status,
40        method,
41        path,
42        id_display,
43        user_display,
44        started.elapsed()
45    );
46    result
47}
48
49#[tokio::main]
50async fn main() -> std::io::Result<()> {
51    let addr: SocketAddr = ([127, 0, 0, 1], 3000).into();
52    let router = with_defaults(Router::new())
53        .get("/whoami", |req, _params| async move {
54            let id = req
55                .ctx()
56                .get::<RequestId>()
57                .map(|r| r.0.to_string())
58                .unwrap_or_else(|| "unknown".into());
59            Ok::<Response, Error>(text(format!("your request id is req-{id}\n")))
60        })
61        .get("/me", |req, _params| async move {
62            let id = require_auth(req.ctx())?;
63            Ok::<Response, Error>(text(format!("hello {}\n", id.user_id)))
64        })
65        .get("/admin-only", |req, _params| async move {
66            let id = require_admin(req.ctx())?;
67            Ok::<Response, Error>(text(format!("hello admin {}\n", id.user_id)))
68        })
69        .get("/crash", |_req, _params| async {
70            Err::<Response, Error>(Error::Internal("simulated failure".into()))
71        })
72        .get("/unauth", |_req, _params| async {
73            Err::<Response, Error>(Error::Unauthorized)
74        })
75        .wrap(request_id)
76        .wrap(authenticate)
77        .wrap(logger);
78    Server::bind(addr).serve_router(router).await
79}