Skip to main content

actix_web/
app.rs

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