From b1c85ba85be91b5ea34f31264853b411fadce1ef Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 23 Apr 2022 22:11:45 +0100 Subject: [PATCH] Add `ServiceConfig::default_service` (#2743) * Add `ServiceConfig::default_service` based on https://github.com/actix/actix-web/pull/2338 * update changelog --- actix-web/CHANGES.md | 7 ++- actix-web/src/app.rs | 8 +++- actix-web/src/config.rs | 96 ++++++++++++++++++++++++++++++++--------- actix-web/src/scope.rs | 4 ++ 4 files changed, 92 insertions(+), 23 deletions(-) diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index f89189402..4a16073a6 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -4,13 +4,16 @@ ### Added - Add `ServiceRequest::extract()` to make it easier to use extractors when writing middlewares. [#2647] - Add `Route::wrap()` to allow individual routes to use middleware. [#2725] +- Add `ServiceConfig::default_service()`. [#2338] [#2743] ### Fixed - Clear connection-level data on `HttpRequest` drop. [#2742] +[#2338]: https://github.com/actix/actix-web/pull/2338 [#2647]: https://github.com/actix/actix-web/pull/2647 [#2725]: https://github.com/actix/actix-web/pull/2725 [#2742]: https://github.com/actix/actix-web/pull/2742 +[#2743]: https://github.com/actix/actix-web/pull/2743 ## 4.0.1 - 2022-02-25 @@ -726,7 +729,7 @@ ### Removed - Public modules `middleware::{normalize, err_handlers}`. All necessary middleware types are now exposed directly by the `middleware` module. -- Remove `actix-threadpool` as dependency. `actix_threadpool::BlockingError` error type can be imported +- Remove `actix-threadpool` as dependency. `actix_threadpool::BlockingError` error type can be imported from `actix_web::error` module. [#1878] [#1812]: https://github.com/actix/actix-web/pull/1812 @@ -828,7 +831,7 @@ ## 3.0.0-beta.4 - 2020-09-09 ### Added -- `middleware::NormalizePath` now has configurable behavior for either always having a trailing +- `middleware::NormalizePath` now has configurable behavior for either always having a trailing slash, or as the new addition, always trimming trailing slashes. [#1639] ### Changed diff --git a/actix-web/src/app.rs b/actix-web/src/app.rs index 18749d346..119980a03 100644 --- a/actix-web/src/app.rs +++ b/actix-web/src/app.rs @@ -185,10 +185,17 @@ where F: FnOnce(&mut ServiceConfig), { let mut cfg = ServiceConfig::new(); + f(&mut cfg); + self.services.extend(cfg.services); self.external.extend(cfg.external); self.extensions.extend(cfg.app_data); + + if let Some(default) = cfg.default { + self.default = Some(default); + } + self } @@ -267,7 +274,6 @@ where { let svc = svc .into_factory() - .map(|res| res.map_into_boxed_body()) .map_init_err(|e| log::error!("Can not construct default service: {:?}", e)); self.default = Some(Rc::new(boxed::factory(svc))); diff --git a/actix-web/src/config.rs b/actix-web/src/config.rs index 77fba18ed..dab309175 100644 --- a/actix-web/src/config.rs +++ b/actix-web/src/config.rs @@ -1,33 +1,32 @@ -use std::net::SocketAddr; -use std::rc::Rc; +use std::{net::SocketAddr, rc::Rc}; -use actix_http::Extensions; -use actix_router::ResourceDef; -use actix_service::{boxed, IntoServiceFactory, ServiceFactory}; +use actix_service::{boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _}; -use crate::data::Data; -use crate::error::Error; -use crate::guard::Guard; -use crate::resource::Resource; -use crate::rmap::ResourceMap; -use crate::route::Route; -use crate::service::{ - AppServiceFactory, HttpServiceFactory, ServiceFactoryWrapper, ServiceRequest, - ServiceResponse, +use crate::{ + data::Data, + dev::{Extensions, ResourceDef}, + error::Error, + guard::Guard, + resource::Resource, + rmap::ResourceMap, + route::Route, + service::{ + AppServiceFactory, BoxedHttpServiceFactory, HttpServiceFactory, ServiceFactoryWrapper, + ServiceRequest, ServiceResponse, + }, }; type Guards = Vec>; -type HttpNewService = boxed::BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; /// Application configuration pub struct AppService { config: AppConfig, root: bool, - default: Rc, + default: Rc, #[allow(clippy::type_complexity)] services: Vec<( ResourceDef, - HttpNewService, + BoxedHttpServiceFactory, Option, Option>, )>, @@ -35,7 +34,7 @@ pub struct AppService { impl AppService { /// Crate server settings instance. - pub(crate) fn new(config: AppConfig, default: Rc) -> Self { + pub(crate) fn new(config: AppConfig, default: Rc) -> Self { AppService { config, default, @@ -56,7 +55,7 @@ impl AppService { AppConfig, Vec<( ResourceDef, - HttpNewService, + BoxedHttpServiceFactory, Option, Option>, )>, @@ -81,7 +80,7 @@ impl AppService { } /// Returns default handler factory. - pub fn default_service(&self) -> Rc { + pub fn default_service(&self) -> Rc { self.default.clone() } @@ -187,6 +186,7 @@ pub struct ServiceConfig { pub(crate) services: Vec>, pub(crate) external: Vec, pub(crate) app_data: Extensions, + pub(crate) default: Option>, } impl ServiceConfig { @@ -195,6 +195,7 @@ impl ServiceConfig { services: Vec::new(), external: Vec::new(), app_data: Extensions::new(), + default: None, } } @@ -215,6 +216,29 @@ impl ServiceConfig { self } + /// Default service to be used if no matching resource could be found. + /// + /// Counterpart to [`App::default_service()`](crate::App::default_service). + pub fn default_service(&mut self, f: F) -> &mut Self + where + F: IntoServiceFactory, + U: ServiceFactory< + ServiceRequest, + Config = (), + Response = ServiceResponse, + Error = Error, + > + 'static, + U::InitError: std::fmt::Debug, + { + let svc = f + .into_factory() + .map_init_err(|err| log::error!("Can not construct default service: {:?}", err)); + + self.default = Some(Rc::new(boxed::factory(svc))); + + self + } + /// Run external configuration as part of the application building process /// /// Counterpart to [`App::configure()`](crate::App::configure) that allows for easy nesting. @@ -322,6 +346,38 @@ mod tests { assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345")); } + #[actix_rt::test] + async fn registers_default_service() { + let srv = init_service( + App::new() + .configure(|cfg| { + cfg.default_service( + web::get().to(|| HttpResponse::NotFound().body("four oh four")), + ); + }) + .service(web::scope("/scoped").configure(|cfg| { + cfg.default_service( + web::get().to(|| HttpResponse::NotFound().body("scoped four oh four")), + ); + })), + ) + .await; + + // app registers default service + let req = TestRequest::with_uri("/path/i/did/not-configure").to_request(); + let resp = call_service(&srv, req).await; + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let body = read_body(resp).await; + assert_eq!(body, Bytes::from_static(b"four oh four")); + + // scope registers default service + let req = TestRequest::with_uri("/scoped/path/i/did/not-configure").to_request(); + let resp = call_service(&srv, req).await; + assert_eq!(resp.status(), StatusCode::NOT_FOUND); + let body = read_body(resp).await; + assert_eq!(body, Bytes::from_static(b"scoped four oh four")); + } + #[actix_rt::test] async fn test_service() { let srv = init_service(App::new().configure(|cfg| { diff --git a/actix-web/src/scope.rs b/actix-web/src/scope.rs index 0fcc83d70..f8c042a5f 100644 --- a/actix-web/src/scope.rs +++ b/actix-web/src/scope.rs @@ -198,6 +198,10 @@ where .get_or_insert_with(Extensions::new) .extend(cfg.app_data); + if let Some(default) = cfg.default { + self.default = Some(default); + } + self }