Skip to main content

ntex/web/
httprequest.rs

1use std::{cell::Ref, cell::RefMut, convert::Infallible, fmt, net, rc::Rc};
2
3use urly::Url;
4
5use crate::http::{HeaderMap, HttpMessage, Message, Method, Payload, RequestHead, Version};
6use crate::{Cfg, io::IoRef, io::types, router::Path, util::Extensions};
7
8use super::config::WebAppConfig;
9use super::info::ConnectionInfo;
10use super::rmap::ResourceMap;
11use super::{FromRequest, State};
12
13#[derive(Clone)]
14/// An HTTP Request
15pub struct HttpRequest(pub(crate) Rc<HttpRequestInner>);
16
17pub(crate) struct HttpRequestInner {
18    pub(crate) head: Message<RequestHead>,
19    pub(crate) path: Path<Url>,
20    pub(crate) config: Cfg<WebAppConfig>,
21    pub(crate) rmap: Rc<ResourceMap>,
22}
23
24impl HttpRequest {
25    #[inline]
26    pub(crate) fn new(
27        path: Path<Url>,
28        head: Message<RequestHead>,
29        rmap: Rc<ResourceMap>,
30        config: Cfg<WebAppConfig>,
31    ) -> HttpRequest {
32        HttpRequest(Rc::new(HttpRequestInner {
33            head,
34            path,
35            config,
36            rmap,
37        }))
38    }
39}
40
41impl HttpRequest {
42    /// This method returns reference to the request head
43    #[inline]
44    pub fn head(&self) -> &RequestHead {
45        &self.0.head
46    }
47
48    /// This method returns mutable reference to the request head.
49    /// Panics if multiple references of http request exists.
50    #[inline]
51    pub(crate) fn head_mut(&mut self) -> &mut RequestHead {
52        &mut Rc::get_mut(&mut self.0).unwrap().head
53    }
54
55    /// Request's uri.
56    #[inline]
57    pub fn uri(&self) -> &Url {
58        &self.head().uri
59    }
60
61    /// Read the Request method.
62    #[inline]
63    pub fn method(&self) -> &Method {
64        &self.head().method
65    }
66
67    /// Read the Request Version.
68    #[inline]
69    pub fn version(&self) -> Version {
70        self.head().version
71    }
72
73    #[inline]
74    /// Returns request's headers.
75    pub fn headers(&self) -> &HeaderMap {
76        &self.head().headers
77    }
78
79    /// The target path of this Request.
80    #[inline]
81    pub fn path(&self) -> &str {
82        self.head().uri.path().as_str()
83    }
84
85    /// The query string in the URL.
86    ///
87    /// E.g., id=10
88    #[inline]
89    pub fn query_string(&self) -> &str {
90        self.uri().query().map_or("", urly::Query::as_str)
91    }
92
93    /// Io reference for current connection
94    #[inline]
95    pub fn io(&self) -> Option<&IoRef> {
96        self.head().io.as_ref()
97    }
98
99    /// Peer socket address
100    ///
101    /// Peer address is actual socket address, if proxy is used in front of
102    /// ntex http server, then peer address would be address of this proxy.
103    #[inline]
104    pub fn peer_addr(&self) -> Option<net::SocketAddr> {
105        self.io()
106            .and_then(|io| io.query::<types::PeerAddr>().get().map(|addr| addr.0))
107    }
108
109    /// Get a reference to the Path parameters.
110    ///
111    /// Params is a container for url parameters.
112    /// A variable segment is specified in the form `{identifier}`,
113    /// where the identifier can be used later in a request handler to
114    /// access the matched value for that segment.
115    #[inline]
116    pub fn match_info(&self) -> &Path<Url> {
117        &self.0.path
118    }
119
120    #[inline]
121    pub(crate) fn match_info_mut(&mut self) -> &mut Path<Url> {
122        &mut Rc::get_mut(&mut self.0).unwrap().path
123    }
124
125    /// Request extensions
126    #[inline]
127    pub fn extensions(&self) -> Ref<'_, Extensions> {
128        self.head().extensions()
129    }
130
131    /// Mutable reference to a the request's extensions
132    #[inline]
133    pub fn extensions_mut(&self) -> RefMut<'_, Extensions> {
134        self.head().extensions_mut()
135    }
136
137    /// Generate url for named resource.
138    ///
139    /// Each element is percent-encoded as one literal URL component before it
140    /// replaces a dynamic segment.
141    ///
142    /// ```rust
143    /// # use ntex::web::{self, App, HttpRequest, HttpResponse};
144    /// #
145    /// async fn index(req: HttpRequest) -> HttpResponse {
146    ///     let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource
147    ///     HttpResponse::Ok().into()
148    /// }
149    ///
150    /// fn main() {
151    ///     let app = App::default()
152    ///         .service(web::resource("/test/{one}/{two}/{three}")
153    ///              .name("foo")  // <- set resource name, then it could be used in `url_for`
154    ///              .route(web::get().to(index))
155    ///         );
156    /// }
157    /// ```
158    pub fn url_for<U, I>(
159        &self,
160        name: &str,
161        elements: U,
162    ) -> Result<urly::Url, super::error::UrlGenerationError>
163    where
164        U: IntoIterator<Item = I>,
165        I: AsRef<str>,
166    {
167        self.0.rmap.url_for(self, name, elements)
168    }
169
170    /// Generate url for named resource
171    ///
172    /// This method is similar to `HttpRequest::url_for()` but it can be used
173    /// for urls that do not contain variable parts.
174    pub fn url_for_static(
175        &self,
176        name: &str,
177    ) -> Result<urly::Url, super::error::UrlGenerationError> {
178        const NO_PARAMS: [&str; 0] = [];
179        self.url_for(name, NO_PARAMS)
180    }
181
182    #[inline]
183    /// Get a reference to a `ResourceMap` of current application.
184    pub fn resource_map(&self) -> &ResourceMap {
185        &self.0.rmap
186    }
187
188    /// Get *`ConnectionInfo`* for the current request.
189    ///
190    /// This method panics if request's extensions container is already
191    /// borrowed.
192    #[inline]
193    pub fn connection_info(&self) -> Ref<'_, ConnectionInfo> {
194        ConnectionInfo::get(self.head(), &self.0.config)
195    }
196
197    /// App config
198    #[inline]
199    pub fn app_config(&self) -> &WebAppConfig {
200        &self.0.config
201    }
202
203    #[inline]
204    /// Get an application state object stored with `WebAppConfig`.
205    pub fn app_state<T: 'static>(&self) -> Option<&T> {
206        self.0.config.state::<T>()
207    }
208}
209
210impl HttpMessage for HttpRequest {
211    #[inline]
212    /// Returns Request's headers.
213    fn message_headers(&self) -> &HeaderMap {
214        &self.head().headers
215    }
216
217    /// Request extensions
218    #[inline]
219    fn message_extensions(&self) -> Ref<'_, Extensions> {
220        self.0.head.extensions()
221    }
222
223    /// Mutable reference to a the request's extensions
224    #[inline]
225    fn message_extensions_mut(&self) -> RefMut<'_, Extensions> {
226        self.0.head.extensions_mut()
227    }
228}
229
230impl Drop for HttpRequest {
231    fn drop(&mut self) {
232        let id = self.0.config.id();
233        let pool_size = self.0.config.pool_size;
234        super::config::put_request(id, pool_size, &mut self.0);
235    }
236}
237
238/// It is possible to get `HttpRequest` as an extractor handler parameter
239///
240/// ## Example
241///
242/// ```rust
243/// use ntex::web::{self, App, HttpRequest};
244///
245/// /// extract `HttpRequest` from request
246/// async fn index(req: HttpRequest) -> String {
247///    format!("Got thing: {req:?}")
248/// }
249///
250/// fn main() {
251///     let app = App::default().service(
252///         web::resource("/users/{first}").route(
253///             web::get().to(index))
254///     );
255/// }
256/// ```
257impl<St: State> FromRequest<St> for HttpRequest {
258    type Error = Infallible;
259
260    #[inline]
261    async fn from_request(_: &St, req: &HttpRequest, _: &mut Payload) -> Result<Self, Self::Error> {
262        Ok(req.clone())
263    }
264}
265
266impl fmt::Debug for HttpRequest {
267    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268        writeln!(
269            f,
270            "\nHttpRequest {:?} {}:{}",
271            self.0.head.version,
272            self.0.head.method,
273            self.path()
274        )?;
275        if !self.query_string().is_empty() {
276            writeln!(f, "  query: ?{:?}", self.query_string())?;
277        }
278        if !self.match_info().is_empty() {
279            writeln!(f, "  params: {:?}", self.match_info())?;
280        }
281        writeln!(f, "  headers:")?;
282        for (key, val) in self.headers() {
283            writeln!(f, "    {key:?}: {val:?}")?;
284        }
285        Ok(())
286    }
287}
288
289#[cfg(test)]
290mod tests {
291    use super::*;
292    use crate::http::header;
293    use crate::router::ResourceDef;
294    use crate::web::test::TestRequest;
295
296    #[test]
297    fn test_debug() {
298        let req = TestRequest::with_header("content-type", "text/plain").to_http_request();
299        let dbg = format!("{req:?}");
300        assert!(dbg.contains("HttpRequest"));
301
302        let req = TestRequest::with_uri("/index.html?q=?").to_http_request();
303        let dbg = format!("{req:?}");
304        assert!(dbg.contains("HttpRequest"));
305        assert!(req.peer_addr().is_none());
306        assert_eq!(req.method(), &Method::GET);
307    }
308
309    #[cfg(feature = "cookie")]
310    #[test]
311    fn test_no_request_cookies() {
312        let req = TestRequest::default().to_http_request();
313        assert!(req.cookies().unwrap().is_empty());
314    }
315
316    #[cfg(feature = "cookie")]
317    #[test]
318    fn test_request_cookies() {
319        let req = TestRequest::default()
320            .header(header::COOKIE, "cookie1=value1")
321            .header(header::COOKIE, "cookie2=value2")
322            .to_http_request();
323        {
324            let cookies = req.cookies().unwrap();
325            assert_eq!(cookies.len(), 2);
326            assert_eq!(cookies[0].name(), "cookie1");
327            assert_eq!(cookies[0].value(), "value1");
328            assert_eq!(cookies[1].name(), "cookie2");
329            assert_eq!(cookies[1].value(), "value2");
330        }
331
332        let cookie = req.cookie("cookie1");
333        assert!(cookie.is_some());
334        let cookie = cookie.unwrap();
335        assert_eq!(cookie.name(), "cookie1");
336        assert_eq!(cookie.value(), "value1");
337
338        let cookie = req.cookie("cookie-unknown");
339        assert!(cookie.is_none());
340    }
341
342    #[test]
343    fn test_request_query() {
344        let req = TestRequest::with_uri("/?id=test").to_http_request();
345        assert_eq!(req.query_string(), "id=test");
346    }
347
348    #[test]
349    fn test_url_for() {
350        let mut res = ResourceDef::new("/user/{name}.{ext}");
351        res.set_name("index");
352
353        let mut rmap = ResourceMap::new(ResourceDef::new(""));
354        rmap.add(&mut res, None);
355        //assert!(rmap.has_resource("/user/test.html"));
356        //assert!(!rmap.has_resource("/test/unknown"));
357
358        let req = TestRequest::with_header(header::HOST, "www.rust-lang.org")
359            .rmap(rmap)
360            .to_http_request();
361
362        assert_eq!(
363            req.url_for("unknown", ["test"]),
364            Err(crate::web::error::UrlGenerationError::ResourceNotFound)
365        );
366        assert_eq!(
367            req.url_for("index", ["test"]),
368            Err(crate::web::error::UrlGenerationError::NotEnoughElements)
369        );
370        let url = req.url_for("index", ["test", "html"]);
371        assert_eq!(
372            url.ok().unwrap().as_str(),
373            "http://www.rust-lang.org/user/test.html"
374        );
375    }
376
377    #[test]
378    fn test_url_for_static() {
379        let mut rdef = ResourceDef::new("/index.html");
380        rdef.set_name("index");
381
382        let mut rmap = ResourceMap::new(ResourceDef::new(""));
383        rmap.add(&mut rdef, None);
384
385        // assert!(rmap.has_resource("/index.html"));
386
387        let req = TestRequest::with_uri("/test")
388            .header(header::HOST, "www.rust-lang.org")
389            .rmap(rmap)
390            .to_http_request();
391        let url = req.url_for_static("index");
392        assert_eq!(
393            url.ok().unwrap().as_str(),
394            "http://www.rust-lang.org/index.html"
395        );
396    }
397
398    #[test]
399    fn test_url_for_external() {
400        let mut rdef = ResourceDef::new("https://youtube.com/watch/{video_id}");
401
402        rdef.set_name("youtube");
403
404        let mut rmap = ResourceMap::new(ResourceDef::new(""));
405        rmap.add(&mut rdef, None);
406        // assert!(rmap.has_resource("https://youtube.com/watch/unknown"));
407
408        let req = TestRequest::default().rmap(rmap).to_http_request();
409        let url = req.url_for("youtube", ["oHg5SJYRHA0"]);
410        assert_eq!(
411            url.ok().unwrap().as_str(),
412            "https://youtube.com/watch/oHg5SJYRHA0"
413        );
414    }
415}