1 use std::cell::RefCell; 2 use std::fmt; 3 use std::future::Future; 4 use std::marker::PhantomData; 5 use std::rc::Rc; 6 7 use actix_http::body::{Body, MessageBody}; 8 use actix_http::{Extensions, Request}; 9 use actix_service::boxed::{self, BoxServiceFactory}; 10 use actix_service::{ 11 apply, apply_fn_factory, IntoServiceFactory, ServiceFactory, ServiceFactoryExt, Transform, 12 }; 13 use futures_util::future::FutureExt as _; 14 15 use crate::app_service::{AppEntry, AppInit, AppRoutingFactory}; 16 use crate::config::ServiceConfig; 17 use crate::data::{Data, DataFactory, FnDataFactory}; 18 use crate::dev::ResourceDef; 19 use crate::error::Error; 20 use crate::resource::Resource; 21 use crate::route::Route; 22 use crate::service::{ 23 AppServiceFactory, HttpServiceFactory, ServiceFactoryWrapper, ServiceRequest, 24 ServiceResponse, 25 }; 26 27 type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; 28 29 /// Application builder - structure that follows the builder pattern 30 /// for building application instances. 31 pub struct App<T, B> { 32 endpoint: T, 33 services: Vec<Box<dyn AppServiceFactory>>, 34 default: Option<Rc<HttpNewService>>, 35 factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>, 36 data_factories: Vec<FnDataFactory>, 37 external: Vec<ResourceDef>, 38 extensions: Extensions, 39 _phantom: PhantomData<B>, 40 } 41 42 impl App<AppEntry, Body> { 43 /// Create application builder. Application can be configured with a builder-like pattern. 44 #[allow(clippy::new_without_default)] new() -> Self45 pub fn new() -> Self { 46 let factory_ref = Rc::new(RefCell::new(None)); 47 48 App { 49 endpoint: AppEntry::new(factory_ref.clone()), 50 data_factories: Vec::new(), 51 services: Vec::new(), 52 default: None, 53 factory_ref, 54 external: Vec::new(), 55 extensions: Extensions::new(), 56 _phantom: PhantomData, 57 } 58 } 59 } 60 61 impl<T, B> App<T, B> 62 where 63 B: MessageBody, 64 T: ServiceFactory< 65 ServiceRequest, 66 Config = (), 67 Response = ServiceResponse<B>, 68 Error = Error, 69 InitError = (), 70 >, 71 { 72 /// Set application (root level) data. 73 /// 74 /// Application data stored with `App::app_data()` method is available through the 75 /// [`HttpRequest::app_data`](crate::HttpRequest::app_data) method at runtime. 76 /// 77 /// # [`Data<T>`] 78 /// Any [`Data<T>`] type added here can utilize it's extractor implementation in handlers. 79 /// Types not wrapped in `Data<T>` cannot use this extractor. See [its docs](Data<T>) for more 80 /// about its usage and patterns. 81 /// 82 /// ``` 83 /// use std::cell::Cell; 84 /// use actix_web::{web, App, HttpRequest, HttpResponse, Responder}; 85 /// 86 /// struct MyData { 87 /// count: std::cell::Cell<usize>, 88 /// } 89 /// 90 /// async fn handler(req: HttpRequest, counter: web::Data<MyData>) -> impl Responder { 91 /// // note this cannot use the Data<T> extractor because it was not added with it 92 /// let incr = *req.app_data::<usize>().unwrap(); 93 /// assert_eq!(incr, 3); 94 /// 95 /// // update counter using other value from app data 96 /// counter.count.set(counter.count.get() + incr); 97 /// 98 /// HttpResponse::Ok().body(counter.count.get().to_string()) 99 /// } 100 /// 101 /// let app = App::new().service( 102 /// web::resource("/") 103 /// .app_data(3usize) 104 /// .app_data(web::Data::new(MyData { count: Default::default() })) 105 /// .route(web::get().to(handler)) 106 /// ); 107 /// ``` 108 /// 109 /// # Shared Mutable State 110 /// [`HttpServer::new`](crate::HttpServer::new) accepts an application factory rather than an 111 /// application instance; the factory closure is called on each worker thread independently. 112 /// Therefore, if you want to share a data object between different workers, a shareable object 113 /// needs to be created first, outside the `HttpServer::new` closure and cloned into it. 114 /// [`Data<T>`] is an example of such a sharable object. 115 /// 116 /// ```ignore 117 /// let counter = web::Data::new(AppStateWithCounter { 118 /// counter: Mutex::new(0), 119 /// }); 120 /// 121 /// HttpServer::new(move || { 122 /// // move counter object into the closure and clone for each worker 123 /// 124 /// App::new() 125 /// .app_data(counter.clone()) 126 /// .route("/", web::get().to(handler)) 127 /// }) 128 /// ``` app_data<U: 'static>(mut self, ext: U) -> Self129 pub fn app_data<U: 'static>(mut self, ext: U) -> Self { 130 self.extensions.insert(ext); 131 self 132 } 133 134 /// Add application (root) data after wrapping in `Data<T>`. 135 /// 136 /// Deprecated in favor of [`app_data`](Self::app_data). 137 #[deprecated(since = "4.0.0", note = "Use `.app_data(Data::new(val))` instead.")] data<U: 'static>(self, data: U) -> Self138 pub fn data<U: 'static>(self, data: U) -> Self { 139 self.app_data(Data::new(data)) 140 } 141 142 /// Add application data factory. This function is similar to `.data()` but it accepts a 143 /// "data factory". Data values are constructed asynchronously during application 144 /// initialization, before the server starts accepting requests. 145 #[deprecated( 146 since = "4.0.0", 147 note = "Construct data value before starting server and use `.app_data(Data::new(val))` instead." 148 )] data_factory<F, Out, D, E>(mut self, data: F) -> Self where F: Fn() -> Out + 'static, Out: Future<Output = Result<D, E>> + 'static, D: 'static, E: std::fmt::Debug,149 pub fn data_factory<F, Out, D, E>(mut self, data: F) -> Self 150 where 151 F: Fn() -> Out + 'static, 152 Out: Future<Output = Result<D, E>> + 'static, 153 D: 'static, 154 E: std::fmt::Debug, 155 { 156 self.data_factories.push(Box::new(move || { 157 { 158 let fut = data(); 159 async move { 160 match fut.await { 161 Err(e) => { 162 log::error!("Can not construct data instance: {:?}", e); 163 Err(()) 164 } 165 Ok(data) => { 166 let data: Box<dyn DataFactory> = Box::new(Data::new(data)); 167 Ok(data) 168 } 169 } 170 } 171 } 172 .boxed_local() 173 })); 174 self 175 } 176 177 /// Run external configuration as part of the application building 178 /// process 179 /// 180 /// This function is useful for moving parts of configuration to a 181 /// different module or even library. For example, 182 /// some of the resource's configuration could be moved to different module. 183 /// 184 /// ``` 185 /// use actix_web::{web, App, HttpResponse}; 186 /// 187 /// // this function could be located in different module 188 /// fn config(cfg: &mut web::ServiceConfig) { 189 /// cfg.service(web::resource("/test") 190 /// .route(web::get().to(|| HttpResponse::Ok())) 191 /// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) 192 /// ); 193 /// } 194 /// 195 /// App::new() 196 /// .configure(config) // <- register resources 197 /// .route("/index.html", web::get().to(|| HttpResponse::Ok())); 198 /// ``` configure<F>(mut self, f: F) -> Self where F: FnOnce(&mut ServiceConfig),199 pub fn configure<F>(mut self, f: F) -> Self 200 where 201 F: FnOnce(&mut ServiceConfig), 202 { 203 let mut cfg = ServiceConfig::new(); 204 f(&mut cfg); 205 self.services.extend(cfg.services); 206 self.external.extend(cfg.external); 207 self.extensions.extend(cfg.app_data); 208 self 209 } 210 211 /// Configure route for a specific path. 212 /// 213 /// This is a simplified version of the `App::service()` method. 214 /// This method can be used multiple times with same path, in that case 215 /// multiple resources with one route would be registered for same resource path. 216 /// 217 /// ``` 218 /// use actix_web::{web, App, HttpResponse}; 219 /// 220 /// async fn index(data: web::Path<(String, String)>) -> &'static str { 221 /// "Welcome!" 222 /// } 223 /// 224 /// fn main() { 225 /// let app = App::new() 226 /// .route("/test1", web::get().to(index)) 227 /// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed())); 228 /// } 229 /// ``` route(self, path: &str, mut route: Route) -> Self230 pub fn route(self, path: &str, mut route: Route) -> Self { 231 self.service( 232 Resource::new(path) 233 .add_guards(route.take_guards()) 234 .route(route), 235 ) 236 } 237 238 /// Register HTTP service. 239 /// 240 /// Http service is any type that implements `HttpServiceFactory` trait. 241 /// 242 /// Actix Web provides several services implementations: 243 /// 244 /// * *Resource* is an entry in resource table which corresponds to requested URL. 245 /// * *Scope* is a set of resources with common root path. 246 /// * "StaticFiles" is a service for static files support service<F>(mut self, factory: F) -> Self where F: HttpServiceFactory + 'static,247 pub fn service<F>(mut self, factory: F) -> Self 248 where 249 F: HttpServiceFactory + 'static, 250 { 251 self.services 252 .push(Box::new(ServiceFactoryWrapper::new(factory))); 253 self 254 } 255 256 /// Default service to be used if no matching resource could be found. 257 /// 258 /// It is possible to use services like `Resource`, `Route`. 259 /// 260 /// ``` 261 /// use actix_web::{web, App, HttpResponse}; 262 /// 263 /// async fn index() -> &'static str { 264 /// "Welcome!" 265 /// } 266 /// 267 /// fn main() { 268 /// let app = App::new() 269 /// .service( 270 /// web::resource("/index.html").route(web::get().to(index))) 271 /// .default_service( 272 /// web::route().to(|| HttpResponse::NotFound())); 273 /// } 274 /// ``` 275 /// 276 /// It is also possible to use static files as default service. 277 /// 278 /// ``` 279 /// use actix_web::{web, App, HttpResponse}; 280 /// 281 /// fn main() { 282 /// let app = App::new() 283 /// .service( 284 /// web::resource("/index.html").to(|| HttpResponse::Ok())) 285 /// .default_service( 286 /// web::to(|| HttpResponse::NotFound()) 287 /// ); 288 /// } 289 /// ``` default_service<F, U>(mut self, f: F) -> Self where F: IntoServiceFactory<U, ServiceRequest>, U: ServiceFactory< ServiceRequest, Config = (), Response = ServiceResponse, Error = Error, > + 'static, U::InitError: fmt::Debug,290 pub fn default_service<F, U>(mut self, f: F) -> Self 291 where 292 F: IntoServiceFactory<U, ServiceRequest>, 293 U: ServiceFactory< 294 ServiceRequest, 295 Config = (), 296 Response = ServiceResponse, 297 Error = Error, 298 > + 'static, 299 U::InitError: fmt::Debug, 300 { 301 // create and configure default resource 302 self.default = Some(Rc::new(boxed::factory(f.into_factory().map_init_err( 303 |e| log::error!("Can not construct default service: {:?}", e), 304 )))); 305 306 self 307 } 308 309 /// Register an external resource. 310 /// 311 /// External resources are useful for URL generation purposes only 312 /// and are never considered for matching at request time. Calls to 313 /// `HttpRequest::url_for()` will work as expected. 314 /// 315 /// ``` 316 /// use actix_web::{web, App, HttpRequest, HttpResponse, Result}; 317 /// 318 /// async fn index(req: HttpRequest) -> Result<HttpResponse> { 319 /// let url = req.url_for("youtube", &["asdlkjqme"])?; 320 /// assert_eq!(url.as_str(), "https://youtube.com/watch/asdlkjqme"); 321 /// Ok(HttpResponse::Ok().into()) 322 /// } 323 /// 324 /// fn main() { 325 /// let app = App::new() 326 /// .service(web::resource("/index.html").route( 327 /// web::get().to(index))) 328 /// .external_resource("youtube", "https://youtube.com/watch/{video_id}"); 329 /// } 330 /// ``` external_resource<N, U>(mut self, name: N, url: U) -> Self where N: AsRef<str>, U: AsRef<str>,331 pub fn external_resource<N, U>(mut self, name: N, url: U) -> Self 332 where 333 N: AsRef<str>, 334 U: AsRef<str>, 335 { 336 let mut rdef = ResourceDef::new(url.as_ref()); 337 *rdef.name_mut() = name.as_ref().to_string(); 338 self.external.push(rdef); 339 self 340 } 341 342 /// Registers middleware, in the form of a middleware component (type), 343 /// that runs during inbound and/or outbound processing in the request 344 /// life-cycle (request -> response), modifying request/response as 345 /// necessary, across all requests managed by the *Application*. 346 /// 347 /// Use middleware when you need to read or modify *every* request or 348 /// response in some way. 349 /// 350 /// Notice that the keyword for registering middleware is `wrap`. As you 351 /// register middleware using `wrap` in the App builder, imagine wrapping 352 /// layers around an inner App. The first middleware layer exposed to a 353 /// Request is the outermost layer-- the *last* registered in 354 /// the builder chain. Consequently, the *first* middleware registered 355 /// in the builder chain is the *last* to execute during request processing. 356 /// 357 /// ``` 358 /// use actix_service::Service; 359 /// use actix_web::{middleware, web, App}; 360 /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; 361 /// 362 /// async fn index() -> &'static str { 363 /// "Welcome!" 364 /// } 365 /// 366 /// fn main() { 367 /// let app = App::new() 368 /// .wrap(middleware::Logger::default()) 369 /// .route("/index.html", web::get().to(index)); 370 /// } 371 /// ``` wrap<M, B1>( self, mw: M, ) -> App< impl ServiceFactory< ServiceRequest, Config = (), Response = ServiceResponse<B1>, Error = Error, InitError = (), >, B1, > where M: Transform< T::Service, ServiceRequest, Response = ServiceResponse<B1>, Error = Error, InitError = (), >, B1: MessageBody,372 pub fn wrap<M, B1>( 373 self, 374 mw: M, 375 ) -> App< 376 impl ServiceFactory< 377 ServiceRequest, 378 Config = (), 379 Response = ServiceResponse<B1>, 380 Error = Error, 381 InitError = (), 382 >, 383 B1, 384 > 385 where 386 M: Transform< 387 T::Service, 388 ServiceRequest, 389 Response = ServiceResponse<B1>, 390 Error = Error, 391 InitError = (), 392 >, 393 B1: MessageBody, 394 { 395 App { 396 endpoint: apply(mw, self.endpoint), 397 data_factories: self.data_factories, 398 services: self.services, 399 default: self.default, 400 factory_ref: self.factory_ref, 401 external: self.external, 402 extensions: self.extensions, 403 _phantom: PhantomData, 404 } 405 } 406 407 /// Registers middleware, in the form of a closure, that runs during inbound 408 /// and/or outbound processing in the request life-cycle (request -> response), 409 /// modifying request/response as necessary, across all requests managed by 410 /// the *Application*. 411 /// 412 /// Use middleware when you need to read or modify *every* request or response in some way. 413 /// 414 /// ``` 415 /// use actix_service::Service; 416 /// use actix_web::{web, App}; 417 /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; 418 /// 419 /// async fn index() -> &'static str { 420 /// "Welcome!" 421 /// } 422 /// 423 /// fn main() { 424 /// let app = App::new() 425 /// .wrap_fn(|req, srv| { 426 /// let fut = srv.call(req); 427 /// async { 428 /// let mut res = fut.await?; 429 /// res.headers_mut().insert( 430 /// CONTENT_TYPE, HeaderValue::from_static("text/plain"), 431 /// ); 432 /// Ok(res) 433 /// } 434 /// }) 435 /// .route("/index.html", web::get().to(index)); 436 /// } 437 /// ``` wrap_fn<B1, F, R>( self, mw: F, ) -> App< impl ServiceFactory< ServiceRequest, Config = (), Response = ServiceResponse<B1>, Error = Error, InitError = (), >, B1, > where B1: MessageBody, F: Fn(ServiceRequest, &T::Service) -> R + Clone, R: Future<Output = Result<ServiceResponse<B1>, Error>>,438 pub fn wrap_fn<B1, F, R>( 439 self, 440 mw: F, 441 ) -> App< 442 impl ServiceFactory< 443 ServiceRequest, 444 Config = (), 445 Response = ServiceResponse<B1>, 446 Error = Error, 447 InitError = (), 448 >, 449 B1, 450 > 451 where 452 B1: MessageBody, 453 F: Fn(ServiceRequest, &T::Service) -> R + Clone, 454 R: Future<Output = Result<ServiceResponse<B1>, Error>>, 455 { 456 App { 457 endpoint: apply_fn_factory(self.endpoint, mw), 458 data_factories: self.data_factories, 459 services: self.services, 460 default: self.default, 461 factory_ref: self.factory_ref, 462 external: self.external, 463 extensions: self.extensions, 464 _phantom: PhantomData, 465 } 466 } 467 } 468 469 impl<T, B> IntoServiceFactory<AppInit<T, B>, Request> for App<T, B> 470 where 471 B: MessageBody, 472 T: ServiceFactory< 473 ServiceRequest, 474 Config = (), 475 Response = ServiceResponse<B>, 476 Error = Error, 477 InitError = (), 478 >, 479 T::Future: 'static, 480 { into_factory(self) -> AppInit<T, B>481 fn into_factory(self) -> AppInit<T, B> { 482 AppInit { 483 async_data_factories: self.data_factories.into_boxed_slice().into(), 484 endpoint: self.endpoint, 485 services: Rc::new(RefCell::new(self.services)), 486 external: RefCell::new(self.external), 487 default: self.default, 488 factory_ref: self.factory_ref, 489 extensions: RefCell::new(Some(self.extensions)), 490 } 491 } 492 } 493 494 #[cfg(test)] 495 mod tests { 496 use actix_service::Service; 497 use actix_utils::future::{err, ok}; 498 use bytes::Bytes; 499 500 use super::*; 501 use crate::http::{header, HeaderValue, Method, StatusCode}; 502 use crate::middleware::DefaultHeaders; 503 use crate::service::ServiceRequest; 504 use crate::test::{call_service, init_service, read_body, try_init_service, TestRequest}; 505 use crate::{web, HttpRequest, HttpResponse}; 506 507 #[actix_rt::test] test_default_resource()508 async fn test_default_resource() { 509 let srv = 510 init_service(App::new().service(web::resource("/test").to(HttpResponse::Ok))).await; 511 let req = TestRequest::with_uri("/test").to_request(); 512 let resp = srv.call(req).await.unwrap(); 513 assert_eq!(resp.status(), StatusCode::OK); 514 515 let req = TestRequest::with_uri("/blah").to_request(); 516 let resp = srv.call(req).await.unwrap(); 517 assert_eq!(resp.status(), StatusCode::NOT_FOUND); 518 519 let srv = init_service( 520 App::new() 521 .service(web::resource("/test").to(HttpResponse::Ok)) 522 .service( 523 web::resource("/test2") 524 .default_service(|r: ServiceRequest| { 525 ok(r.into_response(HttpResponse::Created())) 526 }) 527 .route(web::get().to(HttpResponse::Ok)), 528 ) 529 .default_service(|r: ServiceRequest| { 530 ok(r.into_response(HttpResponse::MethodNotAllowed())) 531 }), 532 ) 533 .await; 534 535 let req = TestRequest::with_uri("/blah").to_request(); 536 let resp = srv.call(req).await.unwrap(); 537 assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); 538 539 let req = TestRequest::with_uri("/test2").to_request(); 540 let resp = srv.call(req).await.unwrap(); 541 assert_eq!(resp.status(), StatusCode::OK); 542 543 let req = TestRequest::with_uri("/test2") 544 .method(Method::POST) 545 .to_request(); 546 let resp = srv.call(req).await.unwrap(); 547 assert_eq!(resp.status(), StatusCode::CREATED); 548 } 549 550 // allow deprecated App::data 551 #[allow(deprecated)] 552 #[actix_rt::test] test_data_factory()553 async fn test_data_factory() { 554 let srv = init_service( 555 App::new() 556 .data_factory(|| ok::<_, ()>(10usize)) 557 .service(web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok())), 558 ) 559 .await; 560 let req = TestRequest::default().to_request(); 561 let resp = srv.call(req).await.unwrap(); 562 assert_eq!(resp.status(), StatusCode::OK); 563 564 let srv = init_service( 565 App::new() 566 .data_factory(|| ok::<_, ()>(10u32)) 567 .service(web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok())), 568 ) 569 .await; 570 let req = TestRequest::default().to_request(); 571 let resp = srv.call(req).await.unwrap(); 572 assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); 573 } 574 575 // allow deprecated App::data 576 #[allow(deprecated)] 577 #[actix_rt::test] test_data_factory_errors()578 async fn test_data_factory_errors() { 579 let srv = try_init_service( 580 App::new() 581 .data_factory(|| err::<u32, _>(())) 582 .service(web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok())), 583 ) 584 .await; 585 586 assert!(srv.is_err()); 587 } 588 589 #[actix_rt::test] test_extension()590 async fn test_extension() { 591 let srv = init_service(App::new().app_data(10usize).service(web::resource("/").to( 592 |req: HttpRequest| { 593 assert_eq!(*req.app_data::<usize>().unwrap(), 10); 594 HttpResponse::Ok() 595 }, 596 ))) 597 .await; 598 let req = TestRequest::default().to_request(); 599 let resp = srv.call(req).await.unwrap(); 600 assert_eq!(resp.status(), StatusCode::OK); 601 } 602 603 #[actix_rt::test] test_wrap()604 async fn test_wrap() { 605 let srv = init_service( 606 App::new() 607 .wrap( 608 DefaultHeaders::new() 609 .header(header::CONTENT_TYPE, HeaderValue::from_static("0001")), 610 ) 611 .route("/test", web::get().to(HttpResponse::Ok)), 612 ) 613 .await; 614 let req = TestRequest::with_uri("/test").to_request(); 615 let resp = call_service(&srv, req).await; 616 assert_eq!(resp.status(), StatusCode::OK); 617 assert_eq!( 618 resp.headers().get(header::CONTENT_TYPE).unwrap(), 619 HeaderValue::from_static("0001") 620 ); 621 } 622 623 #[actix_rt::test] test_router_wrap()624 async fn test_router_wrap() { 625 let srv = init_service( 626 App::new() 627 .route("/test", web::get().to(HttpResponse::Ok)) 628 .wrap( 629 DefaultHeaders::new() 630 .header(header::CONTENT_TYPE, HeaderValue::from_static("0001")), 631 ), 632 ) 633 .await; 634 let req = TestRequest::with_uri("/test").to_request(); 635 let resp = call_service(&srv, req).await; 636 assert_eq!(resp.status(), StatusCode::OK); 637 assert_eq!( 638 resp.headers().get(header::CONTENT_TYPE).unwrap(), 639 HeaderValue::from_static("0001") 640 ); 641 } 642 643 #[actix_rt::test] test_wrap_fn()644 async fn test_wrap_fn() { 645 let srv = init_service( 646 App::new() 647 .wrap_fn(|req, srv| { 648 let fut = srv.call(req); 649 async move { 650 let mut res = fut.await?; 651 res.headers_mut() 652 .insert(header::CONTENT_TYPE, HeaderValue::from_static("0001")); 653 Ok(res) 654 } 655 }) 656 .service(web::resource("/test").to(HttpResponse::Ok)), 657 ) 658 .await; 659 let req = TestRequest::with_uri("/test").to_request(); 660 let resp = call_service(&srv, req).await; 661 assert_eq!(resp.status(), StatusCode::OK); 662 assert_eq!( 663 resp.headers().get(header::CONTENT_TYPE).unwrap(), 664 HeaderValue::from_static("0001") 665 ); 666 } 667 668 #[actix_rt::test] test_router_wrap_fn()669 async fn test_router_wrap_fn() { 670 let srv = init_service( 671 App::new() 672 .route("/test", web::get().to(HttpResponse::Ok)) 673 .wrap_fn(|req, srv| { 674 let fut = srv.call(req); 675 async { 676 let mut res = fut.await?; 677 res.headers_mut() 678 .insert(header::CONTENT_TYPE, HeaderValue::from_static("0001")); 679 Ok(res) 680 } 681 }), 682 ) 683 .await; 684 let req = TestRequest::with_uri("/test").to_request(); 685 let resp = call_service(&srv, req).await; 686 assert_eq!(resp.status(), StatusCode::OK); 687 assert_eq!( 688 resp.headers().get(header::CONTENT_TYPE).unwrap(), 689 HeaderValue::from_static("0001") 690 ); 691 } 692 693 #[actix_rt::test] test_external_resource()694 async fn test_external_resource() { 695 let srv = init_service( 696 App::new() 697 .external_resource("youtube", "https://youtube.com/watch/{video_id}") 698 .route( 699 "/test", 700 web::get().to(|req: HttpRequest| { 701 HttpResponse::Ok() 702 .body(req.url_for("youtube", &["12345"]).unwrap().to_string()) 703 }), 704 ), 705 ) 706 .await; 707 let req = TestRequest::with_uri("/test").to_request(); 708 let resp = call_service(&srv, req).await; 709 assert_eq!(resp.status(), StatusCode::OK); 710 let body = read_body(resp).await; 711 assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345")); 712 } 713 } 714