reinhardt_http/request.rs
1mod body;
2mod methods;
3mod params;
4
5use crate::extensions::Extensions;
6use crate::path_params::PathParams;
7use bytes::Bytes;
8use hyper::{HeaderMap, Method, Uri, Version};
9#[cfg(feature = "parsers")]
10use reinhardt_core::parsers::parser::{ParsedData, Parser};
11use std::collections::HashMap;
12use std::collections::HashSet;
13use std::net::{IpAddr, SocketAddr};
14use std::sync::atomic::AtomicBool;
15use std::sync::{Arc, Mutex};
16
17/// Configuration for trusted proxy IPs.
18///
19/// Only proxy headers (X-Forwarded-For, X-Real-IP, X-Forwarded-Proto) from
20/// these IP addresses will be trusted. By default, no proxies are trusted
21/// and the actual connection information is used.
22#[derive(Debug, Clone, Default, PartialEq, Eq)]
23pub struct TrustedProxies {
24 /// Set of trusted proxy IP addresses.
25 /// Only requests originating from these IPs will have their proxy headers honored.
26 trusted_ips: HashSet<IpAddr>,
27}
28
29impl TrustedProxies {
30 /// Create with no trusted proxies (default, most secure).
31 pub fn none() -> Self {
32 Self {
33 trusted_ips: HashSet::new(),
34 }
35 }
36
37 /// Create with a set of trusted proxy IPs.
38 pub fn new(ips: impl IntoIterator<Item = IpAddr>) -> Self {
39 Self {
40 trusted_ips: ips.into_iter().collect(),
41 }
42 }
43
44 /// Check if the given address is a trusted proxy.
45 pub fn is_trusted(&self, addr: &IpAddr) -> bool {
46 self.trusted_ips.contains(addr)
47 }
48
49 /// Check if any proxies are configured.
50 pub fn has_trusted_proxies(&self) -> bool {
51 !self.trusted_ips.is_empty()
52 }
53}
54
55#[derive(Clone)]
56struct ResolvedPathParams(PathParams);
57
58/// HTTP Request representation
59pub struct Request {
60 /// The HTTP method (GET, POST, PUT, etc.).
61 pub method: Method,
62 /// The request URI (path and query string).
63 pub uri: Uri,
64 /// The HTTP protocol version.
65 pub version: Version,
66 /// The request headers.
67 pub headers: HeaderMap,
68 body: Bytes,
69 /// Path parameters extracted from the URL pattern.
70 ///
71 /// Stored in URL pattern declaration order (see [`PathParams`]).
72 pub path_params: PathParams,
73 /// Query string parameters parsed from the URI.
74 pub query_params: HashMap<String, String>,
75 /// Indicates if this request came over HTTPS
76 pub is_secure: bool,
77 /// Remote address of the client (if available)
78 pub remote_addr: Option<SocketAddr>,
79 /// Parsers for request body
80 #[cfg(feature = "parsers")]
81 parsers: Vec<Box<dyn Parser>>,
82 /// Cached parsed data (lazy parsing)
83 #[cfg(feature = "parsers")]
84 parsed_data: Mutex<Option<ParsedData>>,
85 /// Whether the body has been consumed
86 body_consumed: AtomicBool,
87 /// Extensions for storing arbitrary typed data
88 pub extensions: Extensions,
89 /// Whether routing should publish a shared path-parameter snapshot for an
90 /// installed exception handler.
91 exception_handler_installed: bool,
92}
93
94/// Builder for constructing `Request` instances.
95///
96/// Provides a fluent API for building HTTP requests with optional parameters.
97///
98/// # Examples
99///
100/// ```
101/// use reinhardt_http::Request;
102/// use hyper::Method;
103///
104/// let request = Request::builder()
105/// .method(Method::GET)
106/// .uri("/api/users?page=1")
107/// .build()
108/// .unwrap();
109///
110/// assert_eq!(request.method, Method::GET);
111/// assert_eq!(request.path(), "/api/users");
112/// assert_eq!(request.query_params.get("page"), Some(&"1".to_string()));
113/// ```
114pub struct RequestBuilder {
115 method: Method,
116 uri: Option<Uri>,
117 version: Version,
118 headers: HeaderMap,
119 body: Bytes,
120 is_secure: bool,
121 remote_addr: Option<SocketAddr>,
122 path_params: PathParams,
123 /// Captured error from invalid URI
124 uri_error: Option<String>,
125 /// Captured error from invalid header value
126 header_error: Option<String>,
127 #[cfg(feature = "parsers")]
128 parsers: Vec<Box<dyn Parser>>,
129}
130
131impl Default for RequestBuilder {
132 fn default() -> Self {
133 Self {
134 method: Method::GET,
135 uri: None,
136 version: Version::HTTP_11,
137 headers: HeaderMap::new(),
138 body: Bytes::new(),
139 is_secure: false,
140 remote_addr: None,
141 path_params: PathParams::new(),
142 uri_error: None,
143 header_error: None,
144 #[cfg(feature = "parsers")]
145 parsers: Vec::new(),
146 }
147 }
148}
149
150impl RequestBuilder {
151 /// Set the HTTP method.
152 ///
153 /// # Examples
154 ///
155 /// ```
156 /// use reinhardt_http::Request;
157 /// use hyper::Method;
158 ///
159 /// let request = Request::builder()
160 /// .method(Method::POST)
161 /// .uri("/api/users")
162 /// .build()
163 /// .unwrap();
164 ///
165 /// assert_eq!(request.method, Method::POST);
166 /// ```
167 pub fn method(mut self, method: Method) -> Self {
168 self.method = method;
169 self
170 }
171
172 /// Set the request URI.
173 ///
174 /// Accepts either a `&str` or `Uri`. Query parameters will be automatically parsed.
175 ///
176 /// # Examples
177 ///
178 /// ```
179 /// use reinhardt_http::Request;
180 /// use hyper::Method;
181 ///
182 /// let request = Request::builder()
183 /// .method(Method::GET)
184 /// .uri("/api/users?page=1&limit=10")
185 /// .build()
186 /// .unwrap();
187 ///
188 /// assert_eq!(request.path(), "/api/users");
189 /// assert_eq!(request.query_params.get("page"), Some(&"1".to_string()));
190 /// assert_eq!(request.query_params.get("limit"), Some(&"10".to_string()));
191 /// ```
192 pub fn uri<T>(mut self, uri: T) -> Self
193 where
194 T: TryInto<Uri>,
195 T::Error: std::fmt::Display,
196 {
197 match uri.try_into() {
198 Ok(uri) => {
199 self.uri = Some(uri);
200 }
201 Err(e) => {
202 self.uri_error = Some(format!("Invalid URI: {}", e));
203 }
204 }
205 self
206 }
207
208 /// Set the HTTP version.
209 ///
210 /// Defaults to HTTP/1.1 if not specified.
211 ///
212 /// # Examples
213 ///
214 /// ```
215 /// use reinhardt_http::Request;
216 /// use hyper::{Method, Version};
217 ///
218 /// let request = Request::builder()
219 /// .method(Method::GET)
220 /// .uri("/api/users")
221 /// .version(Version::HTTP_2)
222 /// .build()
223 /// .unwrap();
224 ///
225 /// assert_eq!(request.version, Version::HTTP_2);
226 /// ```
227 pub fn version(mut self, version: Version) -> Self {
228 self.version = version;
229 self
230 }
231
232 /// Set the request headers.
233 ///
234 /// Replaces all existing headers.
235 ///
236 /// # Examples
237 ///
238 /// ```
239 /// use reinhardt_http::Request;
240 /// use hyper::{Method, HeaderMap, header};
241 ///
242 /// let mut headers = HeaderMap::new();
243 /// headers.insert(header::CONTENT_TYPE, "application/json".parse().unwrap());
244 ///
245 /// let request = Request::builder()
246 /// .method(Method::POST)
247 /// .uri("/api/users")
248 /// .headers(headers.clone())
249 /// .build()
250 /// .unwrap();
251 ///
252 /// assert_eq!(request.headers.get(header::CONTENT_TYPE).unwrap(), "application/json");
253 /// ```
254 pub fn headers(mut self, headers: HeaderMap) -> Self {
255 self.headers = headers;
256 self
257 }
258
259 /// Add a single header to the request.
260 ///
261 /// # Examples
262 ///
263 /// ```
264 /// use reinhardt_http::Request;
265 /// use hyper::{Method, header};
266 ///
267 /// let request = Request::builder()
268 /// .method(Method::POST)
269 /// .uri("/api/users")
270 /// .header(header::CONTENT_TYPE, "application/json")
271 /// .header(header::AUTHORIZATION, "Bearer token123")
272 /// .build()
273 /// .unwrap();
274 ///
275 /// assert_eq!(request.headers.get(header::CONTENT_TYPE).unwrap(), "application/json");
276 /// assert_eq!(request.headers.get(header::AUTHORIZATION).unwrap(), "Bearer token123");
277 /// ```
278 pub fn header<K, V>(mut self, key: K, value: V) -> Self
279 where
280 K: hyper::header::IntoHeaderName,
281 V: TryInto<hyper::header::HeaderValue>,
282 V::Error: std::fmt::Display,
283 {
284 match value.try_into() {
285 Ok(val) => {
286 self.headers.insert(key, val);
287 }
288 Err(e) => {
289 self.header_error = Some(format!("Invalid header value: {}", e));
290 }
291 }
292 self
293 }
294
295 /// Set the request body.
296 ///
297 /// # Examples
298 ///
299 /// ```
300 /// use reinhardt_http::Request;
301 /// use hyper::Method;
302 /// use bytes::Bytes;
303 ///
304 /// let body = Bytes::from(r#"{"name":"Alice"}"#);
305 /// let request = Request::builder()
306 /// .method(Method::POST)
307 /// .uri("/api/users")
308 /// .body(body.clone())
309 /// .build()
310 /// .unwrap();
311 ///
312 /// assert_eq!(request.body(), &body);
313 /// ```
314 pub fn body(mut self, body: Bytes) -> Self {
315 self.body = body;
316 self
317 }
318
319 /// Set whether the request is secure (HTTPS).
320 ///
321 /// Defaults to `false` if not specified.
322 ///
323 /// # Examples
324 ///
325 /// ```
326 /// use reinhardt_http::Request;
327 /// use hyper::Method;
328 ///
329 /// let request = Request::builder()
330 /// .method(Method::GET)
331 /// .uri("/")
332 /// .secure(true)
333 /// .build()
334 /// .unwrap();
335 ///
336 /// assert!(request.is_secure());
337 /// assert_eq!(request.scheme(), "https");
338 /// ```
339 pub fn secure(mut self, is_secure: bool) -> Self {
340 self.is_secure = is_secure;
341 self
342 }
343
344 /// Set the remote address of the client.
345 ///
346 /// # Examples
347 ///
348 /// ```
349 /// use reinhardt_http::Request;
350 /// use hyper::Method;
351 /// use std::net::{SocketAddr, IpAddr, Ipv4Addr};
352 ///
353 /// let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
354 /// let request = Request::builder()
355 /// .method(Method::GET)
356 /// .uri("/")
357 /// .remote_addr(addr)
358 /// .build()
359 /// .unwrap();
360 ///
361 /// assert_eq!(request.remote_addr, Some(addr));
362 /// ```
363 pub fn remote_addr(mut self, addr: SocketAddr) -> Self {
364 self.remote_addr = Some(addr);
365 self
366 }
367
368 /// Add a parser to the request.
369 ///
370 /// Parsers are used to parse the request body into specific formats.
371 /// The parser will be boxed internally.
372 ///
373 /// # Examples
374 ///
375 /// ```ignore
376 /// use reinhardt_http::Request;
377 /// use hyper::Method;
378 ///
379 /// let request = Request::builder()
380 /// .method(Method::POST)
381 /// .uri("/api/users")
382 /// .parser(JsonParser::new())
383 /// .build()
384 /// .unwrap();
385 /// ```
386 #[cfg(feature = "parsers")]
387 pub fn parser<P: Parser + 'static>(mut self, parser: P) -> Self {
388 self.parsers.push(Box::new(parser));
389 self
390 }
391
392 /// Set path parameters (used for testing views without router).
393 ///
394 /// This is primarily useful in test environments where you need to simulate
395 /// path parameters that would normally be extracted by the router. Accepts
396 /// any value that can be converted into [`PathParams`], including a
397 /// `HashMap<String, String>` (note: converting from a `HashMap` does not
398 /// preserve ordering — pass a `Vec<(String, String)>` or [`PathParams`]
399 /// directly when ordering matters).
400 ///
401 /// # Examples
402 ///
403 /// ```
404 /// use reinhardt_http::Request;
405 /// use hyper::Method;
406 /// use std::collections::HashMap;
407 ///
408 /// let mut params = HashMap::new();
409 /// params.insert("id".to_string(), "42".to_string());
410 ///
411 /// let request = Request::builder()
412 /// .method(Method::GET)
413 /// .uri("/api/users/42")
414 /// .path_params(params)
415 /// .build()
416 /// .unwrap();
417 ///
418 /// assert_eq!(request.path_params.get("id"), Some(&"42".to_string()));
419 /// ```
420 pub fn path_params(mut self, params: impl Into<PathParams>) -> Self {
421 self.path_params = params.into();
422 self
423 }
424
425 /// Build the final `Request` instance.
426 ///
427 /// Returns an error if the URI is missing.
428 ///
429 /// # Examples
430 ///
431 /// ```
432 /// use reinhardt_http::Request;
433 /// use hyper::Method;
434 ///
435 /// let request = Request::builder()
436 /// .method(Method::GET)
437 /// .uri("/api/users")
438 /// .build()
439 /// .unwrap();
440 ///
441 /// assert_eq!(request.method, Method::GET);
442 /// assert_eq!(request.path(), "/api/users");
443 /// ```
444 pub fn build(self) -> Result<Request, String> {
445 // Report captured errors from builder methods
446 if let Some(err) = self.uri_error {
447 return Err(err);
448 }
449 if let Some(err) = self.header_error {
450 return Err(err);
451 }
452 let uri = self.uri.ok_or_else(|| "URI is required".to_string())?;
453 let query_params = Request::parse_query_params(&uri);
454
455 Ok(Request {
456 method: self.method,
457 uri,
458 version: self.version,
459 headers: self.headers,
460 body: self.body,
461 path_params: self.path_params,
462 query_params,
463 is_secure: self.is_secure,
464 remote_addr: self.remote_addr,
465 #[cfg(feature = "parsers")]
466 parsers: self.parsers,
467 #[cfg(feature = "parsers")]
468 parsed_data: Mutex::new(None),
469 body_consumed: AtomicBool::new(false),
470 extensions: Extensions::new(),
471 exception_handler_installed: false,
472 })
473 }
474}
475
476impl Request {
477 /// Create a new `RequestBuilder`.
478 ///
479 /// # Examples
480 ///
481 /// ```
482 /// use reinhardt_http::Request;
483 /// use hyper::Method;
484 ///
485 /// let request = Request::builder()
486 /// .method(Method::GET)
487 /// .uri("/api/users")
488 /// .build()
489 /// .unwrap();
490 ///
491 /// assert_eq!(request.method, Method::GET);
492 /// ```
493 pub fn builder() -> RequestBuilder {
494 RequestBuilder::default()
495 }
496
497 /// Set the DI context for this request (used by routers with dependency injection)
498 ///
499 /// This method stores the DI context in the request's extensions,
500 /// allowing handlers to access dependency injection services.
501 ///
502 /// The context will be wrapped in an Arc internally for efficient sharing.
503 /// The DI context type is generic to avoid circular dependencies.
504 ///
505 /// # Examples
506 ///
507 /// ```rust,no_run
508 /// use reinhardt_http::Request;
509 /// use hyper::Method;
510 ///
511 /// # struct DummyDiContext;
512 /// let mut request = Request::builder()
513 /// .method(Method::GET)
514 /// .uri("/")
515 /// .build()
516 /// .unwrap();
517 ///
518 /// let di_ctx = DummyDiContext;
519 /// request.set_di_context(di_ctx);
520 /// ```
521 pub fn set_di_context<T: Send + Sync + 'static>(&mut self, ctx: T) {
522 self.extensions.insert(Arc::new(ctx));
523 }
524
525 /// Installs an exception handler and enables routing-context snapshots.
526 #[doc(hidden)]
527 pub fn install_exception_handler(&mut self, handler: Arc<dyn crate::ExceptionHandler>) {
528 self.extensions.insert(handler);
529 self.exception_handler_installed = true;
530 }
531
532 /// Replaces the path parameters after a router resolves a request.
533 ///
534 /// When an exception handler is installed, the resolved values are also kept
535 /// in the shared extensions store so a context captured before routing can
536 /// refresh its copied fields.
537 #[doc(hidden)]
538 pub fn set_path_params(&mut self, params: PathParams) {
539 self.path_params = params;
540 if self.exception_handler_installed {
541 self.extensions
542 .insert(ResolvedPathParams(self.path_params.clone()));
543 }
544 }
545
546 /// Refreshes copied path parameters from the shared routing context.
547 ///
548 /// Exception handlers call this after an inner router has consumed the
549 /// original request and populated its resolved parameters.
550 #[doc(hidden)]
551 pub fn sync_path_params_from_shared_state(&mut self) {
552 if let Some(params) = self.extensions.get::<ResolvedPathParams>() {
553 self.path_params = params.0;
554 }
555 }
556
557 /// Get the DI context from this request
558 ///
559 /// Returns `None` if no DI context was set.
560 ///
561 /// The DI context type is generic to avoid circular dependencies.
562 /// Returns a reference to the context.
563 ///
564 /// # Examples
565 ///
566 /// ```rust,no_run
567 /// use reinhardt_http::Request;
568 /// use hyper::Method;
569 ///
570 /// # struct DummyDiContext;
571 /// let mut request = Request::builder()
572 /// .method(Method::GET)
573 /// .uri("/")
574 /// .build()
575 /// .unwrap();
576 ///
577 /// let di_ctx = DummyDiContext;
578 /// request.set_di_context(di_ctx);
579 ///
580 /// let ctx = request.get_di_context::<DummyDiContext>();
581 /// assert!(ctx.is_some());
582 /// ```
583 pub fn get_di_context<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
584 self.extensions.get::<Arc<T>>()
585 }
586
587 /// Extract Bearer token from Authorization header
588 ///
589 /// Extracts JWT or other bearer tokens from the Authorization header.
590 /// Returns `None` if the header is missing or not in "Bearer `<token>`" format.
591 ///
592 /// # Examples
593 ///
594 /// ```
595 /// use reinhardt_http::Request;
596 /// use hyper::{Method, Version, HeaderMap, header};
597 /// use bytes::Bytes;
598 ///
599 /// let mut headers = HeaderMap::new();
600 /// headers.insert(
601 /// header::AUTHORIZATION,
602 /// "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9".parse().unwrap()
603 /// );
604 ///
605 /// let request = Request::builder()
606 /// .method(Method::GET)
607 /// .uri("/")
608 /// .version(Version::HTTP_11)
609 /// .headers(headers)
610 /// .body(Bytes::new())
611 /// .build()
612 /// .unwrap();
613 ///
614 /// let token = request.extract_bearer_token();
615 /// assert_eq!(token, Some("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9".to_string()));
616 /// ```
617 ///
618 /// # Missing or invalid header
619 ///
620 /// ```
621 /// use reinhardt_http::Request;
622 /// use hyper::{Method, Version, HeaderMap};
623 /// use bytes::Bytes;
624 ///
625 /// let request = Request::builder()
626 /// .method(Method::GET)
627 /// .uri("/")
628 /// .version(Version::HTTP_11)
629 /// .headers(HeaderMap::new())
630 /// .body(Bytes::new())
631 /// .build()
632 /// .unwrap();
633 ///
634 /// let token = request.extract_bearer_token();
635 /// assert_eq!(token, None);
636 /// ```
637 pub fn extract_bearer_token(&self) -> Option<String> {
638 self.headers
639 .get(hyper::header::AUTHORIZATION)
640 .and_then(|value| value.to_str().ok())
641 .and_then(|auth_str| auth_str.strip_prefix("Bearer ").map(|s| s.to_string()))
642 }
643
644 /// Get a specific header value from the request
645 ///
646 /// Returns `None` if the header is missing or cannot be converted to a string.
647 ///
648 /// # Examples
649 ///
650 /// ```
651 /// use reinhardt_http::Request;
652 /// use hyper::{Method, Version, HeaderMap, header};
653 /// use bytes::Bytes;
654 ///
655 /// let mut headers = HeaderMap::new();
656 /// headers.insert(
657 /// header::USER_AGENT,
658 /// "Mozilla/5.0".parse().unwrap()
659 /// );
660 ///
661 /// let request = Request::builder()
662 /// .method(Method::GET)
663 /// .uri("/")
664 /// .version(Version::HTTP_11)
665 /// .headers(headers)
666 /// .body(Bytes::new())
667 /// .build()
668 /// .unwrap();
669 ///
670 /// let user_agent = request.get_header("user-agent");
671 /// assert_eq!(user_agent, Some("Mozilla/5.0".to_string()));
672 /// ```
673 ///
674 /// # Missing header
675 ///
676 /// ```
677 /// use reinhardt_http::Request;
678 /// use hyper::{Method, Version, HeaderMap};
679 /// use bytes::Bytes;
680 ///
681 /// let request = Request::builder()
682 /// .method(Method::GET)
683 /// .uri("/")
684 /// .version(Version::HTTP_11)
685 /// .headers(HeaderMap::new())
686 /// .body(Bytes::new())
687 /// .build()
688 /// .unwrap();
689 ///
690 /// let header = request.get_header("x-custom-header");
691 /// assert_eq!(header, None);
692 /// ```
693 pub fn get_header(&self, name: &str) -> Option<String> {
694 self.headers
695 .get(name)
696 .and_then(|value| value.to_str().ok())
697 .map(|s| s.to_string())
698 }
699
700 /// Extract client IP address from the request
701 ///
702 /// Only trusts proxy headers (X-Forwarded-For, X-Real-IP) when the request
703 /// originates from a configured trusted proxy. Without trusted proxies,
704 /// falls back to the actual connection address.
705 ///
706 /// # Examples
707 ///
708 /// ```
709 /// use reinhardt_http::{Request, TrustedProxies};
710 /// use hyper::{Method, Version, HeaderMap, header};
711 /// use bytes::Bytes;
712 /// use std::net::{SocketAddr, IpAddr, Ipv4Addr};
713 ///
714 /// let proxy_ip = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1));
715 /// let mut headers = HeaderMap::new();
716 /// headers.insert(
717 /// header::HeaderName::from_static("x-forwarded-for"),
718 /// "203.0.113.1, 198.51.100.1".parse().unwrap()
719 /// );
720 ///
721 /// let request = Request::builder()
722 /// .method(Method::GET)
723 /// .uri("/")
724 /// .version(Version::HTTP_11)
725 /// .headers(headers)
726 /// .remote_addr(SocketAddr::new(proxy_ip, 8080))
727 /// .body(Bytes::new())
728 /// .build()
729 /// .unwrap();
730 ///
731 /// // Configure trusted proxies to honor X-Forwarded-For
732 /// request.set_trusted_proxies(TrustedProxies::new(vec![proxy_ip]));
733 ///
734 /// let ip = request.get_client_ip();
735 /// assert_eq!(ip, Some("203.0.113.1".parse().unwrap()));
736 /// ```
737 ///
738 /// # No trusted proxy, fallback to remote_addr
739 ///
740 /// ```
741 /// use reinhardt_http::Request;
742 /// use hyper::{Method, Version, HeaderMap};
743 /// use bytes::Bytes;
744 /// use std::net::{SocketAddr, IpAddr, Ipv4Addr};
745 ///
746 /// let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
747 /// let request = Request::builder()
748 /// .method(Method::GET)
749 /// .uri("/")
750 /// .version(Version::HTTP_11)
751 /// .headers(HeaderMap::new())
752 /// .remote_addr(addr)
753 /// .body(Bytes::new())
754 /// .build()
755 /// .unwrap();
756 ///
757 /// let ip = request.get_client_ip();
758 /// assert_eq!(ip, Some(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))));
759 /// ```
760 pub fn get_client_ip(&self) -> Option<std::net::IpAddr> {
761 // Only trust proxy headers if the request comes from a configured trusted proxy
762 if self.is_from_trusted_proxy() {
763 // Try X-Forwarded-For header first (common in proxy setups)
764 if let Some(forwarded) = self.get_header("x-forwarded-for") {
765 // X-Forwarded-For can contain multiple IPs, take the first one
766 if let Some(first_ip) = forwarded.split(',').next()
767 && let Ok(ip) = first_ip.trim().parse()
768 {
769 return Some(ip);
770 }
771 }
772
773 // Try X-Real-IP header
774 if let Some(real_ip) = self.get_header("x-real-ip")
775 && let Ok(ip) = real_ip.parse()
776 {
777 return Some(ip);
778 }
779 }
780
781 // Fallback to remote_addr (actual connection info)
782 self.remote_addr.map(|addr| addr.ip())
783 }
784
785 /// Check if the request originates from a trusted proxy.
786 ///
787 /// Returns `true` only if [`TrustedProxies`] are configured (via
788 /// [`set_trusted_proxies`](Self::set_trusted_proxies)) **and** the
789 /// remote address of the connection is contained in the trusted set.
790 ///
791 /// # Security
792 ///
793 /// This method gates whether proxy-forwarded headers (e.g.
794 /// `X-Forwarded-For`, `X-Forwarded-Proto`) should be honoured.
795 /// Trusting headers from a non-proxy source allows clients to spoof
796 /// their IP address or protocol, which can bypass IP-based access
797 /// controls and HTTPS enforcement.
798 ///
799 /// **Callers must ensure that [`TrustedProxies`] is configured only
800 /// with IP addresses of reverse proxies actually deployed in front
801 /// of the application.** Misconfiguration (e.g. trusting `0.0.0.0/0`)
802 /// re-introduces header-spoofing vulnerabilities.
803 ///
804 /// # Examples
805 ///
806 /// ```
807 /// use reinhardt_http::Request;
808 /// use reinhardt_http::TrustedProxies;
809 /// use bytes::Bytes;
810 /// use std::net::{IpAddr, Ipv4Addr, SocketAddr};
811 /// use hyper::Method;
812 ///
813 /// let proxy_ip: IpAddr = Ipv4Addr::new(10, 0, 0, 1).into();
814 /// let request = Request::builder()
815 /// .method(Method::GET)
816 /// .uri("/")
817 /// .remote_addr(SocketAddr::new(proxy_ip, 8080))
818 /// .body(Bytes::new())
819 /// .build()
820 /// .unwrap();
821 /// request.set_trusted_proxies(TrustedProxies::new(vec![proxy_ip]));
822 ///
823 /// assert!(request.is_from_trusted_proxy());
824 /// ```
825 pub fn is_from_trusted_proxy(&self) -> bool {
826 if let Some(trusted) = self.extensions.get::<TrustedProxies>()
827 && let Some(addr) = self.remote_addr
828 {
829 return trusted.is_trusted(&addr.ip());
830 }
831 false
832 }
833
834 /// Set trusted proxy configuration for this request.
835 ///
836 /// This is typically called by the server/middleware layer to configure
837 /// which proxy IPs are trusted for header forwarding.
838 pub fn set_trusted_proxies(&self, proxies: TrustedProxies) {
839 self.extensions.insert(proxies);
840 }
841
842 /// Validate Content-Type header
843 ///
844 /// Checks if the Content-Type header matches the expected value.
845 /// Returns an error if the header is missing or doesn't match.
846 ///
847 /// # Examples
848 ///
849 /// ```
850 /// use reinhardt_http::Request;
851 /// use hyper::{Method, Version, HeaderMap, header};
852 /// use bytes::Bytes;
853 ///
854 /// let mut headers = HeaderMap::new();
855 /// headers.insert(
856 /// header::CONTENT_TYPE,
857 /// "application/json".parse().unwrap()
858 /// );
859 ///
860 /// let request = Request::builder()
861 /// .method(Method::POST)
862 /// .uri("/")
863 /// .version(Version::HTTP_11)
864 /// .headers(headers)
865 /// .body(Bytes::new())
866 /// .build()
867 /// .unwrap();
868 ///
869 /// assert!(request.validate_content_type("application/json").is_ok());
870 /// ```
871 ///
872 /// # Content-Type mismatch
873 ///
874 /// ```
875 /// use reinhardt_http::Request;
876 /// use hyper::{Method, Version, HeaderMap, header};
877 /// use bytes::Bytes;
878 ///
879 /// let mut headers = HeaderMap::new();
880 /// headers.insert(
881 /// header::CONTENT_TYPE,
882 /// "text/plain".parse().unwrap()
883 /// );
884 ///
885 /// let request = Request::builder()
886 /// .method(Method::POST)
887 /// .uri("/")
888 /// .version(Version::HTTP_11)
889 /// .headers(headers)
890 /// .body(Bytes::new())
891 /// .build()
892 /// .unwrap();
893 ///
894 /// let result = request.validate_content_type("application/json");
895 /// assert!(result.is_err());
896 /// ```
897 ///
898 /// # Missing Content-Type header
899 ///
900 /// ```
901 /// use reinhardt_http::Request;
902 /// use hyper::{Method, Version, HeaderMap};
903 /// use bytes::Bytes;
904 ///
905 /// let request = Request::builder()
906 /// .method(Method::POST)
907 /// .uri("/")
908 /// .version(Version::HTTP_11)
909 /// .headers(HeaderMap::new())
910 /// .body(Bytes::new())
911 /// .build()
912 /// .unwrap();
913 ///
914 /// let result = request.validate_content_type("application/json");
915 /// assert!(result.is_err());
916 /// ```
917 pub fn validate_content_type(&self, expected: &str) -> crate::Result<()> {
918 match self.get_header("content-type") {
919 Some(content_type) if content_type.starts_with(expected) => Ok(()),
920 Some(content_type) => Err(crate::Error::Http(format!(
921 "Invalid Content-Type: expected '{}', got '{}'",
922 expected, content_type
923 ))),
924 None => Err(crate::Error::Http(
925 "Missing Content-Type header".to_string(),
926 )),
927 }
928 }
929
930 /// Parse query parameters into typed struct
931 ///
932 /// Deserializes query string parameters into the specified type `T`.
933 /// Returns an error if deserialization fails.
934 ///
935 /// # Examples
936 ///
937 /// ```
938 /// use reinhardt_http::Request;
939 /// use hyper::{Method, Version, HeaderMap};
940 /// use bytes::Bytes;
941 /// use serde::Deserialize;
942 ///
943 /// #[derive(Deserialize, Debug, PartialEq)]
944 /// struct Pagination {
945 /// page: u32,
946 /// limit: u32,
947 /// }
948 ///
949 /// let request = Request::builder()
950 /// .method(Method::GET)
951 /// .uri("/api/users?page=2&limit=10")
952 /// .version(Version::HTTP_11)
953 /// .headers(HeaderMap::new())
954 /// .body(Bytes::new())
955 /// .build()
956 /// .unwrap();
957 ///
958 /// let params: Pagination = request.query_as().unwrap();
959 /// assert_eq!(params, Pagination { page: 2, limit: 10 });
960 /// ```
961 ///
962 /// # Type mismatch error
963 ///
964 /// ```
965 /// use reinhardt_http::Request;
966 /// use hyper::{Method, Version, HeaderMap};
967 /// use bytes::Bytes;
968 /// use serde::Deserialize;
969 ///
970 /// #[derive(Deserialize)]
971 /// struct Pagination {
972 /// page: u32,
973 /// limit: u32,
974 /// }
975 ///
976 /// let request = Request::builder()
977 /// .method(Method::GET)
978 /// .uri("/api/users?page=invalid")
979 /// .version(Version::HTTP_11)
980 /// .headers(HeaderMap::new())
981 /// .body(Bytes::new())
982 /// .build()
983 /// .unwrap();
984 ///
985 /// let result: Result<Pagination, _> = request.query_as();
986 /// assert!(result.is_err());
987 /// ```
988 pub fn query_as<T: serde::de::DeserializeOwned>(&self) -> crate::Result<T> {
989 // Convert HashMap<String, String> to Vec<(String, String)> for serde_urlencoded
990 let params: Vec<(String, String)> = self
991 .query_params
992 .iter()
993 .map(|(k, v)| (k.clone(), v.clone()))
994 .collect();
995
996 let encoded = serde_urlencoded::to_string(¶ms)
997 .map_err(|e| crate::Error::Http(format!("Failed to encode query parameters: {}", e)))?;
998 serde_urlencoded::from_str(&encoded)
999 .map_err(|e| crate::Error::Http(format!("Failed to parse query parameters: {}", e)))
1000 }
1001
1002 /// Creates a lightweight copy of this request for dependency injection.
1003 ///
1004 /// The clone shares the same extensions store (via internal `Arc`),
1005 /// so `AuthState` and other extensions set on the original request
1006 /// are accessible in the clone. Body and parsers are not copied
1007 /// as they are not needed for DI resolution.
1008 pub fn clone_for_di(&self) -> Self {
1009 Request {
1010 method: self.method.clone(),
1011 uri: self.uri.clone(),
1012 version: self.version,
1013 headers: self.headers.clone(),
1014 body: Bytes::new(),
1015 path_params: self.path_params.clone(),
1016 query_params: self.query_params.clone(),
1017 is_secure: self.is_secure,
1018 remote_addr: self.remote_addr,
1019 #[cfg(feature = "parsers")]
1020 parsers: Vec::new(),
1021 #[cfg(feature = "parsers")]
1022 parsed_data: Mutex::new(None),
1023 body_consumed: AtomicBool::new(false),
1024 extensions: self.extensions.clone(),
1025 exception_handler_installed: self.exception_handler_installed,
1026 }
1027 }
1028}
1029
1030#[cfg(test)]
1031mod tests {
1032 use super::*;
1033 use bytes::Bytes;
1034 use hyper::{HeaderMap, Method, Version, header};
1035 use rstest::rstest;
1036
1037 #[rstest]
1038 fn test_extract_bearer_token() {
1039 let mut headers = HeaderMap::new();
1040 headers.insert(
1041 header::AUTHORIZATION,
1042 "Bearer test_token_123".parse().unwrap(),
1043 );
1044
1045 let request = Request::builder()
1046 .method(Method::GET)
1047 .uri("/")
1048 .version(Version::HTTP_11)
1049 .headers(headers)
1050 .body(Bytes::new())
1051 .build()
1052 .unwrap();
1053
1054 let token = request.extract_bearer_token();
1055 assert_eq!(token, Some("test_token_123".to_string()));
1056 }
1057
1058 #[rstest]
1059 fn test_extract_bearer_token_missing() {
1060 let request = Request::builder()
1061 .method(Method::GET)
1062 .uri("/")
1063 .version(Version::HTTP_11)
1064 .headers(HeaderMap::new())
1065 .body(Bytes::new())
1066 .build()
1067 .unwrap();
1068
1069 let token = request.extract_bearer_token();
1070 assert_eq!(token, None);
1071 }
1072
1073 #[rstest]
1074 fn test_get_header() {
1075 let mut headers = HeaderMap::new();
1076 headers.insert(header::USER_AGENT, "TestClient/1.0".parse().unwrap());
1077
1078 let request = Request::builder()
1079 .method(Method::GET)
1080 .uri("/")
1081 .version(Version::HTTP_11)
1082 .headers(headers)
1083 .body(Bytes::new())
1084 .build()
1085 .unwrap();
1086
1087 let user_agent = request.get_header("user-agent");
1088 assert_eq!(user_agent, Some("TestClient/1.0".to_string()));
1089 }
1090
1091 #[rstest]
1092 fn test_get_header_missing() {
1093 let request = Request::builder()
1094 .method(Method::GET)
1095 .uri("/")
1096 .version(Version::HTTP_11)
1097 .headers(HeaderMap::new())
1098 .body(Bytes::new())
1099 .build()
1100 .unwrap();
1101
1102 let header = request.get_header("x-custom-header");
1103 assert_eq!(header, None);
1104 }
1105
1106 #[rstest]
1107 fn test_get_client_ip_forwarded_for_with_trusted_proxy() {
1108 // Arrange
1109 let proxy_ip: std::net::IpAddr = "10.0.0.254".parse().unwrap();
1110 let mut headers = HeaderMap::new();
1111 headers.insert(
1112 header::HeaderName::from_static("x-forwarded-for"),
1113 "192.168.1.1, 10.0.0.1".parse().unwrap(),
1114 );
1115
1116 let request = Request::builder()
1117 .method(Method::GET)
1118 .uri("/")
1119 .version(Version::HTTP_11)
1120 .headers(headers)
1121 .body(Bytes::new())
1122 .remote_addr(std::net::SocketAddr::new(proxy_ip, 8080))
1123 .build()
1124 .unwrap();
1125
1126 // Configure trusted proxies
1127 request.set_trusted_proxies(TrustedProxies::new(vec![proxy_ip]));
1128
1129 // Act & Assert
1130 let ip = request.get_client_ip();
1131 assert_eq!(ip, Some("192.168.1.1".parse().unwrap()));
1132 }
1133
1134 #[rstest]
1135 fn test_get_client_ip_forwarded_for_without_trusted_proxy() {
1136 // Arrange - proxy headers present but no trusted proxy configured
1137 let mut headers = HeaderMap::new();
1138 headers.insert(
1139 header::HeaderName::from_static("x-forwarded-for"),
1140 "192.168.1.1, 10.0.0.1".parse().unwrap(),
1141 );
1142
1143 let remote_ip: std::net::IpAddr = "10.0.0.254".parse().unwrap();
1144 let request = Request::builder()
1145 .method(Method::GET)
1146 .uri("/")
1147 .version(Version::HTTP_11)
1148 .headers(headers)
1149 .body(Bytes::new())
1150 .remote_addr(std::net::SocketAddr::new(remote_ip, 8080))
1151 .build()
1152 .unwrap();
1153
1154 // Act - no trusted proxies, should use remote_addr
1155 let ip = request.get_client_ip();
1156 assert_eq!(ip, Some(remote_ip));
1157 }
1158
1159 #[rstest]
1160 fn test_get_client_ip_real_ip_with_trusted_proxy() {
1161 // Arrange
1162 let proxy_ip: std::net::IpAddr = "10.0.0.254".parse().unwrap();
1163 let mut headers = HeaderMap::new();
1164 headers.insert(
1165 header::HeaderName::from_static("x-real-ip"),
1166 "203.0.113.5".parse().unwrap(),
1167 );
1168
1169 let request = Request::builder()
1170 .method(Method::GET)
1171 .uri("/")
1172 .version(Version::HTTP_11)
1173 .headers(headers)
1174 .body(Bytes::new())
1175 .remote_addr(std::net::SocketAddr::new(proxy_ip, 8080))
1176 .build()
1177 .unwrap();
1178
1179 request.set_trusted_proxies(TrustedProxies::new(vec![proxy_ip]));
1180
1181 // Act & Assert
1182 let ip = request.get_client_ip();
1183 assert_eq!(ip, Some("203.0.113.5".parse().unwrap()));
1184 }
1185
1186 #[rstest]
1187 fn test_get_client_ip_none() {
1188 let request = Request::builder()
1189 .method(Method::GET)
1190 .uri("/")
1191 .version(Version::HTTP_11)
1192 .headers(HeaderMap::new())
1193 .body(Bytes::new())
1194 .build()
1195 .unwrap();
1196
1197 let ip = request.get_client_ip();
1198 assert_eq!(ip, None);
1199 }
1200
1201 #[rstest]
1202 fn test_validate_content_type_valid() {
1203 let mut headers = HeaderMap::new();
1204 headers.insert(header::CONTENT_TYPE, "application/json".parse().unwrap());
1205
1206 let request = Request::builder()
1207 .method(Method::POST)
1208 .uri("/")
1209 .version(Version::HTTP_11)
1210 .headers(headers)
1211 .body(Bytes::new())
1212 .build()
1213 .unwrap();
1214
1215 assert!(request.validate_content_type("application/json").is_ok());
1216 }
1217
1218 #[rstest]
1219 fn test_validate_content_type_invalid() {
1220 let mut headers = HeaderMap::new();
1221 headers.insert(header::CONTENT_TYPE, "text/plain".parse().unwrap());
1222
1223 let request = Request::builder()
1224 .method(Method::POST)
1225 .uri("/")
1226 .version(Version::HTTP_11)
1227 .headers(headers)
1228 .body(Bytes::new())
1229 .build()
1230 .unwrap();
1231
1232 assert!(request.validate_content_type("application/json").is_err());
1233 }
1234
1235 #[rstest]
1236 fn test_validate_content_type_missing() {
1237 let request = Request::builder()
1238 .method(Method::POST)
1239 .uri("/")
1240 .version(Version::HTTP_11)
1241 .headers(HeaderMap::new())
1242 .body(Bytes::new())
1243 .build()
1244 .unwrap();
1245
1246 assert!(request.validate_content_type("application/json").is_err());
1247 }
1248
1249 #[rstest]
1250 fn test_clone_for_di_shares_extensions() {
1251 // Arrange
1252 let request = Request::builder()
1253 .method(Method::POST)
1254 .uri("/api/users/42?page=1")
1255 .version(Version::HTTP_11)
1256 .header(header::CONTENT_TYPE, "application/json")
1257 .body(Bytes::from("request body"))
1258 .build()
1259 .unwrap();
1260
1261 request.extensions.insert(42u32);
1262
1263 // Act
1264 let cloned = request.clone_for_di();
1265
1266 // Assert - extensions are shared (same Arc backing store)
1267 assert_eq!(cloned.extensions.get::<u32>(), Some(42));
1268
1269 // Verify metadata is preserved
1270 assert_eq!(cloned.method, Method::POST);
1271 assert_eq!(cloned.uri.path(), "/api/users/42");
1272 assert_eq!(cloned.version, Version::HTTP_11);
1273 assert!(cloned.headers.contains_key(header::CONTENT_TYPE));
1274 assert_eq!(cloned.query_params.get("page"), Some(&"1".to_string()));
1275
1276 // Body should be empty (not needed for DI)
1277 assert!(cloned.body().is_empty());
1278 }
1279
1280 #[rstest]
1281 fn test_clone_for_di_shares_extensions_bidirectionally() {
1282 // Arrange
1283 let request = Request::builder()
1284 .method(Method::GET)
1285 .uri("/")
1286 .build()
1287 .unwrap();
1288
1289 let cloned = request.clone_for_di();
1290
1291 // Act - insert into cloned extensions
1292 cloned.extensions.insert("from_clone".to_string());
1293
1294 // Assert - original also sees it (shared backing store)
1295 assert_eq!(
1296 request.extensions.get::<String>(),
1297 Some("from_clone".to_string())
1298 );
1299 }
1300
1301 #[rstest]
1302 fn test_set_path_params_skips_shared_snapshot_without_exception_handler() {
1303 // Arrange
1304 let mut request = Request::builder()
1305 .method(Method::GET)
1306 .uri("/items/42")
1307 .build()
1308 .unwrap();
1309 let params = PathParams::from_iter([(String::from("id"), String::from("42"))]);
1310
1311 // Act
1312 request.set_path_params(params);
1313
1314 // Assert
1315 assert_eq!(
1316 request.path_params.get("id").map(String::as_str),
1317 Some("42")
1318 );
1319 assert!(request.extensions.get::<ResolvedPathParams>().is_none());
1320 }
1321}