an http based roguelike rpg about jelqing
| 1 | use std::{collections::HashMap, fmt::Write as _, time::Instant}; |
| 2 | |
| 3 | use axum::{ |
| 4 | extract::{FromRequest, FromRequestParts, Query, RawPathParams, Request}, |
| 5 | http::{HeaderMap, HeaderName, Method, Uri, Version, header}, |
| 6 | response::Response, |
| 7 | }; |
| 8 | use hyper::upgrade::OnUpgrade; |
| 9 | |
| 10 | /// Everything a handler may look at, gathered once per request. |
| 11 | pub struct Ctx { |
| 12 | method: Method, |
| 13 | uri: Uri, |
| 14 | version: Version, |
| 15 | headers: HeaderMap, |
| 16 | query: HashMap<String, String>, |
| 17 | params: HashMap<String, String>, |
| 18 | now: Instant, |
| 19 | upgrade: Option<OnUpgrade>, |
| 20 | } |
| 21 | |
| 22 | impl Ctx { |
| 23 | pub fn header(&self, name: HeaderName) -> Option<&str> { |
| 24 | self.headers.get(name).and_then(|v| v.to_str().ok()) |
| 25 | } |
| 26 | |
| 27 | pub fn cookie(&self, name: &str) -> Option<&str> { |
| 28 | self.headers |
| 29 | .get_all(header::COOKIE) |
| 30 | .iter() |
| 31 | .filter_map(|v| v.to_str().ok()) |
| 32 | .flat_map(|v| v.split(';')) |
| 33 | .filter_map(|pair| pair.trim().split_once('=')) |
| 34 | .find(|(key, _)| *key == name) |
| 35 | .map(|(_, value)| value) |
| 36 | } |
| 37 | |
| 38 | pub fn param(&self, name: &str) -> &str { |
| 39 | self.params.get(name).map_or("", String::as_str) |
| 40 | } |
| 41 | |
| 42 | pub fn query(&self, name: &str) -> Option<&str> { |
| 43 | self.query.get(name).map(String::as_str) |
| 44 | } |
| 45 | |
| 46 | pub fn method(&self) -> &Method { |
| 47 | &self.method |
| 48 | } |
| 49 | |
| 50 | pub fn now(&self) -> Instant { |
| 51 | self.now |
| 52 | } |
| 53 | |
| 54 | pub fn take_upgrade(&mut self) -> Option<OnUpgrade> { |
| 55 | self.upgrade.take() |
| 56 | } |
| 57 | |
| 58 | /// The request as it arrived, which is what TRACE sends back. |
| 59 | pub fn echo(&self) -> String { |
| 60 | let mut message = format!("{} {} {:?}\r\n", self.method, self.uri, self.version); |
| 61 | for (name, value) in &self.headers { |
| 62 | let _ = write!( |
| 63 | message, |
| 64 | "{name}: {}\r\n", |
| 65 | value.to_str().unwrap_or("<binary>") |
| 66 | ); |
| 67 | } |
| 68 | message.push_str("\r\n"); |
| 69 | message |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | impl<S: Send + Sync> FromRequest<S> for Ctx { |
| 74 | type Rejection = Response; |
| 75 | |
| 76 | async fn from_request(mut request: Request, state: &S) -> Result<Self, Self::Rejection> { |
| 77 | let upgrade = request.extensions_mut().remove::<OnUpgrade>(); |
| 78 | let (mut parts, _) = request.into_parts(); |
| 79 | let params = RawPathParams::from_request_parts(&mut parts, state) |
| 80 | .await |
| 81 | .map(|raw| { |
| 82 | raw.iter() |
| 83 | .map(|(k, v)| (k.to_owned(), v.to_owned())) |
| 84 | .collect() |
| 85 | }) |
| 86 | .unwrap_or_default(); |
| 87 | let query = Query::<HashMap<String, String>>::try_from_uri(&parts.uri) |
| 88 | .map(|q| q.0) |
| 89 | .unwrap_or_default(); |
| 90 | Ok(Self { |
| 91 | method: parts.method, |
| 92 | uri: parts.uri, |
| 93 | version: parts.version, |
| 94 | headers: parts.headers, |
| 95 | query, |
| 96 | params, |
| 97 | now: Instant::now(), |
| 98 | upgrade, |
| 99 | }) |
| 100 | } |
| 101 | } |