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
10pub 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 extensions: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
24 parsed_body: Option<ParsedBody>,
26}
27
28enum ParsedBody {
29 Json(Json),
30 Form(Vec<(String, String)>),
31 None,
32}
33
34impl Request {
35 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 pub fn path(&self) -> &str {
62 &self.path
63 }
64
65 pub fn target(&self) -> &str {
67 &self.target
68 }
69
70 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 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 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 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 pub fn forwarded_host(&self) -> Option<&str> {
179 self.extension::<crate::trusted_proxies::Forwarded>()?.host.as_deref()
180 }
181
182 pub fn forwarded_port(&self) -> Option<u16> {
184 self.extension::<crate::trusted_proxies::Forwarded>()?.port
185 }
186
187 pub fn param(&self, name: &str) -> Option<&str> {
190 self.params.get(name).map(String::as_str)
191 }
192
193 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 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 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 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 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 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 pub fn extend<T: Send + Sync + 'static>(&mut self, value: T) {
337 self.extensions.insert(TypeId::of::<T>(), Box::new(value));
338 }
339
340 pub fn api_version(&self) -> Option<&str> {
344 self.extension::<crate::versioning::ApiVersion>().map(|v| v.0.as_str())
345 }
346
347 pub fn request_id(&self) -> Option<&str> {
350 self.extension::<crate::request_id::Assigned>().map(|id| id.0.as_str())
351 }
352
353 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 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 #[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 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 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 #[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 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 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 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 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}