diff --git a/src/request.rs b/src/request.rs index e3da991de..97b01afaf 100644 --- a/src/request.rs +++ b/src/request.rs @@ -775,7 +775,7 @@ mod tests { }, ))) .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() })), ), diff --git a/src/rmap.rs b/src/rmap.rs index 3c8805d57..018872696 100644 --- a/src/rmap.rs +++ b/src/rmap.rs @@ -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>) { pattern.set_id(self.patterns.len() as u16); 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)] mod tests { use super::*; diff --git a/src/scope.rs b/src/scope.rs index 412c01d95..4014efcfa 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,17 +1,14 @@ -use std::cell::RefCell; use std::fmt; use std::future::Future; use std::rc::Rc; use actix_http::Extensions; -use actix_router::{ResourceDef, Router}; -use actix_service::boxed::{self, BoxService, BoxServiceFactory}; +use actix_router::ResourceDef; +use actix_service::boxed::{self, BoxServiceFactory}; use actix_service::{ apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt, Transform, }; -use futures_core::future::LocalBoxFuture; -use futures_util::future::join_all; use crate::config::ServiceConfig; use crate::data::Data; @@ -19,15 +16,51 @@ use crate::dev::{AppService, HttpServiceFactory}; use crate::error::Error; use crate::guard::Guard; use crate::resource::Resource; -use crate::rmap::ResourceMap; +use crate::rmap::{self, ResourceMap}; use crate::route::Route; use crate::service::{ AppServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse, }; -type Guards = Vec>; -type HttpService = BoxService; -type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; +type BoxedFactory = 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 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. /// @@ -57,44 +90,33 @@ type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Err /// * /{project_id}/path1 - responds to all http method /// * /{project_id}/path2 - `GET` requests /// * /{project_id}/path3 - `HEAD` requests -pub struct Scope { - endpoint: T, - rdef: String, +// use actix_service::{boxed, IntoServiceFactory, ServiceFactory}; +pub struct Scope { + constructor: Cons, + prefix: String, app_data: Option, services: Vec>, - guards: Vec>, - default: Option>, + guards: Vec>, + default: Option, external: Vec, - factory_ref: Rc>>, } impl Scope { /// Create a new scope pub fn new(path: &str) -> Scope { - let fref = Rc::new(RefCell::new(None)); Scope { - endpoint: ScopeEndpoint::new(fref.clone()), - rdef: path.to_string(), + constructor: (), + prefix: path.to_string(), app_data: None, guards: Vec::new(), services: Vec::new(), default: None, external: Vec::new(), - factory_ref: fref, } } } -impl Scope -where - T: ServiceFactory< - ServiceRequest, - Config = (), - Response = ServiceResponse, - Error = Error, - InitError = (), - >, -{ +impl Scope { /// Add match guard to a scope. /// /// ``` @@ -116,7 +138,7 @@ where /// } /// ``` pub fn guard(mut self, guard: G) -> Self { - self.guards.push(Box::new(guard)); + self.guards.push(Rc::new(guard)); self } @@ -285,9 +307,9 @@ where U::InitError: 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.default = Some(boxed::factory(f.into_factory().map_init_err(|e| { + log::error!("Can not construct default service: {:?}", e) + }))); self } @@ -301,36 +323,26 @@ where /// ServiceResponse. /// /// Use middleware when you need to read or modify *every* request in some way. - pub fn wrap( - self, - mw: M, - ) -> Scope< - impl ServiceFactory< - ServiceRequest, - Config = (), - Response = ServiceResponse, - Error = Error, - InitError = (), - >, - > + pub fn wrap(self, mw: M) -> Scope where M: Transform< - T::Service, - ServiceRequest, - Response = ServiceResponse, - Error = Error, - InitError = (), - >, + >::Service, + ServiceRequest, + Response = ServiceResponse, + Error = Error, + InitError = (), + > + 'static, { + let mw = Rc::new(mw); + let constructor = self.constructor; Scope { - endpoint: apply(mw, self.endpoint), - rdef: self.rdef, + constructor: move |factory| apply(mw.clone(), constructor.call(factory)), + prefix: self.prefix, app_data: self.app_data, guards: self.guards, services: self.services, default: self.default, external: self.external, - factory_ref: self.factory_ref, } } @@ -366,212 +378,107 @@ where /// .route("/index.html", web::get().to(index))); /// } /// ``` - pub fn wrap_fn( - self, - mw: F, - ) -> Scope< - impl ServiceFactory< - ServiceRequest, - Config = (), - Response = ServiceResponse, - Error = Error, - InitError = (), - >, - > + pub fn wrap_fn(self, mw: F) -> Scope where - F: Fn(ServiceRequest, &T::Service) -> R + Clone, + F: Fn(ServiceRequest, &>::Service) -> R + + Clone + + 'static, + R: Future>, { + let constructor = self.constructor; Scope { - endpoint: apply_fn_factory(self.endpoint, mw), - rdef: self.rdef, + constructor: move |factory| apply_fn_factory(constructor.call(factory), mw.clone()), + prefix: self.prefix, app_data: self.app_data, guards: self.guards, services: self.services, default: self.default, external: self.external, - factory_ref: self.factory_ref, } } -} -impl HttpServiceFactory for Scope -where - T: ServiceFactory< - ServiceRequest, - 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()); + fn _register(mut self, config: &mut AppService) { + let default_service = self.default.take(); + let services = self.services.drain(..).collect::>(); + let mut external_resources = self.external.drain(..).collect::>(); - // register nested services - let mut cfg = config.clone_config(); - self.services - .into_iter() - .for_each(|mut srv| srv.register(&mut cfg)); - - let mut rmap = ResourceMap::new(ResourceDef::root_prefix(&self.rdef)); - - // external resources - for mut rdef in std::mem::take(&mut self.external) { - 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::>() - .into_boxed_slice() - .into(), - }); - - // get guards - let guards = if self.guards.is_empty() { - None - } else { - Some(self.guards) + let wrapped_guards = |guards: Option>>| { + let guards = self + .guards + .iter() + .map(|g| Box::new(g.clone()) as Box) + .chain(guards.into_iter().flatten()) + .collect::>(); + match guards { + guards if !guards.is_empty() => Some(guards), + _ => None, + } }; - // register final service - config.register_service( - ResourceDef::root_prefix(&self.rdef), - guards, - self.endpoint, - Some(Rc::new(rmap)), - ) - } -} + let wrapped_rdef = |mut rdef: ResourceDef| { + rmap::rdef_set_root_prefix(&mut rdef, &self.prefix); + rdef + }; -pub struct ScopeFactory { - app_data: Option>, - services: Rc<[(ResourceDef, HttpNewService, RefCell>)]>, - default: Rc, -} - -impl ServiceFactory for ScopeFactory { - type Response = ServiceResponse; - type Error = Error; - type Config = (); - type Service = ScopeService; - type InitError = (); - type Future = LocalBoxFuture<'static, Result>; - - 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::, _>>()? - .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>, - router: Router>>, - default: HttpService, -} - -impl Service for ScopeService { - type Response = ServiceResponse; - type Error = Error; - type Future = LocalBoxFuture<'static, Result>; - - 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; - } + let mut wrapped_rmap = |rmap: Option>, rdef: &ResourceDef| { + let rmap = rmap.map(|rmap| { + let mut rmap = (*rmap).clone(); + rmap.set_root_prefix(&self.prefix); + rmap + }); + if external_resources.is_empty() { + rmap + } else { + let mut rmap = rmap.unwrap_or_else(|| ResourceMap::new(rdef.clone())); + for ext in external_resources.iter_mut() { + rmap.add(ext, None); } + Some(rmap) } - true - }); + .map(Rc::new) + }; - if let Some(ref app_data) = self.app_data { - req.add_data_container(app_data.clone()); + let mut config_dummy = config.clone_config(); + 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 { - srv.call(req) + if let Some(default) = default_service { + 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 HttpServiceFactory for Scope { + 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 { - self.default.call(req) + self._register(config); } } } -#[doc(hidden)] -pub struct ScopeEndpoint { - factory: Rc>>, -} - -impl ScopeEndpoint { - fn new(factory: Rc>>) -> Self { - ScopeEndpoint { factory } - } -} - -impl ServiceFactory for ScopeEndpoint { - type Response = ServiceResponse; - type Error = Error; - type Config = (); - type Service = ScopeService; - type InitError = (); - type Future = LocalBoxFuture<'static, Result>; - - fn new_service(&self, _: ()) -> Self::Future { - self.factory.borrow_mut().as_mut().unwrap().new_service(()) - } -} - #[cfg(test)] mod tests { use actix_service::Service;