Skip to main content

actix_web/
request.rs

1use std::{
2    cell::{Ref, RefCell, RefMut},
3    collections::HashMap,
4    fmt,
5    hash::{BuildHasher, Hash},
6    net,
7    rc::Rc,
8    str,
9};
10
11use actix_http::{Message, RequestHead};
12use actix_router::{Path, Url};
13use actix_utils::future::{ok, Ready};
14#[cfg(feature = "cookies")]
15use cookie::{Cookie, ParseError as CookieParseError};
16use smallvec::SmallVec;
17
18use crate::{
19    app_service::AppInitServiceState,
20    config::AppConfig,
21    dev::{Extensions, Payload},
22    error::UrlGenerationError,
23    http::{header::HeaderMap, Method, Uri, Version},
24    info::ConnectionInfo,
25    rmap::ResourceMap,
26    Error, FromRequest, HttpMessage,
27};
28
29#[cfg(feature = "cookies")]
30struct Cookies(Vec<Cookie<'static>>);
31
32/// An incoming request.
33#[derive(Clone)]
34pub struct HttpRequest {
35    /// # Invariant
36    /// `Rc<HttpRequestInner>` is used exclusively and NO `Weak<HttpRequestInner>`
37    /// is allowed anywhere in the code. Weak pointer is purposely ignored when
38    /// doing `Rc`'s ref counter check. Expect panics if this invariant is violated.
39    pub(crate) inner: Rc<HttpRequestInner>,
40}
41
42pub(crate) struct HttpRequestInner {
43    pub(crate) head: Message<RequestHead>,
44    pub(crate) path: Path<Url>,
45    pub(crate) app_data: SmallVec<[Rc<Extensions>; 4]>,
46    pub(crate) conn_data: Option<Rc<Extensions>>,
47    pub(crate) extensions: Rc<RefCell<Extensions>>,
48    app_state: Rc<AppInitServiceState>,
49}
50
51impl HttpRequest {
52    #[inline]
53    pub(crate) fn new(
54        path: Path<Url>,
55        head: Message<RequestHead>,
56        app_state: Rc<AppInitServiceState>,
57        app_data: Rc<Extensions>,
58        conn_data: Option<Rc<Extensions>>,
59        extensions: Rc<RefCell<Extensions>>,
60    ) -> HttpRequest {
61        let mut data = SmallVec::<[Rc<Extensions>; 4]>::new();
62        data.push(app_data);
63
64        HttpRequest {
65            inner: Rc::new(HttpRequestInner {
66                head,
67                path,
68                app_state,
69                app_data: data,
70                conn_data,
71                extensions,
72            }),
73        }
74    }
75}
76
77impl HttpRequest {
78    /// This method returns reference to the request head
79    #[inline]
80    pub fn head(&self) -> &RequestHead {
81        &self.inner.head
82    }
83
84    /// This method returns mutable reference to the request head.
85    /// panics if multiple references of HTTP request exists.
86    #[inline]
87    pub(crate) fn head_mut(&mut self) -> &mut RequestHead {
88        &mut Rc::get_mut(&mut self.inner).unwrap().head
89    }
90
91    /// Request's uri.
92    #[inline]
93    pub fn uri(&self) -> &Uri {
94        &self.head().uri
95    }
96
97    /// Returns request's original full URL.
98    ///
99    /// Reconstructed URL is best-effort, using [`connection_info`](HttpRequest::connection_info())
100    /// to get forwarded scheme & host.
101    ///
102    /// ```
103    /// use actix_web::test::TestRequest;
104    /// let req = TestRequest::with_uri("http://10.1.2.3:8443/api?id=4&name=foo")
105    ///     .insert_header(("host", "example.com"))
106    ///     .to_http_request();
107    ///
108    /// assert_eq!(
109    ///     req.full_url().as_str(),
110    ///     "http://example.com/api?id=4&name=foo",
111    /// );
112    /// ```
113    pub fn full_url(&self) -> url::Url {
114        let info = self.connection_info();
115        let scheme = info.scheme();
116        let host = info.host();
117        let path_and_query = self
118            .uri()
119            .path_and_query()
120            .map(|paq| paq.as_str())
121            .unwrap_or("/");
122
123        url::Url::parse(&format!("{scheme}://{host}{path_and_query}")).unwrap()
124    }
125
126    /// Read the Request method.
127    #[inline]
128    pub fn method(&self) -> &Method {
129        &self.head().method
130    }
131
132    /// Read the Request Version.
133    #[inline]
134    pub fn version(&self) -> Version {
135        self.head().version
136    }
137
138    #[inline]
139    /// Returns request's headers.
140    pub fn headers(&self) -> &HeaderMap {
141        &self.head().headers
142    }
143
144    /// The target path of this request.
145    #[inline]
146    pub fn path(&self) -> &str {
147        self.head().uri.path()
148    }
149
150    /// The query string in the URL.
151    ///
152    /// Example: `id=10`
153    #[inline]
154    pub fn query_string(&self) -> &str {
155        self.uri().query().unwrap_or_default()
156    }
157
158    /// Returns a reference to the URL parameters container.
159    ///
160    /// A URL parameter is specified in the form `{identifier}`, where the identifier can be used
161    /// later in a request handler to access the matched value for that parameter.
162    ///
163    /// # Percent Encoding and URL Parameters
164    /// Because each URL parameter is able to capture multiple path segments, none of
165    /// `["%2F", "%25", "%2B"]` found in the request URI are decoded into `["/", "%", "+"]` in order
166    /// to preserve path integrity. If a URL parameter is expected to contain these characters, then
167    /// it is on the user to decode them or use the [`web::Path`](crate::web::Path) extractor which
168    /// _will_ decode these special sequences.
169    #[inline]
170    pub fn match_info(&self) -> &Path<Url> {
171        &self.inner.path
172    }
173
174    /// Returns a mutable reference to the URL parameters container.
175    ///
176    /// # Panics
177    /// Panics if this `HttpRequest` has been cloned.
178    #[inline]
179    pub(crate) fn match_info_mut(&mut self) -> &mut Path<Url> {
180        &mut Rc::get_mut(&mut self.inner).unwrap().path
181    }
182
183    /// The resource definition pattern that matched the path. Useful for logging and metrics.
184    ///
185    /// For example, when a resource with pattern `/user/{id}/profile` is defined and a call is made
186    /// to `/user/123/profile` this function would return `Some("/user/{id}/profile")`.
187    ///
188    /// Returns a None when no resource is fully matched, including default services.
189    #[inline]
190    pub fn match_pattern(&self) -> Option<String> {
191        self.resource_map().match_pattern(self.path())
192    }
193
194    /// The resource name that matched the path. Useful for logging and metrics.
195    ///
196    /// Returns a None when no resource is fully matched, including default services.
197    #[inline]
198    pub fn match_name(&self) -> Option<&str> {
199        self.resource_map().match_name(self.path())
200    }
201
202    /// Returns a reference a piece of connection data set in an [on-connect] callback.
203    ///
204    /// ```ignore
205    /// let opt_t = req.conn_data::<PeerCertificate>();
206    /// ```
207    ///
208    /// [on-connect]: crate::HttpServer::on_connect
209    pub fn conn_data<T: 'static>(&self) -> Option<&T> {
210        self.inner
211            .conn_data
212            .as_deref()
213            .and_then(|container| container.get::<T>())
214    }
215
216    /// Generates URL for a named resource.
217    ///
218    /// This substitutes in sequence all URL parameters that appear in the resource itself and in
219    /// parent [scopes](crate::web::scope), if any.
220    ///
221    /// It is worth noting that the characters `['/', '%']` are not escaped and therefore a single
222    /// URL parameter may expand into multiple path segments and `elements` can be percent-encoded
223    /// beforehand without worrying about double encoding. Any other character that is not valid in
224    /// a URL path context is escaped using percent-encoding.
225    ///
226    /// # Examples
227    /// ```
228    /// # use actix_web::{web, App, HttpRequest, HttpResponse};
229    /// fn index(req: HttpRequest) -> HttpResponse {
230    ///     let url = req.url_for("foo", &["1", "2", "3"]); // <- generate URL for "foo" resource
231    ///     HttpResponse::Ok().into()
232    /// }
233    ///
234    /// let app = App::new()
235    ///     .service(web::resource("/test/{one}/{two}/{three}")
236    ///          .name("foo")  // <- set resource name so it can be used in `url_for`
237    ///          .route(web::get().to(|| HttpResponse::Ok()))
238    ///     );
239    /// ```
240    pub fn url_for<U, I>(&self, name: &str, elements: U) -> Result<url::Url, UrlGenerationError>
241    where
242        U: IntoIterator<Item = I>,
243        I: AsRef<str>,
244    {
245        self.resource_map().url_for(self, name, elements)
246    }
247
248    /// Generates URL for a named resource using a map of dynamic segment values.
249    ///
250    /// This substitutes URL parameters by name from `elements`, including parameters from parent
251    /// scopes.
252    ///
253    /// # Examples
254    /// ```
255    /// # use std::collections::HashMap;
256    /// # use actix_web::{web, App, HttpRequest, HttpResponse};
257    /// fn index(req: HttpRequest) -> HttpResponse {
258    ///     let mut params = HashMap::new();
259    ///     params.insert("one", "1");
260    ///     params.insert("two", "2");
261    ///     let url = req.url_for_map("foo", &params); // <- generate URL for "foo" resource
262    ///     HttpResponse::Ok().into()
263    /// }
264    ///
265    /// let app = App::new()
266    ///     .service(web::resource("/test/{one}/{two}")
267    ///          .name("foo")  // <- set resource name so it can be used in `url_for_map`
268    ///          .route(web::get().to(|| HttpResponse::Ok()))
269    ///     );
270    /// ```
271    pub fn url_for_map<K, V, S>(
272        &self,
273        name: &str,
274        elements: &HashMap<K, V, S>,
275    ) -> Result<url::Url, UrlGenerationError>
276    where
277        K: std::borrow::Borrow<str> + Eq + Hash,
278        V: AsRef<str>,
279        S: BuildHasher,
280    {
281        self.resource_map().url_for_map(self, name, elements)
282    }
283
284    /// Generates URL for a named resource using an iterator of key-value pairs.
285    ///
286    /// This is a convenience wrapper around [`HttpRequest::url_for_map`].
287    ///
288    /// Note: passing a borrowed map (e.g. `&HashMap<String, String>`) directly does not satisfy the
289    /// trait bounds because the iterator yields `(&String, &String)`. Prefer `url_for_map` for
290    /// borrowed maps, or map entries to `&str`:
291    ///
292    /// ```
293    /// # use std::collections::HashMap;
294    /// # use actix_web::{web, App, HttpRequest, HttpResponse};
295    /// fn index(req: HttpRequest) -> HttpResponse {
296    ///     let mut params = HashMap::new();
297    ///     params.insert("one".to_string(), "1".to_string());
298    ///     params.insert("two".to_string(), "2".to_string());
299    ///
300    ///     let iter = params.iter().map(|(k, v)| (k.as_str(), v.as_str()));
301    ///     let url = req.url_for_iter("foo", iter);
302    ///     HttpResponse::Ok().into()
303    /// }
304    /// ```
305    pub fn url_for_iter<K, V, I>(
306        &self,
307        name: &str,
308        elements: I,
309    ) -> Result<url::Url, UrlGenerationError>
310    where
311        I: IntoIterator<Item = (K, V)>,
312        K: std::borrow::Borrow<str> + Eq + Hash,
313        V: AsRef<str>,
314    {
315        self.resource_map().url_for_iter(self, name, elements)
316    }
317
318    /// Generate URL for named resource
319    ///
320    /// This method is similar to `HttpRequest::url_for()` but it can be used
321    /// for urls that do not contain variable parts.
322    pub fn url_for_static(&self, name: &str) -> Result<url::Url, UrlGenerationError> {
323        const NO_PARAMS: [&str; 0] = [];
324        self.url_for(name, NO_PARAMS)
325    }
326
327    /// Get a reference to a `ResourceMap` of current application.
328    #[inline]
329    pub fn resource_map(&self) -> &ResourceMap {
330        self.app_state().rmap()
331    }
332
333    /// Returns peer socket address.
334    ///
335    /// Peer address is the directly connected peer's socket address. If a proxy is used in front of
336    /// the Actix Web server, then it would be address of this proxy.
337    ///
338    /// For expanded client connection information, use [`connection_info`] instead.
339    ///
340    /// Will only return `None` when server is listening on [UDS socket] or when called in unit
341    /// tests unless [`TestRequest::peer_addr`] is used.
342    ///
343    /// [UDS socket]: crate::HttpServer::bind_uds
344    /// [`TestRequest::peer_addr`]: crate::test::TestRequest::peer_addr
345    /// [`connection_info`]: Self::connection_info
346    #[inline]
347    pub fn peer_addr(&self) -> Option<net::SocketAddr> {
348        self.head().peer_addr
349    }
350
351    /// Returns connection info for the current request.
352    ///
353    /// The return type, [`ConnectionInfo`], can also be used as an extractor.
354    ///
355    /// # Panics
356    /// Panics if request's extensions container is already borrowed.
357    #[inline]
358    pub fn connection_info(&self) -> Ref<'_, ConnectionInfo> {
359        if !self.extensions().contains::<ConnectionInfo>() {
360            let info = ConnectionInfo::new(self.head(), self.app_config());
361            self.extensions_mut().insert(info);
362        }
363
364        Ref::map(self.extensions(), |data| data.get().unwrap())
365    }
366
367    /// Returns a reference to the application's connection configuration.
368    #[inline]
369    pub fn app_config(&self) -> &AppConfig {
370        self.app_state().config()
371    }
372
373    /// Retrieves a piece of application state.
374    ///
375    /// Extracts any object stored with [`App::app_data()`](crate::App::app_data) (or the
376    /// counterpart methods on [`Scope`](crate::Scope::app_data) and
377    /// [`Resource`](crate::Resource::app_data)) during application configuration.
378    ///
379    /// Since the Actix Web router layers application data, the returned object will reference the
380    /// "closest" instance of the type. For example, if an `App` stores a `u32`, a nested `Scope`
381    /// also stores a `u32`, and the delegated request handler falls within that `Scope`, then
382    /// calling `.app_data::<u32>()` on an `HttpRequest` within that handler will return the
383    /// `Scope`'s instance. However, using the same router set up and a request that does not get
384    /// captured by the `Scope`, `.app_data::<u32>()` would return the `App`'s instance.
385    ///
386    /// If the state was stored using the [`Data`] wrapper, then it must also be retrieved using
387    /// this same type.
388    ///
389    /// See also the [`Data`] extractor.
390    ///
391    /// # Examples
392    /// ```no_run
393    /// # use actix_web::{test::TestRequest, web::Data};
394    /// # let req = TestRequest::default().to_http_request();
395    /// # type T = u32;
396    /// let opt_t: Option<&Data<T>> = req.app_data::<Data<T>>();
397    /// ```
398    ///
399    /// [`Data`]: crate::web::Data
400    #[doc(alias = "state")]
401    pub fn app_data<T: 'static>(&self) -> Option<&T> {
402        for container in self.inner.app_data.iter().rev() {
403            if let Some(data) = container.get::<T>() {
404                return Some(data);
405            }
406        }
407
408        None
409    }
410
411    #[inline]
412    fn app_state(&self) -> &AppInitServiceState {
413        &self.inner.app_state
414    }
415
416    /// Load request cookies.
417    ///
418    /// Any cookie that cannot be parsed is omitted from the result.
419    /// This includes cookies with an empty name (e.g. `document.cookie = "=value"`).
420    #[cfg(feature = "cookies")]
421    pub fn cookies(&self) -> Result<Ref<'_, Vec<Cookie<'static>>>, CookieParseError> {
422        use actix_http::header::COOKIE;
423
424        if self.extensions().get::<Cookies>().is_none() {
425            let mut cookies = Vec::new();
426            for hdr in self.headers().get_all(COOKIE) {
427                let s = str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?;
428                for cookie_str in s.split(';').map(|s| s.trim()).filter(|s| !s.is_empty()) {
429                    if let Ok(cookie) = Cookie::parse_encoded(cookie_str) {
430                        cookies.push(cookie.into_owned());
431                    }
432                }
433            }
434            self.extensions_mut().insert(Cookies(cookies));
435        }
436
437        Ok(Ref::map(self.extensions(), |ext| {
438            &ext.get::<Cookies>().unwrap().0
439        }))
440    }
441
442    /// Return request cookie.
443    #[cfg(feature = "cookies")]
444    pub fn cookie(&self, name: &str) -> Option<Cookie<'static>> {
445        if let Ok(cookies) = self.cookies() {
446            for cookie in cookies.iter() {
447                if cookie.name() == name {
448                    return Some(cookie.to_owned());
449                }
450            }
451        }
452        None
453    }
454}
455
456impl HttpMessage for HttpRequest {
457    type Stream = ();
458
459    #[inline]
460    fn headers(&self) -> &HeaderMap {
461        &self.head().headers
462    }
463
464    #[inline]
465    fn extensions(&self) -> Ref<'_, Extensions> {
466        self.inner.extensions.borrow()
467    }
468
469    #[inline]
470    fn extensions_mut(&self) -> RefMut<'_, Extensions> {
471        self.inner.extensions.borrow_mut()
472    }
473
474    #[inline]
475    fn take_payload(&mut self) -> Payload<Self::Stream> {
476        Payload::None
477    }
478}
479
480impl Drop for HttpRequest {
481    fn drop(&mut self) {
482        // if possible, contribute to current worker's HttpRequest allocation pool
483
484        // This relies on no weak references to inner existing anywhere within the codebase.
485        if let Some(inner) = Rc::get_mut(&mut self.inner) {
486            if inner.app_state.pool().is_available() {
487                // clear additional app_data and keep the root one for reuse.
488                inner.app_data.truncate(1);
489
490                // Inner is borrowed mut here and; get req data mutably to reduce borrow check. Also
491                // we know the req_data Rc will not have any clones at this point to unwrap is okay.
492                Rc::get_mut(&mut inner.extensions)
493                    .unwrap()
494                    .get_mut()
495                    .clear();
496
497                // We can't use the same trick as req data because the conn_data is held by the
498                // dispatcher, too.
499                inner.conn_data = None;
500
501                // a re-borrow of pool is necessary here.
502                let req = Rc::clone(&self.inner);
503                self.app_state().pool().push(req);
504            }
505        }
506    }
507}
508
509/// It is possible to get `HttpRequest` as an extractor handler parameter
510///
511/// # Examples
512/// ```
513/// use actix_web::{web, App, HttpRequest};
514/// use serde::Deserialize;
515///
516/// /// extract `Thing` from request
517/// async fn index(req: HttpRequest) -> String {
518///    format!("Got thing: {:?}", req)
519/// }
520///
521/// let app = App::new().service(
522///     web::resource("/users/{first}").route(
523///         web::get().to(index))
524/// );
525/// ```
526impl FromRequest for HttpRequest {
527    type Error = Error;
528    type Future = Ready<Result<Self, Error>>;
529
530    #[inline]
531    fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
532        ok(req.clone())
533    }
534}
535
536impl fmt::Debug for HttpRequest {
537    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
538        writeln!(
539            f,
540            "\nHttpRequest {:?} {}:{}",
541            self.inner.head.version,
542            self.inner.head.method,
543            self.path()
544        )?;
545
546        if !self.query_string().is_empty() {
547            writeln!(f, "  query: ?{:?}", self.query_string())?;
548        }
549
550        if !self.match_info().is_empty() {
551            writeln!(f, "  params: {:?}", self.match_info())?;
552        }
553
554        writeln!(f, "  headers:")?;
555
556        for (key, val) in self.headers().iter() {
557            match key {
558                // redact sensitive header values from debug output
559                &crate::http::header::AUTHORIZATION
560                | &crate::http::header::PROXY_AUTHORIZATION
561                | &crate::http::header::COOKIE => writeln!(f, "    {:?}: {:?}", key, "*redacted*")?,
562
563                _ => writeln!(f, "    {:?}: {:?}", key, val)?,
564            }
565        }
566
567        Ok(())
568    }
569}
570
571/// Slab-allocated `HttpRequest` Pool
572///
573/// Since request processing may yield for asynchronous events to complete, a worker may have many
574/// requests in-flight at any time. Pooling requests like this amortizes the performance and memory
575/// costs of allocating and de-allocating HttpRequest objects as frequently as they otherwise would.
576///
577/// Request objects are added when they are dropped (see `<HttpRequest as Drop>::drop`) and re-used
578/// in `<AppInitService as Service>::call` when there are available objects in the list.
579///
580/// The pool's default capacity is 128 items.
581pub(crate) struct HttpRequestPool {
582    inner: RefCell<Vec<Rc<HttpRequestInner>>>,
583    cap: usize,
584}
585
586impl Default for HttpRequestPool {
587    fn default() -> Self {
588        Self::with_capacity(128)
589    }
590}
591
592impl HttpRequestPool {
593    pub(crate) fn with_capacity(cap: usize) -> Self {
594        HttpRequestPool {
595            inner: RefCell::new(Vec::with_capacity(cap)),
596            cap,
597        }
598    }
599
600    /// Re-use a previously allocated (but now completed/discarded) HttpRequest object.
601    #[inline]
602    pub(crate) fn pop(&self) -> Option<HttpRequest> {
603        self.inner
604            .borrow_mut()
605            .pop()
606            .map(|inner| HttpRequest { inner })
607    }
608
609    /// Check if the pool still has capacity for request storage.
610    #[inline]
611    pub(crate) fn is_available(&self) -> bool {
612        self.inner.borrow_mut().len() < self.cap
613    }
614
615    /// Push a request to pool.
616    #[inline]
617    pub(crate) fn push(&self, req: Rc<HttpRequestInner>) {
618        self.inner.borrow_mut().push(req);
619    }
620
621    /// Clears all allocated HttpRequest objects.
622    pub(crate) fn clear(&self) {
623        self.inner.borrow_mut().clear()
624    }
625}
626
627#[cfg(test)]
628mod tests {
629    use std::collections::HashMap;
630
631    use bytes::Bytes;
632
633    use super::*;
634    use crate::{
635        dev::{ResourceDef, Service},
636        http::{header, StatusCode},
637        test::{self, call_service, init_service, read_body, TestRequest},
638        web, App, HttpResponse,
639    };
640
641    #[test]
642    fn test_debug() {
643        let req = TestRequest::default()
644            .insert_header(("content-type", "text/plain"))
645            .to_http_request();
646        let dbg = format!("{:?}", req);
647        assert!(dbg.contains("HttpRequest"));
648    }
649
650    #[test]
651    #[cfg(feature = "cookies")]
652    fn test_no_request_cookies() {
653        let req = TestRequest::default().to_http_request();
654        assert!(req.cookies().unwrap().is_empty());
655    }
656
657    #[test]
658    #[cfg(feature = "cookies")]
659    fn test_request_cookies() {
660        let req = TestRequest::default()
661            .append_header((header::COOKIE, "cookie1=value1"))
662            .append_header((header::COOKIE, "cookie2=value2"))
663            .to_http_request();
664        {
665            let cookies = req.cookies().unwrap();
666            assert_eq!(cookies.len(), 2);
667            assert_eq!(cookies[0].name(), "cookie1");
668            assert_eq!(cookies[0].value(), "value1");
669            assert_eq!(cookies[1].name(), "cookie2");
670            assert_eq!(cookies[1].value(), "value2");
671        }
672
673        let cookie = req.cookie("cookie1");
674        assert!(cookie.is_some());
675        let cookie = cookie.unwrap();
676        assert_eq!(cookie.name(), "cookie1");
677        assert_eq!(cookie.value(), "value1");
678
679        let cookie = req.cookie("cookie-unknown");
680        assert!(cookie.is_none());
681    }
682
683    #[test]
684    #[cfg(feature = "cookies")]
685    fn test_empty_key() {
686        let req = TestRequest::default()
687            .append_header((header::COOKIE, "cookie1=value1; value2; cookie3=value3"))
688            .to_http_request();
689        {
690            let cookies = req.cookies().unwrap();
691            assert_eq!(cookies.len(), 2);
692            assert_eq!(cookies[0].name(), "cookie1");
693            assert_eq!(cookies[0].value(), "value1");
694            assert_eq!(cookies[1].name(), "cookie3");
695            assert_eq!(cookies[1].value(), "value3");
696        }
697    }
698
699    #[test]
700    fn test_request_query() {
701        let req = TestRequest::with_uri("/?id=test").to_http_request();
702        assert_eq!(req.query_string(), "id=test");
703    }
704
705    #[test]
706    fn test_url_for() {
707        let mut res = ResourceDef::new("/user/{name}.{ext}");
708        res.set_name("index");
709
710        let mut rmap = ResourceMap::new(ResourceDef::prefix(""));
711        rmap.add(&mut res, None);
712        assert!(rmap.has_resource("/user/test.html"));
713        assert!(!rmap.has_resource("/test/unknown"));
714
715        let req = TestRequest::default()
716            .insert_header((header::HOST, "www.rust-lang.org"))
717            .rmap(rmap)
718            .to_http_request();
719
720        assert_eq!(
721            req.url_for("unknown", ["test"]),
722            Err(UrlGenerationError::ResourceNotFound)
723        );
724        assert_eq!(
725            req.url_for("index", ["test"]),
726            Err(UrlGenerationError::NotEnoughElements)
727        );
728        let url = req.url_for("index", ["test", "html"]);
729        assert_eq!(
730            url.ok().unwrap().as_str(),
731            "http://www.rust-lang.org/user/test.html"
732        );
733    }
734
735    #[test]
736    fn test_url_for_map() {
737        let mut res = ResourceDef::new("/user/{name}.{ext}");
738        res.set_name("index");
739
740        let mut rmap = ResourceMap::new(ResourceDef::prefix(""));
741        rmap.add(&mut res, None);
742
743        let req = TestRequest::default()
744            .insert_header((header::HOST, "www.actix.rs"))
745            .rmap(rmap)
746            .to_http_request();
747
748        let mut params = HashMap::new();
749        params.insert("name", "test");
750        params.insert("ext", "html");
751
752        let url = req.url_for_map("index", &params);
753        assert_eq!(
754            url.ok().unwrap().as_str(),
755            "http://www.actix.rs/user/test.html"
756        );
757
758        params.remove("ext");
759        assert_eq!(
760            req.url_for_map("index", &params),
761            Err(UrlGenerationError::NotEnoughElements)
762        );
763    }
764
765    #[test]
766    fn test_url_for_iter() {
767        let mut res = ResourceDef::new("/user/{name}.{ext}");
768        res.set_name("index");
769
770        let mut rmap = ResourceMap::new(ResourceDef::prefix(""));
771        rmap.add(&mut res, None);
772
773        let req = TestRequest::default()
774            .insert_header((header::HOST, "www.actix.rs"))
775            .rmap(rmap)
776            .to_http_request();
777
778        let url = req.url_for_iter("index", [("ext", "html"), ("name", "test")]);
779        assert_eq!(
780            url.ok().unwrap().as_str(),
781            "http://www.actix.rs/user/test.html"
782        );
783
784        let url = req.url_for_iter("index", [("name", "test")]);
785        assert_eq!(url, Err(UrlGenerationError::NotEnoughElements));
786    }
787
788    #[test]
789    fn test_url_for_static() {
790        let mut rdef = ResourceDef::new("/index.html");
791        rdef.set_name("index");
792
793        let mut rmap = ResourceMap::new(ResourceDef::prefix(""));
794        rmap.add(&mut rdef, None);
795
796        assert!(rmap.has_resource("/index.html"));
797
798        let req = TestRequest::with_uri("/test")
799            .insert_header((header::HOST, "www.rust-lang.org"))
800            .rmap(rmap)
801            .to_http_request();
802        let url = req.url_for_static("index");
803        assert_eq!(
804            url.ok().unwrap().as_str(),
805            "http://www.rust-lang.org/index.html"
806        );
807    }
808
809    #[test]
810    fn test_match_name() {
811        let mut rdef = ResourceDef::new("/index.html");
812        rdef.set_name("index");
813
814        let mut rmap = ResourceMap::new(ResourceDef::prefix(""));
815        rmap.add(&mut rdef, None);
816
817        assert!(rmap.has_resource("/index.html"));
818
819        let req = TestRequest::default()
820            .uri("/index.html")
821            .rmap(rmap)
822            .to_http_request();
823
824        assert_eq!(req.match_name(), Some("index"));
825    }
826
827    #[test]
828    fn test_url_for_external() {
829        let mut rdef = ResourceDef::new("https://youtube.com/watch/{video_id}");
830
831        rdef.set_name("youtube");
832
833        let mut rmap = ResourceMap::new(ResourceDef::prefix(""));
834        rmap.add(&mut rdef, None);
835
836        let req = TestRequest::default().rmap(rmap).to_http_request();
837        let url = req.url_for("youtube", ["oHg5SJYRHA0"]);
838        assert_eq!(
839            url.ok().unwrap().as_str(),
840            "https://youtube.com/watch/oHg5SJYRHA0"
841        );
842    }
843
844    #[actix_rt::test]
845    async fn test_drop_http_request_pool() {
846        let srv = init_service(
847            App::new().service(web::resource("/").to(|req: HttpRequest| {
848                HttpResponse::Ok()
849                    .insert_header(("pool_cap", req.app_state().pool().cap))
850                    .finish()
851            })),
852        )
853        .await;
854
855        let req = TestRequest::default().to_request();
856        let resp = call_service(&srv, req).await;
857
858        drop(srv);
859
860        assert_eq!(resp.headers().get("pool_cap").unwrap(), "128");
861    }
862
863    #[actix_rt::test]
864    async fn test_data() {
865        let srv = init_service(App::new().app_data(10usize).service(web::resource("/").to(
866            |req: HttpRequest| {
867                if req.app_data::<usize>().is_some() {
868                    HttpResponse::Ok()
869                } else {
870                    HttpResponse::BadRequest()
871                }
872            },
873        )))
874        .await;
875
876        let req = TestRequest::default().to_request();
877        let resp = call_service(&srv, req).await;
878        assert_eq!(resp.status(), StatusCode::OK);
879
880        let srv = init_service(App::new().app_data(10u32).service(web::resource("/").to(
881            |req: HttpRequest| {
882                if req.app_data::<usize>().is_some() {
883                    HttpResponse::Ok()
884                } else {
885                    HttpResponse::BadRequest()
886                }
887            },
888        )))
889        .await;
890
891        let req = TestRequest::default().to_request();
892        let resp = call_service(&srv, req).await;
893        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
894    }
895
896    #[actix_rt::test]
897    async fn test_cascading_data() {
898        #[allow(dead_code)]
899        fn echo_usize(req: HttpRequest) -> HttpResponse {
900            let num = req.app_data::<usize>().unwrap();
901            HttpResponse::Ok().body(num.to_string())
902        }
903
904        let srv = init_service(
905            App::new()
906                .app_data(88usize)
907                .service(web::resource("/").route(web::get().to(echo_usize)))
908                .service(
909                    web::resource("/one")
910                        .app_data(1u32)
911                        .route(web::get().to(echo_usize)),
912                ),
913        )
914        .await;
915
916        let req = TestRequest::get().uri("/").to_request();
917        let resp = srv.call(req).await.unwrap();
918        let body = read_body(resp).await;
919        assert_eq!(body, Bytes::from_static(b"88"));
920
921        let req = TestRequest::get().uri("/one").to_request();
922        let resp = srv.call(req).await.unwrap();
923        let body = read_body(resp).await;
924        assert_eq!(body, Bytes::from_static(b"88"));
925    }
926
927    #[actix_rt::test]
928    async fn test_overwrite_data() {
929        #[allow(dead_code)]
930        fn echo_usize(req: HttpRequest) -> HttpResponse {
931            let num = req.app_data::<usize>().unwrap();
932            HttpResponse::Ok().body(num.to_string())
933        }
934
935        let srv = init_service(
936            App::new()
937                .app_data(88usize)
938                .service(web::resource("/").route(web::get().to(echo_usize)))
939                .service(
940                    web::resource("/one")
941                        .app_data(1usize)
942                        .route(web::get().to(echo_usize)),
943                ),
944        )
945        .await;
946
947        let req = TestRequest::get().uri("/").to_request();
948        let resp = srv.call(req).await.unwrap();
949        let body = read_body(resp).await;
950        assert_eq!(body, Bytes::from_static(b"88"));
951
952        let req = TestRequest::get().uri("/one").to_request();
953        let resp = srv.call(req).await.unwrap();
954        let body = read_body(resp).await;
955        assert_eq!(body, Bytes::from_static(b"1"));
956    }
957
958    #[actix_rt::test]
959    async fn test_app_data_dropped() {
960        struct Tracker {
961            pub dropped: bool,
962        }
963        struct Foo {
964            tracker: Rc<RefCell<Tracker>>,
965        }
966        impl Drop for Foo {
967            fn drop(&mut self) {
968                self.tracker.borrow_mut().dropped = true;
969            }
970        }
971
972        let tracker = Rc::new(RefCell::new(Tracker { dropped: false }));
973        {
974            let tracker2 = Rc::clone(&tracker);
975            let srv = init_service(App::new().service(web::resource("/").to(
976                move |req: HttpRequest| {
977                    req.extensions_mut().insert(Foo {
978                        tracker: Rc::clone(&tracker2),
979                    });
980                    HttpResponse::Ok()
981                },
982            )))
983            .await;
984
985            let req = TestRequest::default().to_request();
986            let resp = call_service(&srv, req).await;
987            assert_eq!(resp.status(), StatusCode::OK);
988        }
989
990        assert!(tracker.borrow().dropped);
991    }
992
993    #[actix_rt::test]
994    async fn extract_path_pattern() {
995        let srv = init_service(
996            App::new().service(
997                web::scope("/user/{id}")
998                    .service(web::resource("/profile").route(web::get().to(
999                        move |req: HttpRequest| {
1000                            assert_eq!(req.match_pattern(), Some("/user/{id}/profile".to_owned()));
1001
1002                            HttpResponse::Ok().finish()
1003                        },
1004                    )))
1005                    .default_service(web::to(move |req: HttpRequest| {
1006                        assert!(req.match_pattern().is_none());
1007                        HttpResponse::Ok().finish()
1008                    })),
1009            ),
1010        )
1011        .await;
1012
1013        let req = TestRequest::get().uri("/user/22/profile").to_request();
1014        let res = call_service(&srv, req).await;
1015        assert_eq!(res.status(), StatusCode::OK);
1016
1017        let req = TestRequest::get().uri("/user/22/not-exist").to_request();
1018        let res = call_service(&srv, req).await;
1019        assert_eq!(res.status(), StatusCode::OK);
1020    }
1021
1022    #[actix_rt::test]
1023    async fn extract_path_pattern_complex() {
1024        let srv = init_service(
1025            App::new()
1026                .service(web::scope("/user").service(web::scope("/{id}").service(
1027                    web::resource("").to(move |req: HttpRequest| {
1028                        assert_eq!(req.match_pattern(), Some("/user/{id}".to_owned()));
1029
1030                        HttpResponse::Ok().finish()
1031                    }),
1032                )))
1033                .service(web::resource("/").to(move |req: HttpRequest| {
1034                    assert_eq!(req.match_pattern(), Some("/".to_owned()));
1035
1036                    HttpResponse::Ok().finish()
1037                }))
1038                .default_service(web::to(move |req: HttpRequest| {
1039                    assert!(req.match_pattern().is_none());
1040                    HttpResponse::Ok().finish()
1041                })),
1042        )
1043        .await;
1044
1045        let req = TestRequest::get().uri("/user/test").to_request();
1046        let res = call_service(&srv, req).await;
1047        assert_eq!(res.status(), StatusCode::OK);
1048
1049        let req = TestRequest::get().uri("/").to_request();
1050        let res = call_service(&srv, req).await;
1051        assert_eq!(res.status(), StatusCode::OK);
1052
1053        let req = TestRequest::get().uri("/not-exist").to_request();
1054        let res = call_service(&srv, req).await;
1055        assert_eq!(res.status(), StatusCode::OK);
1056    }
1057
1058    #[actix_rt::test]
1059    async fn url_for_closest_named_resource() {
1060        // we mount the route named 'nested' on 2 different scopes, 'a' and 'b'
1061        let srv = test::init_service(
1062            App::new()
1063                .service(
1064                    web::scope("/foo")
1065                        .service(web::resource("/nested").name("nested").route(web::get().to(
1066                            |req: HttpRequest| {
1067                                HttpResponse::Ok()
1068                                    .body(format!("{}", req.url_for_static("nested").unwrap()))
1069                            },
1070                        )))
1071                        .service(web::scope("/baz").service(web::resource("deep")))
1072                        .service(web::resource("{foo_param}")),
1073                )
1074                .service(web::scope("/bar").service(
1075                    web::resource("/nested").name("nested").route(web::get().to(
1076                        |req: HttpRequest| {
1077                            HttpResponse::Ok()
1078                                .body(format!("{}", req.url_for_static("nested").unwrap()))
1079                        },
1080                    )),
1081                )),
1082        )
1083        .await;
1084
1085        let foo_resp =
1086            test::call_service(&srv, TestRequest::with_uri("/foo/nested").to_request()).await;
1087        assert_eq!(foo_resp.status(), StatusCode::OK);
1088        let body = read_body(foo_resp).await;
1089        // `body` equals http://localhost:8080/bar/nested
1090        // because nested from /bar overrides /foo's
1091        // to do this any other way would require something like a custom tree search
1092        // see https://github.com/actix/actix-web/issues/1763
1093        assert_eq!(body, "http://localhost:8080/bar/nested");
1094
1095        let bar_resp =
1096            test::call_service(&srv, TestRequest::with_uri("/bar/nested").to_request()).await;
1097        assert_eq!(bar_resp.status(), StatusCode::OK);
1098        let body = read_body(bar_resp).await;
1099        assert_eq!(body, "http://localhost:8080/bar/nested");
1100    }
1101
1102    #[test]
1103    fn authorization_header_hidden_in_debug() {
1104        let authorization_header = "Basic bXkgdXNlcm5hbWU6bXkgcGFzc3dvcmQK";
1105        let req = TestRequest::get()
1106            .insert_header((crate::http::header::AUTHORIZATION, authorization_header))
1107            .to_http_request();
1108
1109        assert!(!format!("{:?}", req).contains(authorization_header));
1110    }
1111
1112    #[test]
1113    fn proxy_authorization_header_hidden_in_debug() {
1114        let proxy_authorization_header = "secret value";
1115        let req = TestRequest::get()
1116            .insert_header((
1117                crate::http::header::PROXY_AUTHORIZATION,
1118                proxy_authorization_header,
1119            ))
1120            .to_http_request();
1121
1122        assert!(!format!("{:?}", req).contains(proxy_authorization_header));
1123    }
1124
1125    #[test]
1126    fn cookie_header_hidden_in_debug() {
1127        let cookie_header = "secret";
1128        let req = TestRequest::get()
1129            .insert_header((crate::http::header::COOKIE, cookie_header))
1130            .to_http_request();
1131
1132        assert!(!format!("{:?}", req).contains(cookie_header));
1133    }
1134
1135    #[test]
1136    fn other_header_visible_in_debug() {
1137        let location_header = "192.0.0.1";
1138        let req = TestRequest::get()
1139            .insert_header((crate::http::header::LOCATION, location_header))
1140            .to_http_request();
1141
1142        assert!(format!("{:?}", req).contains(location_header));
1143    }
1144
1145    #[test]
1146    fn check_full_url() {
1147        let req = TestRequest::with_uri("/api?id=4&name=foo").to_http_request();
1148        assert_eq!(
1149            req.full_url().as_str(),
1150            "http://localhost:8080/api?id=4&name=foo",
1151        );
1152
1153        let req = TestRequest::with_uri("https://example.com/api?id=4&name=foo").to_http_request();
1154        assert_eq!(
1155            req.full_url().as_str(),
1156            "https://example.com/api?id=4&name=foo",
1157        );
1158
1159        let req = TestRequest::with_uri("http://10.1.2.3:8443/api?id=4&name=foo")
1160            .insert_header(("host", "example.com"))
1161            .to_http_request();
1162        assert_eq!(
1163            req.full_url().as_str(),
1164            "http://example.com/api?id=4&name=foo",
1165        );
1166    }
1167}