replace services with 500 error when data_factory construction failed

This commit is contained in:
fakeshadow 2021-01-25 09:08:33 +08:00
parent c201c15f8c
commit bb01b691b9
2 changed files with 99 additions and 73 deletions

View File

@ -11,11 +11,10 @@ use actix_service::{
apply, apply_fn_factory, IntoServiceFactory, ServiceFactory, ServiceFactoryExt, apply, apply_fn_factory, IntoServiceFactory, ServiceFactory, ServiceFactoryExt,
Transform, Transform,
}; };
use futures_util::future::FutureExt;
use crate::app_service::{AppEntry, AppInit, AppRoutingFactory}; use crate::app_service::{AppEntry, AppInit, AppRoutingFactory};
use crate::config::ServiceConfig; use crate::config::ServiceConfig;
use crate::data::{Data, DataFactory, FnDataFactory}; use crate::data::{Data, FnDataFactory};
use crate::dev::ResourceDef; use crate::dev::ResourceDef;
use crate::error::Error; use crate::error::Error;
use crate::resource::Resource; use crate::resource::Resource;
@ -114,22 +113,19 @@ where
E: std::fmt::Debug, E: std::fmt::Debug,
{ {
self.data_factories.push(Box::new(move || { self.data_factories.push(Box::new(move || {
{ let fut = data();
let fut = data(); Box::pin(async move {
async move { match fut.await {
match fut.await { Err(e) => {
Err(e) => { log::error!("Can not construct data instance: {:?}", e);
log::error!("Can not construct data instance: {:?}", e); Err(())
Err(()) }
} Ok(data) => {
Ok(data) => { let data = Box::new(Data::new(data)) as _;
let data: Box<dyn DataFactory> = Box::new(Data::new(data)); Ok(data)
Ok(data)
}
} }
} }
} })
.boxed_local()
})); }));
self self
} }
@ -453,7 +449,7 @@ where
fn into_factory(self) -> AppInit<T, B> { fn into_factory(self) -> AppInit<T, B> {
AppInit { AppInit {
async_data_factories: self.data_factories.into_boxed_slice().into(), async_data_factories: self.data_factories.into_boxed_slice().into(),
endpoint: self.endpoint, endpoint: Rc::new(self.endpoint),
services: Rc::new(RefCell::new(self.services)), services: Rc::new(RefCell::new(self.services)),
external: RefCell::new(self.external), external: RefCell::new(self.external),
default: self.default, default: self.default,
@ -473,9 +469,7 @@ mod tests {
use crate::http::{header, HeaderValue, Method, StatusCode}; use crate::http::{header, HeaderValue, Method, StatusCode};
use crate::middleware::DefaultHeaders; use crate::middleware::DefaultHeaders;
use crate::service::ServiceRequest; use crate::service::ServiceRequest;
use crate::test::{ use crate::test::{call_service, init_service, read_body, TestRequest};
call_service, init_service, read_body, try_init_service, TestRequest,
};
use crate::{web, HttpRequest, HttpResponse}; use crate::{web, HttpRequest, HttpResponse};
#[actix_rt::test] #[actix_rt::test]
@ -546,13 +540,21 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_data_factory_errors() { async fn test_data_factory_errors() {
let srv = let mut srv = init_service(
try_init_service(App::new().data_factory(|| err::<u32, _>(())).service( App::new().data_factory(|| err::<u32, _>(())).service(
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()), web::resource("/")
)) .route(web::get().to(|_: web::Data<usize>| HttpResponse::Ok())),
.await; ),
)
.await;
assert!(srv.is_err()); let req = TestRequest::get().uri("/").to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
let req = TestRequest::post().uri("/test").to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
} }
#[actix_rt::test] #[actix_rt::test]

View File

@ -26,14 +26,14 @@ type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Err
pub struct AppInit<T, B> pub struct AppInit<T, B>
where where
T: ServiceFactory< T: ServiceFactory<
ServiceRequest, ServiceRequest,
Config = (), Config = (),
Response = ServiceResponse<B>, Response = ServiceResponse<B>,
Error = Error, Error = Error,
InitError = (), InitError = (),
>, > + 'static,
{ {
pub(crate) endpoint: T, pub(crate) endpoint: Rc<T>,
pub(crate) extensions: RefCell<Option<Extensions>>, pub(crate) extensions: RefCell<Option<Extensions>>,
pub(crate) async_data_factories: Rc<[FnDataFactory]>, pub(crate) async_data_factories: Rc<[FnDataFactory]>,
pub(crate) services: Rc<RefCell<Vec<Box<dyn AppServiceFactory>>>>, pub(crate) services: Rc<RefCell<Vec<Box<dyn AppServiceFactory>>>>,
@ -45,12 +45,12 @@ where
impl<T, B> ServiceFactory<Request> for AppInit<T, B> impl<T, B> ServiceFactory<Request> for AppInit<T, B>
where where
T: ServiceFactory< T: ServiceFactory<
ServiceRequest, ServiceRequest,
Config = (), Config = (),
Response = ServiceResponse<B>, Response = ServiceResponse<B>,
Error = Error, Error = Error,
InitError = (), InitError = (),
>, > + 'static,
T::Future: 'static, T::Future: 'static,
{ {
type Response = ServiceResponse<B>; type Response = ServiceResponse<B>;
@ -77,38 +77,21 @@ where
.into_iter() .into_iter()
.for_each(|mut srv| srv.register(&mut config)); .for_each(|mut srv| srv.register(&mut config));
let mut rmap = ResourceMap::new(ResourceDef::new("")); let (config, mut services) = config.into_services();
let (config, services) = config.into_services();
// complete pipeline creation.
*self.factory_ref.borrow_mut() = Some(AppRoutingFactory {
default,
services: services
.into_iter()
.map(|(mut rdef, srv, guards, nested)| {
rmap.add(&mut rdef, nested);
(rdef, srv, RefCell::new(guards))
})
.collect::<Vec<_>>()
.into_boxed_slice()
.into(),
});
// external resources
for mut rdef in std::mem::take(&mut *self.external.borrow_mut()) {
rmap.add(&mut rdef, None);
}
// complete ResourceMap tree creation
let rmap = Rc::new(rmap);
rmap.finish(rmap.clone());
// construct all async data factory futures // construct all async data factory futures
let factory_futs = join_all(self.async_data_factories.iter().map(|f| f())); let factory_futs = join_all(self.async_data_factories.iter().map(|f| f()));
// construct app service and middleware service factory future. // clone factory_ref and endpoint to use them in async block.
let endpoint_fut = self.endpoint.new_service(()); let factory_ref = self.factory_ref.clone();
let endpoint = self.endpoint.clone();
// construct resource map. it would move to async block afterwards.
let mut rmap = ResourceMap::new(ResourceDef::new(""));
// add external resources
for mut rdef in std::mem::take(&mut *self.external.borrow_mut()) {
rmap.add(&mut rdef, None);
}
// take extensions or create new one as app data container. // take extensions or create new one as app data container.
let mut app_data = self let mut app_data = self
@ -118,15 +101,56 @@ where
.unwrap_or_else(Extensions::new); .unwrap_or_else(Extensions::new);
Box::pin(async move { Box::pin(async move {
// async data factories // construct async data factories.
let async_data_factories = factory_futs let res = factory_futs
.await .await
.into_iter() .into_iter()
.collect::<Result<Vec<_>, _>>() .collect::<Result<Vec<_>, _>>();
.map_err(|_| ())?;
// app service and middleware let async_data_factories = match res {
let service = endpoint_fut.await?; Ok(async_data_factories) => async_data_factories,
Err(_) => {
// error occur producing app_data.
// replace services with 500 error response
services = services
.into_iter()
.map(|(rdef, _, guards, nested)| {
let srv = boxed::factory(fn_service(
|req: ServiceRequest| async {
Ok(req.into_response(
Response::InternalServerError().finish(),
))
},
));
(rdef, srv, guards, nested)
})
.collect();
// throw away async_data.
Vec::new()
}
};
// complete pipeline creation.
*factory_ref.borrow_mut() = Some(AppRoutingFactory {
default,
services: services
.into_iter()
.map(|(mut rdef, srv, guards, nested)| {
rmap.add(&mut rdef, nested);
(rdef, srv, RefCell::new(guards))
})
.collect::<Vec<_>>()
.into_boxed_slice()
.into(),
});
// complete ResourceMap tree creation
let rmap = Rc::new(rmap);
rmap.finish(rmap.clone());
// construct app service and middleware service.
let service = endpoint.new_service(()).await?;
// populate app data container from (async) data factories. // populate app data container from (async) data factories.
async_data_factories.iter().for_each(|factory| { async_data_factories.iter().for_each(|factory| {