This commit is contained in:
Ali MJ Al-Nasrawy 2021-06-23 00:13:52 -04:00 committed by GitHub
commit 98bfff0951
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 160 additions and 235 deletions

View File

@ -764,7 +764,7 @@ mod tests {
}, },
))) )))
.default_service(web::to(move |req: HttpRequest| { .default_service(web::to(move |req: HttpRequest| {
assert!(req.match_pattern().is_none()); assert_eq!(req.match_pattern().as_deref(), Some("/user/{id}"));
HttpResponse::Ok().finish() HttpResponse::Ok().finish()
})), })),
), ),

View File

@ -26,6 +26,10 @@ impl ResourceMap {
} }
} }
pub fn set_root_prefix(&mut self, prefix: &str) {
rdef_set_root_prefix(&mut self.root, prefix);
}
pub fn add(&mut self, pattern: &mut ResourceDef, nested: Option<Rc<ResourceMap>>) { pub fn add(&mut self, pattern: &mut ResourceDef, nested: Option<Rc<ResourceMap>>) {
pattern.set_id(self.patterns.len() as u16); pattern.set_id(self.patterns.len() as u16);
self.patterns.push((pattern.clone(), nested)); self.patterns.push((pattern.clone(), nested));
@ -247,6 +251,20 @@ impl ResourceMap {
} }
} }
pub fn rdef_set_root_prefix(rdef: &mut ResourceDef, prefix: &str) {
// TODO Doesn't work with multiple patterns
let pattern = if prefix.starts_with("/") {
[prefix, rdef.pattern()].concat()
} else {
["/", prefix, rdef.pattern()].concat()
};
let mut res = ResourceDef::new(pattern);
res.set_id(rdef.id());
*res.name_mut() = rdef.name().to_string();
*rdef = res;
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -1,17 +1,14 @@
use std::cell::RefCell;
use std::fmt; use std::fmt;
use std::future::Future; use std::future::Future;
use std::rc::Rc; use std::rc::Rc;
use actix_http::Extensions; use actix_http::Extensions;
use actix_router::{ResourceDef, Router}; use actix_router::ResourceDef;
use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::boxed::{self, BoxServiceFactory};
use actix_service::{ use actix_service::{
apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt, apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt,
Transform, Transform,
}; };
use futures_core::future::LocalBoxFuture;
use futures_util::future::join_all;
use crate::config::ServiceConfig; use crate::config::ServiceConfig;
use crate::data::Data; use crate::data::Data;
@ -19,15 +16,51 @@ use crate::dev::{AppService, HttpServiceFactory};
use crate::error::Error; use crate::error::Error;
use crate::guard::Guard; use crate::guard::Guard;
use crate::resource::Resource; use crate::resource::Resource;
use crate::rmap::ResourceMap; use crate::rmap::{self, ResourceMap};
use crate::route::Route; use crate::route::Route;
use crate::service::{ use crate::service::{
AppServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse, AppServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse,
}; };
type Guards = Vec<Box<dyn Guard>>; type BoxedFactory = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>;
type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>;
type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; pub trait EndpointConstructor {
type Output: ServiceFactory<
ServiceRequest,
Config = (),
Response = ServiceResponse,
Error = Error,
InitError = (),
> + 'static;
fn call(&self, f: BoxedFactory) -> Self::Output;
}
impl EndpointConstructor for () {
type Output = BoxedFactory;
fn call(&self, f: BoxedFactory) -> Self::Output {
f
}
}
impl<F, O> EndpointConstructor for F
where
F: Fn(BoxedFactory) -> O,
O: ServiceFactory<
ServiceRequest,
Config = (),
Response = ServiceResponse,
Error = Error,
InitError = (),
> + 'static,
{
type Output = O;
fn call(&self, f: BoxedFactory) -> Self::Output {
self(f)
}
}
/// Resources scope. /// Resources scope.
/// ///
@ -57,44 +90,33 @@ type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Err
/// * /{project_id}/path1 - responds to all http method /// * /{project_id}/path1 - responds to all http method
/// * /{project_id}/path2 - `GET` requests /// * /{project_id}/path2 - `GET` requests
/// * /{project_id}/path3 - `HEAD` requests /// * /{project_id}/path3 - `HEAD` requests
pub struct Scope<T = ScopeEndpoint> { // use actix_service::{boxed, IntoServiceFactory, ServiceFactory};
endpoint: T, pub struct Scope<Cons = ()> {
rdef: String, constructor: Cons,
prefix: String,
app_data: Option<Extensions>, app_data: Option<Extensions>,
services: Vec<Box<dyn AppServiceFactory>>, services: Vec<Box<dyn AppServiceFactory>>,
guards: Vec<Box<dyn Guard>>, guards: Vec<Rc<dyn Guard>>,
default: Option<Rc<HttpNewService>>, default: Option<BoxedFactory>,
external: Vec<ResourceDef>, external: Vec<ResourceDef>,
factory_ref: Rc<RefCell<Option<ScopeFactory>>>,
} }
impl Scope { impl Scope {
/// Create a new scope /// Create a new scope
pub fn new(path: &str) -> Scope { pub fn new(path: &str) -> Scope {
let fref = Rc::new(RefCell::new(None));
Scope { Scope {
endpoint: ScopeEndpoint::new(fref.clone()), constructor: (),
rdef: path.to_string(), prefix: path.to_string(),
app_data: None, app_data: None,
guards: Vec::new(), guards: Vec::new(),
services: Vec::new(), services: Vec::new(),
default: None, default: None,
external: Vec::new(), external: Vec::new(),
factory_ref: fref,
} }
} }
} }
impl<T> Scope<T> impl<C: EndpointConstructor> Scope<C> {
where
T: ServiceFactory<
ServiceRequest,
Config = (),
Response = ServiceResponse,
Error = Error,
InitError = (),
>,
{
/// Add match guard to a scope. /// Add match guard to a scope.
/// ///
/// ``` /// ```
@ -116,7 +138,7 @@ where
/// } /// }
/// ``` /// ```
pub fn guard<G: Guard + 'static>(mut self, guard: G) -> Self { pub fn guard<G: Guard + 'static>(mut self, guard: G) -> Self {
self.guards.push(Box::new(guard)); self.guards.push(Rc::new(guard));
self self
} }
@ -286,9 +308,9 @@ where
U::InitError: fmt::Debug, U::InitError: fmt::Debug,
{ {
// create and configure default resource // create and configure default resource
self.default = Some(Rc::new(boxed::factory(f.into_factory().map_init_err( self.default = Some(boxed::factory(f.into_factory().map_init_err(|e| {
|e| log::error!("Can not construct default service: {:?}", e), log::error!("Can not construct default service: {:?}", e)
)))); })));
self self
} }
@ -302,36 +324,26 @@ where
/// ServiceResponse. /// ServiceResponse.
/// ///
/// Use middleware when you need to read or modify *every* request in some way. /// Use middleware when you need to read or modify *every* request in some way.
pub fn wrap<M>( pub fn wrap<M>(self, mw: M) -> Scope<impl EndpointConstructor>
self,
mw: M,
) -> Scope<
impl ServiceFactory<
ServiceRequest,
Config = (),
Response = ServiceResponse,
Error = Error,
InitError = (),
>,
>
where where
M: Transform< M: Transform<
T::Service, <C::Output as ServiceFactory<ServiceRequest>>::Service,
ServiceRequest, ServiceRequest,
Response = ServiceResponse, Response = ServiceResponse,
Error = Error, Error = Error,
InitError = (), InitError = (),
>, > + 'static,
{ {
let mw = Rc::new(mw);
let constructor = self.constructor;
Scope { Scope {
endpoint: apply(mw, self.endpoint), constructor: move |factory| apply(mw.clone(), constructor.call(factory)),
rdef: self.rdef, prefix: self.prefix,
app_data: self.app_data, app_data: self.app_data,
guards: self.guards, guards: self.guards,
services: self.services, services: self.services,
default: self.default, default: self.default,
external: self.external, external: self.external,
factory_ref: self.factory_ref,
} }
} }
@ -367,212 +379,107 @@ where
/// .route("/index.html", web::get().to(index))); /// .route("/index.html", web::get().to(index)));
/// } /// }
/// ``` /// ```
pub fn wrap_fn<F, R>( pub fn wrap_fn<F, R>(self, mw: F) -> Scope<impl EndpointConstructor>
self,
mw: F,
) -> Scope<
impl ServiceFactory<
ServiceRequest,
Config = (),
Response = ServiceResponse,
Error = Error,
InitError = (),
>,
>
where where
F: Fn(ServiceRequest, &T::Service) -> R + Clone, F: Fn(ServiceRequest, &<C::Output as ServiceFactory<ServiceRequest>>::Service) -> R
+ Clone
+ 'static,
R: Future<Output = Result<ServiceResponse, Error>>, R: Future<Output = Result<ServiceResponse, Error>>,
{ {
let constructor = self.constructor;
Scope { Scope {
endpoint: apply_fn_factory(self.endpoint, mw), constructor: move |factory| apply_fn_factory(constructor.call(factory), mw.clone()),
rdef: self.rdef, prefix: self.prefix,
app_data: self.app_data, app_data: self.app_data,
guards: self.guards, guards: self.guards,
services: self.services, services: self.services,
default: self.default, default: self.default,
external: self.external, external: self.external,
factory_ref: self.factory_ref,
} }
} }
}
impl<T> HttpServiceFactory for Scope<T> fn _register(mut self, config: &mut AppService) {
where let default_service = self.default.take();
T: ServiceFactory< let services = self.services.drain(..).collect::<Vec<_>>();
ServiceRequest, let mut external_resources = self.external.drain(..).collect::<Vec<_>>();
Config = (),
Response = ServiceResponse,
Error = Error,
InitError = (),
> + 'static,
{
fn register(mut self, config: &mut AppService) {
// update default resource if needed
let default = self.default.unwrap_or_else(|| config.default_service());
// register nested services let wrapped_guards = |guards: Option<Vec<Box<dyn Guard>>>| {
let mut cfg = config.clone_config(); let guards = self
self.services .guards
.into_iter() .iter()
.for_each(|mut srv| srv.register(&mut cfg)); .map(|g| Box::new(g.clone()) as Box<dyn Guard>)
.chain(guards.into_iter().flatten())
let mut rmap = ResourceMap::new(ResourceDef::root_prefix(&self.rdef)); .collect::<Vec<_>>();
match guards {
// external resources guards if !guards.is_empty() => Some(guards),
for mut rdef in std::mem::take(&mut self.external) { _ => None,
rmap.add(&mut rdef, None); }
}
// complete scope pipeline creation
*self.factory_ref.borrow_mut() = Some(ScopeFactory {
app_data: self.app_data.take().map(Rc::new),
default,
services: cfg
.into_services()
.1
.into_iter()
.map(|(mut rdef, srv, guards, nested)| {
rmap.add(&mut rdef, nested);
(rdef, srv, RefCell::new(guards))
})
.collect::<Vec<_>>()
.into_boxed_slice()
.into(),
});
// get guards
let guards = if self.guards.is_empty() {
None
} else {
Some(self.guards)
}; };
// register final service let wrapped_rdef = |mut rdef: ResourceDef| {
config.register_service( rmap::rdef_set_root_prefix(&mut rdef, &self.prefix);
ResourceDef::root_prefix(&self.rdef), rdef
guards, };
self.endpoint,
Some(Rc::new(rmap)),
)
}
}
pub struct ScopeFactory { let mut wrapped_rmap = |rmap: Option<Rc<ResourceMap>>, rdef: &ResourceDef| {
app_data: Option<Rc<Extensions>>, let rmap = rmap.map(|rmap| {
services: Rc<[(ResourceDef, HttpNewService, RefCell<Option<Guards>>)]>, let mut rmap = (*rmap).clone();
default: Rc<HttpNewService>, rmap.set_root_prefix(&self.prefix);
} rmap
});
impl ServiceFactory<ServiceRequest> for ScopeFactory { if external_resources.is_empty() {
type Response = ServiceResponse; rmap
type Error = Error; } else {
type Config = (); let mut rmap = rmap.unwrap_or_else(|| ResourceMap::new(rdef.clone()));
type Service = ScopeService; for ext in external_resources.iter_mut() {
type InitError = (); rmap.add(ext, None);
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: ()) -> Self::Future {
// construct default service factory future
let default_fut = self.default.new_service(());
// construct all services factory future with it's resource def and guards.
let factory_fut = join_all(self.services.iter().map(|(path, factory, guards)| {
let path = path.clone();
let guards = guards.borrow_mut().take();
let factory_fut = factory.new_service(());
async move {
let service = factory_fut.await?;
Ok((path, guards, service))
}
}));
let app_data = self.app_data.clone();
Box::pin(async move {
let default = default_fut.await?;
// build router from the factory future result.
let router = factory_fut
.await
.into_iter()
.collect::<Result<Vec<_>, _>>()?
.drain(..)
.fold(Router::build(), |mut router, (path, guards, service)| {
router.rdef(path, service).2 = guards;
router
})
.finish();
Ok(ScopeService {
app_data,
router,
default,
})
})
}
}
pub struct ScopeService {
app_data: Option<Rc<Extensions>>,
router: Router<HttpService, Vec<Box<dyn Guard>>>,
default: HttpService,
}
impl Service<ServiceRequest> for ScopeService {
type Response = ServiceResponse;
type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
actix_service::always_ready!();
fn call(&self, mut req: ServiceRequest) -> Self::Future {
let res = self.router.recognize_checked(&mut req, |req, guards| {
if let Some(ref guards) = guards {
for f in guards {
if !f.check(req.head()) {
return false;
}
} }
Some(rmap)
} }
true .map(Rc::new)
}); };
if let Some(ref app_data) = self.app_data { let mut config_dummy = config.clone_config();
req.add_data_container(app_data.clone()); services
.into_iter()
.for_each(|mut srv| srv.register(&mut config_dummy));
let (_, services) = config_dummy.into_services();
for (rdef, factory, guards, rmap) in services {
let rdef = wrapped_rdef(rdef);
let rmap = wrapped_rmap(rmap, &rdef);
let guards = wrapped_guards(guards);
let factory = self.constructor.call(factory);
config.register_service(rdef, guards, factory, rmap);
} }
if let Some((srv, _info)) = res { if let Some(default) = default_service {
srv.call(req) let root_rdef = ResourceDef::root_prefix(&self.prefix);
let rmap = wrapped_rmap(None, &root_rdef);
let guards = wrapped_guards(None);
let factory = self.constructor.call(default);
config.register_service(root_rdef, guards, factory, rmap);
}
}
}
impl<C: EndpointConstructor> HttpServiceFactory for Scope<C> {
fn register(mut self, config: &mut AppService) {
if let Some(app_data) = self.app_data.take().map(Rc::new) {
return self
.wrap_fn(move |mut req, srv| {
req.add_data_container(app_data.clone());
srv.call(req)
})
._register(config);
} else { } else {
self.default.call(req) self._register(config);
} }
} }
} }
#[doc(hidden)]
pub struct ScopeEndpoint {
factory: Rc<RefCell<Option<ScopeFactory>>>,
}
impl ScopeEndpoint {
fn new(factory: Rc<RefCell<Option<ScopeFactory>>>) -> Self {
ScopeEndpoint { factory }
}
}
impl ServiceFactory<ServiceRequest> for ScopeEndpoint {
type Response = ServiceResponse;
type Error = Error;
type Config = ();
type Service = ScopeService;
type InitError = ();
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
fn new_service(&self, _: ()) -> Self::Future {
self.factory.borrow_mut().as_mut().unwrap().new_service(())
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use actix_service::Service; use actix_service::Service;