Add `ServiceConfig::default_service`

The `App` struct supports a "default" service which will be called when there
are no other matching services available. I propose that we support adding this
default service from the `ServiceConfig` struct, which will enable it to be used
from `App::configure`. Currently if you want to test a default service (if
you're creating a proxy for example) you must re-specify this default service
each time you construct your app. This change will allow users to put their
default services into the shared `App::configure` helper.
This commit is contained in:
Lily Mara 2021-07-19 17:24:50 -07:00
parent 293c52c3ef
commit 75c3968e66
2 changed files with 44 additions and 1 deletions

View File

@ -205,6 +205,10 @@ where
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
}

View File

@ -3,7 +3,7 @@ use std::rc::Rc;
use actix_http::Extensions;
use actix_router::ResourceDef;
use actix_service::{boxed, IntoServiceFactory, ServiceFactory};
use actix_service::{boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt};
use crate::data::Data;
use crate::error::Error;
@ -185,6 +185,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<HttpNewService>>,
}
impl ServiceConfig {
@ -193,6 +194,7 @@ impl ServiceConfig {
services: Vec::new(),
external: Vec::new(),
app_data: Extensions::new(),
default: None,
}
}
@ -213,6 +215,28 @@ 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,
{
// create and configure default resource
self.default = Some(Rc::new(boxed::factory(f.into_factory().map_init_err(
|e| log::error!("Can not construct default service: {:?}", e),
))));
self
}
/// Configure route for a specific path.
///
/// Counterpart to [`App::route()`](crate::App::route).
@ -341,6 +365,21 @@ mod tests {
assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345"));
}
#[actix_rt::test]
async fn test_default_service() {
let srv = init_service(App::new().configure(|cfg| {
cfg.default_service(
web::get().to(|| HttpResponse::NotFound().body("four oh four")),
);
}))
.await;
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"));
}
#[actix_rt::test]
async fn test_service() {
let srv = init_service(App::new().configure(|cfg| {