Skip to main content

actix_web/
scope.rs

1use std::{cell::RefCell, fmt, future::Future, mem, rc::Rc};
2
3use actix_http::{body::MessageBody, Extensions};
4use actix_router::{ResourceDef, Router};
5use actix_service::{
6    apply, apply_fn_factory, boxed, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt,
7    Transform,
8};
9use futures_core::future::LocalBoxFuture;
10use futures_util::future::join_all;
11
12use crate::{
13    config::ServiceConfig,
14    data::Data,
15    dev::AppService,
16    guard::Guard,
17    rmap::ResourceMap,
18    service::{
19        AppServiceFactory, BoxedHttpService, BoxedHttpServiceFactory, HttpServiceFactory,
20        ServiceFactoryWrapper, ServiceRequest, ServiceResponse,
21    },
22    Error, Resource, Route,
23};
24
25type Guards = Vec<Box<dyn Guard>>;
26
27/// A collection of [`Route`]s, [`Resource`]s, or other services that share a common path prefix.
28///
29/// The `Scope`'s path can contain [dynamic segments]. The dynamic segments can be extracted from
30/// requests using the [`Path`](crate::web::Path) extractor or
31/// with [`HttpRequest::match_info()`](crate::HttpRequest::match_info).
32///
33/// # Avoid Trailing Slashes
34/// Avoid using trailing slashes in the scope prefix (e.g., `web::scope("/scope/")`). It will almost
35/// certainly not have the expected behavior. See the [documentation on resource definitions][pat]
36/// to understand why this is the case and how to correctly construct scope/prefix definitions.
37///
38/// # Examples
39/// ```
40/// use actix_web::{web, App, HttpResponse};
41///
42/// let app = App::new().service(
43///     web::scope("/{project_id}")
44///         .service(web::resource("/path1").to(|| async { "OK" }))
45///         .service(web::resource("/path2").route(web::get().to(|| HttpResponse::Ok())))
46///         .service(web::resource("/path3").route(web::head().to(HttpResponse::MethodNotAllowed)))
47/// );
48/// ```
49///
50/// In the above example three routes get registered:
51/// - /{project_id}/path1 - responds to all HTTP methods
52/// - /{project_id}/path2 - responds to `GET` requests
53/// - /{project_id}/path3 - responds to `HEAD` requests
54///
55/// [pat]: crate::dev::ResourceDef#prefix-resources
56/// [dynamic segments]: crate::dev::ResourceDef#dynamic-segments
57pub struct Scope<T = ScopeEndpoint> {
58    endpoint: T,
59    rdef: String,
60    app_data: Option<Extensions>,
61    services: Vec<Box<dyn AppServiceFactory>>,
62    guards: Vec<Box<dyn Guard>>,
63    default: Option<Rc<BoxedHttpServiceFactory>>,
64    external: Vec<ResourceDef>,
65    factory_ref: Rc<RefCell<Option<ScopeFactory>>>,
66}
67
68impl Scope {
69    /// Create a new scope
70    pub fn new(path: &str) -> Scope {
71        let factory_ref = Rc::new(RefCell::new(None));
72
73        Scope {
74            endpoint: ScopeEndpoint::new(Rc::clone(&factory_ref)),
75            rdef: path.to_string(),
76            app_data: None,
77            guards: Vec::new(),
78            services: Vec::new(),
79            default: None,
80            external: Vec::new(),
81            factory_ref,
82        }
83    }
84}
85
86impl<T> Scope<T>
87where
88    T: ServiceFactory<ServiceRequest, Config = (), Error = Error, InitError = ()>,
89{
90    /// Add match guard to a scope.
91    ///
92    /// ```
93    /// use actix_web::{web, guard, App, HttpRequest, HttpResponse};
94    ///
95    /// async fn index(data: web::Path<(String, String)>) -> &'static str {
96    ///     "Welcome!"
97    /// }
98    ///
99    /// let app = App::new().service(
100    ///     web::scope("/app")
101    ///         .guard(guard::Header("content-type", "text/plain"))
102    ///         .route("/test1", web::get().to(index))
103    ///         .route("/test2", web::post().to(|r: HttpRequest| {
104    ///             HttpResponse::MethodNotAllowed()
105    ///         }))
106    /// );
107    /// ```
108    pub fn guard<G: Guard + 'static>(mut self, guard: G) -> Self {
109        self.guards.push(Box::new(guard));
110        self
111    }
112
113    /// Add scope data.
114    ///
115    /// Data of different types from parent contexts will still be accessible. Any `Data<T>` types
116    /// set here can be extracted in handlers using the `Data<T>` extractor.
117    ///
118    /// # Examples
119    /// ```
120    /// use std::cell::Cell;
121    /// use actix_web::{web, App, HttpRequest, HttpResponse, Responder};
122    ///
123    /// struct MyData {
124    ///     count: std::cell::Cell<usize>,
125    /// }
126    ///
127    /// async fn handler(req: HttpRequest, counter: web::Data<MyData>) -> impl Responder {
128    ///     // note this cannot use the Data<T> extractor because it was not added with it
129    ///     let incr = *req.app_data::<usize>().unwrap();
130    ///     assert_eq!(incr, 3);
131    ///
132    ///     // update counter using other value from app data
133    ///     counter.count.set(counter.count.get() + incr);
134    ///
135    ///     HttpResponse::Ok().body(counter.count.get().to_string())
136    /// }
137    ///
138    /// let app = App::new().service(
139    ///     web::scope("/app")
140    ///         .app_data(3usize)
141    ///         .app_data(web::Data::new(MyData { count: Default::default() }))
142    ///         .route("/", web::get().to(handler))
143    /// );
144    /// ```
145    #[doc(alias = "manage")]
146    pub fn app_data<U: 'static>(mut self, data: U) -> Self {
147        self.app_data
148            .get_or_insert_with(Extensions::new)
149            .insert(data);
150
151        self
152    }
153
154    /// Add scope data after wrapping in `Data<T>`.
155    ///
156    /// Deprecated in favor of [`app_data`](Self::app_data).
157    #[deprecated(since = "4.0.0", note = "Use `.app_data(Data::new(val))` instead.")]
158    pub fn data<U: 'static>(self, data: U) -> Self {
159        self.app_data(Data::new(data))
160    }
161
162    /// Run external configuration as part of the scope building process.
163    ///
164    /// This function is useful for moving parts of configuration to a different module or library.
165    /// For example, some of the resource's configuration could be moved to different module.
166    ///
167    /// ```
168    /// use actix_web::{web, middleware, App, HttpResponse};
169    ///
170    /// // this function could be located in different module
171    /// fn config(cfg: &mut web::ServiceConfig) {
172    ///     cfg.service(web::resource("/test")
173    ///         .route(web::get().to(|| HttpResponse::Ok()))
174    ///         .route(web::head().to(|| HttpResponse::MethodNotAllowed()))
175    ///     );
176    /// }
177    ///
178    /// let app = App::new()
179    ///     .wrap(middleware::Logger::default())
180    ///     .service(
181    ///         web::scope("/api")
182    ///             .configure(config)
183    ///     )
184    ///     .route("/index.html", web::get().to(|| HttpResponse::Ok()));
185    /// ```
186    pub fn configure<F>(mut self, cfg_fn: F) -> Self
187    where
188        F: FnOnce(&mut ServiceConfig),
189    {
190        let mut cfg = ServiceConfig::new();
191        cfg_fn(&mut cfg);
192
193        self.services.extend(cfg.services);
194        self.external.extend(cfg.external);
195
196        // TODO: add Extensions::is_empty check and conditionally insert data
197        self.app_data
198            .get_or_insert_with(Extensions::new)
199            .extend(cfg.app_data);
200
201        if let Some(default) = cfg.default {
202            self.default = Some(default);
203        }
204
205        self
206    }
207
208    /// Register HTTP service.
209    ///
210    /// This is similar to `App's` service registration.
211    ///
212    /// Actix Web provides several services implementations:
213    ///
214    /// * *Resource* is an entry in resource table which corresponds to requested URL.
215    /// * *Scope* is a set of resources with common root path.
216    ///
217    /// ```
218    /// use actix_web::{web, App, HttpRequest};
219    ///
220    /// struct AppState;
221    ///
222    /// async fn index(req: HttpRequest) -> &'static str {
223    ///     "Welcome!"
224    /// }
225    ///
226    /// let app = App::new().service(
227    ///     web::scope("/app").service(
228    ///         web::scope("/v1")
229    ///             .service(web::resource("/test1").to(index)))
230    /// );
231    /// ```
232    pub fn service<F>(mut self, factory: F) -> Self
233    where
234        F: HttpServiceFactory + 'static,
235    {
236        self.services
237            .push(Box::new(ServiceFactoryWrapper::new(factory)));
238        self
239    }
240
241    /// Configure route for a specific path.
242    ///
243    /// This is a simplified version of the `Scope::service()` method.
244    /// This method can be called multiple times, in that case
245    /// multiple resources with one route would be registered for same resource path.
246    ///
247    /// ```
248    /// use actix_web::{web, App, HttpResponse};
249    ///
250    /// async fn index(data: web::Path<(String, String)>) -> &'static str {
251    ///     "Welcome!"
252    /// }
253    ///
254    /// let app = App::new().service(
255    ///     web::scope("/app")
256    ///         .route("/test1", web::get().to(index))
257    ///         .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed()))
258    /// );
259    /// ```
260    pub fn route(self, path: &str, mut route: Route) -> Self {
261        self.service(
262            Resource::new(path)
263                .add_guards(route.take_guards())
264                .route(route),
265        )
266    }
267
268    /// Default service to be used if no matching resource could be found.
269    ///
270    /// If a default service is not registered, it will fall back to the default service of
271    /// the parent [`App`](crate::App) (see [`App::default_service`](crate::App::default_service)).
272    pub fn default_service<F, U>(mut self, f: F) -> Self
273    where
274        F: IntoServiceFactory<U, ServiceRequest>,
275        U: ServiceFactory<ServiceRequest, Config = (), Response = ServiceResponse, Error = Error>
276            + 'static,
277        U::InitError: fmt::Debug,
278    {
279        // create and configure default resource
280        self.default = Some(Rc::new(boxed::factory(f.into_factory().map_init_err(
281            |err| {
282                log::error!("Can not construct default service: {err:?}");
283            },
284        ))));
285
286        self
287    }
288
289    /// Registers a scope-wide middleware.
290    ///
291    /// `mw` is a middleware component (type), that can modify the request and response across all
292    /// sub-resources managed by this `Scope`.
293    ///
294    /// See [`App::wrap`](crate::App::wrap) for more details.
295    #[doc(alias = "middleware")]
296    #[doc(alias = "use")] // nodejs terminology
297    pub fn wrap<M, B>(
298        self,
299        mw: M,
300    ) -> Scope<
301        impl ServiceFactory<
302            ServiceRequest,
303            Config = (),
304            Response = ServiceResponse<B>,
305            Error = Error,
306            InitError = (),
307        >,
308    >
309    where
310        M: Transform<
311                T::Service,
312                ServiceRequest,
313                Response = ServiceResponse<B>,
314                Error = Error,
315                InitError = (),
316            > + 'static,
317        B: MessageBody,
318    {
319        Scope {
320            endpoint: apply(mw, self.endpoint),
321            rdef: self.rdef,
322            app_data: self.app_data,
323            guards: self.guards,
324            services: self.services,
325            default: self.default,
326            external: self.external,
327            factory_ref: self.factory_ref,
328        }
329    }
330
331    /// Registers a scope-wide function middleware.
332    ///
333    /// `mw` is a closure that runs during inbound and/or outbound processing in the request
334    /// life-cycle (request -> response), modifying request/response as necessary, across all
335    /// requests handled by the `Scope`.
336    ///
337    /// See [`App::wrap_fn`](crate::App::wrap_fn) for examples and more details.
338    #[doc(alias = "middleware")]
339    #[doc(alias = "use")] // nodejs terminology
340    pub fn wrap_fn<F, R, B>(
341        self,
342        mw: F,
343    ) -> Scope<
344        impl ServiceFactory<
345            ServiceRequest,
346            Config = (),
347            Response = ServiceResponse<B>,
348            Error = Error,
349            InitError = (),
350        >,
351    >
352    where
353        F: Fn(ServiceRequest, &T::Service) -> R + Clone + 'static,
354        R: Future<Output = Result<ServiceResponse<B>, Error>>,
355        B: MessageBody,
356    {
357        Scope {
358            endpoint: apply_fn_factory(self.endpoint, mw),
359            rdef: self.rdef,
360            app_data: self.app_data,
361            guards: self.guards,
362            services: self.services,
363            default: self.default,
364            external: self.external,
365            factory_ref: self.factory_ref,
366        }
367    }
368}
369
370impl<T, B> HttpServiceFactory for Scope<T>
371where
372    T: ServiceFactory<
373            ServiceRequest,
374            Config = (),
375            Response = ServiceResponse<B>,
376            Error = Error,
377            InitError = (),
378        > + 'static,
379    B: MessageBody + 'static,
380{
381    fn register(mut self, config: &mut AppService) {
382        // update default resource if needed
383        let default = self.default.unwrap_or_else(|| config.default_service());
384
385        // register nested services
386        let mut cfg = config.clone_config();
387
388        // Update the prefix for the nested scope
389        #[cfg(feature = "experimental-introspection")]
390        {
391            let scope_id = config.prepare_scope_id();
392            cfg.scope_id_stack.push(scope_id);
393            cfg.update_prefix(&self.rdef);
394        }
395
396        self.services
397            .into_iter()
398            .for_each(|mut srv| srv.register(&mut cfg));
399
400        let mut rmap = ResourceMap::new(ResourceDef::root_prefix(&self.rdef));
401
402        #[cfg(feature = "experimental-introspection")]
403        let origin_scope = cfg.current_prefix.clone();
404
405        // external resources
406        for mut rdef in mem::take(&mut self.external) {
407            #[cfg(feature = "experimental-introspection")]
408            {
409                cfg.introspector
410                    .borrow_mut()
411                    .register_external(&rdef, &origin_scope);
412            }
413            rmap.add(&mut rdef, None);
414        }
415
416        // complete scope pipeline creation
417        *self.factory_ref.borrow_mut() = Some(ScopeFactory {
418            default,
419            services: cfg
420                .into_services()
421                .1
422                .into_iter()
423                .map(|(mut rdef, srv, guards, nested)| {
424                    rmap.add(&mut rdef, nested);
425                    (rdef, srv, RefCell::new(guards))
426                })
427                .collect::<Vec<_>>()
428                .into_boxed_slice()
429                .into(),
430        });
431
432        // get guards
433        let guards = if self.guards.is_empty() {
434            None
435        } else {
436            Some(self.guards)
437        };
438
439        let scope_data = self.app_data.map(Rc::new);
440
441        // wraps endpoint service (including middleware) call and injects app data for this scope
442        let endpoint = apply_fn_factory(self.endpoint, move |mut req: ServiceRequest, srv| {
443            if let Some(ref data) = scope_data {
444                req.add_data_container(Rc::clone(data));
445            }
446
447            let fut = srv.call(req);
448
449            async { Ok(fut.await?.map_into_boxed_body()) }
450        });
451
452        // register final service
453        config.register_service(
454            ResourceDef::root_prefix(&self.rdef),
455            guards,
456            endpoint,
457            Some(Rc::new(rmap)),
458        )
459    }
460}
461
462pub struct ScopeFactory {
463    #[allow(clippy::type_complexity)]
464    services: Rc<
465        [(
466            ResourceDef,
467            BoxedHttpServiceFactory,
468            RefCell<Option<Guards>>,
469        )],
470    >,
471    default: Rc<BoxedHttpServiceFactory>,
472}
473
474impl ServiceFactory<ServiceRequest> for ScopeFactory {
475    type Response = ServiceResponse;
476    type Error = Error;
477    type Config = ();
478    type Service = ScopeService;
479    type InitError = ();
480    type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
481
482    fn new_service(&self, _: ()) -> Self::Future {
483        // construct default service factory future
484        let default_fut = self.default.new_service(());
485
486        // construct all services factory future with it's resource def and guards.
487        let factory_fut = join_all(self.services.iter().map(|(path, factory, guards)| {
488            let path = path.clone();
489            let guards = guards.borrow_mut().take().unwrap_or_default();
490            let factory_fut = factory.new_service(());
491            async move {
492                factory_fut
493                    .await
494                    .map(move |service| (path, guards, service))
495            }
496        }));
497
498        Box::pin(async move {
499            let default = default_fut.await?;
500
501            // build router from the factory future result.
502            let router = factory_fut
503                .await
504                .into_iter()
505                .collect::<Result<Vec<_>, _>>()?
506                .drain(..)
507                .fold(Router::build(), |mut router, (path, guards, service)| {
508                    router.push(path, service, guards);
509                    router
510                })
511                .finish();
512
513            Ok(ScopeService { router, default })
514        })
515    }
516}
517
518pub struct ScopeService {
519    router: Router<BoxedHttpService, Vec<Box<dyn Guard>>>,
520    default: BoxedHttpService,
521}
522
523impl Service<ServiceRequest> for ScopeService {
524    type Response = ServiceResponse;
525    type Error = Error;
526    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
527
528    actix_service::always_ready!();
529
530    fn call(&self, mut req: ServiceRequest) -> Self::Future {
531        let res = self.router.recognize_fn(&mut req, |req, guards| {
532            let guard_ctx = req.guard_ctx();
533            guards.iter().all(|guard| guard.check(&guard_ctx))
534        });
535
536        if let Some((srv, info)) = res {
537            req.push_resource_id(info.0);
538
539            let matched = req
540                .resource_map()
541                .is_resource_path_match(req.resource_id_path());
542
543            req.mark_resource_path(matched);
544
545            srv.call(req)
546        } else {
547            self.default.call(req)
548        }
549    }
550}
551
552#[doc(hidden)]
553pub struct ScopeEndpoint {
554    factory: Rc<RefCell<Option<ScopeFactory>>>,
555}
556
557impl ScopeEndpoint {
558    fn new(factory: Rc<RefCell<Option<ScopeFactory>>>) -> Self {
559        ScopeEndpoint { factory }
560    }
561}
562
563impl ServiceFactory<ServiceRequest> for ScopeEndpoint {
564    type Response = ServiceResponse;
565    type Error = Error;
566    type Config = ();
567    type Service = ScopeService;
568    type InitError = ();
569    type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
570
571    fn new_service(&self, _: ()) -> Self::Future {
572        self.factory.borrow_mut().as_mut().unwrap().new_service(())
573    }
574}
575
576#[cfg(test)]
577mod tests {
578    use actix_utils::future::ok;
579    use bytes::Bytes;
580
581    use super::*;
582    use crate::{
583        guard,
584        http::{
585            header::{self, HeaderValue},
586            Method, StatusCode,
587        },
588        middleware::DefaultHeaders,
589        test::{assert_body_eq, call_service, init_service, read_body, TestRequest},
590        web, App, HttpMessage, HttpRequest, HttpResponse,
591    };
592
593    #[test]
594    fn can_be_returned_from_fn() {
595        fn my_scope_1() -> Scope {
596            web::scope("/test")
597                .service(web::resource("").route(web::get().to(|| async { "hello" })))
598        }
599
600        fn my_scope_2() -> Scope<
601            impl ServiceFactory<
602                ServiceRequest,
603                Config = (),
604                Response = ServiceResponse<impl MessageBody>,
605                Error = Error,
606                InitError = (),
607            >,
608        > {
609            web::scope("/test-compat")
610                .wrap_fn(|req, srv| {
611                    let fut = srv.call(req);
612                    async { Ok(fut.await?.map_into_right_body::<()>()) }
613                })
614                .service(web::resource("").route(web::get().to(|| async { "hello" })))
615        }
616
617        fn my_scope_3() -> impl HttpServiceFactory {
618            my_scope_2()
619        }
620
621        App::new()
622            .service(my_scope_1())
623            .service(my_scope_2())
624            .service(my_scope_3());
625    }
626
627    #[actix_rt::test]
628    async fn test_scope() {
629        let srv = init_service(
630            App::new()
631                .service(web::scope("/app").service(web::resource("/path1").to(HttpResponse::Ok))),
632        )
633        .await;
634
635        let req = TestRequest::with_uri("/app/path1").to_request();
636        let resp = srv.call(req).await.unwrap();
637        assert_eq!(resp.status(), StatusCode::OK);
638    }
639
640    #[actix_rt::test]
641    async fn test_scope_root() {
642        let srv = init_service(
643            App::new().service(
644                web::scope("/app")
645                    .service(web::resource("").to(HttpResponse::Ok))
646                    .service(web::resource("/").to(HttpResponse::Created)),
647            ),
648        )
649        .await;
650
651        let req = TestRequest::with_uri("/app").to_request();
652        let resp = srv.call(req).await.unwrap();
653        assert_eq!(resp.status(), StatusCode::OK);
654
655        let req = TestRequest::with_uri("/app/").to_request();
656        let resp = srv.call(req).await.unwrap();
657        assert_eq!(resp.status(), StatusCode::CREATED);
658    }
659
660    #[actix_rt::test]
661    async fn test_scope_root2() {
662        let srv = init_service(
663            App::new().service(web::scope("/app/").service(web::resource("").to(HttpResponse::Ok))),
664        )
665        .await;
666
667        let req = TestRequest::with_uri("/app").to_request();
668        let resp = srv.call(req).await.unwrap();
669        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
670
671        let req = TestRequest::with_uri("/app/").to_request();
672        let resp = srv.call(req).await.unwrap();
673        assert_eq!(resp.status(), StatusCode::OK);
674    }
675
676    #[actix_rt::test]
677    async fn test_scope_root3() {
678        let srv = init_service(
679            App::new()
680                .service(web::scope("/app/").service(web::resource("/").to(HttpResponse::Ok))),
681        )
682        .await;
683
684        let req = TestRequest::with_uri("/app").to_request();
685        let resp = srv.call(req).await.unwrap();
686        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
687
688        let req = TestRequest::with_uri("/app/").to_request();
689        let resp = srv.call(req).await.unwrap();
690        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
691    }
692
693    #[actix_rt::test]
694    async fn test_scope_route() {
695        let srv = init_service(
696            App::new().service(
697                web::scope("app")
698                    .route("/path1", web::get().to(HttpResponse::Ok))
699                    .route("/path1", web::delete().to(HttpResponse::Ok)),
700            ),
701        )
702        .await;
703
704        let req = TestRequest::with_uri("/app/path1").to_request();
705        let resp = srv.call(req).await.unwrap();
706        assert_eq!(resp.status(), StatusCode::OK);
707
708        let req = TestRequest::with_uri("/app/path1")
709            .method(Method::DELETE)
710            .to_request();
711        let resp = srv.call(req).await.unwrap();
712        assert_eq!(resp.status(), StatusCode::OK);
713
714        let req = TestRequest::with_uri("/app/path1")
715            .method(Method::POST)
716            .to_request();
717        let resp = srv.call(req).await.unwrap();
718        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
719    }
720
721    #[actix_rt::test]
722    async fn test_scope_route_without_leading_slash() {
723        let srv = init_service(
724            App::new().service(
725                web::scope("app").service(
726                    web::resource("path1")
727                        .route(web::get().to(HttpResponse::Ok))
728                        .route(web::delete().to(HttpResponse::Ok)),
729                ),
730            ),
731        )
732        .await;
733
734        let req = TestRequest::with_uri("/app/path1").to_request();
735        let resp = srv.call(req).await.unwrap();
736        assert_eq!(resp.status(), StatusCode::OK);
737
738        let req = TestRequest::with_uri("/app/path1")
739            .method(Method::DELETE)
740            .to_request();
741        let resp = srv.call(req).await.unwrap();
742        assert_eq!(resp.status(), StatusCode::OK);
743
744        let req = TestRequest::with_uri("/app/path1")
745            .method(Method::POST)
746            .to_request();
747        let resp = srv.call(req).await.unwrap();
748        assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
749    }
750
751    #[actix_rt::test]
752    async fn test_scope_guard() {
753        let srv = init_service(
754            App::new().service(
755                web::scope("/app")
756                    .guard(guard::Get())
757                    .service(web::resource("/path1").to(HttpResponse::Ok)),
758            ),
759        )
760        .await;
761
762        let req = TestRequest::with_uri("/app/path1")
763            .method(Method::POST)
764            .to_request();
765        let resp = srv.call(req).await.unwrap();
766        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
767
768        let req = TestRequest::with_uri("/app/path1")
769            .method(Method::GET)
770            .to_request();
771        let resp = srv.call(req).await.unwrap();
772        assert_eq!(resp.status(), StatusCode::OK);
773    }
774
775    #[actix_rt::test]
776    async fn test_scope_variable_segment() {
777        let srv = init_service(App::new().service(web::scope("/ab-{project}").service(
778            web::resource("/path1").to(|r: HttpRequest| {
779                HttpResponse::Ok().body(format!("project: {}", &r.match_info()["project"]))
780            }),
781        )))
782        .await;
783
784        let req = TestRequest::with_uri("/ab-project1/path1").to_request();
785        let res = srv.call(req).await.unwrap();
786        assert_eq!(res.status(), StatusCode::OK);
787        assert_body_eq!(res, b"project: project1");
788
789        let req = TestRequest::with_uri("/aa-project1/path1").to_request();
790        let res = srv.call(req).await.unwrap();
791        assert_eq!(res.status(), StatusCode::NOT_FOUND);
792    }
793
794    #[actix_rt::test]
795    async fn test_nested_scope() {
796        let srv = init_service(App::new().service(web::scope("/app").service(
797            web::scope("/t1").service(web::resource("/path1").to(HttpResponse::Created)),
798        )))
799        .await;
800
801        let req = TestRequest::with_uri("/app/t1/path1").to_request();
802        let resp = srv.call(req).await.unwrap();
803        assert_eq!(resp.status(), StatusCode::CREATED);
804    }
805
806    #[actix_rt::test]
807    async fn test_nested_scope_no_slash() {
808        let srv =
809            init_service(App::new().service(web::scope("/app").service(
810                web::scope("t1").service(web::resource("/path1").to(HttpResponse::Created)),
811            )))
812            .await;
813
814        let req = TestRequest::with_uri("/app/t1/path1").to_request();
815        let resp = srv.call(req).await.unwrap();
816        assert_eq!(resp.status(), StatusCode::CREATED);
817    }
818
819    #[actix_rt::test]
820    async fn test_nested_scope_root() {
821        let srv = init_service(
822            App::new().service(
823                web::scope("/app").service(
824                    web::scope("/t1")
825                        .service(web::resource("").to(HttpResponse::Ok))
826                        .service(web::resource("/").to(HttpResponse::Created)),
827                ),
828            ),
829        )
830        .await;
831
832        let req = TestRequest::with_uri("/app/t1").to_request();
833        let resp = srv.call(req).await.unwrap();
834        assert_eq!(resp.status(), StatusCode::OK);
835
836        let req = TestRequest::with_uri("/app/t1/").to_request();
837        let resp = srv.call(req).await.unwrap();
838        assert_eq!(resp.status(), StatusCode::CREATED);
839    }
840
841    #[actix_rt::test]
842    async fn test_nested_scope_filter() {
843        let srv = init_service(
844            App::new().service(
845                web::scope("/app").service(
846                    web::scope("/t1")
847                        .guard(guard::Get())
848                        .service(web::resource("/path1").to(HttpResponse::Ok)),
849                ),
850            ),
851        )
852        .await;
853
854        let req = TestRequest::with_uri("/app/t1/path1")
855            .method(Method::POST)
856            .to_request();
857        let resp = srv.call(req).await.unwrap();
858        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
859
860        let req = TestRequest::with_uri("/app/t1/path1")
861            .method(Method::GET)
862            .to_request();
863        let resp = srv.call(req).await.unwrap();
864        assert_eq!(resp.status(), StatusCode::OK);
865    }
866
867    #[actix_rt::test]
868    async fn test_nested_scope_with_variable_segment() {
869        let srv = init_service(App::new().service(web::scope("/app").service(
870            web::scope("/{project_id}").service(web::resource("/path1").to(|r: HttpRequest| {
871                HttpResponse::Created().body(format!("project: {}", &r.match_info()["project_id"]))
872            })),
873        )))
874        .await;
875
876        let req = TestRequest::with_uri("/app/project_1/path1").to_request();
877        let res = srv.call(req).await.unwrap();
878        assert_eq!(res.status(), StatusCode::CREATED);
879        assert_body_eq!(res, b"project: project_1");
880    }
881
882    #[actix_rt::test]
883    async fn test_nested2_scope_with_variable_segment() {
884        let srv = init_service(App::new().service(web::scope("/app").service(
885            web::scope("/{project}").service(web::scope("/{id}").service(
886                web::resource("/path1").to(|r: HttpRequest| {
887                    HttpResponse::Created().body(format!(
888                        "project: {} - {}",
889                        &r.match_info()["project"],
890                        &r.match_info()["id"],
891                    ))
892                }),
893            )),
894        )))
895        .await;
896
897        let req = TestRequest::with_uri("/app/test/1/path1").to_request();
898        let res = srv.call(req).await.unwrap();
899        assert_eq!(res.status(), StatusCode::CREATED);
900        assert_body_eq!(res, b"project: test - 1");
901
902        let req = TestRequest::with_uri("/app/test/1/path2").to_request();
903        let res = srv.call(req).await.unwrap();
904        assert_eq!(res.status(), StatusCode::NOT_FOUND);
905    }
906
907    #[actix_rt::test]
908    async fn test_default_resource() {
909        let srv = init_service(
910            App::new().service(
911                web::scope("/app")
912                    .service(web::resource("/path1").to(HttpResponse::Ok))
913                    .default_service(|r: ServiceRequest| {
914                        ok(r.into_response(HttpResponse::BadRequest()))
915                    }),
916            ),
917        )
918        .await;
919
920        let req = TestRequest::with_uri("/app/path2").to_request();
921        let resp = srv.call(req).await.unwrap();
922        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
923
924        let req = TestRequest::with_uri("/path2").to_request();
925        let resp = srv.call(req).await.unwrap();
926        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
927    }
928
929    #[actix_rt::test]
930    async fn test_default_resource_propagation() {
931        let srv = init_service(
932            App::new()
933                .service(web::scope("/app1").default_service(web::to(HttpResponse::BadRequest)))
934                .service(web::scope("/app2"))
935                .default_service(|r: ServiceRequest| {
936                    ok(r.into_response(HttpResponse::MethodNotAllowed()))
937                }),
938        )
939        .await;
940
941        let req = TestRequest::with_uri("/non-exist").to_request();
942        let resp = srv.call(req).await.unwrap();
943        assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
944
945        let req = TestRequest::with_uri("/app1/non-exist").to_request();
946        let resp = srv.call(req).await.unwrap();
947        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
948
949        let req = TestRequest::with_uri("/app2/non-exist").to_request();
950        let resp = srv.call(req).await.unwrap();
951        assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
952    }
953
954    #[actix_rt::test]
955    async fn test_middleware() {
956        let srv = init_service(
957            App::new().service(
958                web::scope("app")
959                    .wrap(
960                        DefaultHeaders::new()
961                            .add((header::CONTENT_TYPE, HeaderValue::from_static("0001"))),
962                    )
963                    .service(web::resource("/test").route(web::get().to(HttpResponse::Ok))),
964            ),
965        )
966        .await;
967
968        let req = TestRequest::with_uri("/app/test").to_request();
969        let resp = call_service(&srv, req).await;
970        assert_eq!(resp.status(), StatusCode::OK);
971        assert_eq!(
972            resp.headers().get(header::CONTENT_TYPE).unwrap(),
973            HeaderValue::from_static("0001")
974        );
975    }
976
977    #[actix_rt::test]
978    async fn test_middleware_body_type() {
979        // Compile test that Scope accepts any body type; test for `EitherBody`
980        let srv = init_service(
981            App::new().service(
982                web::scope("app")
983                    .wrap_fn(|req, srv| {
984                        let fut = srv.call(req);
985                        async { Ok(fut.await?.map_into_right_body::<()>()) }
986                    })
987                    .service(web::resource("/test").route(web::get().to(|| async { "hello" }))),
988            ),
989        )
990        .await;
991
992        // test if `MessageBody::try_into_bytes()` is preserved across scope layer
993        use actix_http::body::MessageBody as _;
994        let req = TestRequest::with_uri("/app/test").to_request();
995        let resp = call_service(&srv, req).await;
996        let body = resp.into_body();
997        assert_eq!(body.try_into_bytes().unwrap(), b"hello".as_ref());
998    }
999
1000    #[actix_rt::test]
1001    async fn test_middleware_fn() {
1002        let srv = init_service(
1003            App::new().service(
1004                web::scope("app")
1005                    .wrap_fn(|req, srv| {
1006                        let fut = srv.call(req);
1007                        async move {
1008                            let mut res = fut.await?;
1009                            res.headers_mut()
1010                                .insert(header::CONTENT_TYPE, HeaderValue::from_static("0001"));
1011                            Ok(res)
1012                        }
1013                    })
1014                    .route("/test", web::get().to(HttpResponse::Ok)),
1015            ),
1016        )
1017        .await;
1018
1019        let req = TestRequest::with_uri("/app/test").to_request();
1020        let resp = call_service(&srv, req).await;
1021        assert_eq!(resp.status(), StatusCode::OK);
1022        assert_eq!(
1023            resp.headers().get(header::CONTENT_TYPE).unwrap(),
1024            HeaderValue::from_static("0001")
1025        );
1026    }
1027
1028    #[actix_rt::test]
1029    async fn test_middleware_app_data() {
1030        let srv = init_service(
1031            App::new().service(
1032                web::scope("app")
1033                    .app_data(1usize)
1034                    .wrap_fn(|req, srv| {
1035                        assert_eq!(req.app_data::<usize>(), Some(&1usize));
1036                        req.extensions_mut().insert(1usize);
1037                        srv.call(req)
1038                    })
1039                    .route("/test", web::get().to(HttpResponse::Ok))
1040                    .default_service(|req: ServiceRequest| async move {
1041                        let (req, _) = req.into_parts();
1042
1043                        assert_eq!(req.extensions().get::<usize>(), Some(&1));
1044
1045                        Ok(ServiceResponse::new(
1046                            req,
1047                            HttpResponse::BadRequest().finish(),
1048                        ))
1049                    }),
1050            ),
1051        )
1052        .await;
1053
1054        let req = TestRequest::with_uri("/app/test").to_request();
1055        let resp = call_service(&srv, req).await;
1056        assert_eq!(resp.status(), StatusCode::OK);
1057
1058        let req = TestRequest::with_uri("/app/default").to_request();
1059        let resp = call_service(&srv, req).await;
1060        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
1061    }
1062
1063    // allow deprecated {App, Scope}::data
1064    #[allow(deprecated)]
1065    #[actix_rt::test]
1066    async fn test_override_data() {
1067        let srv = init_service(App::new().data(1usize).service(
1068            web::scope("app").data(10usize).route(
1069                "/t",
1070                web::get().to(|data: web::Data<usize>| {
1071                    assert_eq!(**data, 10);
1072                    HttpResponse::Ok()
1073                }),
1074            ),
1075        ))
1076        .await;
1077
1078        let req = TestRequest::with_uri("/app/t").to_request();
1079        let resp = call_service(&srv, req).await;
1080        assert_eq!(resp.status(), StatusCode::OK);
1081    }
1082
1083    // allow deprecated `{App, Scope}::data`
1084    #[allow(deprecated)]
1085    #[actix_rt::test]
1086    async fn test_override_data_default_service() {
1087        let srv =
1088            init_service(App::new().data(1usize).service(
1089                web::scope("app").data(10usize).default_service(web::to(
1090                    |data: web::Data<usize>| {
1091                        assert_eq!(**data, 10);
1092                        HttpResponse::Ok()
1093                    },
1094                )),
1095            ))
1096            .await;
1097
1098        let req = TestRequest::with_uri("/app/t").to_request();
1099        let resp = call_service(&srv, req).await;
1100        assert_eq!(resp.status(), StatusCode::OK);
1101    }
1102
1103    #[actix_rt::test]
1104    async fn test_override_app_data() {
1105        let srv = init_service(App::new().app_data(web::Data::new(1usize)).service(
1106            web::scope("app").app_data(web::Data::new(10usize)).route(
1107                "/t",
1108                web::get().to(|data: web::Data<usize>| {
1109                    assert_eq!(**data, 10);
1110                    HttpResponse::Ok()
1111                }),
1112            ),
1113        ))
1114        .await;
1115
1116        let req = TestRequest::with_uri("/app/t").to_request();
1117        let resp = call_service(&srv, req).await;
1118        assert_eq!(resp.status(), StatusCode::OK);
1119    }
1120
1121    #[actix_rt::test]
1122    async fn test_scope_config() {
1123        let srv = init_service(App::new().service(web::scope("/app").configure(|s| {
1124            s.route("/path1", web::get().to(HttpResponse::Ok));
1125        })))
1126        .await;
1127
1128        let req = TestRequest::with_uri("/app/path1").to_request();
1129        let resp = srv.call(req).await.unwrap();
1130        assert_eq!(resp.status(), StatusCode::OK);
1131    }
1132
1133    #[actix_rt::test]
1134    async fn test_scope_config_2() {
1135        let srv = init_service(App::new().service(web::scope("/app").configure(|s| {
1136            s.service(web::scope("/v1").configure(|s| {
1137                s.route("/", web::get().to(HttpResponse::Ok));
1138            }));
1139        })))
1140        .await;
1141
1142        let req = TestRequest::with_uri("/app/v1/").to_request();
1143        let resp = srv.call(req).await.unwrap();
1144        assert_eq!(resp.status(), StatusCode::OK);
1145    }
1146
1147    #[actix_rt::test]
1148    async fn test_url_for_external() {
1149        let srv = init_service(App::new().service(web::scope("/app").configure(|s| {
1150            s.service(web::scope("/v1").configure(|s| {
1151                s.external_resource("youtube", "https://youtube.com/watch/{video_id}");
1152                s.route(
1153                    "/",
1154                    web::get().to(|req: HttpRequest| {
1155                        HttpResponse::Ok()
1156                            .body(req.url_for("youtube", ["xxxxxx"]).unwrap().to_string())
1157                    }),
1158                );
1159            }));
1160        })))
1161        .await;
1162
1163        let req = TestRequest::with_uri("/app/v1/").to_request();
1164        let resp = srv.call(req).await.unwrap();
1165        assert_eq!(resp.status(), StatusCode::OK);
1166        let body = read_body(resp).await;
1167        assert_eq!(body, &b"https://youtube.com/watch/xxxxxx"[..]);
1168    }
1169
1170    #[actix_rt::test]
1171    async fn test_url_for_nested() {
1172        let srv = init_service(App::new().service(web::scope("/a").service(
1173            web::scope("/b").service(web::resource("/c/{stuff}").name("c").route(web::get().to(
1174                |req: HttpRequest| {
1175                    HttpResponse::Ok().body(format!("{}", req.url_for("c", ["12345"]).unwrap()))
1176                },
1177            ))),
1178        )))
1179        .await;
1180
1181        let req = TestRequest::with_uri("/a/b/c/test").to_request();
1182        let resp = call_service(&srv, req).await;
1183        assert_eq!(resp.status(), StatusCode::OK);
1184        let body = read_body(resp).await;
1185        assert_eq!(
1186            body,
1187            Bytes::from_static(b"http://localhost:8080/a/b/c/12345")
1188        );
1189    }
1190
1191    #[actix_rt::test]
1192    async fn dynamic_scopes() {
1193        let srv = init_service(
1194            App::new().service(
1195                web::scope("/{a}/").service(
1196                    web::scope("/{b}/")
1197                        .route("", web::get().to(|_: HttpRequest| HttpResponse::Created()))
1198                        .route(
1199                            "/",
1200                            web::get().to(|_: HttpRequest| HttpResponse::Accepted()),
1201                        )
1202                        .route("/{c}", web::get().to(|_: HttpRequest| HttpResponse::Ok())),
1203                ),
1204            ),
1205        )
1206        .await;
1207
1208        // note the unintuitive behavior with trailing slashes on scopes with dynamic segments
1209        let req = TestRequest::with_uri("/a//b//c").to_request();
1210        let resp = call_service(&srv, req).await;
1211        assert_eq!(resp.status(), StatusCode::OK);
1212
1213        let req = TestRequest::with_uri("/a//b/").to_request();
1214        let resp = call_service(&srv, req).await;
1215        assert_eq!(resp.status(), StatusCode::CREATED);
1216
1217        let req = TestRequest::with_uri("/a//b//").to_request();
1218        let resp = call_service(&srv, req).await;
1219        assert_eq!(resp.status(), StatusCode::ACCEPTED);
1220
1221        let req = TestRequest::with_uri("/a//b//c/d").to_request();
1222        let resp = call_service(&srv, req).await;
1223        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
1224
1225        let srv = init_service(
1226            App::new().service(
1227                web::scope("/{a}").service(
1228                    web::scope("/{b}")
1229                        .route("", web::get().to(|_: HttpRequest| HttpResponse::Created()))
1230                        .route(
1231                            "/",
1232                            web::get().to(|_: HttpRequest| HttpResponse::Accepted()),
1233                        )
1234                        .route("/{c}", web::get().to(|_: HttpRequest| HttpResponse::Ok())),
1235                ),
1236            ),
1237        )
1238        .await;
1239
1240        let req = TestRequest::with_uri("/a/b/c").to_request();
1241        let resp = call_service(&srv, req).await;
1242        assert_eq!(resp.status(), StatusCode::OK);
1243
1244        let req = TestRequest::with_uri("/a/b").to_request();
1245        let resp = call_service(&srv, req).await;
1246        assert_eq!(resp.status(), StatusCode::CREATED);
1247
1248        let req = TestRequest::with_uri("/a/b/").to_request();
1249        let resp = call_service(&srv, req).await;
1250        assert_eq!(resp.status(), StatusCode::ACCEPTED);
1251
1252        let req = TestRequest::with_uri("/a/b/c/d").to_request();
1253        let resp = call_service(&srv, req).await;
1254        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
1255    }
1256}