Skip to main content

rustlavel_http/
request.rs

1use crate::cookie;
2use crate::headers::Headers;
3use crate::method::Method;
4use crate::url;
5use rustlavel_core::{Config, Context, Json};
6use std::any::{Any, TypeId};
7use std::collections::{BTreeMap, HashMap};
8use std::net::SocketAddr;
9
10/// An incoming request, already parsed and matched against a route.
11pub struct Request {
12    pub(crate) method: Method,
13    pub(crate) target: String,
14    pub(crate) path: String,
15    pub(crate) query: Vec<(String, String)>,
16    pub(crate) headers: Headers,
17    pub(crate) body: Vec<u8>,
18    pub(crate) params: BTreeMap<String, String>,
19    pub(crate) context: Context,
20    pub(crate) peer: Option<SocketAddr>,
21    pub(crate) route: Option<String>,
22    /// Values attached by middleware — the authenticated user, a request id.
23    extensions: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
24    /// Parsed lazily on first access, since most requests never read a body.
25    parsed_body: Option<ParsedBody>,
26}
27
28enum ParsedBody {
29    Json(Json),
30    Form(Vec<(String, String)>),
31    None,
32}
33
34impl Request {
35    /// Build a request directly. This is what the test client and the server
36    /// parser both go through.
37    pub fn new(method: Method, target: impl Into<String>) -> Self {
38        let target = target.into();
39        let (path, query) = url::split_target(&target);
40        Request {
41            method,
42            path: path.to_string(),
43            query: url::parse_query(query),
44            target,
45            headers: Headers::new(),
46            body: Vec::new(),
47            params: BTreeMap::new(),
48            context: Context::default(),
49            peer: None,
50            route: None,
51            extensions: HashMap::new(),
52            parsed_body: None,
53        }
54    }
55
56    pub fn method(&self) -> Method {
57        self.method
58    }
59
60    /// The path with no query string: `/users/7`.
61    pub fn path(&self) -> &str {
62        &self.path
63    }
64
65    /// The raw request target, query string included.
66    pub fn target(&self) -> &str {
67        &self.target
68    }
69
70    /// The pattern this request matched: `/users/{id}`. Useful for metrics
71    /// that must not explode into one series per id.
72    pub fn route(&self) -> Option<&str> {
73        self.route.as_deref()
74    }
75
76    pub fn headers(&self) -> &Headers {
77        &self.headers
78    }
79
80    pub fn headers_mut(&mut self) -> &mut Headers {
81        &mut self.headers
82    }
83
84    pub fn header(&self, name: &str) -> Option<&str> {
85        self.headers.get(name)
86    }
87
88    pub fn body(&self) -> &[u8] {
89        &self.body
90    }
91
92    pub fn body_string(&self) -> String {
93        String::from_utf8_lossy(&self.body).into_owned()
94    }
95
96    pub fn context(&self) -> &Context {
97        &self.context
98    }
99
100    pub fn config(&self) -> &Config {
101        self.context.config()
102    }
103
104    /// A service for this request: `req.state::<Database>()`.
105    ///
106    /// **This request's own copy first, then the application's.** Middleware
107    /// can put a `T` on the request with [`extend`](Request::extend), and every
108    /// handler that asks for a `T` from then on gets that one instead of the
109    /// application-wide one. Nothing else changes: a request that was given
110    /// nothing gets what `main.rs` registered, which is every request in an
111    /// application that has no such middleware.
112    ///
113    /// The case this exists for is one connection per tenant. An application
114    /// serving a holding company and its subsidiaries resolves the tenant from
115    /// the host or the signed-in user, opens or reuses that tenant's
116    /// `Database`, and calls `req.extend(db)`. Every controller underneath goes
117    /// on saying `req.state::<Database>()` and is talking to the right database
118    /// without knowing that tenants exist. The alternative — threading a
119    /// `tenant::db(&req).await?` through every handler — is the same program
120    /// written five hundred more times, and it only takes one missed call site
121    /// to read another company's data.
122    ///
123    /// **This is a lookup order, not discovery.** The rule against runtime
124    /// magic is about things that happen with no line you can find: reflection,
125    /// auto-registration, a scan of a directory. The middleware that overrides
126    /// a service is an ordinary explicit line in `main.rs`, and the rule here
127    /// is one sentence long. What it must not become is a way for a value to
128    /// appear from nowhere.
129    pub fn state<T: Send + Sync + 'static>(&self) -> Option<&T> {
130        self.extension::<T>().or_else(|| self.context.state::<T>())
131    }
132
133    pub fn peer_addr(&self) -> Option<SocketAddr> {
134        self.peer
135    }
136
137    /// The client IP, honouring `X-Forwarded-For` when behind a proxy.
138    /// The client's address.
139    ///
140    /// The address that opened the socket, unless
141    /// [`TrustProxies`](crate::trusted_proxies::TrustProxies) ran and the
142    /// connection came from a proxy on its list — then it is the client
143    /// address that proxy reported.
144    ///
145    /// It deliberately does *not* read `X-Forwarded-For` on its own. A header
146    /// is something any client can send, so believing one unconditionally
147    /// does not reveal the client's address, it lets the client choose one —
148    /// and everything keyed on this, the rate limiter included, would be
149    /// defeated by a header.
150    pub fn ip(&self) -> Option<String> {
151        if let Some(forwarded) = self.extension::<crate::trusted_proxies::Forwarded>()
152            && let Some(ip) = &forwarded.ip
153        {
154            return Some(ip.clone());
155        }
156        self.peer.map(|addr| addr.ip().to_string())
157    }
158
159    /// `https` when a trusted proxy said the client used TLS, or the
160    /// connection itself did; `http` otherwise.
161    ///
162    /// A proxy that terminates TLS forwards a plain request, so without this
163    /// an application behind one would build `http://` links for a site that
164    /// is entirely `https://`.
165    pub fn scheme(&self) -> &str {
166        match self.extension::<crate::trusted_proxies::Forwarded>().and_then(|f| f.scheme.as_deref())
167        {
168            Some(scheme) => scheme,
169            None => "http",
170        }
171    }
172
173    pub fn is_secure(&self) -> bool {
174        self.scheme() == "https"
175    }
176
177    /// The `Host` a trusted proxy said the client asked for.
178    pub fn forwarded_host(&self) -> Option<&str> {
179        self.extension::<crate::trusted_proxies::Forwarded>()?.host.as_deref()
180    }
181
182    /// The port a trusted proxy said the client connected to.
183    pub fn forwarded_port(&self) -> Option<u16> {
184        self.extension::<crate::trusted_proxies::Forwarded>()?.port
185    }
186
187    /// A route parameter: for `/users/{id}` matching `/users/7`, `param("id")`
188    /// is `"7"`.
189    pub fn param(&self, name: &str) -> Option<&str> {
190        self.params.get(name).map(String::as_str)
191    }
192
193    /// A route parameter parsed into a type, so a handler can ask for an id as
194    /// a number without unwrapping twice.
195    pub fn param_as<T: std::str::FromStr>(&self, name: &str) -> Option<T> {
196        self.param(name)?.parse().ok()
197    }
198
199    pub fn params(&self) -> &BTreeMap<String, String> {
200        &self.params
201    }
202
203    pub fn query(&self, name: &str) -> Option<&str> {
204        self.query.iter().find(|(key, _)| key == name).map(|(_, value)| value.as_str())
205    }
206
207    /// Every value for a repeated query key: `?tag=a&tag=b`.
208    pub fn query_all(&self, name: &str) -> Vec<&str> {
209        self.query
210            .iter()
211            .filter(|(key, _)| key == name)
212            .map(|(_, value)| value.as_str())
213            .collect()
214    }
215
216    pub fn query_pairs(&self) -> &[(String, String)] {
217        &self.query
218    }
219
220    pub fn content_type(&self) -> Option<&str> {
221        self.headers.content_type()
222    }
223
224    pub fn is_json(&self) -> bool {
225        self.content_type().is_some_and(|ct| ct.ends_with("json"))
226    }
227
228    /// Whether the client wants JSON back — an API client or a fetch() call.
229    pub fn wants_json(&self) -> bool {
230        self.is_json()
231            || self.headers.get("accept").is_some_and(|a| a.contains("application/json"))
232            || self.headers.get("x-requested-with").is_some_and(|x| x == "XMLHttpRequest")
233    }
234
235    /// The body parsed as JSON, or `None` if it is absent or malformed.
236    pub fn json(&mut self) -> Option<&Json> {
237        self.parse_body();
238        match self.parsed_body.as_ref()? {
239            ParsedBody::Json(value) => Some(value),
240            _ => None,
241        }
242    }
243
244    /// One input value, looked up in the JSON body, then the form body, then
245    /// the query string — the resolution order of Laravel's `$request->input()`.
246    pub fn input(&mut self, name: &str) -> Option<String> {
247        self.parse_body();
248        match self.parsed_body.as_ref() {
249            Some(ParsedBody::Json(value)) => {
250                if let Some(found) = value.get(name) {
251                    return Some(match found {
252                        Json::String(s) => s.clone(),
253                        Json::Null => String::new(),
254                        other => other.to_string(),
255                    });
256                }
257            }
258            Some(ParsedBody::Form(pairs)) => {
259                if let Some((_, value)) = pairs.iter().find(|(key, _)| key == name) {
260                    return Some(value.clone());
261                }
262            }
263            _ => {}
264        }
265        self.query(name).map(str::to_string)
266    }
267
268    /// All decoded form fields of a `application/x-www-form-urlencoded` body.
269    /// Every value submitted under one name.
270    ///
271    /// A form with several checkboxes sharing a name — `roles[]`, which is how
272    /// PHP and every HTML tutorial spell it — sends the name once per ticked
273    /// box. [`Request::input`] returns only the first, which for a checkbox
274    /// group silently means "whichever happened to come first".
275    ///
276    /// A trailing `[]` is optional here: `inputs("roles")` and
277    /// `inputs("roles[]")` both find them, because which one a form used is a
278    /// detail of the markup rather than a decision the handler should have to
279    /// track.
280    pub fn inputs(&mut self, name: &str) -> Vec<String> {
281        let bare = name.strip_suffix("[]").unwrap_or(name).to_string();
282        let bracketed = format!("{bare}[]");
283
284        let from_query: Vec<String> = self
285            .query_pairs()
286            .iter()
287            .filter(|(key, _)| *key == bare || *key == bracketed)
288            .map(|(_, value)| value.clone())
289            .collect();
290
291        let mut values = from_query;
292        values.extend(
293            self.form()
294                .iter()
295                .filter(|(key, _)| *key == bare || *key == bracketed)
296                .map(|(_, value)| value.clone()),
297        );
298        values
299    }
300
301    pub fn form(&mut self) -> &[(String, String)] {
302        self.parse_body();
303        match self.parsed_body.as_ref() {
304            Some(ParsedBody::Form(pairs)) => pairs,
305            _ => &[],
306        }
307    }
308
309    fn parse_body(&mut self) {
310        if self.parsed_body.is_some() {
311            return;
312        }
313        let parsed = match self.headers.content_type() {
314            _ if self.body.is_empty() => ParsedBody::None,
315            Some(ct) if ct.ends_with("json") => match std::str::from_utf8(&self.body) {
316                Ok(text) => Json::parse(text).map_or(ParsedBody::None, ParsedBody::Json),
317                Err(_) => ParsedBody::None,
318            },
319            Some("application/x-www-form-urlencoded") => {
320                ParsedBody::Form(url::parse_query(&String::from_utf8_lossy(&self.body)))
321            }
322            _ => ParsedBody::None,
323        };
324        self.parsed_body = Some(parsed);
325    }
326
327    pub fn cookies(&self) -> BTreeMap<String, String> {
328        self.headers.get("cookie").map(cookie::parse_header).unwrap_or_default()
329    }
330
331    pub fn cookie(&self, name: &str) -> Option<String> {
332        self.cookies().remove(name)
333    }
334
335    /// Attach a value for later middleware or the handler to read.
336    pub fn extend<T: Send + Sync + 'static>(&mut self, value: T) {
337        self.extensions.insert(TypeId::of::<T>(), Box::new(value));
338    }
339
340    /// The API version this request is for — from the route's
341    /// [`Router::version`](crate::Router::version) group, or from the
342    /// [`VersionHeader`](crate::versioning::VersionHeader) middleware.
343    pub fn api_version(&self) -> Option<&str> {
344        self.extension::<crate::versioning::ApiVersion>().map(|v| v.0.as_str())
345    }
346
347    /// The identifier the [`RequestId`](crate::request_id::RequestId)
348    /// middleware assigned, for log lines and error reports.
349    pub fn request_id(&self) -> Option<&str> {
350        self.extension::<crate::request_id::Assigned>().map(|id| id.0.as_str())
351    }
352
353    /// Read a value attached by earlier middleware.
354    pub fn extension<T: Send + Sync + 'static>(&self) -> Option<&T> {
355        self.extensions.get(&TypeId::of::<T>()).and_then(|value| value.downcast_ref::<T>())
356    }
357
358    // --- Builders, used by the server, the router, and the test client. ---
359
360    /// Set the address the request arrived from, as the server does.
361    pub fn with_peer(mut self, peer: SocketAddr) -> Self {
362        self.peer = Some(peer);
363        self
364    }
365
366    pub fn with_header(mut self, name: &str, value: impl Into<String>) -> Self {
367        self.headers.set(name, value);
368        self
369    }
370
371    pub fn with_body(mut self, body: impl Into<Vec<u8>>) -> Self {
372        self.body = body.into();
373        self.parsed_body = None;
374        self
375    }
376
377    pub fn with_json(self, value: Json) -> Self {
378        self.with_header("content-type", "application/json").with_body(value.to_string())
379    }
380
381    pub fn with_form(self, fields: &[(&str, &str)]) -> Self {
382        let encoded = fields
383            .iter()
384            .map(|(key, value)| format!("{}={}", url::encode(key), url::encode(value)))
385            .collect::<Vec<_>>()
386            .join("&");
387        self.with_header("content-type", "application/x-www-form-urlencoded").with_body(encoded)
388    }
389
390    pub fn with_context(mut self, context: Context) -> Self {
391        self.context = context;
392        self
393    }
394
395    pub(crate) fn set_params(&mut self, params: BTreeMap<String, String>) {
396        self.params = params;
397    }
398}
399
400impl std::fmt::Debug for Request {
401    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
402        f.debug_struct("Request")
403            .field("method", &self.method)
404            .field("target", &self.target)
405            .field("headers", &self.headers)
406            .field("body_len", &self.body.len())
407            .finish()
408    }
409}
410
411#[cfg(test)]
412mod tests {
413    use crate::middleware::Next;
414    use crate::response::Response;
415    use crate::router::Router;
416    use crate::testing::TestClient;
417    use crate::BoxFuture;
418    use rustlavel_core::Context;
419
420    /// The whole of database-per-tenant multi-tenancy, from a controller's
421    /// point of view: it asks for the same service and gets this request's one.
422    #[tokio::test]
423    async fn a_service_put_on_the_request_is_what_a_handler_gets() {
424        #[derive(Debug, PartialEq)]
425        struct Db(&'static str);
426
427        let mut router = Router::new();
428        // Stands in for the tenancy middleware: resolve the tenant, open or
429        // reuse its connection, put it on the request.
430        router.middleware(|mut request: Request, next: Next| {
431            Box::pin(async move {
432                if request.header("x-tenant").is_some() {
433                    request.extend(Db("tenant"));
434                }
435                next.run(request).await
436            }) as BoxFuture<Response>
437        });
438        // The handler is written as if tenants did not exist.
439        router.get("/", |req: Request| async move {
440            req.state::<Db>().map(|db| db.0).unwrap_or("none").to_string()
441        });
442
443        let client = TestClient::new(router)
444            .with_context(Context::builder().state(Db("application")).build());
445
446        assert_eq!(client.get("/").await.body(), "application");
447        assert_eq!(
448            client
449                .send(Request::new(Method::Get, "/").with_header("x-tenant", "acme"))
450                .await
451                .body(),
452            "tenant"
453        );
454    }
455
456    /// And the override lasts exactly one request — the next one sees the
457    /// application's service again. A tenant connection leaking into the next
458    /// visitor's request would be the worst possible failure of this feature.
459    #[tokio::test]
460    async fn an_override_does_not_outlive_its_request() {
461        struct Db(&'static str);
462
463        let mut router = Router::new();
464        router.middleware(|mut request: Request, next: Next| {
465            Box::pin(async move {
466                if request.target().starts_with("/tenant") {
467                    request.extend(Db("tenant"));
468                }
469                next.run(request).await
470            }) as BoxFuture<Response>
471        });
472        router.get("/tenant", |req: Request| async move {
473            req.state::<Db>().map(|db| db.0).unwrap_or("none").to_string()
474        });
475        router.get("/plain", |req: Request| async move {
476            req.state::<Db>().map(|db| db.0).unwrap_or("none").to_string()
477        });
478
479        let client = TestClient::new(router)
480            .with_context(Context::builder().state(Db("application")).build());
481
482        assert_eq!(client.get("/tenant").await.body(), "tenant");
483        assert_eq!(client.get("/plain").await.body(), "application");
484        assert_eq!(client.get("/tenant").await.body(), "tenant");
485    }
486    #[test]
487    fn inputs_collects_every_value_under_one_name() {
488        let mut request = Request::new(Method::Post, "/roles?scope=a&scope=b")
489            .with_body(b"roles[]=admin&roles[]=editor&name=Ada".to_vec())
490            .with_header("content-type", "application/x-www-form-urlencoded");
491
492        // The bracketed and bare spellings find the same fields, because which
493        // one the markup used is not the handler's problem.
494        assert_eq!(request.inputs("roles"), vec!["admin", "editor"]);
495        assert_eq!(request.inputs("roles[]"), vec!["admin", "editor"]);
496        assert_eq!(request.inputs("name"), vec!["Ada"]);
497        assert!(request.inputs("missing").is_empty());
498        // Query and body both count, query first.
499        assert_eq!(request.inputs("scope"), vec!["a", "b"]);
500    }
501
502    use super::*;
503
504    #[test]
505    fn splits_path_and_query() {
506        let request = Request::new(Method::Get, "/users?page=2&tag=a&tag=b");
507
508        assert_eq!(request.path(), "/users");
509        assert_eq!(request.query("page"), Some("2"));
510        assert_eq!(request.query_all("tag"), ["a", "b"]);
511        assert_eq!(request.query("missing"), None);
512    }
513
514    #[test]
515    fn input_prefers_the_body_over_the_query() {
516        let mut request = Request::new(Method::Post, "/users?name=from-query")
517            .with_json(Json::object([("name", "from-body".into())]));
518
519        assert_eq!(request.input("name").as_deref(), Some("from-body"));
520        // A key absent from the body still falls through to the query string.
521        assert_eq!(request.input("missing"), None);
522    }
523
524    #[test]
525    fn reads_urlencoded_form_bodies() {
526        let mut request =
527            Request::new(Method::Post, "/login").with_form(&[("email", "a@b.com"), ("password", "s e c")]);
528
529        assert_eq!(request.input("email").as_deref(), Some("a@b.com"));
530        assert_eq!(request.input("password").as_deref(), Some("s e c"));
531        assert_eq!(request.form().len(), 2);
532    }
533
534    #[test]
535    fn parses_cookies_from_the_header() {
536        let request = Request::new(Method::Get, "/").with_header("cookie", "session=abc; theme=dark");
537
538        assert_eq!(request.cookie("session").as_deref(), Some("abc"));
539        assert_eq!(request.cookies().len(), 2);
540    }
541
542    #[test]
543    fn extensions_round_trip_through_middleware() {
544        struct User(&'static str);
545        let mut request = Request::new(Method::Get, "/");
546        request.extend(User("ada"));
547
548        assert_eq!(request.extension::<User>().unwrap().0, "ada");
549    }
550
551    #[test]
552    fn a_forwarded_header_alone_does_not_decide_the_client_address() {
553        // This test used to assert the opposite, which was a way for any
554        // client to choose its own address and so its own rate limit bucket.
555        // The header is only evidence once `TrustProxies` has established that
556        // the connection came from a proxy that would have written it.
557        let request = Request::new(Method::Get, "/")
558            .with_peer("198.51.100.7:44321".parse().unwrap())
559            .with_header("x-forwarded-for", "203.0.113.9, 10.0.0.1");
560        assert_eq!(request.ip().as_deref(), Some("198.51.100.7"));
561        assert_eq!(request.scheme(), "http");
562        assert!(!request.is_secure());
563    }
564
565    #[test]
566    fn detects_clients_that_want_json() {
567        let api = Request::new(Method::Get, "/").with_header("accept", "application/json");
568        let browser = Request::new(Method::Get, "/").with_header("accept", "text/html");
569
570        assert!(api.wants_json());
571        assert!(!browser.wants_json());
572    }
573}