Skip to main content

ntex/web/
request.rs

1use std::{cell::Ref, cell::RefMut, fmt, net};
2
3use urly::Url;
4
5use crate::http::header;
6use crate::http::{HeaderMap, HttpMessage, Method, Payload, RequestHead, Response, Version};
7use crate::io::{IoRef, types};
8use crate::router::{Path, Resource};
9use crate::util::Extensions;
10
11use super::config::WebAppConfig;
12use super::info::ConnectionInfo;
13use super::rmap::ResourceMap;
14use super::{HttpRequest, State, WebResponse, WebResponseError};
15
16/// An service http request
17///
18/// `WebRequest` allows mutable access to request's internal structures
19pub struct WebRequest<St = ()> {
20    pub(crate) req: HttpRequest,
21    payload: Payload,
22    state: St,
23}
24
25impl<St> WebRequest<St> {
26    /// Construct web request
27    pub(crate) fn new(req: HttpRequest, payload: Payload, state: St) -> Self {
28        WebRequest {
29            req,
30            payload,
31            state,
32        }
33    }
34
35    /// Create web response for error
36    #[inline]
37    pub fn error_response<AppSt, E>(self, st: &AppSt, err: &E) -> WebResponse
38    where
39        AppSt: State,
40        E: WebResponseError<AppSt, AppSt::Error>,
41    {
42        WebResponse::new(err.error_response(st), self.req)
43    }
44}
45
46impl WebRequest<()> {
47    /// Construct request from request.
48    pub fn from_request(req: HttpRequest) -> Self {
49        WebRequest {
50            req,
51            payload: Payload::None,
52            state: (),
53        }
54    }
55}
56
57impl<St> WebRequest<St> {
58    /// Deconstruct request into parts
59    pub fn into_parts(self) -> (HttpRequest, Payload, St) {
60        (self.req, self.payload, self.state)
61    }
62
63    /// Construct request from parts.
64    pub fn from_parts(req: HttpRequest, payload: Payload, state: St) -> Self {
65        WebRequest {
66            req,
67            payload,
68            state,
69        }
70    }
71
72    /// Create web response
73    #[inline]
74    pub fn into_response<R: Into<Response>>(self, res: R) -> WebResponse {
75        WebResponse::new(res.into(), self.req)
76    }
77
78    /// Io reference for current connection
79    #[inline]
80    pub fn io(&self) -> Option<&IoRef> {
81        self.head().io.as_ref()
82    }
83
84    /// This method returns reference to the request head
85    #[inline]
86    pub fn head(&self) -> &RequestHead {
87        self.req.head()
88    }
89
90    /// This method returns mutable reference to the request head
91    #[inline]
92    pub fn head_mut(&mut self) -> &mut RequestHead {
93        self.req.head_mut()
94    }
95
96    /// Request's uri.
97    #[inline]
98    pub fn uri(&self) -> &Url {
99        &self.head().uri
100    }
101
102    /// Read the Request method.
103    #[inline]
104    pub fn method(&self) -> &Method {
105        &self.head().method
106    }
107
108    /// Read the Request Version.
109    #[inline]
110    pub fn version(&self) -> Version {
111        self.head().version
112    }
113
114    /// Returns request's headers.
115    #[inline]
116    pub fn headers(&self) -> &HeaderMap {
117        &self.head().headers
118    }
119
120    /// Returns mutable request's headers.
121    pub fn headers_mut(&mut self) -> &mut HeaderMap {
122        &mut self.head_mut().headers
123    }
124
125    /// The target path of this Request.
126    #[inline]
127    pub fn path(&self) -> &str {
128        self.head().uri.path().as_str()
129    }
130
131    /// The query string in the URL.
132    ///
133    /// E.g., id=10
134    #[inline]
135    pub fn query_string(&self) -> &str {
136        self.uri().query().map_or("", urly::Query::as_str)
137    }
138
139    /// Peer socket address
140    ///
141    /// Peer address is actual socket address, if proxy is used in front of
142    /// ntex http server, then peer address would be address of this proxy.
143    ///
144    /// To get client connection information `ConnectionInfo` should be used.
145    #[inline]
146    pub fn peer_addr(&self) -> Option<net::SocketAddr> {
147        self.head()
148            .io
149            .as_ref()
150            .and_then(|io| io.query::<types::PeerAddr>().get().map(|addr| addr.0))
151    }
152
153    /// Take request's payload.
154    ///
155    /// The payload is replaced with an empty one, so subsequent calls return
156    /// an empty payload.
157    #[inline]
158    pub fn take_payload(&mut self) -> Payload {
159        self.payload.take()
160    }
161
162    /// Set request payload.
163    pub fn set_payload(&mut self, payload: Payload) {
164        self.payload = payload;
165    }
166
167    /// Get `ConnectionInfo` for the current request.
168    pub fn connection_info(&self) -> Ref<'_, ConnectionInfo> {
169        ConnectionInfo::get(self.head(), self.app_config())
170    }
171
172    /// Get a reference to the Path parameters.
173    ///
174    /// Params is a container for url parameters.
175    /// A variable segment is specified in the form `{identifier}`,
176    /// where the identifier can be used later in a request handler to
177    /// access the matched value for that segment.
178    #[inline]
179    pub fn match_info(&self) -> &Path<Url> {
180        self.req.match_info()
181    }
182
183    /// Get a mutable reference to the Path parameters.
184    #[inline]
185    pub fn match_info_mut(&mut self) -> &mut Path<Url> {
186        self.req.match_info_mut()
187    }
188
189    /// Get a reference to a `ResourceMap` of current application.
190    #[inline]
191    pub fn resource_map(&self) -> &ResourceMap {
192        self.req.resource_map()
193    }
194
195    /// Service configuration
196    #[inline]
197    pub fn app_config(&self) -> &WebAppConfig {
198        self.req.app_config()
199    }
200
201    /// Get an application state.
202    #[inline]
203    pub fn app_state<T: 'static>(&self) -> Option<&T> {
204        (self.req).app_config().state()
205    }
206
207    /// Request extensions
208    #[inline]
209    pub fn extensions(&self) -> Ref<'_, Extensions> {
210        self.req.extensions()
211    }
212
213    /// Mutable reference to a the request's extensions
214    #[inline]
215    pub fn extensions_mut(&self) -> RefMut<'_, Extensions> {
216        self.req.extensions_mut()
217    }
218
219    /// Get request state ref.
220    #[inline]
221    pub fn st(&self) -> &St {
222        &self.state
223    }
224
225    /// Get request state mut ref
226    #[inline]
227    pub fn st_mut(&mut self) -> &mut St {
228        &mut self.state
229    }
230
231    /// Transform the request state into a new type.
232    ///
233    /// This method consumes the request and returns request with different
234    /// state a `WebRequest<NewSt>`. The HTTP request and payload are preserved.
235    /// It is useful in application and resource filters that add
236    /// request-specific data before passing the request to the next service.
237    ///
238    /// Application state is not affected; only the state stored in this
239    /// `WebRequest` is replaced.
240    ///
241    /// ```rust
242    /// use std::convert::Infallible;
243    /// use ntex::web::{self, WebRequest};
244    ///
245    /// async fn index(_: &(), user_id: usize) -> String {
246    ///     format!("User {user_id}")
247    /// }
248    ///
249    /// web::App::default()
250    ///     .filter(async |req: WebRequest<()>| {
251    ///         Ok::<_, Infallible>(req.map_state(|()| 42usize))
252    ///     })
253    ///     .service(web::resource("/").to_with_state(index));
254    /// ```
255    #[inline]
256    pub fn map_state<F, NewSt>(self, f: F) -> WebRequest<NewSt>
257    where
258        F: FnOnce(St) -> NewSt,
259    {
260        let WebRequest {
261            req,
262            payload,
263            state,
264        } = self;
265
266        WebRequest {
267            req,
268            payload,
269            state: f(state),
270        }
271    }
272}
273
274impl<St> Resource<Url> for WebRequest<St> {
275    fn path(&self) -> &str {
276        self.match_info().path()
277    }
278
279    fn resource_path(&mut self) -> &mut Path<Url> {
280        self.match_info_mut()
281    }
282}
283
284impl<St> HttpMessage for WebRequest<St> {
285    #[inline]
286    /// Returns Request's headers.
287    fn message_headers(&self) -> &HeaderMap {
288        &self.head().headers
289    }
290
291    /// Request extensions
292    #[inline]
293    fn message_extensions(&self) -> Ref<'_, Extensions> {
294        self.req.extensions()
295    }
296
297    /// Mutable reference to a the request's extensions
298    #[inline]
299    fn message_extensions_mut(&self) -> RefMut<'_, Extensions> {
300        self.req.extensions_mut()
301    }
302}
303
304impl<St> fmt::Debug for WebRequest<St> {
305    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306        writeln!(
307            f,
308            "\nWebRequest {:?} {}:{}",
309            self.head().version,
310            self.head().method,
311            self.path()
312        )?;
313        if !self.query_string().is_empty() {
314            writeln!(f, "  query: ?{:?}", self.query_string())?;
315        }
316        if !self.match_info().is_empty() {
317            writeln!(f, "  params: {:?}", self.match_info())?;
318        }
319        writeln!(f, "  headers:")?;
320        for (key, val) in self.headers() {
321            if key == header::AUTHORIZATION {
322                writeln!(f, "    {key:?}: <REDACTED>")?;
323            } else {
324                writeln!(f, "    {key:?}: {val:?}")?;
325            }
326        }
327        Ok(())
328    }
329}
330
331#[cfg(test)]
332mod tests {
333    use crate::http::{self, HttpMessage, header};
334    use crate::web::{HttpResponse, test::TestRequest};
335
336    #[test]
337    fn test_request() {
338        let mut req = TestRequest::default().to_srv_request();
339        assert_eq!(req.head_mut().version, http::Version::HTTP_11);
340        assert!(req.peer_addr().is_none());
341        let err = http::error::PayloadError::Overflow;
342
343        let res: HttpResponse = req.error_response::<(), _>(&(), &err).into();
344        assert_eq!(res.status(), http::StatusCode::PAYLOAD_TOO_LARGE);
345
346        let mut req = TestRequest::default().to_srv_request();
347        req.headers_mut().insert(
348            header::CONTENT_TYPE,
349            header::HeaderValue::from_static("text"),
350        );
351        req.headers_mut().insert(
352            header::AUTHORIZATION,
353            header::HeaderValue::from_static("text"),
354        );
355        req.headers_mut().remove(header::CONTENT_TYPE);
356        assert!(!req.headers().contains_key(header::CONTENT_TYPE));
357        assert!(!req.message_headers().contains_key(header::CONTENT_TYPE));
358
359        req.extensions_mut().insert("TEXT".to_string());
360        assert_eq!(req.message_extensions().get::<String>().unwrap(), "TEXT");
361        req.message_extensions_mut().remove::<String>();
362        assert!(!req.extensions().contains::<String>());
363
364        let t = format!("{req:?}");
365        assert!(t.contains("\"authorization\": <REDACTED>"));
366    }
367
368    #[test]
369    fn test_request_parts() {
370        use crate::web::{WebRequest, types::Payload};
371
372        let (req, _, ()) = TestRequest::with_uri("/path").to_http_parts();
373        let mut wreq = WebRequest::from_request(req.clone());
374        assert_eq!(wreq.path(), "/path");
375        assert!(wreq.io().is_none());
376        assert!(
377            wreq.resource_map()
378                .url_for(&req, "unknown", [""; 0])
379                .is_err()
380        );
381
382        assert!(matches!(wreq.take_payload(), crate::http::Payload::None));
383
384        let mut preq = TestRequest::default().payload("data").to_srv_request();
385        let payload = Payload(preq.take_payload()).into_inner();
386        assert!(!matches!(payload, crate::http::Payload::None));
387        assert!(matches!(preq.take_payload(), crate::http::Payload::None));
388        wreq.set_payload(payload);
389        assert!(!matches!(wreq.take_payload(), crate::http::Payload::None));
390
391        let wreq = WebRequest::from_parts(req, crate::http::Payload::None, 10usize);
392        assert_eq!(*wreq.st(), 10);
393    }
394
395    #[test]
396    fn test_request_debug_params() {
397        let req = TestRequest::with_uri("/test")
398            .param("id", "10")
399            .to_srv_request();
400        let s = format!("{req:?}");
401        assert!(s.contains("params:"), "{s}");
402        assert!(s.contains("id"), "{s}");
403
404        let req = TestRequest::with_uri("/test")
405            .param("id", "10")
406            .to_http_request();
407        let s = format!("{req:?}");
408        assert!(s.contains("params:"), "{s}");
409    }
410}