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#[derive(Clone)]
34pub struct HttpRequest {
35 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 #[inline]
80 pub fn head(&self) -> &RequestHead {
81 &self.inner.head
82 }
83
84 #[inline]
87 pub(crate) fn head_mut(&mut self) -> &mut RequestHead {
88 &mut Rc::get_mut(&mut self.inner).unwrap().head
89 }
90
91 #[inline]
93 pub fn uri(&self) -> &Uri {
94 &self.head().uri
95 }
96
97 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 #[inline]
128 pub fn method(&self) -> &Method {
129 &self.head().method
130 }
131
132 #[inline]
134 pub fn version(&self) -> Version {
135 self.head().version
136 }
137
138 #[inline]
139 pub fn headers(&self) -> &HeaderMap {
141 &self.head().headers
142 }
143
144 #[inline]
146 pub fn path(&self) -> &str {
147 self.head().uri.path()
148 }
149
150 #[inline]
154 pub fn query_string(&self) -> &str {
155 self.uri().query().unwrap_or_default()
156 }
157
158 #[inline]
170 pub fn match_info(&self) -> &Path<Url> {
171 &self.inner.path
172 }
173
174 #[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 #[inline]
190 pub fn match_pattern(&self) -> Option<String> {
191 self.resource_map().match_pattern(self.path())
192 }
193
194 #[inline]
198 pub fn match_name(&self) -> Option<&str> {
199 self.resource_map().match_name(self.path())
200 }
201
202 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 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 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 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 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 #[inline]
329 pub fn resource_map(&self) -> &ResourceMap {
330 self.app_state().rmap()
331 }
332
333 #[inline]
347 pub fn peer_addr(&self) -> Option<net::SocketAddr> {
348 self.head().peer_addr
349 }
350
351 #[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 #[inline]
369 pub fn app_config(&self) -> &AppConfig {
370 self.app_state().config()
371 }
372
373 #[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 #[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 #[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 let Some(inner) = Rc::get_mut(&mut self.inner) {
486 if inner.app_state.pool().is_available() {
487 inner.app_data.truncate(1);
489
490 Rc::get_mut(&mut inner.extensions)
493 .unwrap()
494 .get_mut()
495 .clear();
496
497 inner.conn_data = None;
500
501 let req = Rc::clone(&self.inner);
503 self.app_state().pool().push(req);
504 }
505 }
506 }
507}
508
509impl 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 &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
571pub(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 #[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 #[inline]
611 pub(crate) fn is_available(&self) -> bool {
612 self.inner.borrow_mut().len() < self.cap
613 }
614
615 #[inline]
617 pub(crate) fn push(&self, req: Rc<HttpRequestInner>) {
618 self.inner.borrow_mut().push(req);
619 }
620
621 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", ¶ms);
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", ¶ms),
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 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 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}