use std::cell::RefCell; use std::fmt; use std::future::Future; use std::pin::Pin; use std::rc::Rc; use std::task::{Context, Poll}; use actix_http::{Extensions, Response}; use actix_router::{ResourceDef, ResourceInfo, Router}; use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::{ apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, Transform, }; use futures_core::future::LocalBoxFuture; use crate::config::ServiceConfig; use crate::data::Data; use crate::dev::{AppService, HttpServiceFactory}; use crate::error::Error; use crate::guard::Guard; use crate::resource::Resource; use crate::rmap::ResourceMap; use crate::route::Route; use crate::service::{ AppServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse, }; type Guards = Vec>; type HttpService = BoxService; type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; /// Resources scope. /// /// Scope is a set of resources with common root path. /// Scopes collect multiple paths under a common path prefix. /// Scope path can contain variable path segments as resources. /// Scope prefix is always complete path segment, i.e `/app` would /// be converted to a `/app/` and it would not match `/app` path. /// /// You can get variable path segments from `HttpRequest::match_info()`. /// `Path` extractor also is able to extract scope level variable segments. /// /// ```rust /// use actix_web::{web, App, HttpResponse}; /// /// fn main() { /// let app = App::new().service( /// web::scope("/{project_id}/") /// .service(web::resource("/path1").to(|| async { HttpResponse::Ok() })) /// .service(web::resource("/path2").route(web::get().to(|| HttpResponse::Ok()))) /// .service(web::resource("/path3").route(web::head().to(|| HttpResponse::MethodNotAllowed()))) /// ); /// } /// ``` /// /// In the above example three routes get registered: /// * /{project_id}/path1 - responds to all http method /// * /{project_id}/path2 - `GET` requests /// * /{project_id}/path3 - `HEAD` requests pub struct Scope { endpoint: T, rdef: String, data: Option, services: Vec>, guards: Vec>, default: Rc>>>, external: Vec, factory_ref: Rc>>, } impl Scope { /// Create a new scope pub fn new(path: &str) -> Scope { let fref = Rc::new(RefCell::new(None)); Scope { endpoint: ScopeEndpoint::new(fref.clone()), rdef: path.to_string(), data: None, guards: Vec::new(), services: Vec::new(), default: Rc::new(RefCell::new(None)), external: Vec::new(), factory_ref: fref, } } } impl Scope where T: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), >, { /// Add match guard to a scope. /// /// ```rust /// use actix_web::{web, guard, App, HttpRequest, HttpResponse}; /// /// async fn index(data: web::Path<(String, String)>) -> &'static str { /// "Welcome!" /// } /// /// fn main() { /// let app = App::new().service( /// web::scope("/app") /// .guard(guard::Header("content-type", "text/plain")) /// .route("/test1", web::get().to(index)) /// .route("/test2", web::post().to(|r: HttpRequest| { /// HttpResponse::MethodNotAllowed() /// })) /// ); /// } /// ``` pub fn guard(mut self, guard: G) -> Self { self.guards.push(Box::new(guard)); self } /// Set or override application data. Application data could be accessed /// by using `Data` extractor where `T` is data type. /// /// ```rust /// use std::cell::Cell; /// use actix_web::{web, App, HttpResponse, Responder}; /// /// struct MyData { /// counter: Cell, /// } /// /// async fn index(data: web::Data) -> impl Responder { /// data.counter.set(data.counter.get() + 1); /// HttpResponse::Ok() /// } /// /// fn main() { /// let app = App::new().service( /// web::scope("/app") /// .data(MyData{ counter: Cell::new(0) }) /// .service( /// web::resource("/index.html").route( /// web::get().to(index))) /// ); /// } /// ``` pub fn data(self, data: U) -> Self { self.app_data(Data::new(data)) } /// Add scope data. /// /// Data of different types from parent contexts will still be accessible. pub fn app_data(mut self, data: U) -> Self { if self.data.is_none() { self.data = Some(Extensions::new()); } self.data.as_mut().unwrap().insert(data); self } /// Run external configuration as part of the scope building /// process /// /// This function is useful for moving parts of configuration to a /// different module or even library. For example, /// some of the resource's configuration could be moved to different module. /// /// ```rust /// # extern crate actix_web; /// use actix_web::{web, middleware, App, HttpResponse}; /// /// // this function could be located in different module /// fn config(cfg: &mut web::ServiceConfig) { /// cfg.service(web::resource("/test") /// .route(web::get().to(|| HttpResponse::Ok())) /// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) /// ); /// } /// /// fn main() { /// let app = App::new() /// .wrap(middleware::Logger::default()) /// .service( /// web::scope("/api") /// .configure(config) /// ) /// .route("/index.html", web::get().to(|| HttpResponse::Ok())); /// } /// ``` pub fn configure(mut self, f: F) -> Self where F: FnOnce(&mut ServiceConfig), { let mut cfg = ServiceConfig::new(); f(&mut cfg); self.services.extend(cfg.services); self.external.extend(cfg.external); if !cfg.data.is_empty() { let mut data = self.data.unwrap_or_else(Extensions::new); for value in cfg.data.iter() { value.create(&mut data); } self.data = Some(data); } self.data .get_or_insert_with(Extensions::new) .extend(cfg.extensions); self } /// Register http service. /// /// This is similar to `App's` service registration. /// /// Actix web provides several services implementations: /// /// * *Resource* is an entry in resource table which corresponds to requested URL. /// * *Scope* is a set of resources with common root path. /// * "StaticFiles" is a service for static files support /// /// ```rust /// use actix_web::{web, App, HttpRequest}; /// /// struct AppState; /// /// async fn index(req: HttpRequest) -> &'static str { /// "Welcome!" /// } /// /// fn main() { /// let app = App::new().service( /// web::scope("/app").service( /// web::scope("/v1") /// .service(web::resource("/test1").to(index))) /// ); /// } /// ``` pub fn service(mut self, factory: F) -> Self where F: HttpServiceFactory + 'static, { self.services .push(Box::new(ServiceFactoryWrapper::new(factory))); self } /// Configure route for a specific path. /// /// This is a simplified version of the `Scope::service()` method. /// This method can be called multiple times, in that case /// multiple resources with one route would be registered for same resource path. /// /// ```rust /// use actix_web::{web, App, HttpResponse}; /// /// async fn index(data: web::Path<(String, String)>) -> &'static str { /// "Welcome!" /// } /// /// fn main() { /// let app = App::new().service( /// web::scope("/app") /// .route("/test1", web::get().to(index)) /// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed())) /// ); /// } /// ``` pub fn route(self, path: &str, mut route: Route) -> Self { self.service( Resource::new(path) .add_guards(route.take_guards()) .route(route), ) } /// Default service to be used if no matching route could be found. /// /// If default resource is not registered, app's default resource is being used. pub fn default_service(mut self, f: F) -> Self where F: IntoServiceFactory, U: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = Error, > + 'static, U::InitError: fmt::Debug, { // create and configure default resource self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::factory( f.into_factory().map_init_err(|e| { log::error!("Can not construct default service: {:?}", e) }), ))))); self } /// Registers middleware, in the form of a middleware component (type), /// that runs during inbound processing in the request /// life-cycle (request -> response), modifying request as /// necessary, across all requests managed by the *Scope*. Scope-level /// middleware is more limited in what it can modify, relative to Route or /// Application level middleware, in that Scope-level middleware can not modify /// ServiceResponse. /// /// Use middleware when you need to read or modify *every* request in some way. pub fn wrap( self, mw: M, ) -> Scope< impl ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), >, > where M: Transform< T::Service, Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), >, { Scope { endpoint: apply(mw, self.endpoint), rdef: self.rdef, data: self.data, guards: self.guards, services: self.services, default: self.default, external: self.external, factory_ref: self.factory_ref, } } /// Registers middleware, in the form of a closure, that runs during inbound /// processing in the request life-cycle (request -> response), modifying /// request as necessary, across all requests managed by the *Scope*. /// Scope-level middleware is more limited in what it can modify, relative /// to Route or Application level middleware, in that Scope-level middleware /// can not modify ServiceResponse. /// /// ```rust /// use actix_service::Service; /// use actix_web::{web, App}; /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; /// /// async fn index() -> &'static str { /// "Welcome!" /// } /// /// fn main() { /// let app = App::new().service( /// web::scope("/app") /// .wrap_fn(|req, srv| { /// let fut = srv.call(req); /// async { /// let mut res = fut.await?; /// res.headers_mut().insert( /// CONTENT_TYPE, HeaderValue::from_static("text/plain"), /// ); /// Ok(res) /// } /// }) /// .route("/index.html", web::get().to(index))); /// } /// ``` pub fn wrap_fn( self, mw: F, ) -> Scope< impl ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), >, > where F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone, R: Future>, { Scope { endpoint: apply_fn_factory(self.endpoint, mw), rdef: self.rdef, data: self.data, guards: self.guards, services: self.services, default: self.default, external: self.external, factory_ref: self.factory_ref, } } } impl HttpServiceFactory for Scope where T: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), > + 'static, { fn register(mut self, config: &mut AppService) { // update default resource if needed if self.default.borrow().is_none() { *self.default.borrow_mut() = Some(config.default_service()); } // register nested services let mut cfg = config.clone_config(); self.services .into_iter() .for_each(|mut srv| srv.register(&mut cfg)); let mut rmap = ResourceMap::new(ResourceDef::root_prefix(&self.rdef)); // external resources for mut rdef in std::mem::take(&mut self.external) { rmap.add(&mut rdef, None); } // custom app data storage if let Some(ref mut ext) = self.data { config.set_service_data(ext); } // complete scope pipeline creation *self.factory_ref.borrow_mut() = Some(ScopeFactory { data: self.data.take().map(Rc::new), default: self.default.clone(), services: cfg .into_services() .1 .into_iter() .map(|(mut rdef, srv, guards, nested)| { rmap.add(&mut rdef, nested); (rdef, srv, RefCell::new(guards)) }) .collect::>() .into_boxed_slice() .into(), }); // get guards let guards = if self.guards.is_empty() { None } else { Some(self.guards) }; // register final service config.register_service( ResourceDef::root_prefix(&self.rdef), guards, self.endpoint, Some(Rc::new(rmap)), ) } } pub struct ScopeFactory { data: Option>, services: Rc<[(ResourceDef, HttpNewService, RefCell>)]>, default: Rc>>>, } impl ServiceFactory for ScopeFactory { type Config = (); type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type InitError = (); type Service = ScopeService; type Future = ScopeFactoryResponse; fn new_service(&self, _: ()) -> Self::Future { let default_fut = if let Some(ref default) = *self.default.borrow() { Some(default.new_service(())) } else { None }; ScopeFactoryResponse { fut: self .services .iter() .map(|(path, service, guards)| { CreateScopeServiceItem::Future( Some(path.clone()), guards.borrow_mut().take(), service.new_service(()), ) }) .collect(), default: None, data: self.data.clone(), default_fut, } } } /// Create scope service #[doc(hidden)] #[pin_project::pin_project] pub struct ScopeFactoryResponse { fut: Vec, data: Option>, default: Option, default_fut: Option>>, } type HttpServiceFut = LocalBoxFuture<'static, Result>; enum CreateScopeServiceItem { Future(Option, Option, HttpServiceFut), Service(ResourceDef, Option, HttpService), } impl Future for ScopeFactoryResponse { type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let mut done = true; if let Some(ref mut fut) = self.default_fut { match Pin::new(fut).poll(cx)? { Poll::Ready(default) => self.default = Some(default), Poll::Pending => done = false, } } // poll http services for item in &mut self.fut { let res = match item { CreateScopeServiceItem::Future( ref mut path, ref mut guards, ref mut fut, ) => match Pin::new(fut).poll(cx)? { Poll::Ready(service) => { Some((path.take().unwrap(), guards.take(), service)) } Poll::Pending => { done = false; None } }, CreateScopeServiceItem::Service(_, _, _) => continue, }; if let Some((path, guards, service)) = res { *item = CreateScopeServiceItem::Service(path, guards, service); } } if done { let router = self .fut .drain(..) .fold(Router::build(), |mut router, item| { match item { CreateScopeServiceItem::Service(path, guards, service) => { router.rdef(path, service).2 = guards; } CreateScopeServiceItem::Future(_, _, _) => unreachable!(), } router }); Poll::Ready(Ok(ScopeService { data: self.data.clone(), router: router.finish(), default: self.default.take(), _ready: None, })) } else { Poll::Pending } } } pub struct ScopeService { data: Option>, router: Router>>, default: Option, _ready: Option<(ServiceRequest, ResourceInfo)>, } impl Service for ScopeService { type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type Future = LocalBoxFuture<'static, Result>; fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } fn call(&mut self, mut req: ServiceRequest) -> Self::Future { let res = self.router.recognize_mut_checked(&mut req, |req, guards| { if let Some(ref guards) = guards { for f in guards { if !f.check(req.head()) { return false; } } } true }); if let Some((srv, _info)) = res { if let Some(ref data) = self.data { req.add_data_container(data.clone()); } srv.call(req) } else if let Some(ref mut default) = self.default { if let Some(ref data) = self.data { req.add_data_container(data.clone()); } default.call(req) } else { let req = req.into_parts().0; Box::pin(async { Ok(ServiceResponse::new(req, Response::NotFound().finish())) }) } } } #[doc(hidden)] pub struct ScopeEndpoint { factory: Rc>>, } impl ScopeEndpoint { fn new(factory: Rc>>) -> Self { ScopeEndpoint { factory } } } impl ServiceFactory for ScopeEndpoint { type Config = (); type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type InitError = (); type Service = ScopeService; type Future = ScopeFactoryResponse; fn new_service(&self, _: ()) -> Self::Future { self.factory.borrow_mut().as_mut().unwrap().new_service(()) } } #[cfg(test)] mod tests { use actix_service::Service; use bytes::Bytes; use futures_util::future::ok; use crate::dev::{Body, ResponseBody}; use crate::http::{header, HeaderValue, Method, StatusCode}; use crate::middleware::DefaultHeaders; use crate::service::ServiceRequest; use crate::test::{call_service, init_service, read_body, TestRequest}; use crate::{guard, web, App, HttpRequest, HttpResponse}; #[actix_rt::test] async fn test_scope() { let mut srv = init_service(App::new().service( web::scope("/app").service(web::resource("/path1").to(HttpResponse::Ok)), )) .await; let req = TestRequest::with_uri("/app/path1").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_scope_root() { let mut srv = init_service( App::new().service( web::scope("/app") .service(web::resource("").to(HttpResponse::Ok)) .service(web::resource("/").to(HttpResponse::Created)), ), ) .await; let req = TestRequest::with_uri("/app").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/app/").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::CREATED); } #[actix_rt::test] async fn test_scope_root2() { let mut srv = init_service(App::new().service( web::scope("/app/").service(web::resource("").to(HttpResponse::Ok)), )) .await; let req = TestRequest::with_uri("/app").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); let req = TestRequest::with_uri("/app/").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_scope_root3() { let mut srv = init_service(App::new().service( web::scope("/app/").service(web::resource("/").to(HttpResponse::Ok)), )) .await; let req = TestRequest::with_uri("/app").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); let req = TestRequest::with_uri("/app/").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); } #[actix_rt::test] async fn test_scope_route() { let mut srv = init_service( App::new().service( web::scope("app") .route("/path1", web::get().to(HttpResponse::Ok)) .route("/path1", web::delete().to(HttpResponse::Ok)), ), ) .await; let req = TestRequest::with_uri("/app/path1").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/app/path1") .method(Method::DELETE) .to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/app/path1") .method(Method::POST) .to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); } #[actix_rt::test] async fn test_scope_route_without_leading_slash() { let mut srv = init_service( App::new().service( web::scope("app").service( web::resource("path1") .route(web::get().to(HttpResponse::Ok)) .route(web::delete().to(HttpResponse::Ok)), ), ), ) .await; let req = TestRequest::with_uri("/app/path1").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/app/path1") .method(Method::DELETE) .to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/app/path1") .method(Method::POST) .to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); } #[actix_rt::test] async fn test_scope_guard() { let mut srv = init_service( App::new().service( web::scope("/app") .guard(guard::Get()) .service(web::resource("/path1").to(HttpResponse::Ok)), ), ) .await; let req = TestRequest::with_uri("/app/path1") .method(Method::POST) .to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); let req = TestRequest::with_uri("/app/path1") .method(Method::GET) .to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_scope_variable_segment() { let mut srv = init_service(App::new().service(web::scope("/ab-{project}").service( web::resource("/path1").to(|r: HttpRequest| { HttpResponse::Ok() .body(format!("project: {}", &r.match_info()["project"])) }), ))) .await; let req = TestRequest::with_uri("/ab-project1/path1").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); match resp.response().body() { ResponseBody::Body(Body::Bytes(ref b)) => { let bytes = b.clone(); assert_eq!(bytes, Bytes::from_static(b"project: project1")); } _ => panic!(), } let req = TestRequest::with_uri("/aa-project1/path1").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); } #[actix_rt::test] async fn test_nested_scope() { let mut srv = init_service(App::new().service(web::scope("/app").service( web::scope("/t1").service(web::resource("/path1").to(HttpResponse::Created)), ))) .await; let req = TestRequest::with_uri("/app/t1/path1").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::CREATED); } #[actix_rt::test] async fn test_nested_scope_no_slash() { let mut srv = init_service(App::new().service(web::scope("/app").service( web::scope("t1").service(web::resource("/path1").to(HttpResponse::Created)), ))) .await; let req = TestRequest::with_uri("/app/t1/path1").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::CREATED); } #[actix_rt::test] async fn test_nested_scope_root() { let mut srv = init_service( App::new().service( web::scope("/app").service( web::scope("/t1") .service(web::resource("").to(HttpResponse::Ok)) .service(web::resource("/").to(HttpResponse::Created)), ), ), ) .await; let req = TestRequest::with_uri("/app/t1").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/app/t1/").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::CREATED); } #[actix_rt::test] async fn test_nested_scope_filter() { let mut srv = init_service( App::new().service( web::scope("/app").service( web::scope("/t1") .guard(guard::Get()) .service(web::resource("/path1").to(HttpResponse::Ok)), ), ), ) .await; let req = TestRequest::with_uri("/app/t1/path1") .method(Method::POST) .to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); let req = TestRequest::with_uri("/app/t1/path1") .method(Method::GET) .to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_nested_scope_with_variable_segment() { let mut srv = init_service(App::new().service(web::scope("/app").service( web::scope("/{project_id}").service(web::resource("/path1").to( |r: HttpRequest| { HttpResponse::Created() .body(format!("project: {}", &r.match_info()["project_id"])) }, )), ))) .await; let req = TestRequest::with_uri("/app/project_1/path1").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::CREATED); match resp.response().body() { ResponseBody::Body(Body::Bytes(ref b)) => { let bytes = b.clone(); assert_eq!(bytes, Bytes::from_static(b"project: project_1")); } _ => panic!(), } } #[actix_rt::test] async fn test_nested2_scope_with_variable_segment() { let mut srv = init_service(App::new().service(web::scope("/app").service( web::scope("/{project}").service(web::scope("/{id}").service( web::resource("/path1").to(|r: HttpRequest| { HttpResponse::Created().body(format!( "project: {} - {}", &r.match_info()["project"], &r.match_info()["id"], )) }), )), ))) .await; let req = TestRequest::with_uri("/app/test/1/path1").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::CREATED); match resp.response().body() { ResponseBody::Body(Body::Bytes(ref b)) => { let bytes = b.clone(); assert_eq!(bytes, Bytes::from_static(b"project: test - 1")); } _ => panic!(), } let req = TestRequest::with_uri("/app/test/1/path2").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); } #[actix_rt::test] async fn test_default_resource() { let mut srv = init_service( App::new().service( web::scope("/app") .service(web::resource("/path1").to(HttpResponse::Ok)) .default_service(|r: ServiceRequest| { ok(r.into_response(HttpResponse::BadRequest())) }), ), ) .await; let req = TestRequest::with_uri("/app/path2").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_uri("/path2").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); } #[actix_rt::test] async fn test_default_resource_propagation() { let mut srv = init_service( App::new() .service( web::scope("/app1") .default_service(web::resource("").to(HttpResponse::BadRequest)), ) .service(web::scope("/app2")) .default_service(|r: ServiceRequest| { ok(r.into_response(HttpResponse::MethodNotAllowed())) }), ) .await; let req = TestRequest::with_uri("/non-exist").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); let req = TestRequest::with_uri("/app1/non-exist").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_uri("/app2/non-exist").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); } #[actix_rt::test] async fn test_middleware() { let mut srv = init_service( App::new().service( web::scope("app") .wrap( DefaultHeaders::new().header( header::CONTENT_TYPE, HeaderValue::from_static("0001"), ), ) .service( web::resource("/test").route(web::get().to(HttpResponse::Ok)), ), ), ) .await; let req = TestRequest::with_uri("/app/test").to_request(); let resp = call_service(&mut srv, req).await; assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), HeaderValue::from_static("0001") ); } #[actix_rt::test] async fn test_middleware_fn() { let mut srv = init_service( App::new().service( web::scope("app") .wrap_fn(|req, srv| { let fut = srv.call(req); async move { let mut res = fut.await?; res.headers_mut().insert( header::CONTENT_TYPE, HeaderValue::from_static("0001"), ); Ok(res) } }) .route("/test", web::get().to(HttpResponse::Ok)), ), ) .await; let req = TestRequest::with_uri("/app/test").to_request(); let resp = call_service(&mut srv, req).await; assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), HeaderValue::from_static("0001") ); } #[actix_rt::test] async fn test_override_data() { let mut srv = init_service(App::new().data(1usize).service( web::scope("app").data(10usize).route( "/t", web::get().to(|data: web::Data| { assert_eq!(**data, 10); HttpResponse::Ok() }), ), )) .await; let req = TestRequest::with_uri("/app/t").to_request(); let resp = call_service(&mut srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_override_data_default_service() { let mut srv = init_service(App::new().data(1usize).service( web::scope("app").data(10usize).default_service(web::to( |data: web::Data| { assert_eq!(**data, 10); HttpResponse::Ok() }, )), )) .await; let req = TestRequest::with_uri("/app/t").to_request(); let resp = call_service(&mut srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_override_app_data() { let mut srv = init_service(App::new().app_data(web::Data::new(1usize)).service( web::scope("app").app_data(web::Data::new(10usize)).route( "/t", web::get().to(|data: web::Data| { assert_eq!(**data, 10); HttpResponse::Ok() }), ), )) .await; let req = TestRequest::with_uri("/app/t").to_request(); let resp = call_service(&mut srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_scope_config() { let mut srv = init_service(App::new().service(web::scope("/app").configure(|s| { s.route("/path1", web::get().to(HttpResponse::Ok)); }))) .await; let req = TestRequest::with_uri("/app/path1").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_scope_config_2() { let mut srv = init_service(App::new().service(web::scope("/app").configure(|s| { s.service(web::scope("/v1").configure(|s| { s.route("/", web::get().to(HttpResponse::Ok)); })); }))) .await; let req = TestRequest::with_uri("/app/v1/").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_url_for_external() { let mut srv = init_service(App::new().service(web::scope("/app").configure(|s| { s.service(web::scope("/v1").configure(|s| { s.external_resource( "youtube", "https://youtube.com/watch/{video_id}", ); s.route( "/", web::get().to(|req: HttpRequest| { HttpResponse::Ok().body( req.url_for("youtube", &["xxxxxx"]).unwrap().to_string(), ) }), ); })); }))) .await; let req = TestRequest::with_uri("/app/v1/").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); let body = read_body(resp).await; assert_eq!(body, &b"https://youtube.com/watch/xxxxxx"[..]); } #[actix_rt::test] async fn test_url_for_nested() { let mut srv = init_service(App::new().service(web::scope("/a").service( web::scope("/b").service(web::resource("/c/{stuff}").name("c").route( web::get().to(|req: HttpRequest| { HttpResponse::Ok() .body(format!("{}", req.url_for("c", &["12345"]).unwrap())) }), )), ))) .await; let req = TestRequest::with_uri("/a/b/c/test").to_request(); let resp = call_service(&mut srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let body = read_body(resp).await; assert_eq!( body, Bytes::from_static(b"http://localhost:8080/a/b/c/12345") ); } }