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