Skip to content

Commit

Permalink
Add ServiceConfig::default_service (#2743)
Browse files Browse the repository at this point in the history
* Add `ServiceConfig::default_service`

based on #2338

* update changelog
  • Loading branch information
robjtede committed Apr 23, 2022
1 parent 9aab911 commit b1c85ba
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 25 deletions.
7 changes: 5 additions & 2 deletions actix-web/CHANGES.md
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
8 changes: 7 additions & 1 deletion actix-web/src/app.rs
Expand Up @@ -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
}

Expand Down Expand Up @@ -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)));
Expand Down
100 changes: 78 additions & 22 deletions actix-web/src/config.rs
@@ -1,41 +1,40 @@
use std::net::SocketAddr;
use std::rc::Rc;

use actix_http::Extensions;
use actix_router::ResourceDef;
use actix_service::{boxed, IntoServiceFactory, ServiceFactory};

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 std::{net::SocketAddr, rc::Rc};

use actix_service::{boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _};

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<Box<dyn Guard>>;
type HttpNewService = boxed::BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>;

/// Application configuration
pub struct AppService {
config: AppConfig,
root: bool,
default: Rc<HttpNewService>,
default: Rc<BoxedHttpServiceFactory>,
#[allow(clippy::type_complexity)]
services: Vec<(
ResourceDef,
HttpNewService,
BoxedHttpServiceFactory,
Option<Guards>,
Option<Rc<ResourceMap>>,
)>,
}

impl AppService {
/// Crate server settings instance.
pub(crate) fn new(config: AppConfig, default: Rc<HttpNewService>) -> Self {
pub(crate) fn new(config: AppConfig, default: Rc<BoxedHttpServiceFactory>) -> Self {
AppService {
config,
default,
Expand All @@ -56,7 +55,7 @@ impl AppService {
AppConfig,
Vec<(
ResourceDef,
HttpNewService,
BoxedHttpServiceFactory,
Option<Guards>,
Option<Rc<ResourceMap>>,
)>,
Expand All @@ -81,7 +80,7 @@ impl AppService {
}

/// Returns default handler factory.
pub fn default_service(&self) -> Rc<HttpNewService> {
pub fn default_service(&self) -> Rc<BoxedHttpServiceFactory> {
self.default.clone()
}

Expand Down Expand Up @@ -187,6 +186,7 @@ pub struct ServiceConfig {
pub(crate) services: Vec<Box<dyn AppServiceFactory>>,
pub(crate) external: Vec<ResourceDef>,
pub(crate) app_data: Extensions,
pub(crate) default: Option<Rc<BoxedHttpServiceFactory>>,
}

impl ServiceConfig {
Expand All @@ -195,6 +195,7 @@ impl ServiceConfig {
services: Vec::new(),
external: Vec::new(),
app_data: Extensions::new(),
default: None,
}
}

Expand All @@ -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<F, U>(&mut self, f: F) -> &mut Self
where
F: IntoServiceFactory<U, ServiceRequest>,
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.
Expand Down Expand Up @@ -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| {
Expand Down
4 changes: 4 additions & 0 deletions actix-web/src/scope.rs
Expand Up @@ -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
}

Expand Down

0 comments on commit b1c85ba

Please sign in to comment.