1 use std::{ 2 cell::{Ref, RefCell, RefMut}, 3 fmt, net, 4 rc::Rc, 5 str, 6 }; 7 8 use actix_http::{ 9 http::{HeaderMap, Method, Uri, Version}, 10 Extensions, HttpMessage, Message, Payload, RequestHead, 11 }; 12 use actix_router::{Path, Url}; 13 use actix_utils::future::{ok, Ready}; 14 #[cfg(feature = "cookies")] 15 use cookie::{Cookie, ParseError as CookieParseError}; 16 use smallvec::SmallVec; 17 18 use crate::{ 19 app_service::AppInitServiceState, config::AppConfig, error::UrlGenerationError, 20 info::ConnectionInfo, rmap::ResourceMap, Error, FromRequest, 21 }; 22 23 #[cfg(feature = "cookies")] 24 struct Cookies(Vec<Cookie<'static>>); 25 26 #[derive(Clone)] 27 /// An HTTP Request 28 pub struct HttpRequest { 29 /// # Panics 30 /// `Rc<HttpRequestInner>` is used exclusively and NO `Weak<HttpRequestInner>` 31 /// is allowed anywhere in the code. Weak pointer is purposely ignored when 32 /// doing `Rc`'s ref counter check. Expect panics if this invariant is violated. 33 pub(crate) inner: Rc<HttpRequestInner>, 34 } 35 36 pub(crate) struct HttpRequestInner { 37 pub(crate) head: Message<RequestHead>, 38 pub(crate) path: Path<Url>, 39 pub(crate) app_data: SmallVec<[Rc<Extensions>; 4]>, 40 app_state: Rc<AppInitServiceState>, 41 } 42 43 impl HttpRequest { 44 #[inline] new( path: Path<Url>, head: Message<RequestHead>, app_state: Rc<AppInitServiceState>, app_data: Rc<Extensions>, ) -> HttpRequest45 pub(crate) fn new( 46 path: Path<Url>, 47 head: Message<RequestHead>, 48 app_state: Rc<AppInitServiceState>, 49 app_data: Rc<Extensions>, 50 ) -> HttpRequest { 51 let mut data = SmallVec::<[Rc<Extensions>; 4]>::new(); 52 data.push(app_data); 53 54 HttpRequest { 55 inner: Rc::new(HttpRequestInner { 56 head, 57 path, 58 app_state, 59 app_data: data, 60 }), 61 } 62 } 63 } 64 65 impl HttpRequest { 66 /// This method returns reference to the request head 67 #[inline] head(&self) -> &RequestHead68 pub fn head(&self) -> &RequestHead { 69 &self.inner.head 70 } 71 72 /// This method returns mutable reference to the request head. 73 /// panics if multiple references of HTTP request exists. 74 #[inline] head_mut(&mut self) -> &mut RequestHead75 pub(crate) fn head_mut(&mut self) -> &mut RequestHead { 76 &mut Rc::get_mut(&mut self.inner).unwrap().head 77 } 78 79 /// Request's uri. 80 #[inline] uri(&self) -> &Uri81 pub fn uri(&self) -> &Uri { 82 &self.head().uri 83 } 84 85 /// Read the Request method. 86 #[inline] method(&self) -> &Method87 pub fn method(&self) -> &Method { 88 &self.head().method 89 } 90 91 /// Read the Request Version. 92 #[inline] version(&self) -> Version93 pub fn version(&self) -> Version { 94 self.head().version 95 } 96 97 #[inline] 98 /// Returns request's headers. headers(&self) -> &HeaderMap99 pub fn headers(&self) -> &HeaderMap { 100 &self.head().headers 101 } 102 103 /// The target path of this Request. 104 #[inline] path(&self) -> &str105 pub fn path(&self) -> &str { 106 self.head().uri.path() 107 } 108 109 /// The query string in the URL. 110 /// 111 /// E.g., id=10 112 #[inline] query_string(&self) -> &str113 pub fn query_string(&self) -> &str { 114 self.uri().query().unwrap_or_default() 115 } 116 117 /// Get a reference to the Path parameters. 118 /// 119 /// Params is a container for url parameters. 120 /// A variable segment is specified in the form `{identifier}`, 121 /// where the identifier can be used later in a request handler to 122 /// access the matched value for that segment. 123 #[inline] match_info(&self) -> &Path<Url>124 pub fn match_info(&self) -> &Path<Url> { 125 &self.inner.path 126 } 127 128 #[inline] match_info_mut(&mut self) -> &mut Path<Url>129 pub(crate) fn match_info_mut(&mut self) -> &mut Path<Url> { 130 &mut Rc::get_mut(&mut self.inner).unwrap().path 131 } 132 133 /// The resource definition pattern that matched the path. Useful for logging and metrics. 134 /// 135 /// For example, when a resource with pattern `/user/{id}/profile` is defined and a call is made 136 /// to `/user/123/profile` this function would return `Some("/user/{id}/profile")`. 137 /// 138 /// Returns a None when no resource is fully matched, including default services. 139 #[inline] match_pattern(&self) -> Option<String>140 pub fn match_pattern(&self) -> Option<String> { 141 self.resource_map().match_pattern(self.path()) 142 } 143 144 /// The resource name that matched the path. Useful for logging and metrics. 145 /// 146 /// Returns a None when no resource is fully matched, including default services. 147 #[inline] match_name(&self) -> Option<&str>148 pub fn match_name(&self) -> Option<&str> { 149 self.resource_map().match_name(self.path()) 150 } 151 152 /// Request extensions 153 #[inline] extensions(&self) -> Ref<'_, Extensions>154 pub fn extensions(&self) -> Ref<'_, Extensions> { 155 self.head().extensions() 156 } 157 158 /// Mutable reference to a the request's extensions 159 #[inline] extensions_mut(&self) -> RefMut<'_, Extensions>160 pub fn extensions_mut(&self) -> RefMut<'_, Extensions> { 161 self.head().extensions_mut() 162 } 163 164 /// Generate url for named resource 165 /// 166 /// ``` 167 /// # use actix_web::{web, App, HttpRequest, HttpResponse}; 168 /// # 169 /// fn index(req: HttpRequest) -> HttpResponse { 170 /// let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource 171 /// HttpResponse::Ok().into() 172 /// } 173 /// 174 /// fn main() { 175 /// let app = App::new() 176 /// .service(web::resource("/test/{one}/{two}/{three}") 177 /// .name("foo") // <- set resource name, then it could be used in `url_for` 178 /// .route(web::get().to(|| HttpResponse::Ok())) 179 /// ); 180 /// } 181 /// ``` url_for<U, I>(&self, name: &str, elements: U) -> Result<url::Url, UrlGenerationError> where U: IntoIterator<Item = I>, I: AsRef<str>,182 pub fn url_for<U, I>(&self, name: &str, elements: U) -> Result<url::Url, UrlGenerationError> 183 where 184 U: IntoIterator<Item = I>, 185 I: AsRef<str>, 186 { 187 self.resource_map().url_for(&self, name, elements) 188 } 189 190 /// Generate url for named resource 191 /// 192 /// This method is similar to `HttpRequest::url_for()` but it can be used 193 /// for urls that do not contain variable parts. url_for_static(&self, name: &str) -> Result<url::Url, UrlGenerationError>194 pub fn url_for_static(&self, name: &str) -> Result<url::Url, UrlGenerationError> { 195 const NO_PARAMS: [&str; 0] = []; 196 self.url_for(name, &NO_PARAMS) 197 } 198 199 #[inline] 200 /// Get a reference to a `ResourceMap` of current application. resource_map(&self) -> &ResourceMap201 pub fn resource_map(&self) -> &ResourceMap { 202 &self.app_state().rmap() 203 } 204 205 /// Peer socket address. 206 /// 207 /// Peer address is the directly connected peer's socket address. If a proxy is used in front of 208 /// the Actix Web server, then it would be address of this proxy. 209 /// 210 /// To get client connection information `.connection_info()` should be used. 211 /// 212 /// Will only return None when called in unit tests. 213 #[inline] peer_addr(&self) -> Option<net::SocketAddr>214 pub fn peer_addr(&self) -> Option<net::SocketAddr> { 215 self.head().peer_addr 216 } 217 218 /// Get *ConnectionInfo* for the current request. 219 /// 220 /// This method panics if request's extensions container is already 221 /// borrowed. 222 #[inline] connection_info(&self) -> Ref<'_, ConnectionInfo>223 pub fn connection_info(&self) -> Ref<'_, ConnectionInfo> { 224 ConnectionInfo::get(self.head(), self.app_config()) 225 } 226 227 /// App config 228 #[inline] app_config(&self) -> &AppConfig229 pub fn app_config(&self) -> &AppConfig { 230 self.app_state().config() 231 } 232 233 /// Get an application data object stored with `App::data` or `App::app_data` 234 /// methods during application configuration. 235 /// 236 /// If `App::data` was used to store object, use `Data<T>`: 237 /// 238 /// ```ignore 239 /// let opt_t = req.app_data::<Data<T>>(); 240 /// ``` app_data<T: 'static>(&self) -> Option<&T>241 pub fn app_data<T: 'static>(&self) -> Option<&T> { 242 for container in self.inner.app_data.iter().rev() { 243 if let Some(data) = container.get::<T>() { 244 return Some(data); 245 } 246 } 247 248 None 249 } 250 251 #[inline] app_state(&self) -> &AppInitServiceState252 fn app_state(&self) -> &AppInitServiceState { 253 &*self.inner.app_state 254 } 255 256 /// Load request cookies. 257 #[cfg(feature = "cookies")] cookies(&self) -> Result<Ref<'_, Vec<Cookie<'static>>>, CookieParseError>258 pub fn cookies(&self) -> Result<Ref<'_, Vec<Cookie<'static>>>, CookieParseError> { 259 use actix_http::http::header::COOKIE; 260 261 if self.extensions().get::<Cookies>().is_none() { 262 let mut cookies = Vec::new(); 263 for hdr in self.headers().get_all(COOKIE) { 264 let s = str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?; 265 for cookie_str in s.split(';').map(|s| s.trim()) { 266 if !cookie_str.is_empty() { 267 cookies.push(Cookie::parse_encoded(cookie_str)?.into_owned()); 268 } 269 } 270 } 271 self.extensions_mut().insert(Cookies(cookies)); 272 } 273 274 Ok(Ref::map(self.extensions(), |ext| { 275 &ext.get::<Cookies>().unwrap().0 276 })) 277 } 278 279 /// Return request cookie. 280 #[cfg(feature = "cookies")] cookie(&self, name: &str) -> Option<Cookie<'static>>281 pub fn cookie(&self, name: &str) -> Option<Cookie<'static>> { 282 if let Ok(cookies) = self.cookies() { 283 for cookie in cookies.iter() { 284 if cookie.name() == name { 285 return Some(cookie.to_owned()); 286 } 287 } 288 } 289 None 290 } 291 } 292 293 impl HttpMessage for HttpRequest { 294 type Stream = (); 295 296 #[inline] 297 /// Returns Request's headers. headers(&self) -> &HeaderMap298 fn headers(&self) -> &HeaderMap { 299 &self.head().headers 300 } 301 302 /// Request extensions 303 #[inline] extensions(&self) -> Ref<'_, Extensions>304 fn extensions(&self) -> Ref<'_, Extensions> { 305 self.inner.head.extensions() 306 } 307 308 /// Mutable reference to a the request's extensions 309 #[inline] extensions_mut(&self) -> RefMut<'_, Extensions>310 fn extensions_mut(&self) -> RefMut<'_, Extensions> { 311 self.inner.head.extensions_mut() 312 } 313 314 #[inline] take_payload(&mut self) -> Payload<Self::Stream>315 fn take_payload(&mut self) -> Payload<Self::Stream> { 316 Payload::None 317 } 318 } 319 320 impl Drop for HttpRequest { drop(&mut self)321 fn drop(&mut self) { 322 // if possible, contribute to current worker's HttpRequest allocation pool 323 324 // This relies on no Weak<HttpRequestInner> exists anywhere.(There is none) 325 if let Some(inner) = Rc::get_mut(&mut self.inner) { 326 if inner.app_state.pool().is_available() { 327 // clear additional app_data and keep the root one for reuse. 328 inner.app_data.truncate(1); 329 // inner is borrowed mut here. get head's Extension mutably 330 // to reduce borrow check 331 inner.head.extensions.get_mut().clear(); 332 333 // a re-borrow of pool is necessary here. 334 let req = self.inner.clone(); 335 self.app_state().pool().push(req); 336 } 337 } 338 } 339 } 340 341 /// It is possible to get `HttpRequest` as an extractor handler parameter 342 /// 343 /// # Examples 344 /// ``` 345 /// use actix_web::{web, App, HttpRequest}; 346 /// use serde::Deserialize; 347 /// 348 /// /// extract `Thing` from request 349 /// async fn index(req: HttpRequest) -> String { 350 /// format!("Got thing: {:?}", req) 351 /// } 352 /// 353 /// fn main() { 354 /// let app = App::new().service( 355 /// web::resource("/users/{first}").route( 356 /// web::get().to(index)) 357 /// ); 358 /// } 359 /// ``` 360 impl FromRequest for HttpRequest { 361 type Config = (); 362 type Error = Error; 363 type Future = Ready<Result<Self, Error>>; 364 365 #[inline] from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future366 fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { 367 ok(req.clone()) 368 } 369 } 370 371 impl fmt::Debug for HttpRequest { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result372 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 373 writeln!( 374 f, 375 "\nHttpRequest {:?} {}:{}", 376 self.inner.head.version, 377 self.inner.head.method, 378 self.path() 379 )?; 380 if !self.query_string().is_empty() { 381 writeln!(f, " query: ?{:?}", self.query_string())?; 382 } 383 if !self.match_info().is_empty() { 384 writeln!(f, " params: {:?}", self.match_info())?; 385 } 386 writeln!(f, " headers:")?; 387 for (key, val) in self.headers().iter() { 388 writeln!(f, " {:?}: {:?}", key, val)?; 389 } 390 Ok(()) 391 } 392 } 393 394 /// Slab-allocated `HttpRequest` Pool 395 /// 396 /// Since request processing may yield for asynchronous events to complete, a worker may have many 397 /// requests in-flight at any time. Pooling requests like this amortizes the performance and memory 398 /// costs of allocating and de-allocating HttpRequest objects as frequently as they otherwise would. 399 /// 400 /// Request objects are added when they are dropped (see `<HttpRequest as Drop>::drop`) and re-used 401 /// in `<AppInitService as Service>::call` when there are available objects in the list. 402 /// 403 /// The pool's default capacity is 128 items. 404 pub(crate) struct HttpRequestPool { 405 inner: RefCell<Vec<Rc<HttpRequestInner>>>, 406 cap: usize, 407 } 408 409 impl Default for HttpRequestPool { default() -> Self410 fn default() -> Self { 411 Self::with_capacity(128) 412 } 413 } 414 415 impl HttpRequestPool { with_capacity(cap: usize) -> Self416 pub(crate) fn with_capacity(cap: usize) -> Self { 417 HttpRequestPool { 418 inner: RefCell::new(Vec::with_capacity(cap)), 419 cap, 420 } 421 } 422 423 /// Re-use a previously allocated (but now completed/discarded) HttpRequest object. 424 #[inline] pop(&self) -> Option<HttpRequest>425 pub(crate) fn pop(&self) -> Option<HttpRequest> { 426 self.inner 427 .borrow_mut() 428 .pop() 429 .map(|inner| HttpRequest { inner }) 430 } 431 432 /// Check if the pool still has capacity for request storage. 433 #[inline] is_available(&self) -> bool434 pub(crate) fn is_available(&self) -> bool { 435 self.inner.borrow_mut().len() < self.cap 436 } 437 438 /// Push a request to pool. 439 #[inline] push(&self, req: Rc<HttpRequestInner>)440 pub(crate) fn push(&self, req: Rc<HttpRequestInner>) { 441 self.inner.borrow_mut().push(req); 442 } 443 444 /// Clears all allocated HttpRequest objects. clear(&self)445 pub(crate) fn clear(&self) { 446 self.inner.borrow_mut().clear() 447 } 448 } 449 450 #[cfg(test)] 451 mod tests { 452 use actix_service::Service; 453 use bytes::Bytes; 454 455 use super::*; 456 use crate::dev::{ResourceDef, ResourceMap}; 457 use crate::http::{header, StatusCode}; 458 use crate::test::{call_service, init_service, read_body, TestRequest}; 459 use crate::{web, App, HttpResponse}; 460 461 #[test] test_debug()462 fn test_debug() { 463 let req = TestRequest::default() 464 .insert_header(("content-type", "text/plain")) 465 .to_http_request(); 466 let dbg = format!("{:?}", req); 467 assert!(dbg.contains("HttpRequest")); 468 } 469 470 #[test] 471 #[cfg(feature = "cookies")] test_no_request_cookies()472 fn test_no_request_cookies() { 473 let req = TestRequest::default().to_http_request(); 474 assert!(req.cookies().unwrap().is_empty()); 475 } 476 477 #[test] 478 #[cfg(feature = "cookies")] test_request_cookies()479 fn test_request_cookies() { 480 let req = TestRequest::default() 481 .append_header((header::COOKIE, "cookie1=value1")) 482 .append_header((header::COOKIE, "cookie2=value2")) 483 .to_http_request(); 484 { 485 let cookies = req.cookies().unwrap(); 486 assert_eq!(cookies.len(), 2); 487 assert_eq!(cookies[0].name(), "cookie1"); 488 assert_eq!(cookies[0].value(), "value1"); 489 assert_eq!(cookies[1].name(), "cookie2"); 490 assert_eq!(cookies[1].value(), "value2"); 491 } 492 493 let cookie = req.cookie("cookie1"); 494 assert!(cookie.is_some()); 495 let cookie = cookie.unwrap(); 496 assert_eq!(cookie.name(), "cookie1"); 497 assert_eq!(cookie.value(), "value1"); 498 499 let cookie = req.cookie("cookie-unknown"); 500 assert!(cookie.is_none()); 501 } 502 503 #[test] test_request_query()504 fn test_request_query() { 505 let req = TestRequest::with_uri("/?id=test").to_http_request(); 506 assert_eq!(req.query_string(), "id=test"); 507 } 508 509 #[test] test_url_for()510 fn test_url_for() { 511 let mut res = ResourceDef::new("/user/{name}.{ext}"); 512 *res.name_mut() = "index".to_string(); 513 514 let mut rmap = ResourceMap::new(ResourceDef::new("")); 515 rmap.add(&mut res, None); 516 assert!(rmap.has_resource("/user/test.html")); 517 assert!(!rmap.has_resource("/test/unknown")); 518 519 let req = TestRequest::default() 520 .insert_header((header::HOST, "www.rust-lang.org")) 521 .rmap(rmap) 522 .to_http_request(); 523 524 assert_eq!( 525 req.url_for("unknown", &["test"]), 526 Err(UrlGenerationError::ResourceNotFound) 527 ); 528 assert_eq!( 529 req.url_for("index", &["test"]), 530 Err(UrlGenerationError::NotEnoughElements) 531 ); 532 let url = req.url_for("index", &["test", "html"]); 533 assert_eq!( 534 url.ok().unwrap().as_str(), 535 "http://www.rust-lang.org/user/test.html" 536 ); 537 } 538 539 #[test] test_url_for_static()540 fn test_url_for_static() { 541 let mut rdef = ResourceDef::new("/index.html"); 542 *rdef.name_mut() = "index".to_string(); 543 544 let mut rmap = ResourceMap::new(ResourceDef::new("")); 545 rmap.add(&mut rdef, None); 546 547 assert!(rmap.has_resource("/index.html")); 548 549 let req = TestRequest::with_uri("/test") 550 .insert_header((header::HOST, "www.rust-lang.org")) 551 .rmap(rmap) 552 .to_http_request(); 553 let url = req.url_for_static("index"); 554 assert_eq!( 555 url.ok().unwrap().as_str(), 556 "http://www.rust-lang.org/index.html" 557 ); 558 } 559 560 #[test] test_match_name()561 fn test_match_name() { 562 let mut rdef = ResourceDef::new("/index.html"); 563 *rdef.name_mut() = "index".to_string(); 564 565 let mut rmap = ResourceMap::new(ResourceDef::new("")); 566 rmap.add(&mut rdef, None); 567 568 assert!(rmap.has_resource("/index.html")); 569 570 let req = TestRequest::default() 571 .uri("/index.html") 572 .rmap(rmap) 573 .to_http_request(); 574 575 assert_eq!(req.match_name(), Some("index")); 576 } 577 578 #[test] test_url_for_external()579 fn test_url_for_external() { 580 let mut rdef = ResourceDef::new("https://youtube.com/watch/{video_id}"); 581 582 *rdef.name_mut() = "youtube".to_string(); 583 584 let mut rmap = ResourceMap::new(ResourceDef::new("")); 585 rmap.add(&mut rdef, None); 586 assert!(rmap.has_resource("https://youtube.com/watch/unknown")); 587 588 let req = TestRequest::default().rmap(rmap).to_http_request(); 589 let url = req.url_for("youtube", &["oHg5SJYRHA0"]); 590 assert_eq!( 591 url.ok().unwrap().as_str(), 592 "https://youtube.com/watch/oHg5SJYRHA0" 593 ); 594 } 595 596 #[actix_rt::test] test_drop_http_request_pool()597 async fn test_drop_http_request_pool() { 598 let srv = init_service(App::new().service(web::resource("/").to( 599 |req: HttpRequest| { 600 HttpResponse::Ok() 601 .insert_header(("pool_cap", req.app_state().pool().cap)) 602 .finish() 603 }, 604 ))) 605 .await; 606 607 let req = TestRequest::default().to_request(); 608 let resp = call_service(&srv, req).await; 609 610 drop(srv); 611 612 assert_eq!(resp.headers().get("pool_cap").unwrap(), "128"); 613 } 614 615 #[actix_rt::test] test_data()616 async fn test_data() { 617 let srv = init_service(App::new().app_data(10usize).service(web::resource("/").to( 618 |req: HttpRequest| { 619 if req.app_data::<usize>().is_some() { 620 HttpResponse::Ok() 621 } else { 622 HttpResponse::BadRequest() 623 } 624 }, 625 ))) 626 .await; 627 628 let req = TestRequest::default().to_request(); 629 let resp = call_service(&srv, req).await; 630 assert_eq!(resp.status(), StatusCode::OK); 631 632 let srv = init_service(App::new().app_data(10u32).service(web::resource("/").to( 633 |req: HttpRequest| { 634 if req.app_data::<usize>().is_some() { 635 HttpResponse::Ok() 636 } else { 637 HttpResponse::BadRequest() 638 } 639 }, 640 ))) 641 .await; 642 643 let req = TestRequest::default().to_request(); 644 let resp = call_service(&srv, req).await; 645 assert_eq!(resp.status(), StatusCode::BAD_REQUEST); 646 } 647 648 #[actix_rt::test] test_cascading_data()649 async fn test_cascading_data() { 650 #[allow(dead_code)] 651 fn echo_usize(req: HttpRequest) -> HttpResponse { 652 let num = req.app_data::<usize>().unwrap(); 653 HttpResponse::Ok().body(num.to_string()) 654 } 655 656 let srv = init_service( 657 App::new() 658 .app_data(88usize) 659 .service(web::resource("/").route(web::get().to(echo_usize))) 660 .service( 661 web::resource("/one") 662 .app_data(1u32) 663 .route(web::get().to(echo_usize)), 664 ), 665 ) 666 .await; 667 668 let req = TestRequest::get().uri("/").to_request(); 669 let resp = srv.call(req).await.unwrap(); 670 let body = read_body(resp).await; 671 assert_eq!(body, Bytes::from_static(b"88")); 672 673 let req = TestRequest::get().uri("/one").to_request(); 674 let resp = srv.call(req).await.unwrap(); 675 let body = read_body(resp).await; 676 assert_eq!(body, Bytes::from_static(b"88")); 677 } 678 679 #[actix_rt::test] test_overwrite_data()680 async fn test_overwrite_data() { 681 #[allow(dead_code)] 682 fn echo_usize(req: HttpRequest) -> HttpResponse { 683 let num = req.app_data::<usize>().unwrap(); 684 HttpResponse::Ok().body(num.to_string()) 685 } 686 687 let srv = init_service( 688 App::new() 689 .app_data(88usize) 690 .service(web::resource("/").route(web::get().to(echo_usize))) 691 .service( 692 web::resource("/one") 693 .app_data(1usize) 694 .route(web::get().to(echo_usize)), 695 ), 696 ) 697 .await; 698 699 let req = TestRequest::get().uri("/").to_request(); 700 let resp = srv.call(req).await.unwrap(); 701 let body = read_body(resp).await; 702 assert_eq!(body, Bytes::from_static(b"88")); 703 704 let req = TestRequest::get().uri("/one").to_request(); 705 let resp = srv.call(req).await.unwrap(); 706 let body = read_body(resp).await; 707 assert_eq!(body, Bytes::from_static(b"1")); 708 } 709 710 // allow deprecated App::data 711 #[allow(deprecated)] 712 #[actix_rt::test] test_extensions_dropped()713 async fn test_extensions_dropped() { 714 struct Tracker { 715 pub dropped: bool, 716 } 717 struct Foo { 718 tracker: Rc<RefCell<Tracker>>, 719 } 720 impl Drop for Foo { 721 fn drop(&mut self) { 722 self.tracker.borrow_mut().dropped = true; 723 } 724 } 725 726 let tracker = Rc::new(RefCell::new(Tracker { dropped: false })); 727 { 728 let tracker2 = Rc::clone(&tracker); 729 let srv = init_service(App::new().data(10u32).service(web::resource("/").to( 730 move |req: HttpRequest| { 731 req.extensions_mut().insert(Foo { 732 tracker: Rc::clone(&tracker2), 733 }); 734 HttpResponse::Ok() 735 }, 736 ))) 737 .await; 738 739 let req = TestRequest::default().to_request(); 740 let resp = call_service(&srv, req).await; 741 assert_eq!(resp.status(), StatusCode::OK); 742 } 743 744 assert!(tracker.borrow().dropped); 745 } 746 747 #[actix_rt::test] extract_path_pattern()748 async fn extract_path_pattern() { 749 let srv = init_service( 750 App::new().service( 751 web::scope("/user/{id}") 752 .service(web::resource("/profile").route(web::get().to( 753 move |req: HttpRequest| { 754 assert_eq!( 755 req.match_pattern(), 756 Some("/user/{id}/profile".to_owned()) 757 ); 758 759 HttpResponse::Ok().finish() 760 }, 761 ))) 762 .default_service(web::to(move |req: HttpRequest| { 763 assert!(req.match_pattern().is_none()); 764 HttpResponse::Ok().finish() 765 })), 766 ), 767 ) 768 .await; 769 770 let req = TestRequest::get().uri("/user/22/profile").to_request(); 771 let res = call_service(&srv, req).await; 772 assert_eq!(res.status(), StatusCode::OK); 773 774 let req = TestRequest::get().uri("/user/22/not-exist").to_request(); 775 let res = call_service(&srv, req).await; 776 assert_eq!(res.status(), StatusCode::OK); 777 } 778 779 #[actix_rt::test] extract_path_pattern_complex()780 async fn extract_path_pattern_complex() { 781 let srv = init_service( 782 App::new() 783 .service(web::scope("/user").service(web::scope("/{id}").service( 784 web::resource("").to(move |req: HttpRequest| { 785 assert_eq!(req.match_pattern(), Some("/user/{id}".to_owned())); 786 787 HttpResponse::Ok().finish() 788 }), 789 ))) 790 .service(web::resource("/").to(move |req: HttpRequest| { 791 assert_eq!(req.match_pattern(), Some("/".to_owned())); 792 793 HttpResponse::Ok().finish() 794 })) 795 .default_service(web::to(move |req: HttpRequest| { 796 assert!(req.match_pattern().is_none()); 797 HttpResponse::Ok().finish() 798 })), 799 ) 800 .await; 801 802 let req = TestRequest::get().uri("/user/test").to_request(); 803 let res = call_service(&srv, req).await; 804 assert_eq!(res.status(), StatusCode::OK); 805 806 let req = TestRequest::get().uri("/").to_request(); 807 let res = call_service(&srv, req).await; 808 assert_eq!(res.status(), StatusCode::OK); 809 810 let req = TestRequest::get().uri("/not-exist").to_request(); 811 let res = call_service(&srv, req).await; 812 assert_eq!(res.status(), StatusCode::OK); 813 } 814 } 815