1use std::{
2 cell::{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};
13#[expect(deprecated, reason = "preserve the public ready future type")]
14use actix_utils::future::{ready, Ready};
15#[cfg(feature = "cookies")]
16use cookie::{Cookie, ParseError as CookieParseError};
17use smallvec::SmallVec;
18
19use crate::{
20 app_service::AppInitServiceState,
21 config::AppConfig,
22 dev::{Extensions, Payload},
23 error::UrlGenerationError,
24 http::{header::HeaderMap, Method, Uri, Version},
25 info::ConnectionInfo,
26 rmap::ResourceMap,
27 Error, FromRequest, HttpMessage,
28};
29
30#[cfg(feature = "cookies")]
31struct Cookies(Vec<Cookie<'static>>);
32
33#[cfg(feature = "cookies")]
34struct RawCookies(Vec<Cookie<'static>>);
35
36#[derive(Clone)]
38pub struct HttpRequest {
39 pub(crate) inner: Rc<HttpRequestInner>,
44}
45
46pub(crate) struct HttpRequestInner {
47 pub(crate) head: Message<RequestHead>,
48 pub(crate) path: Path<Url>,
49 pub(crate) resource_path: SmallVec<[u16; 4]>,
50 pub(crate) resource_path_matched: bool,
51 pub(crate) app_data: SmallVec<[Rc<Extensions>; 4]>,
52 pub(crate) conn_data: Option<Rc<Extensions>>,
53 pub(crate) extensions: Rc<RefCell<Extensions>>,
54 app_state: Rc<AppInitServiceState>,
55}
56
57impl HttpRequest {
58 #[inline]
59 pub(crate) fn new(
60 path: Path<Url>,
61 head: Message<RequestHead>,
62 app_state: Rc<AppInitServiceState>,
63 app_data: Rc<Extensions>,
64 conn_data: Option<Rc<Extensions>>,
65 extensions: Rc<RefCell<Extensions>>,
66 ) -> HttpRequest {
67 let mut data = SmallVec::<[Rc<Extensions>; 4]>::new();
68 data.push(app_data);
69
70 HttpRequest {
71 inner: Rc::new(HttpRequestInner {
72 head,
73 path,
74 resource_path: SmallVec::new(),
75 resource_path_matched: false,
76 app_state,
77 app_data: data,
78 conn_data,
79 extensions,
80 }),
81 }
82 }
83}
84
85impl HttpRequest {
86 #[inline]
88 pub fn head(&self) -> &RequestHead {
89 &self.inner.head
90 }
91
92 #[inline]
98 pub(crate) fn head_mut(&mut self) -> &mut RequestHead {
99 &mut Rc::get_mut(&mut self.inner).unwrap().head
100 }
101
102 #[inline]
104 pub fn uri(&self) -> &Uri {
105 &self.head().uri
106 }
107
108 pub fn full_url(&self) -> url::Url {
131 let info = self.connection_info();
132 let scheme = info.scheme();
133 let host = info.host();
134 let path_and_query = self
135 .uri()
136 .path_and_query()
137 .map(|paq| paq.as_str())
138 .unwrap_or("/");
139
140 url::Url::parse(&format!("{scheme}://{host}{path_and_query}")).unwrap()
141 }
142
143 #[inline]
145 pub fn method(&self) -> &Method {
146 &self.head().method
147 }
148
149 #[inline]
151 pub fn version(&self) -> Version {
152 self.head().version
153 }
154
155 #[inline]
156 pub fn headers(&self) -> &HeaderMap {
158 &self.head().headers
159 }
160
161 #[inline]
163 pub fn path(&self) -> &str {
164 self.head().uri.path()
165 }
166
167 #[inline]
171 pub fn query_string(&self) -> &str {
172 self.uri().query().unwrap_or_default()
173 }
174
175 #[inline]
187 pub fn match_info(&self) -> &Path<Url> {
188 &self.inner.path
189 }
190
191 #[inline]
196 pub(crate) fn match_info_mut(&mut self) -> &mut Path<Url> {
197 &mut Rc::get_mut(&mut self.inner).unwrap().path
198 }
199
200 #[inline]
201 pub(crate) fn push_resource_id(&mut self, id: u16) {
202 Rc::get_mut(&mut self.inner).unwrap().resource_path.push(id);
203 }
204
205 #[inline]
206 pub(crate) fn mark_resource_path(&mut self, is_matched: bool) {
207 Rc::get_mut(&mut self.inner).unwrap().resource_path_matched = is_matched;
208 }
209
210 #[inline]
211 pub(crate) fn resource_path(&self) -> &[u16] {
212 &self.inner.resource_path
213 }
214
215 #[inline]
216 pub(crate) fn is_resource_path_matched(&self) -> bool {
217 self.inner.resource_path_matched
218 }
219
220 #[inline]
227 pub fn match_pattern(&self) -> Option<String> {
228 if self.is_resource_path_matched() {
229 if let Some(pattern) = self
230 .resource_map()
231 .match_pattern_by_resource_path(self.resource_path())
232 {
233 return Some(pattern);
234 }
235 }
236
237 self.resource_map().match_pattern(self.path())
238 }
239
240 #[inline]
244 pub fn match_name(&self) -> Option<&str> {
245 if self.is_resource_path_matched() {
246 if let Some(name) = self
247 .resource_map()
248 .match_name_by_resource_path(self.resource_path())
249 {
250 return Some(name);
251 }
252 }
253
254 self.resource_map().match_name(self.path())
255 }
256
257 pub fn conn_data<T: 'static>(&self) -> Option<&T> {
265 self.inner
266 .conn_data
267 .as_deref()
268 .and_then(|container| container.get::<T>())
269 }
270
271 pub fn url_for<U, I>(&self, name: &str, elements: U) -> Result<url::Url, UrlGenerationError>
296 where
297 U: IntoIterator<Item = I>,
298 I: AsRef<str>,
299 {
300 self.resource_map().url_for(self, name, elements)
301 }
302
303 pub fn url_for_map<K, V, S>(
327 &self,
328 name: &str,
329 elements: &HashMap<K, V, S>,
330 ) -> Result<url::Url, UrlGenerationError>
331 where
332 K: std::borrow::Borrow<str> + Eq + Hash,
333 V: AsRef<str>,
334 S: BuildHasher,
335 {
336 self.resource_map().url_for_map(self, name, elements)
337 }
338
339 pub fn url_for_iter<K, V, I>(
361 &self,
362 name: &str,
363 elements: I,
364 ) -> Result<url::Url, UrlGenerationError>
365 where
366 I: IntoIterator<Item = (K, V)>,
367 K: std::borrow::Borrow<str> + Eq + Hash,
368 V: AsRef<str>,
369 {
370 self.resource_map().url_for_iter(self, name, elements)
371 }
372
373 pub fn url_for_static(&self, name: &str) -> Result<url::Url, UrlGenerationError> {
378 const NO_PARAMS: [&str; 0] = [];
379 self.url_for(name, NO_PARAMS)
380 }
381
382 #[inline]
384 pub fn resource_map(&self) -> &ResourceMap {
385 self.app_state().rmap()
386 }
387
388 #[inline]
402 pub fn peer_addr(&self) -> Option<net::SocketAddr> {
403 self.head().peer_addr
404 }
405
406 #[inline]
413 pub fn connection_info(&self) -> Ref<'_, ConnectionInfo> {
414 if !self.extensions().contains::<ConnectionInfo>() {
415 let info = ConnectionInfo::new(self.head(), self.app_config());
416 self.extensions_mut().insert(info);
417 }
418
419 Ref::map(self.extensions(), |data| data.get().unwrap())
420 }
421
422 #[inline]
424 pub fn app_config(&self) -> &AppConfig {
425 self.app_state().config()
426 }
427
428 #[doc(alias = "state")]
456 pub fn app_data<T: 'static>(&self) -> Option<&T> {
457 for container in self.inner.app_data.iter().rev() {
458 if let Some(data) = container.get::<T>() {
459 return Some(data);
460 }
461 }
462
463 None
464 }
465
466 #[inline]
467 fn app_state(&self) -> &AppInitServiceState {
468 &self.inner.app_state
469 }
470
471 #[cfg(feature = "cookies")]
478 pub fn cookies(&self) -> Result<Ref<'_, Vec<Cookie<'static>>>, CookieParseError> {
479 use actix_http::header::COOKIE;
480
481 if self.extensions().get::<Cookies>().is_none() {
482 let mut cookies = Vec::new();
483 for hdr in self.headers().get_all(COOKIE) {
484 let s = str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?;
485 for cookie_str in s.split(';').map(|s| s.trim()).filter(|s| !s.is_empty()) {
486 if let Ok(cookie) = Cookie::parse_encoded(cookie_str) {
487 cookies.push(cookie.into_owned());
488 }
489 }
490 }
491 self.extensions_mut().insert(Cookies(cookies));
492 }
493
494 Ok(Ref::map(self.extensions(), |ext| {
495 &ext.get::<Cookies>().unwrap().0
496 }))
497 }
498
499 #[cfg(feature = "cookies")]
504 pub fn cookies_raw(&self) -> Result<Ref<'_, Vec<Cookie<'static>>>, CookieParseError> {
505 use actix_http::header::COOKIE;
506
507 if self.extensions().get::<RawCookies>().is_none() {
508 let mut cookies = Vec::new();
509 for hdr in self.headers().get_all(COOKIE) {
510 let s = str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?;
511 for cookie_str in s.split(';').map(|s| s.trim()).filter(|s| !s.is_empty()) {
512 if let Ok(cookie) = Cookie::parse(cookie_str) {
513 cookies.push(cookie.into_owned());
514 }
515 }
516 }
517 self.extensions_mut().insert(RawCookies(cookies));
518 }
519
520 Ok(Ref::map(self.extensions(), |ext| {
521 &ext.get::<RawCookies>().unwrap().0
522 }))
523 }
524
525 #[cfg(feature = "cookies")]
527 pub fn cookie(&self, name: &str) -> Option<Cookie<'static>> {
528 if let Ok(cookies) = self.cookies() {
529 return cookies.iter().find(|cookie| cookie.name() == name).cloned();
530 }
531
532 None
533 }
534
535 #[cfg(feature = "cookies")]
537 pub fn cookie_raw(&self, name: &str) -> Option<Cookie<'static>> {
538 if let Ok(cookies) = self.cookies_raw() {
539 return cookies.iter().find(|cookie| cookie.name() == name).cloned();
540 }
541
542 None
543 }
544}
545
546impl HttpMessage for HttpRequest {
547 type Stream = ();
548
549 #[inline]
550 fn headers(&self) -> &HeaderMap {
551 &self.head().headers
552 }
553
554 #[inline]
555 fn extensions(&self) -> Ref<'_, Extensions> {
556 self.inner.extensions.borrow()
557 }
558
559 #[inline]
560 fn extensions_mut(&self) -> RefMut<'_, Extensions> {
561 self.inner.extensions.borrow_mut()
562 }
563
564 #[inline]
565 fn take_payload(&mut self) -> Payload<Self::Stream> {
566 Payload::None
567 }
568}
569
570impl Drop for HttpRequest {
571 fn drop(&mut self) {
572 if let Some(inner) = Rc::get_mut(&mut self.inner) {
576 if inner.app_state.pool().is_available() {
577 inner.app_data.truncate(1);
579
580 Rc::get_mut(&mut inner.extensions)
583 .unwrap()
584 .get_mut()
585 .clear();
586
587 inner.conn_data = None;
590
591 let req = Rc::clone(&self.inner);
593 self.app_state().pool().push(req);
594 }
595 }
596 }
597}
598
599#[expect(deprecated, reason = "preserve the public ready future type")]
617impl FromRequest for HttpRequest {
618 type Error = Error;
619 type Future = Ready<Result<Self, Error>>;
620
621 #[inline]
622 fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
623 ready(Ok(req.clone()))
624 }
625}
626
627impl fmt::Debug for HttpRequest {
628 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
629 writeln!(
630 f,
631 "\nHttpRequest {:?} {}:{}",
632 self.inner.head.version,
633 self.inner.head.method,
634 self.path()
635 )?;
636
637 if !self.query_string().is_empty() {
638 writeln!(f, " query: ?{:?}", self.query_string())?;
639 }
640
641 if !self.match_info().is_empty() {
642 writeln!(f, " params: {:?}", self.match_info())?;
643 }
644
645 writeln!(f, " headers:")?;
646
647 for (key, val) in self.headers().iter() {
648 match key {
649 &crate::http::header::AUTHORIZATION
651 | &crate::http::header::PROXY_AUTHORIZATION
652 | &crate::http::header::COOKIE => writeln!(f, " {:?}: {:?}", key, "*redacted*")?,
653
654 _ => writeln!(f, " {:?}: {:?}", key, val)?,
655 }
656 }
657
658 Ok(())
659 }
660}
661
662pub(crate) struct HttpRequestPool {
673 inner: RefCell<Vec<Rc<HttpRequestInner>>>,
674 enabled: Cell<bool>,
675 cap: usize,
676}
677
678impl Default for HttpRequestPool {
679 fn default() -> Self {
680 Self::with_capacity(128)
681 }
682}
683
684impl HttpRequestPool {
685 pub(crate) fn with_capacity(cap: usize) -> Self {
686 HttpRequestPool {
687 inner: RefCell::new(Vec::with_capacity(cap)),
688 enabled: Cell::new(true),
689 cap,
690 }
691 }
692
693 #[inline]
695 pub(crate) fn pop(&self) -> Option<HttpRequest> {
696 self.inner
697 .borrow_mut()
698 .pop()
699 .map(|inner| HttpRequest { inner })
700 }
701
702 #[inline]
704 pub(crate) fn is_available(&self) -> bool {
705 self.enabled.get() && self.inner.borrow().len() < self.cap
706 }
707
708 #[inline]
710 pub(crate) fn push(&self, req: Rc<HttpRequestInner>) {
711 self.inner.borrow_mut().push(req);
712 }
713
714 pub(crate) fn disable(&self) {
716 self.enabled.set(false);
717 self.inner.borrow_mut().clear();
718 }
719}
720
721#[cfg(test)]
722mod tests {
723 use std::{collections::HashMap, sync::Arc};
724
725 use bytes::Bytes;
726
727 use super::*;
728 use crate::{
729 dev::{ResourceDef, Service},
730 guard,
731 http::{header, StatusCode},
732 test::{self, call_service, init_service, read_body, TestRequest},
733 web, App, HttpResponse,
734 };
735
736 #[test]
737 fn test_debug() {
738 let req = TestRequest::default()
739 .insert_header(("content-type", "text/plain"))
740 .to_http_request();
741 let dbg = format!("{:?}", req);
742 assert!(dbg.contains("HttpRequest"));
743 }
744
745 #[test]
746 #[cfg(feature = "cookies")]
747 fn test_no_request_cookies() {
748 let req = TestRequest::default().to_http_request();
749 assert!(req.cookies().unwrap().is_empty());
750 }
751
752 #[test]
753 #[cfg(feature = "cookies")]
754 fn test_request_cookies() {
755 let req = TestRequest::default()
756 .append_header((header::COOKIE, "cookie1=value1"))
757 .append_header((header::COOKIE, "cookie2=value2"))
758 .to_http_request();
759 {
760 let cookies = req.cookies().unwrap();
761 assert_eq!(cookies.len(), 2);
762 assert_eq!(cookies[0].name(), "cookie1");
763 assert_eq!(cookies[0].value(), "value1");
764 assert_eq!(cookies[1].name(), "cookie2");
765 assert_eq!(cookies[1].value(), "value2");
766 }
767
768 let cookie = req.cookie("cookie1");
769 assert!(cookie.is_some());
770 let cookie = cookie.unwrap();
771 assert_eq!(cookie.name(), "cookie1");
772 assert_eq!(cookie.value(), "value1");
773
774 let cookie = req.cookie("cookie-unknown");
775 assert!(cookie.is_none());
776 }
777
778 #[test]
779 #[cfg(feature = "cookies")]
780 fn test_request_cookies_raw() {
781 let req = TestRequest::default()
782 .append_header((header::COOKIE, "cookie1=hello%20world"))
783 .append_header((header::COOKIE, "cookie2=%db"))
784 .to_http_request();
785 {
786 let cookies = req.cookies_raw().unwrap();
787 assert_eq!(cookies.len(), 2);
788 assert_eq!(cookies[0].name(), "cookie1");
789 assert_eq!(cookies[0].value(), "hello%20world");
790 assert_eq!(cookies[1].name(), "cookie2");
791 assert_eq!(cookies[1].value(), "%db");
792 }
793
794 let cookie = req.cookie_raw("cookie1");
795 assert!(cookie.is_some());
796 let cookie = cookie.unwrap();
797 assert_eq!(cookie.name(), "cookie1");
798 assert_eq!(cookie.value(), "hello%20world");
799
800 let cookie = req.cookie_raw("cookie2");
801 assert!(cookie.is_some());
802 let cookie = cookie.unwrap();
803 assert_eq!(cookie.name(), "cookie2");
804 assert_eq!(cookie.value(), "%db");
805 }
806
807 #[test]
808 #[cfg(feature = "cookies")]
809 fn test_request_cookies_raw_is_independent_from_encoded_cookies() {
810 let req = TestRequest::default()
811 .append_header((header::COOKIE, "cookie=%20"))
812 .to_http_request();
813
814 let cookie = req.cookie("cookie").unwrap();
815 assert_eq!(cookie.value(), " ");
816
817 let raw_cookie = req.cookie_raw("cookie").unwrap();
818 assert_eq!(raw_cookie.value(), "%20");
819 }
820
821 #[test]
822 #[cfg(feature = "cookies")]
823 fn test_empty_key() {
824 let req = TestRequest::default()
825 .append_header((header::COOKIE, "cookie1=value1; value2; cookie3=value3"))
826 .to_http_request();
827 {
828 let cookies = req.cookies().unwrap();
829 assert_eq!(cookies.len(), 2);
830 assert_eq!(cookies[0].name(), "cookie1");
831 assert_eq!(cookies[0].value(), "value1");
832 assert_eq!(cookies[1].name(), "cookie3");
833 assert_eq!(cookies[1].value(), "value3");
834 }
835 }
836
837 #[test]
838 fn test_request_query() {
839 let req = TestRequest::with_uri("/?id=test").to_http_request();
840 assert_eq!(req.query_string(), "id=test");
841 }
842
843 #[test]
844 fn test_url_for() {
845 let mut res = ResourceDef::new("/user/{name}.{ext}");
846 res.set_name("index");
847
848 let mut rmap = ResourceMap::new(ResourceDef::prefix(""));
849 rmap.add(&mut res, None);
850 assert!(rmap.has_resource("/user/test.html"));
851 assert!(!rmap.has_resource("/test/unknown"));
852
853 let req = TestRequest::default()
854 .insert_header((header::HOST, "www.rust-lang.org"))
855 .rmap(rmap)
856 .to_http_request();
857
858 assert_eq!(
859 req.url_for("unknown", ["test"]),
860 Err(UrlGenerationError::ResourceNotFound)
861 );
862 assert_eq!(
863 req.url_for("index", ["test"]),
864 Err(UrlGenerationError::NotEnoughElements)
865 );
866 let url = req.url_for("index", ["test", "html"]);
867 assert_eq!(
868 url.ok().unwrap().as_str(),
869 "http://www.rust-lang.org/user/test.html"
870 );
871 }
872
873 #[test]
874 fn test_url_for_map() {
875 let mut res = ResourceDef::new("/user/{name}.{ext}");
876 res.set_name("index");
877
878 let mut rmap = ResourceMap::new(ResourceDef::prefix(""));
879 rmap.add(&mut res, None);
880
881 let req = TestRequest::default()
882 .insert_header((header::HOST, "www.actix.rs"))
883 .rmap(rmap)
884 .to_http_request();
885
886 let mut params = HashMap::new();
887 params.insert("name", "test");
888 params.insert("ext", "html");
889
890 let url = req.url_for_map("index", ¶ms);
891 assert_eq!(
892 url.ok().unwrap().as_str(),
893 "http://www.actix.rs/user/test.html"
894 );
895
896 params.remove("ext");
897 assert_eq!(
898 req.url_for_map("index", ¶ms),
899 Err(UrlGenerationError::NotEnoughElements)
900 );
901 }
902
903 #[test]
904 fn test_url_for_iter() {
905 let mut res = ResourceDef::new("/user/{name}.{ext}");
906 res.set_name("index");
907
908 let mut rmap = ResourceMap::new(ResourceDef::prefix(""));
909 rmap.add(&mut res, None);
910
911 let req = TestRequest::default()
912 .insert_header((header::HOST, "www.actix.rs"))
913 .rmap(rmap)
914 .to_http_request();
915
916 let url = req.url_for_iter("index", [("ext", "html"), ("name", "test")]);
917 assert_eq!(
918 url.ok().unwrap().as_str(),
919 "http://www.actix.rs/user/test.html"
920 );
921
922 let url = req.url_for_iter("index", [("name", "test")]);
923 assert_eq!(url, Err(UrlGenerationError::NotEnoughElements));
924 }
925
926 #[test]
927 fn test_url_for_static() {
928 let mut rdef = ResourceDef::new("/index.html");
929 rdef.set_name("index");
930
931 let mut rmap = ResourceMap::new(ResourceDef::prefix(""));
932 rmap.add(&mut rdef, None);
933
934 assert!(rmap.has_resource("/index.html"));
935
936 let req = TestRequest::with_uri("/test")
937 .insert_header((header::HOST, "www.rust-lang.org"))
938 .rmap(rmap)
939 .to_http_request();
940 let url = req.url_for_static("index");
941 assert_eq!(
942 url.ok().unwrap().as_str(),
943 "http://www.rust-lang.org/index.html"
944 );
945 }
946
947 #[test]
948 fn test_match_name() {
949 let mut rdef = ResourceDef::new("/index.html");
950 rdef.set_name("index");
951
952 let mut rmap = ResourceMap::new(ResourceDef::prefix(""));
953 rmap.add(&mut rdef, None);
954
955 assert!(rmap.has_resource("/index.html"));
956
957 let req = TestRequest::default()
958 .uri("/index.html")
959 .rmap(rmap)
960 .to_http_request();
961
962 assert_eq!(req.match_name(), Some("index"));
963 }
964
965 #[test]
966 fn test_url_for_external() {
967 let mut rdef = ResourceDef::new("https://youtube.com/watch/{video_id}");
968
969 rdef.set_name("youtube");
970
971 let mut rmap = ResourceMap::new(ResourceDef::prefix(""));
972 rmap.add(&mut rdef, None);
973
974 let req = TestRequest::default().rmap(rmap).to_http_request();
975 let url = req.url_for("youtube", ["oHg5SJYRHA0"]);
976 assert_eq!(
977 url.ok().unwrap().as_str(),
978 "https://youtube.com/watch/oHg5SJYRHA0"
979 );
980 }
981
982 #[actix_rt::test]
983 async fn test_drop_http_request_pool() {
984 let srv = init_service(
985 App::new().service(web::resource("/").to(|req: HttpRequest| {
986 HttpResponse::Ok()
987 .insert_header(("pool_cap", req.app_state().pool().cap))
988 .finish()
989 })),
990 )
991 .await;
992
993 let req = TestRequest::default().to_request();
994 let resp = call_service(&srv, req).await;
995
996 drop(srv);
997
998 assert_eq!(resp.headers().get("pool_cap").unwrap(), "128");
999 }
1000
1001 #[actix_rt::test]
1002 async fn test_request_dropped_after_service_does_not_reenter_pool() {
1003 struct State {
1004 _data: Arc<String>,
1005 }
1006
1007 let (weak_data, app_data) = {
1008 let data = Arc::new("data".to_owned());
1009 (Arc::downgrade(&data), web::Data::new(State { _data: data }))
1010 };
1011
1012 let held_req = Rc::new(RefCell::new(None));
1013
1014 {
1015 let held_req = Rc::clone(&held_req);
1016 let srv = init_service(App::new().app_data(app_data).service(web::resource("/").to(
1017 move |req: HttpRequest| {
1018 *held_req.borrow_mut() = Some(req.clone());
1019 HttpResponse::Ok()
1020 },
1021 )))
1022 .await;
1023
1024 let resp = call_service(&srv, TestRequest::default().to_request()).await;
1025 assert_eq!(resp.status(), StatusCode::OK);
1026
1027 drop(resp);
1028 drop(srv);
1029 }
1030
1031 assert!(weak_data.upgrade().is_some());
1032 drop(held_req.borrow_mut().take());
1033 assert!(weak_data.upgrade().is_none());
1034 }
1035
1036 #[actix_rt::test]
1037 async fn test_data() {
1038 let srv = init_service(App::new().app_data(10usize).service(web::resource("/").to(
1039 |req: HttpRequest| {
1040 if req.app_data::<usize>().is_some() {
1041 HttpResponse::Ok()
1042 } else {
1043 HttpResponse::BadRequest()
1044 }
1045 },
1046 )))
1047 .await;
1048
1049 let req = TestRequest::default().to_request();
1050 let resp = call_service(&srv, req).await;
1051 assert_eq!(resp.status(), StatusCode::OK);
1052
1053 let srv = init_service(App::new().app_data(10u32).service(web::resource("/").to(
1054 |req: HttpRequest| {
1055 if req.app_data::<usize>().is_some() {
1056 HttpResponse::Ok()
1057 } else {
1058 HttpResponse::BadRequest()
1059 }
1060 },
1061 )))
1062 .await;
1063
1064 let req = TestRequest::default().to_request();
1065 let resp = call_service(&srv, req).await;
1066 assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
1067 }
1068
1069 #[actix_rt::test]
1070 async fn test_cascading_data() {
1071 #[allow(dead_code)]
1072 fn echo_usize(req: HttpRequest) -> HttpResponse {
1073 let num = req.app_data::<usize>().unwrap();
1074 HttpResponse::Ok().body(num.to_string())
1075 }
1076
1077 let srv = init_service(
1078 App::new()
1079 .app_data(88usize)
1080 .service(web::resource("/").route(web::get().to(echo_usize)))
1081 .service(
1082 web::resource("/one")
1083 .app_data(1u32)
1084 .route(web::get().to(echo_usize)),
1085 ),
1086 )
1087 .await;
1088
1089 let req = TestRequest::get().uri("/").to_request();
1090 let resp = srv.call(req).await.unwrap();
1091 let body = read_body(resp).await;
1092 assert_eq!(body, Bytes::from_static(b"88"));
1093
1094 let req = TestRequest::get().uri("/one").to_request();
1095 let resp = srv.call(req).await.unwrap();
1096 let body = read_body(resp).await;
1097 assert_eq!(body, Bytes::from_static(b"88"));
1098 }
1099
1100 #[actix_rt::test]
1101 async fn test_overwrite_data() {
1102 #[allow(dead_code)]
1103 fn echo_usize(req: HttpRequest) -> HttpResponse {
1104 let num = req.app_data::<usize>().unwrap();
1105 HttpResponse::Ok().body(num.to_string())
1106 }
1107
1108 let srv = init_service(
1109 App::new()
1110 .app_data(88usize)
1111 .service(web::resource("/").route(web::get().to(echo_usize)))
1112 .service(
1113 web::resource("/one")
1114 .app_data(1usize)
1115 .route(web::get().to(echo_usize)),
1116 ),
1117 )
1118 .await;
1119
1120 let req = TestRequest::get().uri("/").to_request();
1121 let resp = srv.call(req).await.unwrap();
1122 let body = read_body(resp).await;
1123 assert_eq!(body, Bytes::from_static(b"88"));
1124
1125 let req = TestRequest::get().uri("/one").to_request();
1126 let resp = srv.call(req).await.unwrap();
1127 let body = read_body(resp).await;
1128 assert_eq!(body, Bytes::from_static(b"1"));
1129 }
1130
1131 #[actix_rt::test]
1132 async fn test_app_data_dropped() {
1133 struct Tracker {
1134 pub dropped: bool,
1135 }
1136 struct Foo {
1137 tracker: Rc<RefCell<Tracker>>,
1138 }
1139 impl Drop for Foo {
1140 fn drop(&mut self) {
1141 self.tracker.borrow_mut().dropped = true;
1142 }
1143 }
1144
1145 let tracker = Rc::new(RefCell::new(Tracker { dropped: false }));
1146 {
1147 let tracker2 = Rc::clone(&tracker);
1148 let srv = init_service(App::new().service(web::resource("/").to(
1149 move |req: HttpRequest| {
1150 req.extensions_mut().insert(Foo {
1151 tracker: Rc::clone(&tracker2),
1152 });
1153 HttpResponse::Ok()
1154 },
1155 )))
1156 .await;
1157
1158 let req = TestRequest::default().to_request();
1159 let resp = call_service(&srv, req).await;
1160 assert_eq!(resp.status(), StatusCode::OK);
1161 }
1162
1163 assert!(tracker.borrow().dropped);
1164 }
1165
1166 #[actix_rt::test]
1167 async fn extract_path_pattern() {
1168 let srv = init_service(
1169 App::new().service(
1170 web::scope("/user/{id}")
1171 .service(web::resource("/profile").route(web::get().to(
1172 move |req: HttpRequest| {
1173 assert_eq!(req.match_pattern(), Some("/user/{id}/profile".to_owned()));
1174
1175 HttpResponse::Ok().finish()
1176 },
1177 )))
1178 .default_service(web::to(move |req: HttpRequest| {
1179 assert!(req.match_pattern().is_none());
1180 HttpResponse::Ok().finish()
1181 })),
1182 ),
1183 )
1184 .await;
1185
1186 let req = TestRequest::get().uri("/user/22/profile").to_request();
1187 let res = call_service(&srv, req).await;
1188 assert_eq!(res.status(), StatusCode::OK);
1189
1190 let req = TestRequest::get().uri("/user/22/not-exist").to_request();
1191 let res = call_service(&srv, req).await;
1192 assert_eq!(res.status(), StatusCode::OK);
1193 }
1194
1195 #[actix_rt::test]
1196 async fn extract_path_pattern_with_guards() {
1197 let srv = init_service(
1198 App::new().service(
1199 web::scope("/widgets")
1200 .service(
1201 web::resource("/{id}")
1202 .name("get_widget")
1203 .guard(guard::Get())
1204 .to(|req: HttpRequest| {
1205 assert_eq!(req.match_pattern(), Some("/widgets/{id}".to_owned()));
1206 assert_eq!(req.match_name(), Some("get_widget"));
1207 HttpResponse::Ok().finish()
1208 }),
1209 )
1210 .service(
1211 web::resource("/action")
1212 .name("widget_action")
1213 .guard(guard::Post())
1214 .to(|req: HttpRequest| {
1215 assert_eq!(req.match_pattern(), Some("/widgets/action".to_owned()));
1216 assert_eq!(req.match_name(), Some("widget_action"));
1217 HttpResponse::Ok().finish()
1218 }),
1219 ),
1220 ),
1221 )
1222 .await;
1223
1224 let req = TestRequest::get().uri("/widgets/42").to_request();
1225 let res = call_service(&srv, req).await;
1226 assert_eq!(res.status(), StatusCode::OK);
1227
1228 let req = TestRequest::post().uri("/widgets/action").to_request();
1229 let res = call_service(&srv, req).await;
1230 assert_eq!(res.status(), StatusCode::OK);
1231 }
1232
1233 #[actix_rt::test]
1234 async fn extract_path_pattern_complex() {
1235 let srv = init_service(
1236 App::new()
1237 .service(web::scope("/user").service(web::scope("/{id}").service(
1238 web::resource("").to(move |req: HttpRequest| {
1239 assert_eq!(req.match_pattern(), Some("/user/{id}".to_owned()));
1240
1241 HttpResponse::Ok().finish()
1242 }),
1243 )))
1244 .service(web::resource("/").to(move |req: HttpRequest| {
1245 assert_eq!(req.match_pattern(), Some("/".to_owned()));
1246
1247 HttpResponse::Ok().finish()
1248 }))
1249 .default_service(web::to(move |req: HttpRequest| {
1250 assert!(req.match_pattern().is_none());
1251 HttpResponse::Ok().finish()
1252 })),
1253 )
1254 .await;
1255
1256 let req = TestRequest::get().uri("/user/test").to_request();
1257 let res = call_service(&srv, req).await;
1258 assert_eq!(res.status(), StatusCode::OK);
1259
1260 let req = TestRequest::get().uri("/").to_request();
1261 let res = call_service(&srv, req).await;
1262 assert_eq!(res.status(), StatusCode::OK);
1263
1264 let req = TestRequest::get().uri("/not-exist").to_request();
1265 let res = call_service(&srv, req).await;
1266 assert_eq!(res.status(), StatusCode::OK);
1267 }
1268
1269 #[actix_rt::test]
1270 async fn url_for_closest_named_resource() {
1271 let srv = test::init_service(
1273 App::new()
1274 .service(
1275 web::scope("/foo")
1276 .service(web::resource("/nested").name("nested").route(web::get().to(
1277 |req: HttpRequest| {
1278 HttpResponse::Ok()
1279 .body(format!("{}", req.url_for_static("nested").unwrap()))
1280 },
1281 )))
1282 .service(web::scope("/baz").service(web::resource("deep")))
1283 .service(web::resource("{foo_param}")),
1284 )
1285 .service(web::scope("/bar").service(
1286 web::resource("/nested").name("nested").route(web::get().to(
1287 |req: HttpRequest| {
1288 HttpResponse::Ok()
1289 .body(format!("{}", req.url_for_static("nested").unwrap()))
1290 },
1291 )),
1292 )),
1293 )
1294 .await;
1295
1296 let foo_resp =
1297 test::call_service(&srv, TestRequest::with_uri("/foo/nested").to_request()).await;
1298 assert_eq!(foo_resp.status(), StatusCode::OK);
1299 let body = read_body(foo_resp).await;
1300 assert_eq!(body, "http://localhost:8080/bar/nested");
1305
1306 let bar_resp =
1307 test::call_service(&srv, TestRequest::with_uri("/bar/nested").to_request()).await;
1308 assert_eq!(bar_resp.status(), StatusCode::OK);
1309 let body = read_body(bar_resp).await;
1310 assert_eq!(body, "http://localhost:8080/bar/nested");
1311 }
1312
1313 #[test]
1314 fn authorization_header_hidden_in_debug() {
1315 let authorization_header = "Basic bXkgdXNlcm5hbWU6bXkgcGFzc3dvcmQK";
1316 let req = TestRequest::get()
1317 .insert_header((crate::http::header::AUTHORIZATION, authorization_header))
1318 .to_http_request();
1319
1320 assert!(!format!("{:?}", req).contains(authorization_header));
1321 }
1322
1323 #[test]
1324 fn proxy_authorization_header_hidden_in_debug() {
1325 let proxy_authorization_header = "secret value";
1326 let req = TestRequest::get()
1327 .insert_header((
1328 crate::http::header::PROXY_AUTHORIZATION,
1329 proxy_authorization_header,
1330 ))
1331 .to_http_request();
1332
1333 assert!(!format!("{:?}", req).contains(proxy_authorization_header));
1334 }
1335
1336 #[test]
1337 fn cookie_header_hidden_in_debug() {
1338 let cookie_header = "secret";
1339 let req = TestRequest::get()
1340 .insert_header((crate::http::header::COOKIE, cookie_header))
1341 .to_http_request();
1342
1343 assert!(!format!("{:?}", req).contains(cookie_header));
1344 }
1345
1346 #[test]
1347 fn other_header_visible_in_debug() {
1348 let location_header = "192.0.0.1";
1349 let req = TestRequest::get()
1350 .insert_header((crate::http::header::LOCATION, location_header))
1351 .to_http_request();
1352
1353 assert!(format!("{:?}", req).contains(location_header));
1354 }
1355
1356 #[test]
1357 fn check_full_url() {
1358 let req = TestRequest::with_uri("/api?id=4&name=foo").to_http_request();
1359 assert_eq!(
1360 req.full_url().as_str(),
1361 "http://localhost:8080/api?id=4&name=foo",
1362 );
1363
1364 let req = TestRequest::with_uri("https://example.com/api?id=4&name=foo").to_http_request();
1365 assert_eq!(
1366 req.full_url().as_str(),
1367 "https://example.com/api?id=4&name=foo",
1368 );
1369
1370 let req = TestRequest::with_uri("http://10.1.2.3:8443/api?id=4&name=foo")
1371 .insert_header(("host", "example.com"))
1372 .to_http_request();
1373 assert_eq!(
1374 req.full_url().as_str(),
1375 "http://example.com/api?id=4&name=foo",
1376 );
1377 }
1378}