atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
4b1f5f88827de40c3cc25edaf1c8acea7192d6cf
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git
src/http/ctx.rsRust120 lines3.4 KB
1
use std::{collections::HashMap, fmt::Write as _, time::Instant};
2
3
use axum::{
4
    body::Bytes,
5
    extract::{FromRequest, FromRequestParts, Query, RawPathParams, Request},
6
    http::{HeaderMap, HeaderName, Method, StatusCode, Uri, Version, header},
7
    response::{IntoResponse, Response},
8
};
9
use hyper::upgrade::OnUpgrade;
10
11
use crate::problem::Problem;
12
13
pub const BODY_LIMIT: usize = 64 * 1024;
14
15
/// Everything a handler may look at, gathered once per request.
16
pub struct Ctx {
17
    method: Method,
18
    uri: Uri,
19
    version: Version,
20
    headers: HeaderMap,
21
    query: HashMap<String, String>,
22
    params: HashMap<String, String>,
23
    body: Bytes,
24
    now: Instant,
25
    upgrade: Option<OnUpgrade>,
26
}
27
28
impl Ctx {
29
    pub fn header(&self, name: HeaderName) -> Option<&str> {
30
        self.headers.get(name).and_then(|v| v.to_str().ok())
31
    }
32
33
    pub fn cookie(&self, name: &str) -> Option<&str> {
34
        self.headers
35
            .get_all(header::COOKIE)
36
            .iter()
37
            .filter_map(|v| v.to_str().ok())
38
            .flat_map(|v| v.split(';'))
39
            .filter_map(|pair| pair.trim().split_once('='))
40
            .find(|(key, _)| *key == name)
41
            .map(|(_, value)| value)
42
    }
43
44
    pub fn param(&self, name: &str) -> &str {
45
        self.params.get(name).map_or("", String::as_str)
46
    }
47
48
    pub fn query(&self, name: &str) -> Option<&str> {
49
        self.query.get(name).map(String::as_str)
50
    }
51
52
    pub fn method(&self) -> &Method {
53
        &self.method
54
    }
55
56
    pub fn body(&self) -> &[u8] {
57
        &self.body
58
    }
59
60
    pub fn now(&self) -> Instant {
61
        self.now
62
    }
63
64
    pub fn take_upgrade(&mut self) -> Option<OnUpgrade> {
65
        self.upgrade.take()
66
    }
67
68
    /// The request as it arrived, which is what TRACE sends back.
69
    pub fn echo(&self) -> String {
70
        let mut message = format!("{} {} {:?}\r\n", self.method, self.uri, self.version);
71
        for (name, value) in &self.headers {
72
            let _ = write!(
73
                message,
74
                "{name}: {}\r\n",
75
                value.to_str().unwrap_or("<binary>")
76
            );
77
        }
78
        message.push_str("\r\n");
79
        message
80
    }
81
}
82
83
impl<S: Send + Sync> FromRequest<S> for Ctx {
84
    type Rejection = Response;
85
86
    async fn from_request(mut request: Request, state: &S) -> Result<Self, Self::Rejection> {
87
        let upgrade = request.extensions_mut().remove::<OnUpgrade>();
88
        let (mut parts, body) = request.into_parts();
89
        let params = RawPathParams::from_request_parts(&mut parts, state)
90
            .await
91
            .map(|raw| {
92
                raw.iter()
93
                    .map(|(k, v)| (k.to_owned(), v.to_owned()))
94
                    .collect()
95
            })
96
            .unwrap_or_default();
97
        let query = Query::<HashMap<String, String>>::try_from_uri(&parts.uri)
98
            .map(|q| q.0)
99
            .unwrap_or_default();
100
        let body = axum::body::to_bytes(body, BODY_LIMIT).await.map_err(|_| {
101
            Problem::new(
102
                StatusCode::PAYLOAD_TOO_LARGE,
103
                "body-too-large",
104
                "The request body is larger than anything the server is prepared to receive.",
105
            )
106
            .into_response()
107
        })?;
108
        Ok(Self {
109
            method: parts.method,
110
            uri: parts.uri,
111
            version: parts.version,
112
            headers: parts.headers,
113
            query,
114
            params,
115
            body,
116
            now: Instant::now(),
117
            upgrade,
118
        })
119
    }
120
}