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 std::future::ready;
579
580    use bytes::Bytes;
581
582    use super::*;
583    use crate::{
584        guard,
585        http::{
586            header::{self, HeaderValue},
587            Method, StatusCode,
588        },
589        middleware::DefaultHeaders,
590        test::{assert_body_eq, call_service, init_service, read_body, TestRequest},
591        web, App, HttpMessage, HttpRequest, HttpResponse,
592    };
593
594    #[test]
595    fn can_be_returned_from_fn() {
596        fn my_scope_1() -> Scope {
597            web::scope("/test")
598                .service(web::resource("").route(web::get().to(|| async { "hello" })))
599        }
600
601        fn my_scope_2() -> Scope<
602            impl ServiceFactory<
603                ServiceRequest,
604                Config = (),
605                Response = ServiceResponse<impl MessageBody>,
606                Error = Error,
607                InitError = (),
608            >,
609        > {
610            web::scope("/test-compat")
611                .wrap_fn(|req, srv| {
612                    let fut = srv.call(req);
613                    async { Ok(fut.await?.map_into_right_body::<()>()) }
614                })
615                .service(web::resource("").route(web::get().to(|| async { "hello" })))
616        }
617
618        fn my_scope_3() -> impl HttpServiceFactory {
619            my_scope_2()
620        }
621
622        App::new()
623            .service(my_scope_1())
624            .service(my_scope_2())
625            .service(my_scope_3());
626    }
627
628    #[actix_rt::test]
629    async fn test_scope() {
630        let srv = init_service(
631            App::new()
632                .service(web::scope("/app").service(web::resource("/path1").to(HttpResponse::Ok))),
633        )
634        .await;
635
636        let req = TestRequest::with_uri("/app/path1").to_request();
637        let resp = srv.call(req).await.unwrap();
638        assert_eq!(resp.status(), StatusCode::OK);
639    }
640
641    #[actix_rt::test]
642    async fn test_scope_root() {
643        let srv = init_service(
644            App::new().service(
645                web::scope("/app")
646                    .service(web::resource("").to(HttpResponse::Ok))
647                    .service(web::resource("/").to(HttpResponse::Created)),
648            ),
649        )
650        .await;
651
652        let req = TestRequest::with_uri("/app").to_request();
653        let resp = srv.call(req).await.unwrap();
654        assert_eq!(resp.status(), StatusCode::OK);
655
656        let req = TestRequest::with_uri("/app/").to_request();
657        let resp = srv.call(req).await.unwrap();
658        assert_eq!(resp.status(), StatusCode::CREATED);
659    }
660
661    #[actix_rt::test]
662    async fn test_scope_root2() {
663        let srv = init_service(
664            App::new().service(web::scope("/app/").service(web::resource("").to(HttpResponse::Ok))),
665        )
666        .await;
667
668        let req = TestRequest::with_uri("/app").to_request();
669        let resp = srv.call(req).await.unwrap();
670        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
671
672        let req = TestRequest::with_uri("/app/").to_request();
673        let resp = srv.call(req).await.unwrap();
674        assert_eq!(resp.status(), StatusCode::OK);
675    }
676
677    #[actix_rt::test]
678    async fn test_scope_root3() {
679        let srv = init_service(
680            App::new()
681                .service(web::scope("/app/").service(web::resource("/").to(HttpResponse::Ok))),
682        )
683        .await;
684
685        let req = TestRequest::with_uri("/app").to_request();
686        let resp = srv.call(req).await.unwrap();
687        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
688
689        let req = TestRequest::with_uri("/app/").to_request();
690        let resp = srv.call(req).await.unwrap();
691        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
692    }
693
694    #[actix_rt::test]
695    async fn test_scope_route() {
696        let srv = init_service(
697            App::new().service(
698                web::scope("app")
699                    .route("/path1", web::get().to(HttpResponse::Ok))
700                    .route("/path1", web::delete().to(HttpResponse::Ok)),
701            ),
702        )
703        .await;
704
705        let req = TestRequest::with_uri("/app/path1").to_request();
706        let resp = srv.call(req).await.unwrap();
707        assert_eq!(resp.status(), StatusCode::OK);
708
709        let req = TestRequest::with_uri("/app/path1")
710            .method(Method::DELETE)
711            .to_request();
712        let resp = srv.call(req).await.unwrap();
713        assert_eq!(resp.status(), StatusCode::OK);
714
715        let req = TestRequest::with_uri("/app/path1")
716            .method(Method::POST)
717            .to_request();
718        let resp = srv.call(req).await.unwrap();
719        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
720    }
721
722    #[actix_rt::test]
723    async fn test_scope_route_without_leading_slash() {
724        let srv = init_service(
725            App::new().service(
726                web::scope("app").service(
727                    web::resource("path1")
728                        .route(web::get().to(HttpResponse::Ok))
729                        .route(web::delete().to(HttpResponse::Ok)),
730                ),
731            ),
732        )
733        .await;
734
735        let req = TestRequest::with_uri("/app/path1").to_request();
736        let resp = srv.call(req).await.unwrap();
737        assert_eq!(resp.status(), StatusCode::OK);
738
739        let req = TestRequest::with_uri("/app/path1")
740            .method(Method::DELETE)
741            .to_request();
742        let resp = srv.call(req).await.unwrap();
743        assert_eq!(resp.status(), StatusCode::OK);
744
745        let req = TestRequest::with_uri("/app/path1")
746            .method(Method::POST)
747            .to_request();
748        let resp = srv.call(req).await.unwrap();
749        assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
750    }
751
752    #[actix_rt::test]
753    async fn test_scope_guard() {
754        let srv = init_service(
755            App::new().service(
756                web::scope("/app")
757                    .guard(guard::Get())
758                    .service(web::resource("/path1").to(HttpResponse::Ok)),
759            ),
760        )
761        .await;
762
763        let req = TestRequest::with_uri("/app/path1")
764            .method(Method::POST)
765            .to_request();
766        let resp = srv.call(req).await.unwrap();
767        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
768
769        let req = TestRequest::with_uri("/app/path1")
770            .method(Method::GET)
771            .to_request();
772        let resp = srv.call(req).await.unwrap();
773        assert_eq!(resp.status(), StatusCode::OK);
774    }
775
776    #[actix_rt::test]
777    async fn test_scope_variable_segment() {
778        let srv = init_service(App::new().service(web::scope("/ab-{project}").service(
779            web::resource("/path1").to(|r: HttpRequest| {
780                HttpResponse::Ok().body(format!("project: {}", &r.match_info()["project"]))
781            }),
782        )))
783        .await;
784
785        let req = TestRequest::with_uri("/ab-project1/path1").to_request();
786        let res = srv.call(req).await.unwrap();
787        assert_eq!(res.status(), StatusCode::OK);
788        assert_body_eq!(res, b"project: project1");
789
790        let req = TestRequest::with_uri("/aa-project1/path1").to_request();
791        let res = srv.call(req).await.unwrap();
792        assert_eq!(res.status(), StatusCode::NOT_FOUND);
793    }
794
795    #[actix_rt::test]
796    async fn test_nested_scope() {
797        let srv = init_service(App::new().service(web::scope("/app").service(
798            web::scope("/t1").service(web::resource("/path1").to(HttpResponse::Created)),
799        )))
800        .await;
801
802        let req = TestRequest::with_uri("/app/t1/path1").to_request();
803        let resp = srv.call(req).await.unwrap();
804        assert_eq!(resp.status(), StatusCode::CREATED);
805    }
806
807    #[actix_rt::test]
808    async fn test_nested_scope_no_slash() {
809        let srv =
810            init_service(App::new().service(web::scope("/app").service(
811                web::scope("t1").service(web::resource("/path1").to(HttpResponse::Created)),
812            )))
813            .await;
814
815        let req = TestRequest::with_uri("/app/t1/path1").to_request();
816        let resp = srv.call(req).await.unwrap();
817        assert_eq!(resp.status(), StatusCode::CREATED);
818    }
819
820    #[actix_rt::test]
821    async fn test_nested_scope_root() {
822        let srv = init_service(
823            App::new().service(
824                web::scope("/app").service(
825                    web::scope("/t1")
826                        .service(web::resource("").to(HttpResponse::Ok))
827                        .service(web::resource("/").to(HttpResponse::Created)),
828                ),
829            ),
830        )
831        .await;
832
833        let req = TestRequest::with_uri("/app/t1").to_request();
834        let resp = srv.call(req).await.unwrap();
835        assert_eq!(resp.status(), StatusCode::OK);
836
837        let req = TestRequest::with_uri("/app/t1/").to_request();
838        let resp = srv.call(req).await.unwrap();
839        assert_eq!(resp.status(), StatusCode::CREATED);
840    }
841
842    #[actix_rt::test]
843    async fn test_nested_scope_filter() {
844        let srv = init_service(
845            App::new().service(
846                web::scope("/app").service(
847                    web::scope("/t1")
848                        .guard(guard::Get())
849                        .service(web::resource("/path1").to(HttpResponse::Ok)),
850                ),
851            ),
852        )
853        .await;
854
855        let req = TestRequest::with_uri("/app/t1/path1")
856            .method(Method::POST)
857            .to_request();
858        let resp = srv.call(req).await.unwrap();
859        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
860
861        let req = TestRequest::with_uri("/app/t1/path1")
862            .method(Method::GET)
863            .to_request();
864        let resp = srv.call(req).await.unwrap();
865        assert_eq!(resp.status(), StatusCode::OK);
866    }
867
868    #[actix_rt::test]
869    async fn test_nested_scope_with_variable_segment() {
870        let srv = init_service(App::new().service(web::scope("/app").service(
871            web::scope("/{project_id}").service(web::resource("/path1").to(|r: HttpRequest| {
872                HttpResponse::Created().body(format!("project: {}", &r.match_info()["project_id"]))
873            })),
874        )))
875        .await;
876
877        let req = TestRequest::with_uri("/app/project_1/path1").to_request();
878        let res = srv.call(req).await.unwrap();
879        assert_eq!(res.status(), StatusCode::CREATED);
880        assert_body_eq!(res, b"project: project_1");
881    }
882
883    #[actix_rt::test]
884    async fn test_nested2_scope_with_variable_segment() {
885        let srv = init_service(App::new().service(web::scope("/app").service(
886            web::scope("/{project}").service(web::scope("/{id}").service(
887                web::resource("/path1").to(|r: HttpRequest| {
888                    HttpResponse::Created().body(format!(
889                        "project: {} - {}",
890                        &r.match_info()["project"],
891                        &r.match_info()["id"],
892                    ))
893                }),
894            )),
895        )))
896        .await;
897
898        let req = TestRequest::with_uri("/app/test/1/path1").to_request();
899        let res = srv.call(req).await.unwrap();
900        assert_eq!(res.status(), StatusCode::CREATED);
901        assert_body_eq!(res, b"project: test - 1");
902
903        let req = TestRequest::with_uri("/app/test/1/path2").to_request();
904        let res = srv.call(req).await.unwrap();
905        assert_eq!(res.status(), StatusCode::NOT_FOUND);
906    }
907
908    #[actix_rt::test]
909    async fn test_default_resource() {
910        let srv = init_service(
911            App::new().service(
912                web::scope("/app")
913                    .service(web::resource("/path1").to(HttpResponse::Ok))
914                    .default_service(|r: ServiceRequest| {
915                        ready(Ok(r.into_response(HttpResponse::BadRequest())))
916                    }),
917            ),
918        )
919        .await;
920
921        let req = TestRequest::with_uri("/app/path2").to_request();
922        let resp = srv.call(req).await.unwrap();
923        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
924
925        let req = TestRequest::with_uri("/path2").to_request();
926        let resp = srv.call(req).await.unwrap();
927        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
928    }
929
930    #[actix_rt::test]
931    async fn test_default_resource_propagation() {
932        let srv = init_service(
933            App::new()
934                .service(web::scope("/app1").default_service(web::to(HttpResponse::BadRequest)))
935                .service(web::scope("/app2"))
936                .default_service(|r: ServiceRequest| {
937                    ready(Ok(r.into_response(HttpResponse::MethodNotAllowed())))
938                }),
939        )
940        .await;
941
942        let req = TestRequest::with_uri("/non-exist").to_request();
943        let resp = srv.call(req).await.unwrap();
944        assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
945
946        let req = TestRequest::with_uri("/app1/non-exist").to_request();
947        let resp = srv.call(req).await.unwrap();
948        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
949
950        let req = TestRequest::with_uri("/app2/non-exist").to_request();
951        let resp = srv.call(req).await.unwrap();
952        assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
953    }
954
955    #[actix_rt::test]
956    async fn test_middleware() {
957        let srv = init_service(
958            App::new().service(
959                web::scope("app")
960                    .wrap(
961                        DefaultHeaders::new()
962                            .add((header::CONTENT_TYPE, HeaderValue::from_static("0001"))),
963                    )
964                    .service(web::resource("/test").route(web::get().to(HttpResponse::Ok))),
965            ),
966        )
967        .await;
968
969        let req = TestRequest::with_uri("/app/test").to_request();
970        let resp = call_service(&srv, req).await;
971        assert_eq!(resp.status(), StatusCode::OK);
972        assert_eq!(
973            resp.headers().get(header::CONTENT_TYPE).unwrap(),
974            HeaderValue::from_static("0001")
975        );
976    }
977
978    #[actix_rt::test]
979    async fn test_middleware_body_type() {
980        // Compile test that Scope accepts any body type; test for `EitherBody`
981        let srv = init_service(
982            App::new().service(
983                web::scope("app")
984                    .wrap_fn(|req, srv| {
985                        let fut = srv.call(req);
986                        async { Ok(fut.await?.map_into_right_body::<()>()) }
987                    })
988                    .service(web::resource("/test").route(web::get().to(|| async { "hello" }))),
989            ),
990        )
991        .await;
992
993        // test if `MessageBody::try_into_bytes()` is preserved across scope layer
994        use actix_http::body::MessageBody as _;
995        let req = TestRequest::with_uri("/app/test").to_request();
996        let resp = call_service(&srv, req).await;
997        let body = resp.into_body();
998        assert_eq!(body.try_into_bytes().unwrap(), b"hello".as_ref());
999    }
1000
1001    #[actix_rt::test]
1002    async fn test_middleware_fn() {
1003        let srv = init_service(
1004            App::new().service(
1005                web::scope("app")
1006                    .wrap_fn(|req, srv| {
1007                        let fut = srv.call(req);
1008                        async move {
1009                            let mut res = fut.await?;
1010                            res.headers_mut()
1011                                .insert(header::CONTENT_TYPE, HeaderValue::from_static("0001"));
1012                            Ok(res)
1013                        }
1014                    })
1015                    .route("/test", web::get().to(HttpResponse::Ok)),
1016            ),
1017        )
1018        .await;
1019
1020        let req = TestRequest::with_uri("/app/test").to_request();
1021        let resp = call_service(&srv, req).await;
1022        assert_eq!(resp.status(), StatusCode::OK);
1023        assert_eq!(
1024            resp.headers().get(header::CONTENT_TYPE).unwrap(),
1025            HeaderValue::from_static("0001")
1026        );
1027    }
1028
1029    #[actix_rt::test]
1030    async fn test_middleware_app_data() {
1031        let srv = init_service(
1032            App::new().service(
1033                web::scope("app")
1034                    .app_data(1usize)
1035                    .wrap_fn(|req, srv| {
1036                        assert_eq!(req.app_data::<usize>(), Some(&1usize));
1037                        req.extensions_mut().insert(1usize);
1038                        srv.call(req)
1039                    })
1040                    .route("/test", web::get().to(HttpResponse::Ok))
1041                    .default_service(|req: ServiceRequest| async move {
1042                        let (req, _) = req.into_parts();
1043
1044                        assert_eq!(req.extensions().get::<usize>(), Some(&1));
1045
1046                        Ok(ServiceResponse::new(
1047                            req,
1048                            HttpResponse::BadRequest().finish(),
1049                        ))
1050                    }),
1051            ),
1052        )
1053        .await;
1054
1055        let req = TestRequest::with_uri("/app/test").to_request();
1056        let resp = call_service(&srv, req).await;
1057        assert_eq!(resp.status(), StatusCode::OK);
1058
1059        let req = TestRequest::with_uri("/app/default").to_request();
1060        let resp = call_service(&srv, req).await;
1061        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
1062    }
1063
1064    // allow deprecated {App, Scope}::data
1065    #[allow(deprecated)]
1066    #[actix_rt::test]
1067    async fn test_override_data() {
1068        let srv = init_service(App::new().data(1usize).service(
1069            web::scope("app").data(10usize).route(
1070                "/t",
1071                web::get().to(|data: web::Data<usize>| {
1072                    assert_eq!(**data, 10);
1073                    HttpResponse::Ok()
1074                }),
1075            ),
1076        ))
1077        .await;
1078
1079        let req = TestRequest::with_uri("/app/t").to_request();
1080        let resp = call_service(&srv, req).await;
1081        assert_eq!(resp.status(), StatusCode::OK);
1082    }
1083
1084    // allow deprecated `{App, Scope}::data`
1085    #[allow(deprecated)]
1086    #[actix_rt::test]
1087    async fn test_override_data_default_service() {
1088        let srv =
1089            init_service(App::new().data(1usize).service(
1090                web::scope("app").data(10usize).default_service(web::to(
1091                    |data: web::Data<usize>| {
1092                        assert_eq!(**data, 10);
1093                        HttpResponse::Ok()
1094                    },
1095                )),
1096            ))
1097            .await;
1098
1099        let req = TestRequest::with_uri("/app/t").to_request();
1100        let resp = call_service(&srv, req).await;
1101        assert_eq!(resp.status(), StatusCode::OK);
1102    }
1103
1104    #[actix_rt::test]
1105    async fn test_override_app_data() {
1106        let srv = init_service(App::new().app_data(web::Data::new(1usize)).service(
1107            web::scope("app").app_data(web::Data::new(10usize)).route(
1108                "/t",
1109                web::get().to(|data: web::Data<usize>| {
1110                    assert_eq!(**data, 10);
1111                    HttpResponse::Ok()
1112                }),
1113            ),
1114        ))
1115        .await;
1116
1117        let req = TestRequest::with_uri("/app/t").to_request();
1118        let resp = call_service(&srv, req).await;
1119        assert_eq!(resp.status(), StatusCode::OK);
1120    }
1121
1122    #[actix_rt::test]
1123    async fn test_scope_config() {
1124        let srv = init_service(App::new().service(web::scope("/app").configure(|s| {
1125            s.route("/path1", web::get().to(HttpResponse::Ok));
1126        })))
1127        .await;
1128
1129        let req = TestRequest::with_uri("/app/path1").to_request();
1130        let resp = srv.call(req).await.unwrap();
1131        assert_eq!(resp.status(), StatusCode::OK);
1132    }
1133
1134    #[actix_rt::test]
1135    async fn test_scope_config_2() {
1136        let srv = init_service(App::new().service(web::scope("/app").configure(|s| {
1137            s.service(web::scope("/v1").configure(|s| {
1138                s.route("/", web::get().to(HttpResponse::Ok));
1139            }));
1140        })))
1141        .await;
1142
1143        let req = TestRequest::with_uri("/app/v1/").to_request();
1144        let resp = srv.call(req).await.unwrap();
1145        assert_eq!(resp.status(), StatusCode::OK);
1146    }
1147
1148    #[actix_rt::test]
1149    async fn test_url_for_external() {
1150        let srv = init_service(App::new().service(web::scope("/app").configure(|s| {
1151            s.service(web::scope("/v1").configure(|s| {
1152                s.external_resource("youtube", "https://youtube.com/watch/{video_id}");
1153                s.route(
1154                    "/",
1155                    web::get().to(|req: HttpRequest| {
1156                        HttpResponse::Ok()
1157                            .body(req.url_for("youtube", ["xxxxxx"]).unwrap().to_string())
1158                    }),
1159                );
1160            }));
1161        })))
1162        .await;
1163
1164        let req = TestRequest::with_uri("/app/v1/").to_request();
1165        let resp = srv.call(req).await.unwrap();
1166        assert_eq!(resp.status(), StatusCode::OK);
1167        let body = read_body(resp).await;
1168        assert_eq!(body, &b"https://youtube.com/watch/xxxxxx"[..]);
1169    }
1170
1171    #[actix_rt::test]
1172    async fn test_url_for_nested() {
1173        let srv = init_service(App::new().service(web::scope("/a").service(
1174            web::scope("/b").service(web::resource("/c/{stuff}").name("c").route(web::get().to(
1175                |req: HttpRequest| {
1176                    HttpResponse::Ok().body(format!("{}", req.url_for("c", ["12345"]).unwrap()))
1177                },
1178            ))),
1179        )))
1180        .await;
1181
1182        let req = TestRequest::with_uri("/a/b/c/test").to_request();
1183        let resp = call_service(&srv, req).await;
1184        assert_eq!(resp.status(), StatusCode::OK);
1185        let body = read_body(resp).await;
1186        assert_eq!(
1187            body,
1188            Bytes::from_static(b"http://localhost:8080/a/b/c/12345")
1189        );
1190    }
1191
1192    #[actix_rt::test]
1193    async fn dynamic_scopes() {
1194        let srv = init_service(
1195            App::new().service(
1196                web::scope("/{a}/").service(
1197                    web::scope("/{b}/")
1198                        .route("", web::get().to(|_: HttpRequest| HttpResponse::Created()))
1199                        .route(
1200                            "/",
1201                            web::get().to(|_: HttpRequest| HttpResponse::Accepted()),
1202                        )
1203                        .route("/{c}", web::get().to(|_: HttpRequest| HttpResponse::Ok())),
1204                ),
1205            ),
1206        )
1207        .await;
1208
1209        // note the unintuitive behavior with trailing slashes on scopes with dynamic segments
1210        let req = TestRequest::with_uri("/a//b//c").to_request();
1211        let resp = call_service(&srv, req).await;
1212        assert_eq!(resp.status(), StatusCode::OK);
1213
1214        let req = TestRequest::with_uri("/a//b/").to_request();
1215        let resp = call_service(&srv, req).await;
1216        assert_eq!(resp.status(), StatusCode::CREATED);
1217
1218        let req = TestRequest::with_uri("/a//b//").to_request();
1219        let resp = call_service(&srv, req).await;
1220        assert_eq!(resp.status(), StatusCode::ACCEPTED);
1221
1222        let req = TestRequest::with_uri("/a//b//c/d").to_request();
1223        let resp = call_service(&srv, req).await;
1224        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
1225
1226        let srv = init_service(
1227            App::new().service(
1228                web::scope("/{a}").service(
1229                    web::scope("/{b}")
1230                        .route("", web::get().to(|_: HttpRequest| HttpResponse::Created()))
1231                        .route(
1232                            "/",
1233                            web::get().to(|_: HttpRequest| HttpResponse::Accepted()),
1234                        )
1235                        .route("/{c}", web::get().to(|_: HttpRequest| HttpResponse::Ok())),
1236                ),
1237            ),
1238        )
1239        .await;
1240
1241        let req = TestRequest::with_uri("/a/b/c").to_request();
1242        let resp = call_service(&srv, req).await;
1243        assert_eq!(resp.status(), StatusCode::OK);
1244
1245        let req = TestRequest::with_uri("/a/b").to_request();
1246        let resp = call_service(&srv, req).await;
1247        assert_eq!(resp.status(), StatusCode::CREATED);
1248
1249        let req = TestRequest::with_uri("/a/b/").to_request();
1250        let resp = call_service(&srv, req).await;
1251        assert_eq!(resp.status(), StatusCode::ACCEPTED);
1252
1253        let req = TestRequest::with_uri("/a/b/c/d").to_request();
1254        let resp = call_service(&srv, req).await;
1255        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
1256    }
1257}