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 std::future::ready;
475
476    use actix_service::Service as _;
477    use bytes::Bytes;
478
479    use super::*;
480    use crate::{
481        http::{
482            header::{self, HeaderValue},
483            Method, StatusCode,
484        },
485        middleware::DefaultHeaders,
486        test::{call_service, init_service, read_body, try_init_service, TestRequest},
487        web, HttpRequest, HttpResponse,
488    };
489
490    #[actix_rt::test]
491    async fn test_default_resource() {
492        let srv =
493            init_service(App::new().service(web::resource("/test").to(HttpResponse::Ok))).await;
494        let req = TestRequest::with_uri("/test").to_request();
495        let resp = srv.call(req).await.unwrap();
496        assert_eq!(resp.status(), StatusCode::OK);
497
498        let req = TestRequest::with_uri("/blah").to_request();
499        let resp = srv.call(req).await.unwrap();
500        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
501
502        let srv = init_service(
503            App::new()
504                .service(web::resource("/test").to(HttpResponse::Ok))
505                .service(
506                    web::resource("/test2")
507                        .default_service(|r: ServiceRequest| {
508                            ready(Ok(r.into_response(HttpResponse::Created())))
509                        })
510                        .route(web::get().to(HttpResponse::Ok)),
511                )
512                .default_service(|r: ServiceRequest| {
513                    ready(Ok(r.into_response(HttpResponse::MethodNotAllowed())))
514                }),
515        )
516        .await;
517
518        let req = TestRequest::with_uri("/blah").to_request();
519        let resp = srv.call(req).await.unwrap();
520        assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
521
522        let req = TestRequest::with_uri("/test2").to_request();
523        let resp = srv.call(req).await.unwrap();
524        assert_eq!(resp.status(), StatusCode::OK);
525
526        let req = TestRequest::with_uri("/test2")
527            .method(Method::POST)
528            .to_request();
529        let resp = srv.call(req).await.unwrap();
530        assert_eq!(resp.status(), StatusCode::CREATED);
531    }
532
533    // allow deprecated App::data
534    #[allow(deprecated)]
535    #[actix_rt::test]
536    async fn test_data_factory() {
537        let srv = init_service(
538            App::new()
539                .data_factory(|| ready(Ok::<_, ()>(10usize)))
540                .service(web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok())),
541        )
542        .await;
543        let req = TestRequest::default().to_request();
544        let resp = srv.call(req).await.unwrap();
545        assert_eq!(resp.status(), StatusCode::OK);
546
547        let srv = init_service(
548            App::new()
549                .data_factory(|| ready(Ok::<_, ()>(10u32)))
550                .service(web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok())),
551        )
552        .await;
553        let req = TestRequest::default().to_request();
554        let resp = srv.call(req).await.unwrap();
555        assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
556    }
557
558    // allow deprecated App::data
559    #[allow(deprecated)]
560    #[actix_rt::test]
561    async fn test_data_factory_errors() {
562        let srv = try_init_service(
563            App::new()
564                .data_factory(|| ready(Err::<u32, _>(())))
565                .service(web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok())),
566        )
567        .await;
568
569        assert!(srv.is_err());
570    }
571
572    #[actix_rt::test]
573    async fn test_extension() {
574        let srv = init_service(App::new().app_data(10usize).service(web::resource("/").to(
575            |req: HttpRequest| {
576                assert_eq!(*req.app_data::<usize>().unwrap(), 10);
577                HttpResponse::Ok()
578            },
579        )))
580        .await;
581        let req = TestRequest::default().to_request();
582        let resp = srv.call(req).await.unwrap();
583        assert_eq!(resp.status(), StatusCode::OK);
584    }
585
586    #[actix_rt::test]
587    async fn test_wrap() {
588        let srv = init_service(
589            App::new()
590                .wrap(
591                    DefaultHeaders::new()
592                        .add((header::CONTENT_TYPE, HeaderValue::from_static("0001"))),
593                )
594                .route("/test", web::get().to(HttpResponse::Ok)),
595        )
596        .await;
597        let req = TestRequest::with_uri("/test").to_request();
598        let resp = call_service(&srv, req).await;
599        assert_eq!(resp.status(), StatusCode::OK);
600        assert_eq!(
601            resp.headers().get(header::CONTENT_TYPE).unwrap(),
602            HeaderValue::from_static("0001")
603        );
604    }
605
606    #[actix_rt::test]
607    async fn test_router_wrap() {
608        let srv = init_service(
609            App::new()
610                .route("/test", web::get().to(HttpResponse::Ok))
611                .wrap(
612                    DefaultHeaders::new()
613                        .add((header::CONTENT_TYPE, HeaderValue::from_static("0001"))),
614                ),
615        )
616        .await;
617        let req = TestRequest::with_uri("/test").to_request();
618        let resp = call_service(&srv, req).await;
619        assert_eq!(resp.status(), StatusCode::OK);
620        assert_eq!(
621            resp.headers().get(header::CONTENT_TYPE).unwrap(),
622            HeaderValue::from_static("0001")
623        );
624    }
625
626    #[actix_rt::test]
627    async fn test_wrap_fn() {
628        let srv = init_service(
629            App::new()
630                .wrap_fn(|req, srv| {
631                    let fut = srv.call(req);
632                    async move {
633                        let mut res = fut.await?;
634                        res.headers_mut()
635                            .insert(header::CONTENT_TYPE, HeaderValue::from_static("0001"));
636                        Ok(res)
637                    }
638                })
639                .service(web::resource("/test").to(HttpResponse::Ok)),
640        )
641        .await;
642        let req = TestRequest::with_uri("/test").to_request();
643        let resp = call_service(&srv, req).await;
644        assert_eq!(resp.status(), StatusCode::OK);
645        assert_eq!(
646            resp.headers().get(header::CONTENT_TYPE).unwrap(),
647            HeaderValue::from_static("0001")
648        );
649    }
650
651    #[actix_rt::test]
652    async fn test_router_wrap_fn() {
653        let srv = init_service(
654            App::new()
655                .route("/test", web::get().to(HttpResponse::Ok))
656                .wrap_fn(|req, srv| {
657                    let fut = srv.call(req);
658                    async {
659                        let mut res = fut.await?;
660                        res.headers_mut()
661                            .insert(header::CONTENT_TYPE, HeaderValue::from_static("0001"));
662                        Ok(res)
663                    }
664                }),
665        )
666        .await;
667        let req = TestRequest::with_uri("/test").to_request();
668        let resp = call_service(&srv, req).await;
669        assert_eq!(resp.status(), StatusCode::OK);
670        assert_eq!(
671            resp.headers().get(header::CONTENT_TYPE).unwrap(),
672            HeaderValue::from_static("0001")
673        );
674    }
675
676    #[actix_rt::test]
677    async fn test_external_resource() {
678        let srv = init_service(
679            App::new()
680                .external_resource("youtube", "https://youtube.com/watch/{video_id}")
681                .route(
682                    "/test",
683                    web::get().to(|req: HttpRequest| {
684                        HttpResponse::Ok()
685                            .body(req.url_for("youtube", ["12345"]).unwrap().to_string())
686                    }),
687                ),
688        )
689        .await;
690        let req = TestRequest::with_uri("/test").to_request();
691        let resp = call_service(&srv, req).await;
692        assert_eq!(resp.status(), StatusCode::OK);
693        let body = read_body(resp).await;
694        assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345"));
695    }
696
697    #[test]
698    fn can_be_returned_from_fn() {
699        /// compile-only test for returning app type from function
700        pub fn my_app() -> App<
701            impl ServiceFactory<
702                ServiceRequest,
703                Response = ServiceResponse<impl MessageBody>,
704                Config = (),
705                InitError = (),
706                Error = Error,
707            >,
708        > {
709            App::new()
710                // logger can be removed without affecting the return type
711                .wrap(crate::middleware::Logger::default())
712                .route("/", web::to(|| async { "hello" }))
713        }
714
715        #[allow(clippy::let_underscore_future)]
716        let _ = init_service(my_app());
717    }
718}