From fe22e831447551a661f1e3d75185a16b259eba88 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 6 Mar 2019 15:47:15 -0800 Subject: [PATCH] refactor service registration process; unify services and resources --- actix-session/src/cookie.rs | 26 +- actix-session/src/lib.rs | 14 +- actix-staticfiles/src/lib.rs | 30 +- examples/basic.rs | 28 +- src/app.rs | 420 +++++++++------------------- src/config.rs | 103 +++++++ src/extract.rs | 104 +++---- src/guard.rs | 15 +- src/lib.rs | 91 +++++- src/middleware/defaultheaders.rs | 9 +- src/request.rs | 7 +- src/resource.rs | 117 ++++++-- src/responder.rs | 21 +- src/route.rs | 43 +-- src/scope.rs | 456 +++++++++++++------------------ src/server.rs | 40 +-- src/service.rs | 35 +++ tests/test_server.rs | 65 ++--- 18 files changed, 845 insertions(+), 779 deletions(-) create mode 100644 src/config.rs diff --git a/actix-session/src/cookie.rs b/actix-session/src/cookie.rs index 9cde02e0c..7fd5ec643 100644 --- a/actix-session/src/cookie.rs +++ b/actix-session/src/cookie.rs @@ -174,7 +174,7 @@ impl CookieSessionInner { /// /// ```rust /// use actix_session::CookieSession; -/// use actix_web::{App, HttpResponse, HttpServer}; +/// use actix_web::{web, App, HttpResponse, HttpServer}; /// /// fn main() { /// let app = App::new().middleware( @@ -183,7 +183,7 @@ impl CookieSessionInner { /// .name("actix_session") /// .path("/") /// .secure(true)) -/// .resource("/", |r| r.to(|| HttpResponse::Ok())); +/// .service(web::resource("/").to(|| HttpResponse::Ok())); /// } /// ``` pub struct CookieSession(Rc); @@ -314,19 +314,17 @@ where #[cfg(test)] mod tests { use super::*; - use actix_web::{test, App}; + use actix_web::{test, web, App}; #[test] fn cookie_session() { let mut app = test::init_service( App::new() .middleware(CookieSession::signed(&[0; 32]).secure(false)) - .resource("/", |r| { - r.to(|ses: Session| { - let _ = ses.set("counter", 100); - "test" - }) - }), + .service(web::resource("/").to(|ses: Session| { + let _ = ses.set("counter", 100); + "test" + })), ); let request = test::TestRequest::get().to_request(); @@ -342,12 +340,10 @@ mod tests { let mut app = test::init_service( App::new() .middleware(CookieSession::signed(&[0; 32]).secure(false)) - .resource("/", |r| { - r.to(|ses: Session| { - let _ = ses.set("counter", 100); - "test" - }) - }), + .service(web::resource("/").to(|ses: Session| { + let _ = ses.set("counter", 100); + "test" + })), ); let request = test::TestRequest::get().to_request(); diff --git a/actix-session/src/lib.rs b/actix-session/src/lib.rs index f57e11f2f..62bc5c8fb 100644 --- a/actix-session/src/lib.rs +++ b/actix-session/src/lib.rs @@ -13,7 +13,7 @@ //! extractor allows us to get or set session data. //! //! ```rust -//! use actix_web::{App, HttpServer, HttpResponse, Error}; +//! use actix_web::{web, App, HttpServer, HttpResponse, Error}; //! use actix_session::{Session, CookieSession}; //! //! fn index(session: Session) -> Result<&'static str, Error> { @@ -29,19 +29,17 @@ //! } //! //! fn main() -> std::io::Result<()> { -//! let sys = actix_rt::System::new("example"); // <- create Actix runtime -//! +//! # std::thread::spawn(|| //! HttpServer::new( //! || App::new().middleware( //! CookieSession::signed(&[0; 32]) // <- create cookie based session middleware //! .secure(false) //! ) -//! .resource("/", |r| r.to(|| HttpResponse::Ok()))) +//! .service(web::resource("/").to(|| HttpResponse::Ok()))) //! .bind("127.0.0.1:59880")? -//! .start(); -//! # actix_rt::System::current().stop(); -//! sys.run(); -//! Ok(()) +//! .run() +//! # ); +//! # Ok(()) //! } //! ``` use std::cell::RefCell; diff --git a/actix-staticfiles/src/lib.rs b/actix-staticfiles/src/lib.rs index 81d8269c9..7c3f68493 100644 --- a/actix-staticfiles/src/lib.rs +++ b/actix-staticfiles/src/lib.rs @@ -17,7 +17,7 @@ use v_htmlescape::escape as escape_html_entity; use actix_http::error::{Error, ErrorInternalServerError}; use actix_service::{boxed::BoxedNewService, NewService, Service}; -use actix_web::dev::{self, HttpServiceFactory, ResourceDef, Url}; +use actix_web::dev::{self, AppConfig, HttpServiceFactory, ResourceDef, Url}; use actix_web::{ blocking, FromRequest, HttpRequest, HttpResponse, Responder, ServiceFromRequest, ServiceRequest, ServiceResponse, @@ -226,7 +226,7 @@ fn directory_listing( /// } /// ``` pub struct StaticFiles { - path: ResourceDef, + path: String, directory: PathBuf, index: Option, show_index: bool, @@ -259,7 +259,7 @@ impl StaticFiles { } StaticFiles { - path: ResourceDef::root_prefix(path), + path: path.to_string(), directory: dir, index: None, show_index: false, @@ -300,15 +300,21 @@ impl StaticFiles { } } -impl HttpServiceFactory

for StaticFiles { - type Factory = Self; - - fn rdef(&self) -> &ResourceDef { - &self.path - } - - fn create(self) -> Self { - self +impl HttpServiceFactory

for StaticFiles +where + P: 'static, + C: StaticFileConfig + 'static, +{ + fn register(self, config: &mut AppConfig

) { + if self.default.borrow().is_none() { + *self.default.borrow_mut() = Some(config.default_service()); + } + let rdef = if config.is_root() { + ResourceDef::root_prefix(&self.path) + } else { + ResourceDef::prefix(&self.path) + }; + config.register_service(rdef, None, self) } } diff --git a/examples/basic.rs b/examples/basic.rs index 7c72439e7..5fd862d49 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -27,23 +27,23 @@ fn main() -> std::io::Result<()> { App::new() .middleware(middleware::DefaultHeaders::new().header("X-Version", "0.2")) .middleware(middleware::Compress::default()) - .resource("/resource1/index.html", |r| r.route(web::get().to(index))) - .resource("/resource2/index.html", |r| { - r.middleware( - middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"), - ) - .default_resource(|r| { - r.route(web::route().to(|| HttpResponse::MethodNotAllowed())) - }) - .route(web::method(Method::GET).to_async(index_async)) - }) - .resource("/test1.html", |r| r.to(|| "Test\r\n")) - .resource("/", |r| r.to(no_params)) + .service(web::resource("/resource1/index.html").route(web::get().to(index))) + .service( + web::resource("/resource2/index.html") + .middleware( + middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"), + ) + .default_resource(|r| { + r.route(web::route().to(|| HttpResponse::MethodNotAllowed())) + }) + .route(web::method(Method::GET).to_async(index_async)), + ) + .service(web::resource("/test1.html").to(|| "Test\r\n")) + .service(web::resource("/").to(no_params)) }) .bind("127.0.0.1:8080")? .workers(1) .start(); - let _ = sys.run(); - Ok(()) + sys.run() } diff --git a/src/app.rs b/src/app.rs index d503d8ddb..7c194d273 100644 --- a/src/app.rs +++ b/src/app.rs @@ -7,16 +7,20 @@ use actix_http::{Extensions, PayloadStream, Request, Response}; use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url}; use actix_service::boxed::{self, BoxedNewService, BoxedService}; use actix_service::{ - AndThenNewService, ApplyTransform, IntoNewService, IntoTransform, NewService, - Service, Transform, + fn_service, AndThenNewService, ApplyTransform, IntoNewService, IntoTransform, + NewService, Service, Transform, }; use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, IntoFuture, Poll}; +use crate::config::AppConfig; use crate::guard::Guard; use crate::resource::Resource; -use crate::scope::{insert_slash, Scope}; -use crate::service::{ServiceRequest, ServiceResponse}; +use crate::route::Route; +use crate::service::{ + HttpServiceFactory, ServiceFactory, ServiceFactoryWrapper, ServiceRequest, + ServiceResponse, +}; use crate::state::{State, StateFactory, StateFactoryResult}; type Guards = Vec>; @@ -24,19 +28,6 @@ type HttpService

= BoxedService, ServiceResponse, ()>; type HttpNewService

= BoxedNewService<(), ServiceRequest

, ServiceResponse, (), ()>; type BoxedResponse = Box>; -pub trait HttpServiceFactory

{ - type Factory: NewService< - ServiceRequest

, - Response = ServiceResponse, - Error = (), - InitError = (), - >; - - fn rdef(&self) -> &ResourceDef; - - fn create(self) -> Self::Factory; -} - /// Application builder - structure that follows the builder pattern /// for building application instances. pub struct App @@ -97,9 +88,9 @@ where /// fn main() { /// let app = App::new() /// .state(MyState{ counter: Cell::new(0) }) - /// .resource( - /// "/index.html", - /// |r| r.route(web::get().to(index))); + /// .service( + /// web::resource("/index.html").route( + /// web::get().to(index))); /// } /// ``` pub fn state(mut self, state: S) -> Self { @@ -120,112 +111,6 @@ where self } - /// Configure scope for common root path. - /// - /// Scopes collect multiple paths under a common path prefix. - /// Scope path can contain variable path segments as resources. - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{App, HttpRequest, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().scope("/{project_id}", |scope| { - /// scope - /// .resource("/path1", |r| r.to(|| HttpResponse::Ok())) - /// .resource("/path2", |r| r.to(|| HttpResponse::Ok())) - /// .resource("/path3", |r| r.to(|| HttpResponse::MethodNotAllowed())) - /// }); - /// } - /// ``` - /// - /// In the above example, three routes get added: - /// * /{project_id}/path1 - /// * /{project_id}/path2 - /// * /{project_id}/path3 - /// - pub fn scope(self, path: &str, f: F) -> AppRouter> - where - F: FnOnce(Scope

) -> Scope

, - { - let mut scope = f(Scope::new(path)); - let rdef = scope.rdef().clone(); - let default = scope.get_default(); - let guards = scope.take_guards(); - - let fref = Rc::new(RefCell::new(None)); - AppRouter { - chain: self.chain, - services: vec![(rdef, boxed::new_service(scope.into_new_service()), guards)], - default: None, - defaults: vec![default], - endpoint: AppEntry::new(fref.clone()), - factory_ref: fref, - extensions: self.extensions, - state: self.state, - _t: PhantomData, - } - } - - /// Configure resource for a specific path. - /// - /// Resources may have variable path segments. For example, a - /// resource with the path `/a/{name}/c` would match all incoming - /// requests with paths such as `/a/b/c`, `/a/1/c`, or `/a/etc/c`. - /// - /// A variable segment is specified in the form `{identifier}`, - /// where the identifier can be used later in a request handler to - /// access the matched value for that segment. This is done by - /// looking up the identifier in the `Params` object returned by - /// `HttpRequest.match_info()` method. - /// - /// By default, each segment matches the regular expression `[^{}/]+`. - /// - /// You can also specify a custom regex in the form `{identifier:regex}`: - /// - /// For instance, to route `GET`-requests on any route matching - /// `/users/{userid}/{friend}` and store `userid` and `friend` in - /// the exposed `Params` object: - /// - /// ```rust - /// # extern crate actix_web; - /// use actix_web::{web, http, App, HttpResponse}; - /// - /// fn main() { - /// let app = App::new().resource("/users/{userid}/{friend}", |r| { - /// r.route(web::get().to(|| HttpResponse::Ok())) - /// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) - /// }); - /// } - /// ``` - pub fn resource(self, path: &str, f: F) -> AppRouter> - where - F: FnOnce(Resource

) -> Resource, - U: NewService< - ServiceRequest

, - Response = ServiceResponse, - Error = (), - InitError = (), - > + 'static, - { - let rdef = ResourceDef::new(&insert_slash(path)); - let res = f(Resource::new()); - let default = res.get_default(); - - let fref = Rc::new(RefCell::new(None)); - AppRouter { - chain: self.chain, - services: vec![(rdef, boxed::new_service(res.into_new_service()), None)], - default: None, - defaults: vec![default], - endpoint: AppEntry::new(fref.clone()), - factory_ref: fref, - extensions: self.extensions, - state: self.state, - _t: PhantomData, - } - } - /// Register a middleware. pub fn middleware( self, @@ -259,7 +144,6 @@ where state: self.state, services: Vec::new(), default: None, - defaults: Vec::new(), factory_ref: fref, extensions: self.extensions, _t: PhantomData, @@ -298,25 +182,52 @@ where } } - /// Register resource handler service. + /// Configure route for a specific path. + /// + /// This is a simplified version of the `App::service()` method. + /// This method can not be could multiple times, in that case + /// multiple resources with one route would be registered for same resource path. + /// + /// ```rust + /// use actix_web::{web, App, HttpResponse, extract::Path}; + /// + /// fn index(data: Path<(String, String)>) -> &'static str { + /// "Welcome!" + /// } + /// + /// fn main() { + /// let app = App::new() + /// .route("/test1", web::get().to(index)) + /// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed())); + /// } + /// ``` + pub fn route( + self, + path: &str, + mut route: Route

, + ) -> AppRouter> { + self.service( + Resource::new(path) + .add_guards(route.take_guards()) + .route(route), + ) + } + + /// Register http service. pub fn service(self, service: F) -> AppRouter> where F: HttpServiceFactory

+ 'static, { let fref = Rc::new(RefCell::new(None)); + AppRouter { chain: self.chain, - services: vec![( - service.rdef().clone(), - boxed::new_service(service.create().map_init_err(|_| ())), - None, - )], default: None, - defaults: vec![], endpoint: AppEntry::new(fref.clone()), factory_ref: fref, extensions: self.extensions, state: self.state, + services: vec![Box::new(ServiceFactoryWrapper::new(service))], _t: PhantomData, } } @@ -338,10 +249,9 @@ where /// for building application instances. pub struct AppRouter { chain: C, - services: Vec<(ResourceDef, HttpNewService

, Option)>, - default: Option>>, - defaults: Vec>>>>>, endpoint: T, + services: Vec>>, + default: Option>>, factory_ref: Rc>>>, extensions: Extensions, state: Vec>, @@ -359,131 +269,48 @@ where InitError = (), >, { - /// Configure scope for common root path. + /// Configure route for a specific path. /// - /// Scopes collect multiple paths under a common path prefix. - /// Scope path can contain variable path segments as resources. + /// This is a simplified version of the `App::service()` method. + /// This method can not be could multiple times, in that case + /// multiple resources with one route would be registered for same resource path. /// /// ```rust - /// # extern crate actix_web; - /// use actix_web::{App, HttpRequest, HttpResponse}; + /// use actix_web::{web, App, HttpResponse, extract::Path}; /// - /// fn main() { - /// let app = App::new().scope("/{project_id}", |scope| { - /// scope - /// .resource("/path1", |r| r.to(|| HttpResponse::Ok())) - /// .resource("/path2", |r| r.to(|| HttpResponse::Ok())) - /// .resource("/path3", |r| r.to(|| HttpResponse::MethodNotAllowed())) - /// }); + /// fn index(data: Path<(String, String)>) -> &'static str { + /// "Welcome!" /// } - /// ``` - /// - /// In the above example, three routes get added: - /// * /{project_id}/path1 - /// * /{project_id}/path2 - /// * /{project_id}/path3 - /// - pub fn scope(mut self, path: &str, f: F) -> Self - where - F: FnOnce(Scope

) -> Scope

, - { - let mut scope = f(Scope::new(path)); - let rdef = scope.rdef().clone(); - let guards = scope.take_guards(); - self.defaults.push(scope.get_default()); - self.services - .push((rdef, boxed::new_service(scope.into_new_service()), guards)); - self - } - - /// Configure resource for a specific path. - /// - /// Resources may have variable path segments. For example, a - /// resource with the path `/a/{name}/c` would match all incoming - /// requests with paths such as `/a/b/c`, `/a/1/c`, or `/a/etc/c`. - /// - /// A variable segment is specified in the form `{identifier}`, - /// where the identifier can be used later in a request handler to - /// access the matched value for that segment. This is done by - /// looking up the identifier in the `Params` object returned by - /// `HttpRequest.match_info()` method. - /// - /// By default, each segment matches the regular expression `[^{}/]+`. - /// - /// You can also specify a custom regex in the form `{identifier:regex}`: - /// - /// For instance, to route `GET`-requests on any route matching - /// `/users/{userid}/{friend}` and store `userid` and `friend` in - /// the exposed `Params` object: - /// - /// ```rust - /// use actix_web::{web, http, App, HttpResponse}; /// /// fn main() { /// let app = App::new() - /// .resource("/users/{userid}/{friend}", |r| { - /// r.route(web::to(|| HttpResponse::Ok())) - /// }) - /// .resource("/index.html", |r| { - /// r.route(web::head().to(|| HttpResponse::MethodNotAllowed())) - /// }); + /// .route("/test1", web::get().to(index)) + /// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed())); /// } /// ``` - pub fn resource(mut self, path: &str, f: F) -> Self - where - F: FnOnce(Resource

) -> Resource, - U: NewService< - ServiceRequest

, - Response = ServiceResponse, - Error = (), - InitError = (), - > + 'static, - { - let rdef = ResourceDef::new(&insert_slash(path)); - let resource = f(Resource::new()); - self.defaults.push(resource.get_default()); - self.services.push(( - rdef, - boxed::new_service(resource.into_new_service()), - None, - )); - self + pub fn route(self, path: &str, mut route: Route

) -> Self { + self.service( + Resource::new(path) + .add_guards(route.take_guards()) + .route(route), + ) } - /// Default resource to be used if no matching route could be found. + /// Register http service. /// - /// Default resource works with resources only and does not work with - /// custom services. - pub fn default_resource(mut self, f: F) -> Self - where - F: FnOnce(Resource

) -> Resource, - U: NewService< - ServiceRequest

, - Response = ServiceResponse, - Error = (), - InitError = (), - > + 'static, - { - // create and configure default resource - self.default = Some(Rc::new(boxed::new_service( - f(Resource::new()).into_new_service().map_init_err(|_| ()), - ))); - - self - } - - /// Register resource handler service. + /// Http service is any type that implements `HttpServiceFactory` trait. + /// + /// Actix web provides several services implementations: + /// + /// * *Resource* is an entry in route table which corresponds to requested URL. + /// * *Scope* is a set of resources with common root path. + /// * "StaticFiles" is a service for static files support pub fn service(mut self, factory: F) -> Self where F: HttpServiceFactory

+ 'static, { - let rdef = factory.rdef().clone(); - - self.services.push(( - rdef, - boxed::new_service(factory.create().map_init_err(|_| ())), - None, - )); + self.services + .push(Box::new(ServiceFactoryWrapper::new(factory))); self } @@ -520,13 +347,34 @@ where state: self.state, services: self.services, default: self.default, - defaults: self.defaults, factory_ref: self.factory_ref, extensions: self.extensions, _t: PhantomData, } } + /// Default resource to be used if no matching route could be found. + /// + /// Default resource works with resources only and does not work with + /// custom services. + pub fn default_resource(mut self, f: F) -> Self + where + F: FnOnce(Resource

) -> Resource, + U: NewService< + ServiceRequest

, + Response = ServiceResponse, + Error = (), + InitError = (), + > + 'static, + { + // create and configure default resource + self.default = Some(Rc::new(boxed::new_service( + f(Resource::new("")).into_new_service().map_init_err(|_| ()), + ))); + + self + } + /// Register an external resource. /// /// External resources are useful for URL generation purposes only @@ -583,19 +431,30 @@ where { fn into_new_service(self) -> AndThenNewService, T> { // update resource default service - if self.default.is_some() { - for default in &self.defaults { - if default.borrow_mut().is_none() { - *default.borrow_mut() = self.default.clone(); - } - } - } + let default = self.default.unwrap_or_else(|| { + Rc::new(boxed::new_service(fn_service(|req: ServiceRequest

| { + Ok(req.into_response(Response::NotFound().finish())) + }))) + }); + + let mut config = AppConfig::new( + "127.0.0.1:8080".parse().unwrap(), + "localhost:8080".to_owned(), + false, + default.clone(), + ); + + // register services + self.services + .into_iter() + .for_each(|mut srv| srv.register(&mut config)); // set factory *self.factory_ref.borrow_mut() = Some(AppRoutingFactory { - default: self.default.clone(), + default: default, services: Rc::new( - self.services + config + .into_services() .into_iter() .map(|(rdef, srv, guards)| (rdef, srv, RefCell::new(guards))) .collect(), @@ -613,7 +472,7 @@ where pub struct AppRoutingFactory

{ services: Rc, RefCell>)>>, - default: Option>>, + default: Rc>, } impl NewService> for AppRoutingFactory

{ @@ -624,12 +483,6 @@ impl NewService> for AppRoutingFactory

{ type Future = AppRoutingFactoryResponse

; fn new_service(&self, _: &()) -> Self::Future { - let default_fut = if let Some(ref default) = self.default { - Some(default.new_service(&())) - } else { - None - }; - AppRoutingFactoryResponse { fut: self .services @@ -643,7 +496,7 @@ impl NewService> for AppRoutingFactory

{ }) .collect(), default: None, - default_fut, + default_fut: Some(self.default.new_service(&())), } } } @@ -929,16 +782,15 @@ where #[cfg(test)] mod tests { - use actix_http::http::{Method, StatusCode}; - use super::*; - use crate::test::{self, block_on, TestRequest}; + use crate::http::{Method, StatusCode}; + use crate::test::{self, block_on, init_service, TestRequest}; use crate::{web, HttpResponse, State}; #[test] fn test_default_resource() { - let mut srv = test::init_service( - App::new().resource("/test", |r| r.to(|| HttpResponse::Ok())), + let mut srv = init_service( + App::new().service(web::resource("/test").to(|| HttpResponse::Ok())), ); let req = TestRequest::with_uri("/test").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -948,13 +800,14 @@ mod tests { let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let mut srv = test::init_service( + let mut srv = init_service( App::new() - .resource("/test", |r| r.to(|| HttpResponse::Ok())) - .resource("/test2", |r| { - r.default_resource(|r| r.to(|| HttpResponse::Created())) - .route(web::get().to(|| HttpResponse::Ok())) - }) + .service(web::resource("/test").to(|| HttpResponse::Ok())) + .service( + web::resource("/test2") + .default_resource(|r| r.to(|| HttpResponse::Created())) + .route(web::get().to(|| HttpResponse::Ok())), + ) .default_resource(|r| r.to(|| HttpResponse::MethodNotAllowed())), ); @@ -975,22 +828,21 @@ mod tests { #[test] fn test_state() { - let mut srv = test::init_service( + let mut srv = init_service( App::new() .state(10usize) - .resource("/", |r| r.to(|_: State| HttpResponse::Ok())), + .service(web::resource("/").to(|_: State| HttpResponse::Ok())), ); let req = TestRequest::default().to_request(); let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); - let mut srv = test::init_service( + let mut srv = init_service( App::new() .state(10u32) - .resource("/", |r| r.to(|_: State| HttpResponse::Ok())), + .service(web::resource("/").to(|_: State| HttpResponse::Ok())), ); - let req = TestRequest::default().to_request(); let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); @@ -998,22 +850,20 @@ mod tests { #[test] fn test_state_factory() { - let mut srv = test::init_service( + let mut srv = init_service( App::new() .state_factory(|| Ok::<_, ()>(10usize)) - .resource("/", |r| r.to(|_: State| HttpResponse::Ok())), + .service(web::resource("/").to(|_: State| HttpResponse::Ok())), ); - let req = TestRequest::default().to_request(); let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::OK); - let mut srv = test::init_service( + let mut srv = init_service( App::new() .state_factory(|| Ok::<_, ()>(10u32)) - .resource("/", |r| r.to(|_: State| HttpResponse::Ok())), + .service(web::resource("/").to(|_: State| HttpResponse::Ok())), ); - let req = TestRequest::default().to_request(); let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 000000000..483b0a508 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,103 @@ +use std::net::SocketAddr; +use std::rc::Rc; + +use actix_router::ResourceDef; +use actix_service::{boxed, IntoNewService, NewService}; + +use crate::guard::Guard; +use crate::service::{ServiceRequest, ServiceResponse}; + +type Guards = Vec>; +type HttpNewService

= + boxed::BoxedNewService<(), ServiceRequest

, ServiceResponse, (), ()>; + +/// Application configuration +pub struct AppConfig

{ + addr: SocketAddr, + secure: bool, + host: String, + root: bool, + default: Rc>, + services: Vec<(ResourceDef, HttpNewService

, Option)>, +} + +impl AppConfig

{ + /// Crate server settings instance + pub(crate) fn new( + addr: SocketAddr, + host: String, + secure: bool, + default: Rc>, + ) -> Self { + AppConfig { + addr, + secure, + host, + default, + root: true, + services: Vec::new(), + } + } + + /// Check if root is beeing configured + pub fn is_root(&self) -> bool { + self.root + } + + pub(crate) fn into_services( + self, + ) -> Vec<(ResourceDef, HttpNewService

, Option)> { + self.services + } + + pub(crate) fn clone_config(&self) -> Self { + AppConfig { + addr: self.addr, + secure: self.secure, + host: self.host.clone(), + default: self.default.clone(), + services: Vec::new(), + root: false, + } + } + + /// Returns the socket address of the local half of this TCP connection + pub fn local_addr(&self) -> SocketAddr { + self.addr + } + + /// Returns true if connection is secure(https) + pub fn secure(&self) -> bool { + self.secure + } + + /// Returns host header value + pub fn host(&self) -> &str { + &self.host + } + + pub fn default_service(&self) -> Rc> { + self.default.clone() + } + + pub fn register_service( + &mut self, + rdef: ResourceDef, + guards: Option>>, + service: F, + ) where + F: IntoNewService>, + S: NewService< + ServiceRequest

, + Response = ServiceResponse, + Error = (), + InitError = (), + > + 'static, + { + self.services.push(( + rdef, + boxed::new_service(service.into_new_service()), + guards, + )); + } +} diff --git a/src/extract.rs b/src/extract.rs index 7350d7d95..0b212aba5 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -88,9 +88,9 @@ impl ExtractorConfig for () { /// } /// /// fn main() { -/// let app = App::new().resource( -/// "/{username}/{count}/index.html", // <- define path parameters -/// |r| r.route(web::get().to(index)) // <- register handler with `Path` extractor +/// let app = App::new().service( +/// web::resource("/{username}/{count}/index.html") // <- define path parameters +/// .route(web::get().to(index)) // <- register handler with `Path` extractor /// ); /// } /// ``` @@ -113,9 +113,9 @@ impl ExtractorConfig for () { /// } /// /// fn main() { -/// let app = App::new().resource( -/// "/{username}/index.html", // <- define path parameters -/// |r| r.route(web::get().to(index)) // <- use handler with Path` extractor +/// let app = App::new().service( +/// web::resource("/{username}/index.html") // <- define path parameters +/// .route(web::get().to(index)) // <- use handler with Path` extractor /// ); /// } /// ``` @@ -180,9 +180,9 @@ impl From for Path { /// } /// /// fn main() { -/// let app = App::new().resource( -/// "/{username}/{count}/index.html", // <- define path parameters -/// |r| r.route(web::get().to(index)) // <- register handler with `Path` extractor +/// let app = App::new().service( +/// web::resource("/{username}/{count}/index.html") // <- define path parameters +/// .route(web::get().to(index)) // <- register handler with `Path` extractor /// ); /// } /// ``` @@ -205,9 +205,9 @@ impl From for Path { /// } /// /// fn main() { -/// let app = App::new().resource( -/// "/{username}/index.html", // <- define path parameters -/// |r| r.route(web::get().to(index)) // <- use handler with Path` extractor +/// let app = App::new().service( +/// web::resource("/{username}/index.html") // <- define path parameters +/// .route(web::get().to(index)) // <- use handler with Path` extractor /// ); /// } /// ``` @@ -266,9 +266,8 @@ impl fmt::Display for Path { /// } /// /// fn main() { -/// let app = App::new().resource( -/// "/index.html", -/// |r| r.route(web::get().to(index))); // <- use `Query` extractor +/// let app = App::new().service( +/// web::resource("/index.html").route(web::get().to(index))); // <- use `Query` extractor /// } /// ``` pub struct Query(T); @@ -322,9 +321,9 @@ impl Query { /// } /// /// fn main() { -/// let app = App::new().resource( -/// "/index.html", -/// |r| r.route(web::get().to(index))); // <- use `Query` extractor +/// let app = App::new().service( +/// web::resource("/index.html") +/// .route(web::get().to(index))); // <- use `Query` extractor /// } /// ``` impl FromRequest

for Query @@ -462,14 +461,13 @@ impl fmt::Display for Form { /// } /// /// fn main() { -/// let app = App::new().resource( -/// "/index.html", -/// |r| { -/// r.route(web::get() +/// let app = App::new().service( +/// web::resource("/index.html") +/// .route(web::get() /// // change `Form` extractor configuration /// .config(extract::FormConfig::default().limit(4097)) /// .to(index)) -/// }); +/// ); /// } /// ``` #[derive(Clone)] @@ -535,9 +533,10 @@ impl Default for FormConfig { /// } /// /// fn main() { -/// let app = App::new().resource( -/// "/index.html", -/// |r| r.route(web::post().to(index))); +/// let app = App::new().service( +/// web::resource("/index.html").route( +/// web::post().to(index)) +/// ); /// } /// ``` /// @@ -645,9 +644,10 @@ impl Responder for Json { /// } /// /// fn main() { -/// let app = App::new().resource( -/// "/index.html", -/// |r| r.route(web::post().to(index))); +/// let app = App::new().service( +/// web::resource("/index.html").route( +/// web::post().to(index)) +/// ); /// } /// ``` impl FromRequest

for Json @@ -692,16 +692,17 @@ where /// } /// /// fn main() { -/// let app = App::new().resource("/index.html", |r| { -/// r.route(web::post().config( -/// // change json extractor configuration -/// extract::JsonConfig::default().limit(4096) -/// .error_handler(|err, req| { // <- create custom error response -/// error::InternalError::from_response( -/// err, HttpResponse::Conflict().finish()).into() -/// })) -/// .to(index)) -/// }); +/// let app = App::new().service( +/// web::resource("/index.html").route( +/// web::post().config( +/// // change json extractor configuration +/// extract::JsonConfig::default().limit(4096) +/// .error_handler(|err, req| { // <- create custom error response +/// error::InternalError::from_response( +/// err, HttpResponse::Conflict().finish()).into() +/// })) +/// .to(index)) +/// ); /// } /// ``` #[derive(Clone)] @@ -757,8 +758,10 @@ impl Default for JsonConfig { /// } /// /// fn main() { -/// let app = App::new() -/// .resource("/index.html", |r| r.route(web::get().to(index))); +/// let app = App::new().service( +/// web::resource("/index.html").route( +/// web::get().to(index)) +/// ); /// } /// ``` impl

FromRequest

for Bytes @@ -801,12 +804,12 @@ where /// } /// /// fn main() { -/// let app = App::new().resource("/index.html", |r| { -/// r.route( +/// let app = App::new().service( +/// web::resource("/index.html").route( /// web::get() /// .config(extract::PayloadConfig::new(4096)) // <- limit size of the payload /// .to(index)) // <- register handler with extractor params -/// }); +/// ); /// } /// ``` impl

FromRequest

for String @@ -896,9 +899,10 @@ where /// } /// /// fn main() { -/// let app = App::new().resource("/users/:first", |r| { -/// r.route(web::post().to(index)) -/// }); +/// let app = App::new().service( +/// web::resource("/users/:first").route( +/// web::post().to(index)) +/// ); /// } /// ``` impl FromRequest

for Option @@ -959,9 +963,9 @@ where /// } /// /// fn main() { -/// let app = App::new().resource("/users/:first", |r| { -/// r.route(web::post().to(index)) -/// }); +/// let app = App::new().service( +/// web::resource("/users/:first").route(web::post().to(index)) +/// ); /// } /// ``` impl FromRequest

for Result diff --git a/src/guard.rs b/src/guard.rs index 93b6e1325..1632b9975 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -19,11 +19,10 @@ pub trait Guard { /// use actix_web::{web, guard, App, HttpResponse}; /// /// fn main() { -/// App::new().resource("/index.html", |r| -/// r.route( -/// web::route() -/// .guard(guard::Any(guard::Get()).or(guard::Post())) -/// .to(|| HttpResponse::MethodNotAllowed())) +/// App::new().service(web::resource("/index.html").route( +/// web::route() +/// .guard(guard::Any(guard::Get()).or(guard::Post())) +/// .to(|| HttpResponse::MethodNotAllowed())) /// ); /// } /// ``` @@ -60,12 +59,12 @@ impl Guard for AnyGuard { /// use actix_web::{guard, web, App, HttpResponse}; /// /// fn main() { -/// App::new().resource("/index.html", |r| { -/// r.route(web::route() +/// App::new().service(web::resource("/index.html").route( +/// web::route() /// .guard( /// guard::All(guard::Get()).and(guard::Header("content-type", "text/plain"))) /// .to(|| HttpResponse::MethodNotAllowed())) -/// }); +/// ); /// } /// ``` pub fn All(guard: F) -> AllGuard { diff --git a/src/lib.rs b/src/lib.rs index 44dcde35b..3400fe290 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ pub mod extract; mod handler; // mod info; pub mod blocking; +mod config; pub mod guard; pub mod middleware; mod request; @@ -43,13 +44,24 @@ pub mod dev { //! use actix_web::dev::*; //! ``` - pub use crate::app::{AppRouter, HttpServiceFactory}; + pub use crate::app::AppRouter; + pub use crate::config::AppConfig; + pub use crate::service::HttpServiceFactory; + pub use actix_http::body::{Body, MessageBody, ResponseBody}; pub use actix_http::dev::ResponseBuilder as HttpResponseBuilder; pub use actix_http::{ Extensions, Payload, PayloadStream, RequestHead, ResponseHead, }; pub use actix_router::{Path, ResourceDef, Url}; + + pub(crate) fn insert_slash(path: &str) -> String { + let mut path = path.to_owned(); + if !path.is_empty() && !path.starts_with('/') { + path.insert(0, '/'); + }; + path + } } pub mod web { @@ -58,8 +70,74 @@ pub mod web { use crate::extract::FromRequest; use crate::handler::{AsyncFactory, Factory}; + use crate::resource::Resource; use crate::responder::Responder; - use crate::Route; + use crate::route::Route; + use crate::scope::Scope; + + /// Create resource for a specific path. + /// + /// Resources may have variable path segments. For example, a + /// resource with the path `/a/{name}/c` would match all incoming + /// requests with paths such as `/a/b/c`, `/a/1/c`, or `/a/etc/c`. + /// + /// A variable segment is specified in the form `{identifier}`, + /// where the identifier can be used later in a request handler to + /// access the matched value for that segment. This is done by + /// looking up the identifier in the `Params` object returned by + /// `HttpRequest.match_info()` method. + /// + /// By default, each segment matches the regular expression `[^{}/]+`. + /// + /// You can also specify a custom regex in the form `{identifier:regex}`: + /// + /// For instance, to route `GET`-requests on any route matching + /// `/users/{userid}/{friend}` and store `userid` and `friend` in + /// the exposed `Params` object: + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::{web, http, App, HttpResponse}; + /// + /// fn main() { + /// let app = App::new().service( + /// web::resource("/users/{userid}/{friend}") + /// .route(web::get().to(|| HttpResponse::Ok())) + /// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) + /// ); + /// } + /// ``` + pub fn resource(path: &str) -> Resource

{ + Resource::new(path) + } + + /// Configure scope for common root path. + /// + /// Scopes collect multiple paths under a common path prefix. + /// Scope path can contain variable path segments as resources. + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::{web, App, HttpRequest, HttpResponse}; + /// + /// fn main() { + /// let app = App::new().service( + /// web::scope("/{project_id}") + /// .service(web::resource("/path1").to(|| HttpResponse::Ok())) + /// .service(web::resource("/path2").to(|| HttpResponse::Ok())) + /// .service(web::resource("/path3").to(|| HttpResponse::MethodNotAllowed())) + /// ); + /// } + /// ``` + /// + /// In the above example, three routes get added: + /// * /{project_id}/path1 + /// * /{project_id}/path2 + /// * /{project_id}/path3 + /// + pub fn scope(path: &str) -> Scope

{ + Scope::new(path) + } /// Create **route** without configuration. pub fn route() -> Route

{ @@ -105,7 +183,10 @@ pub mod web { /// unimplemented!() /// } /// - /// App::new().resource("/", |r| r.route(web::to(index))); + /// App::new().service( + /// web::resource("/").route( + /// web::to(index)) + /// ); /// ``` pub fn to(handler: F) -> Route

where @@ -125,7 +206,9 @@ pub mod web { /// futures::future::ok(HttpResponse::Ok().finish()) /// } /// - /// App::new().resource("/", |r| r.route(web::to_async(index))); + /// App::new().service(web::resource("/").route( + /// web::to_async(index)) + /// ); /// ``` pub fn to_async(handler: F) -> Route

where diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index 5fd519195..137913d21 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -19,10 +19,11 @@ use crate::service::{ServiceRequest, ServiceResponse}; /// fn main() { /// let app = App::new() /// .middleware(middleware::DefaultHeaders::new().header("X-Version", "0.2")) -/// .resource("/test", |r| { -/// r.route(web::get().to(|| HttpResponse::Ok())) -/// .route(web::method(http::Method::HEAD).to(|| HttpResponse::MethodNotAllowed())) -/// }); +/// .service( +/// web::resource("/test") +/// .route(web::get().to(|| HttpResponse::Ok())) +/// .route(web::method(http::Method::HEAD).to(|| HttpResponse::MethodNotAllowed())) +/// ); /// } /// ``` #[derive(Clone)] diff --git a/src/request.rs b/src/request.rs index 211f60b85..1c86cac35 100644 --- a/src/request.rs +++ b/src/request.rs @@ -149,9 +149,10 @@ impl HttpMessage for HttpRequest { /// } /// /// fn main() { -/// let app = App::new().resource("/users/:first", |r| { -/// r.route(web::get().to(index)) -/// }); +/// let app = App::new().service( +/// web::resource("/users/{first}").route( +/// web::get().to(index)) +/// ); /// } /// ``` impl

FromRequest

for HttpRequest { diff --git a/src/resource.rs b/src/resource.rs index 8d81ead06..b5cf640c4 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -9,7 +9,9 @@ use actix_service::{ use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, IntoFuture, Poll}; +use crate::dev::{insert_slash, AppConfig, HttpServiceFactory, ResourceDef}; use crate::extract::FromRequest; +use crate::guard::Guard; use crate::handler::{AsyncFactory, Factory}; use crate::responder::Responder; use crate::route::{CreateRouteService, Route, RouteService}; @@ -32,38 +34,37 @@ type HttpNewService

= BoxedNewService<(), ServiceRequest

, ServiceResponse, /// use actix_web::{web, App, HttpResponse}; /// /// fn main() { -/// let app = App::new() -/// .resource( -/// "/", |r| r.route(web::get().to(|| HttpResponse::Ok()))); +/// let app = App::new().service( +/// web::resource("/") +/// .route(web::get().to(|| HttpResponse::Ok()))); /// } pub struct Resource> { - routes: Vec>, endpoint: T, + rdef: String, + routes: Vec>, + guards: Vec>, default: Rc>>>>, factory_ref: Rc>>>, } impl

Resource

{ - pub fn new() -> Resource

{ + pub fn new(path: &str) -> Resource

{ let fref = Rc::new(RefCell::new(None)); Resource { routes: Vec::new(), + rdef: path.to_string(), endpoint: ResourceEndpoint::new(fref.clone()), factory_ref: fref, + guards: Vec::new(), default: Rc::new(RefCell::new(None)), } } } -impl

Default for Resource

{ - fn default() -> Self { - Self::new() - } -} - -impl Resource +impl Resource where + P: 'static, T: NewService< ServiceRequest

, Response = ServiceResponse, @@ -71,19 +72,52 @@ where InitError = (), >, { + /// Add match guard to a resource. + /// + /// ```rust + /// use actix_web::{web, guard, App, HttpResponse, extract::Path}; + /// + /// fn index(data: Path<(String, String)>) -> &'static str { + /// "Welcome!" + /// } + /// + /// fn main() { + /// let app = App::new() + /// .service( + /// web::resource("/app") + /// .guard(guard::Header("content-type", "text/plain")) + /// .route(web::get().to(index)) + /// ) + /// .service( + /// web::resource("/app") + /// .guard(guard::Header("content-type", "text/json")) + /// .route(web::get().to(|| HttpResponse::MethodNotAllowed())) + /// ); + /// } + /// ``` + pub fn guard(mut self, guard: G) -> Self { + self.guards.push(Box::new(guard)); + self + } + + pub(crate) fn add_guards(mut self, guards: Vec>) -> Self { + self.guards.extend(guards); + self + } + /// Register a new route. /// /// ```rust /// use actix_web::{web, guard, App, HttpResponse}; /// /// fn main() { - /// let app = App::new() - /// .resource("/", |r| { - /// r.route(web::route() + /// let app = App::new().service( + /// web::resource("/").route( + /// web::route() /// .guard(guard::Any(guard::Get()).or(guard::Put())) /// .guard(guard::Header("Content-Type", "text/plain")) /// .to(|| HttpResponse::Ok())) - /// }); + /// ); /// } /// ``` /// @@ -93,12 +127,12 @@ where /// use actix_web::{web, guard, App, HttpResponse}; /// /// fn main() { - /// let app = App::new() - /// .resource("/container/", |r| { - /// r.route(web::get().to(get_handler)) + /// let app = App::new().service( + /// web::resource("/container/") + /// .route(web::get().to(get_handler)) /// .route(web::post().to(post_handler)) /// .route(web::delete().to(delete_handler)) - /// }); + /// ); /// } /// # fn get_handler() {} /// # fn post_handler() {} @@ -109,8 +143,7 @@ where self } - /// Register a new route and add handler. This route get called for all - /// requests. + /// Register a new route and add handler. This route matches all requests. /// /// ```rust /// use actix_web::*; @@ -119,7 +152,7 @@ where /// unimplemented!() /// } /// - /// App::new().resource("/", |r| r.to(index)); + /// App::new().service(web::resource("/").to(index)); /// ``` /// /// This is shortcut for: @@ -128,7 +161,7 @@ where /// # extern crate actix_web; /// # use actix_web::*; /// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } - /// App::new().resource("/", |r| r.route(web::route().to(index))); + /// App::new().service(web::resource("/").route(web::route().to(index))); /// ``` pub fn to(mut self, handler: F) -> Self where @@ -150,7 +183,7 @@ where /// ok(HttpResponse::Ok().finish()) /// } /// - /// App::new().resource("/", |r| r.to_async(index)); + /// App::new().service(web::resource("/").to_async(index)); /// ``` /// /// This is shortcut for: @@ -161,7 +194,7 @@ where /// # fn index(req: HttpRequest) -> Box> { /// # unimplemented!() /// # } - /// App::new().resource("/", |r| r.route(web::route().to_async(index))); + /// App::new().service(web::resource("/").route(web::route().to_async(index))); /// ``` #[allow(clippy::wrong_self_convention)] pub fn to_async(mut self, handler: F) -> Self @@ -206,6 +239,8 @@ where let endpoint = ApplyTransform::new(mw, self.endpoint); Resource { endpoint, + rdef: self.rdef, + guards: self.guards, routes: self.routes, default: self.default, factory_ref: self.factory_ref, @@ -222,14 +257,38 @@ where { // create and configure default resource self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::new_service( - f(Resource::new()).into_new_service().map_init_err(|_| ()), + f(Resource::new("")).into_new_service().map_init_err(|_| ()), ))))); self } +} - pub(crate) fn get_default(&self) -> Rc>>>> { - self.default.clone() +impl HttpServiceFactory

for Resource +where + P: 'static, + T: NewService< + ServiceRequest

, + Response = ServiceResponse, + Error = (), + InitError = (), + > + 'static, +{ + fn register(mut self, config: &mut AppConfig

) { + if self.default.borrow().is_none() { + *self.default.borrow_mut() = Some(config.default_service()); + } + let guards = if self.guards.is_empty() { + None + } else { + Some(std::mem::replace(&mut self.guards, Vec::new())) + }; + let rdef = if config.is_root() { + ResourceDef::new(&insert_slash(&self.rdef)) + } else { + ResourceDef::new(&insert_slash(&self.rdef)) + }; + config.register_service(rdef, guards, self) } } diff --git a/src/responder.rs b/src/responder.rs index dedfa1b44..9e9e0f109 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -288,22 +288,21 @@ where #[cfg(test)] mod tests { - // use actix_http::body::Body; - use actix_http::body::{Body, ResponseBody}; - use actix_http::http::StatusCode; - use actix_service::{IntoNewService, NewService, Service}; + use actix_service::Service; use bytes::Bytes; - use crate::test::TestRequest; - use crate::App; + use crate::body::{Body, ResponseBody}; + use crate::http::StatusCode; + use crate::test::{init_service, TestRequest}; + use crate::{web, App}; #[test] fn test_option_responder() { - let app = App::new() - .resource("/none", |r| r.to(|| -> Option<&'static str> { None })) - .resource("/some", |r| r.to(|| Some("some"))) - .into_new_service(); - let mut srv = TestRequest::block_on(app.new_service(&())).unwrap(); + let mut srv = init_service( + App::new() + .service(web::resource("/none").to(|| -> Option<&'static str> { None })) + .service(web::resource("/some").to(|| Some("some"))), + ); let req = TestRequest::with_uri("/none").to_request(); let resp = TestRequest::block_on(srv.call(req)).unwrap(); diff --git a/src/route.rs b/src/route.rs index 42e784881..bac897ab1 100644 --- a/src/route.rs +++ b/src/route.rs @@ -84,6 +84,10 @@ impl Route

{ *self.config_ref.borrow_mut() = self.config.storage.clone(); self } + + pub(crate) fn take_guards(&mut self) -> Vec> { + std::mem::replace(Rc::get_mut(&mut self.guards).unwrap(), Vec::new()) + } } impl

NewService> for Route

{ @@ -161,12 +165,12 @@ impl Route

{ /// ```rust /// # use actix_web::*; /// # fn main() { - /// App::new().resource("/path", |r| { - /// r.route(web::get() - /// .guard(guard::Get()) + /// App::new().service(web::resource("/path").route( + /// web::get() + /// .method(http::Method::CONNECT) /// .guard(guard::Header("content-type", "text/plain")) /// .to(|req: HttpRequest| HttpResponse::Ok())) - /// }); + /// ); /// # } /// ``` pub fn method(mut self, method: Method) -> Self { @@ -181,12 +185,12 @@ impl Route

{ /// ```rust /// # use actix_web::*; /// # fn main() { - /// App::new().resource("/path", |r| { - /// r.route(web::route() + /// App::new().service(web::resource("/path").route( + /// web::route() /// .guard(guard::Get()) /// .guard(guard::Header("content-type", "text/plain")) /// .to(|req: HttpRequest| HttpResponse::Ok())) - /// }); + /// ); /// # } /// ``` pub fn guard(mut self, f: F) -> Self { @@ -229,9 +233,9 @@ impl Route

{ /// } /// /// fn main() { - /// let app = App::new().resource( - /// "/{username}/index.html", // <- define path parameters - /// |r| r.route(web::get().to(index)), // <- register handler + /// let app = App::new().service( + /// web::resource("/{username}/index.html") // <- define path parameters + /// .route(web::get().to(index)) // <- register handler /// ); /// } /// ``` @@ -254,9 +258,9 @@ impl Route

{ /// } /// /// fn main() { - /// let app = App::new().resource( - /// "/{username}/index.html", // <- define path parameters - /// |r| r.route(web::get().to(index)), + /// let app = App::new().service( + /// web::resource("/{username}/index.html") // <- define path parameters + /// .route(web::get().to(index)) /// ); /// } /// ``` @@ -294,9 +298,9 @@ impl Route

{ /// } /// /// fn main() { - /// let app = App::new().resource( - /// "/{username}/index.html", // <- define path parameters - /// |r| r.route(web::get().to_async(index)), // <- register async handler + /// let app = App::new().service( + /// web::resource("/{username}/index.html") // <- define path parameters + /// .route(web::get().to_async(index)) // <- register async handler /// ); /// } /// ``` @@ -328,15 +332,14 @@ impl Route

{ /// } /// /// fn main() { - /// let app = App::new().resource("/index.html", |r| { - /// r.route( + /// let app = App::new().service( + /// web::resource("/index.html").route( /// web::get() /// // limit size of the payload /// .config(extract::PayloadConfig::new(4096)) /// // register handler /// .to(index) - /// ) - /// }); + /// )); /// } /// ``` pub fn config(mut self, config: C) -> Self { diff --git a/src/scope.rs b/src/scope.rs index dc88388f8..9bc0b50d7 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -10,10 +10,13 @@ use actix_service::{ use futures::future::{ok, Either, Future, FutureResult}; use futures::{Async, Poll}; +use crate::dev::{insert_slash, AppConfig, HttpServiceFactory}; use crate::guard::Guard; use crate::resource::Resource; use crate::route::Route; -use crate::service::{ServiceRequest, ServiceResponse}; +use crate::service::{ + ServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse, +}; type Guards = Vec>; type HttpService

= BoxedService, ServiceResponse, ()>; @@ -35,12 +38,12 @@ type BoxedResponse = Box>; /// use actix_web::{web, App, HttpResponse}; /// /// fn main() { -/// let app = App::new().scope("/{project_id}/", |scope| { -/// scope -/// .resource("/path1", |r| r.to(|| HttpResponse::Ok())) -/// .resource("/path2", |r| r.route(web::get().to(|| HttpResponse::Ok()))) -/// .resource("/path3", |r| r.route(web::head().to(|| HttpResponse::MethodNotAllowed()))) -/// }); +/// let app = App::new().service( +/// web::scope("/{project_id}/") +/// .service(web::resource("/path1").to(|| HttpResponse::Ok())) +/// .service(web::resource("/path2").route(web::get().to(|| HttpResponse::Ok()))) +/// .service(web::resource("/path3").route(web::head().to(|| HttpResponse::MethodNotAllowed()))) +/// ); /// } /// ``` /// @@ -51,11 +54,10 @@ type BoxedResponse = Box>; /// pub struct Scope> { endpoint: T, - rdef: ResourceDef, - services: Vec<(ResourceDef, HttpNewService

, Option)>, + rdef: String, + services: Vec>>, guards: Vec>, default: Rc>>>>, - defaults: Vec>>>>>, factory_ref: Rc>>>, } @@ -63,21 +65,20 @@ impl Scope

{ /// Create a new scope pub fn new(path: &str) -> Scope

{ let fref = Rc::new(RefCell::new(None)); - let rdef = ResourceDef::prefix(&insert_slash(path)); Scope { endpoint: ScopeEndpoint::new(fref.clone()), - rdef: rdef.clone(), + rdef: path.to_string(), guards: Vec::new(), services: Vec::new(), default: Rc::new(RefCell::new(None)), - defaults: Vec::new(), factory_ref: fref, } } } -impl Scope +impl Scope where + P: 'static, T: NewService< ServiceRequest

, Response = ServiceResponse, @@ -85,12 +86,7 @@ where InitError = (), >, { - #[inline] - pub(crate) fn rdef(&self) -> &ResourceDef { - &self.rdef - } - - /// Add guard to a scope. + /// Add match guard to a scope. /// /// ```rust /// use actix_web::{web, guard, App, HttpRequest, HttpResponse, extract::Path}; @@ -100,14 +96,14 @@ where /// } /// /// fn main() { - /// let app = App::new().scope("/app", |scope| { - /// scope + /// let app = App::new().service( + /// web::scope("/app") /// .guard(guard::Header("content-type", "text/plain")) - /// .route("/test1",web::get().to(index)) + /// .route("/test1", web::get().to(index)) /// .route("/test2", web::post().to(|r: HttpRequest| { /// HttpResponse::MethodNotAllowed() /// })) - /// }); + /// ); /// } /// ``` pub fn guard(mut self, guard: G) -> Self { @@ -115,10 +111,10 @@ where self } - /// Create nested scope. + /// Create nested service. /// /// ```rust - /// use actix_web::{App, HttpRequest}; + /// use actix_web::{web, App, HttpRequest}; /// /// struct AppState; /// @@ -127,28 +123,25 @@ where /// } /// /// fn main() { - /// let app = App::new().scope("/app", |scope| { - /// scope.nested("/v1", |scope| scope.resource("/test1", |r| r.to(index))) - /// }); + /// let app = App::new().service( + /// web::scope("/app").service( + /// web::scope("/v1") + /// .service(web::resource("/test1").to(index))) + /// ); /// } /// ``` - pub fn nested(mut self, path: &str, f: F) -> Self + pub fn service(mut self, factory: F) -> Self where - F: FnOnce(Scope

) -> Scope

, + F: HttpServiceFactory

+ 'static, { - let mut scope = f(Scope::new(path)); - let rdef = scope.rdef().clone(); - let guards = scope.take_guards(); - self.defaults.push(scope.get_default()); self.services - .push((rdef, boxed::new_service(scope.into_new_service()), guards)); - + .push(Box::new(ServiceFactoryWrapper::new(factory))); self } /// Configure route for a specific path. /// - /// This is a simplified version of the `Scope::resource()` method. + /// This is a simplified version of the `Scope::service()` method. /// This method can not be could multiple times, in that case /// multiple resources with one route would be registered for same resource path. /// @@ -160,58 +153,19 @@ where /// } /// /// fn main() { - /// let app = App::new().scope("/app", |scope| { - /// scope.route("/test1", web::get().to(index)) + /// let app = App::new().service( + /// web::scope("/app") + /// .route("/test1", web::get().to(index)) /// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed())) - /// }); + /// ); /// } /// ``` - pub fn route(self, path: &str, route: Route

) -> Self { - self.resource(path, move |r| r.route(route)) - } - - /// configure resource for a specific path. - /// - /// This method is similar to an `App::resource()` method. - /// Resources may have variable path segments. Resource path uses scope - /// path as a path prefix. - /// - /// ```rust - /// use actix_web::*; - /// - /// fn main() { - /// let app = App::new().scope("/api", |scope| { - /// scope.resource("/users/{userid}/{friend}", |r| { - /// r.route(web::get().to(|| HttpResponse::Ok())) - /// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) - /// .route(web::route() - /// .guard(guard::Any(guard::Get()).or(guard::Put())) - /// .guard(guard::Header("Content-Type", "text/plain")) - /// .to(|| HttpResponse::Ok())) - /// }) - /// }); - /// } - /// ``` - pub fn resource(mut self, path: &str, f: F) -> Self - where - F: FnOnce(Resource

) -> Resource, - U: NewService< - ServiceRequest

, - Response = ServiceResponse, - Error = (), - InitError = (), - > + 'static, - { - // add resource - let rdef = ResourceDef::new(&insert_slash(path)); - let resource = f(Resource::new()); - self.defaults.push(resource.get_default()); - self.services.push(( - rdef, - boxed::new_service(resource.into_new_service()), - None, - )); - self + pub fn route(self, path: &str, mut route: Route

) -> Self { + self.service( + Resource::new(path) + .add_guards(route.take_guards()) + .route(route), + ) } /// Default resource to be used if no matching route could be found. @@ -227,7 +181,7 @@ where { // create and configure default resource self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::new_service( - f(Resource::new()).into_new_service().map_init_err(|_| ()), + f(Resource::new("")).into_new_service().map_init_err(|_| ()), ))))); self @@ -267,62 +221,53 @@ where guards: self.guards, services: self.services, default: self.default, - defaults: self.defaults, factory_ref: self.factory_ref, } } - - pub(crate) fn get_default(&self) -> Rc>>>> { - self.default.clone() - } - - pub(crate) fn take_guards(&mut self) -> Option>> { - if self.guards.is_empty() { - None - } else { - Some(std::mem::replace(&mut self.guards, Vec::new())) - } - } } -pub(crate) fn insert_slash(path: &str) -> String { - let mut path = path.to_owned(); - if !path.is_empty() && !path.starts_with('/') { - path.insert(0, '/'); - }; - path -} - -impl IntoNewService> for Scope +impl HttpServiceFactory

for Scope where + P: 'static, T: NewService< - ServiceRequest

, - Response = ServiceResponse, - Error = (), - InitError = (), - >, + ServiceRequest

, + Response = ServiceResponse, + Error = (), + InitError = (), + > + 'static, { - fn into_new_service(self) -> T { - // update resource default service - if let Some(ref d) = *self.default.as_ref().borrow() { - for default in &self.defaults { - if default.borrow_mut().is_none() { - *default.borrow_mut() = Some(d.clone()); - } - } + fn register(self, config: &mut AppConfig

) { + if self.default.borrow().is_none() { + *self.default.borrow_mut() = Some(config.default_service()); } + // register services + let mut cfg = config.clone_config(); + self.services + .into_iter() + .for_each(|mut srv| srv.register(&mut cfg)); + *self.factory_ref.borrow_mut() = Some(ScopeFactory { default: self.default.clone(), services: Rc::new( - self.services + cfg.into_services() .into_iter() .map(|(rdef, srv, guards)| (rdef, srv, RefCell::new(guards))) .collect(), ), }); - self.endpoint + let guards = if self.guards.is_empty() { + None + } else { + Some(self.guards) + }; + let rdef = if config.is_root() { + ResourceDef::prefix(&insert_slash(&self.rdef)) + } else { + ResourceDef::prefix(&insert_slash(&self.rdef)) + }; + config.register_service(rdef, guards, self.endpoint) } } @@ -504,19 +449,22 @@ impl NewService> for ScopeEndpoint

{ #[cfg(test)] mod tests { - use actix_http::body::{Body, ResponseBody}; - use actix_http::http::{Method, StatusCode}; - use actix_service::{IntoNewService, NewService, Service}; + use actix_service::Service; use bytes::Bytes; - use crate::test::{self, block_on, TestRequest}; + use crate::body::{Body, ResponseBody}; + use crate::http::{Method, StatusCode}; + use crate::test::{block_on, init_service, TestRequest}; use crate::{guard, web, App, HttpRequest, HttpResponse}; #[test] fn test_scope() { - let mut srv = test::init_service(App::new().scope("/app", |scope| { - scope.resource("/path1", |r| r.to(|| HttpResponse::Ok())) - })); + let mut srv = init_service( + App::new().service( + web::scope("/app") + .service(web::resource("/path1").to(|| HttpResponse::Ok())), + ), + ); let req = TestRequest::with_uri("/app/path1").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -525,11 +473,13 @@ mod tests { #[test] fn test_scope_root() { - let mut srv = test::init_service(App::new().scope("/app", |scope| { - scope - .resource("", |r| r.to(|| HttpResponse::Ok())) - .resource("/", |r| r.to(|| HttpResponse::Created())) - })); + let mut srv = init_service( + App::new().service( + web::scope("/app") + .service(web::resource("").to(|| HttpResponse::Ok())) + .service(web::resource("/").to(|| HttpResponse::Created())), + ), + ); let req = TestRequest::with_uri("/app").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -542,9 +492,9 @@ mod tests { #[test] fn test_scope_root2() { - let mut srv = test::init_service(App::new().scope("/app/", |scope| { - scope.resource("", |r| r.to(|| HttpResponse::Ok())) - })); + let mut srv = init_service(App::new().service( + web::scope("/app/").service(web::resource("").to(|| HttpResponse::Ok())), + )); let req = TestRequest::with_uri("/app").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -557,12 +507,9 @@ mod tests { #[test] fn test_scope_root3() { - let app = App::new() - .scope("/app/", |scope| { - scope.resource("/", |r| r.to(|| HttpResponse::Ok())) - }) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + let mut srv = init_service(App::new().service( + web::scope("/app/").service(web::resource("/").to(|| HttpResponse::Ok())), + )); let req = TestRequest::with_uri("/app").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -575,15 +522,13 @@ mod tests { #[test] fn test_scope_route() { - let app = App::new() - .scope("app", |scope| { - scope.resource("/path1", |r| { - r.route(web::get().to(|| HttpResponse::Ok())) - .route(web::delete().to(|| HttpResponse::Ok())) - }) - }) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + let mut srv = init_service( + App::new().service( + web::scope("app") + .route("/path1", web::get().to(|| HttpResponse::Ok())) + .route("/path1", web::delete().to(|| HttpResponse::Ok())), + ), + ); let req = TestRequest::with_uri("/app/path1").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -604,15 +549,15 @@ mod tests { #[test] fn test_scope_route_without_leading_slash() { - let app = App::new() - .scope("app", |scope| { - scope.resource("path1", |r| { - r.route(web::get().to(|| HttpResponse::Ok())) - .route(web::delete().to(|| HttpResponse::Ok())) - }) - }) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + let mut srv = init_service( + App::new().service( + web::scope("app").service( + web::resource("path1") + .route(web::get().to(|| HttpResponse::Ok())) + .route(web::delete().to(|| HttpResponse::Ok())), + ), + ), + ); let req = TestRequest::with_uri("/app/path1").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -633,14 +578,13 @@ mod tests { #[test] fn test_scope_guard() { - let app = App::new() - .scope("/app", |scope| { - scope + let mut srv = init_service( + App::new().service( + web::scope("/app") .guard(guard::Get()) - .resource("/path1", |r| r.to(|| HttpResponse::Ok())) - }) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + .service(web::resource("/path1").to(|| HttpResponse::Ok())), + ), + ); let req = TestRequest::with_uri("/app/path1") .method(Method::POST) @@ -657,17 +601,13 @@ mod tests { #[test] fn test_scope_variable_segment() { - let app = App::new() - .scope("/ab-{project}", |scope| { - scope.resource("/path1", |r| { - r.to(|r: HttpRequest| { - HttpResponse::Ok() - .body(format!("project: {}", &r.match_info()["project"])) - }) - }) - }) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + let mut srv = + init_service(App::new().service(web::scope("/ab-{project}").service( + web::resource("/path1").to(|r: HttpRequest| { + HttpResponse::Ok() + .body(format!("project: {}", &r.match_info()["project"])) + }), + ))); let req = TestRequest::with_uri("/ab-project1/path1").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -688,14 +628,14 @@ mod tests { #[test] fn test_nested_scope() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("/t1", |scope| { - scope.resource("/path1", |r| r.to(|| HttpResponse::Created())) - }) - }) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + let mut srv = init_service( + App::new().service( + web::scope("/app") + .service(web::scope("/t1").service( + web::resource("/path1").to(|| HttpResponse::Created()), + )), + ), + ); let req = TestRequest::with_uri("/app/t1/path1").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -704,14 +644,14 @@ mod tests { #[test] fn test_nested_scope_no_slash() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("t1", |scope| { - scope.resource("/path1", |r| r.to(|| HttpResponse::Created())) - }) - }) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + let mut srv = init_service( + App::new().service( + web::scope("/app") + .service(web::scope("t1").service( + web::resource("/path1").to(|| HttpResponse::Created()), + )), + ), + ); let req = TestRequest::with_uri("/app/t1/path1").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -720,16 +660,15 @@ mod tests { #[test] fn test_nested_scope_root() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("/t1", |scope| { - scope - .resource("", |r| r.to(|| HttpResponse::Ok())) - .resource("/", |r| r.to(|| HttpResponse::Created())) - }) - }) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + let mut srv = init_service( + App::new().service( + web::scope("/app").service( + web::scope("/t1") + .service(web::resource("").to(|| HttpResponse::Ok())) + .service(web::resource("/").to(|| HttpResponse::Created())), + ), + ), + ); let req = TestRequest::with_uri("/app/t1").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -742,16 +681,15 @@ mod tests { #[test] fn test_nested_scope_filter() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("/t1", |scope| { - scope + let mut srv = init_service( + App::new().service( + web::scope("/app").service( + web::scope("/t1") .guard(guard::Get()) - .resource("/path1", |r| r.to(|| HttpResponse::Ok())) - }) - }) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + .service(web::resource("/path1").to(|| HttpResponse::Ok())), + ), + ), + ); let req = TestRequest::with_uri("/app/t1/path1") .method(Method::POST) @@ -768,21 +706,14 @@ mod tests { #[test] fn test_nested_scope_with_variable_segment() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("/{project_id}", |scope| { - scope.resource("/path1", |r| { - r.to(|r: HttpRequest| { - HttpResponse::Created().body(format!( - "project: {}", - &r.match_info()["project_id"] - )) - }) - }) - }) - }) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + let mut srv = init_service(App::new().service(web::scope("/app").service( + web::scope("/{project_id}").service(web::resource("/path1").to( + |r: HttpRequest| { + HttpResponse::Created() + .body(format!("project: {}", &r.match_info()["project_id"])) + }, + )), + ))); let req = TestRequest::with_uri("/app/project_1/path1").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -799,24 +730,17 @@ mod tests { #[test] fn test_nested2_scope_with_variable_segment() { - let app = App::new() - .scope("/app", |scope| { - scope.nested("/{project}", |scope| { - scope.nested("/{id}", |scope| { - scope.resource("/path1", |r| { - r.to(|r: HttpRequest| { - HttpResponse::Created().body(format!( - "project: {} - {}", - &r.match_info()["project"], - &r.match_info()["id"], - )) - }) - }) - }) - }) - }) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + let mut srv = init_service(App::new().service(web::scope("/app").service( + web::scope("/{project}").service(web::scope("/{id}").service( + web::resource("/path1").to(|r: HttpRequest| { + HttpResponse::Created().body(format!( + "project: {} - {}", + &r.match_info()["project"], + &r.match_info()["id"], + )) + }), + )), + ))); let req = TestRequest::with_uri("/app/test/1/path1").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -837,14 +761,13 @@ mod tests { #[test] fn test_default_resource() { - let app = App::new() - .scope("/app", |scope| { - scope - .resource("/path1", |r| r.to(|| HttpResponse::Ok())) - .default_resource(|r| r.to(|| HttpResponse::BadRequest())) - }) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + let mut srv = init_service( + App::new().service( + web::scope("/app") + .service(web::resource("/path1").to(|| HttpResponse::Ok())) + .default_resource(|r| r.to(|| HttpResponse::BadRequest())), + ), + ); let req = TestRequest::with_uri("/app/path2").to_request(); let resp = block_on(srv.call(req)).unwrap(); @@ -857,14 +780,15 @@ mod tests { #[test] fn test_default_resource_propagation() { - let app = App::new() - .scope("/app1", |scope| { - scope.default_resource(|r| r.to(|| HttpResponse::BadRequest())) - }) - .scope("/app2", |scope| scope) - .default_resource(|r| r.to(|| HttpResponse::MethodNotAllowed())) - .into_new_service(); - let mut srv = block_on(app.new_service(&())).unwrap(); + let mut srv = init_service( + App::new() + .service( + web::scope("/app1") + .default_resource(|r| r.to(|| HttpResponse::BadRequest())), + ) + .service(web::scope("/app2")) + .default_resource(|r| r.to(|| HttpResponse::MethodNotAllowed())), + ); let req = TestRequest::with_uri("/non-exist").to_request(); let resp = block_on(srv.call(req)).unwrap(); diff --git a/src/server.rs b/src/server.rs index 78ba692e4..c28cb280c 100644 --- a/src/server.rs +++ b/src/server.rs @@ -33,20 +33,19 @@ struct Config { /// /// ```rust /// use std::io; -/// use actix_web::{App, HttpResponse, HttpServer}; +/// use actix_web::{web, App, HttpResponse, HttpServer}; /// /// fn main() -> io::Result<()> { /// let sys = actix_rt::System::new("example"); // <- create Actix runtime /// /// HttpServer::new( /// || App::new() -/// .resource("/", |r| r.to(|| HttpResponse::Ok()))) +/// .service(web::resource("/").to(|| HttpResponse::Ok()))) /// .bind("127.0.0.1:59090")? /// .start(); /// /// # actix_rt::System::current().stop(); -/// sys.run(); -/// Ok(()) +/// sys.run() /// } /// ``` pub struct HttpServer @@ -448,17 +447,17 @@ where /// configured. /// /// ```rust - /// use actix_web::{App, HttpResponse, HttpServer}; + /// use std::io; + /// use actix_web::{web, App, HttpResponse, HttpServer}; /// - /// fn main() { + /// fn main() -> io::Result<()> { /// let sys = actix_rt::System::new("example"); // <- create Actix system /// - /// HttpServer::new(|| App::new().resource("/", |r| r.to(|| HttpResponse::Ok()))) - /// .bind("127.0.0.1:0") - /// .expect("Can not bind to 127.0.0.1:0") + /// HttpServer::new(|| App::new().service(web::resource("/").to(|| HttpResponse::Ok()))) + /// .bind("127.0.0.1:0")? /// .start(); /// # actix_rt::System::current().stop(); - /// sys.run(); // <- Run actix system, this method starts all async processes + /// sys.run() // <- Run actix system, this method starts all async processes /// } /// ``` pub fn start(mut self) -> Server { @@ -472,20 +471,23 @@ where /// /// This methods panics if no socket addresses get bound. /// - /// ```rust,ignore - /// use actix_web::{App, HttpResponse, HttpServer}; + /// ```rust + /// use std::io; + /// use actix_web::{web, App, HttpResponse, HttpServer}; /// - /// fn main() { - /// HttpServer::new(|| App::new().resource("/", |r| r.to(|| HttpResponse::Ok()))) - /// .bind("127.0.0.1:0") - /// .expect("Can not bind to 127.0.0.1:0") - /// .run(); + /// fn main() -> io::Result<()> { + /// # std::thread::spawn(|| { + /// HttpServer::new(|| App::new().service(web::resource("/").to(|| HttpResponse::Ok()))) + /// .bind("127.0.0.1:0")? + /// .run() + /// # }); + /// # Ok(()) /// } /// ``` - pub fn run(self) { + pub fn run(self) -> io::Result<()> { let sys = System::new("http-server"); self.start(); - sys.run(); + sys.run() } } diff --git a/src/service.rs b/src/service.rs index c9666e31e..e2213060c 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,6 +1,7 @@ use std::borrow::Cow; use std::cell::{Ref, RefMut}; use std::fmt; +use std::marker::PhantomData; use std::rc::Rc; use actix_http::body::{Body, MessageBody, ResponseBody}; @@ -12,8 +13,42 @@ use actix_http::{ use actix_router::{Path, Resource, Url}; use futures::future::{ok, FutureResult, IntoFuture}; +use crate::config::AppConfig; use crate::request::HttpRequest; +pub trait HttpServiceFactory

{ + fn register(self, config: &mut AppConfig

); +} + +pub(crate) trait ServiceFactory

{ + fn register(&mut self, config: &mut AppConfig

); +} + +pub(crate) struct ServiceFactoryWrapper { + factory: Option, + _t: PhantomData

, +} + +impl ServiceFactoryWrapper { + pub fn new(factory: T) -> Self { + Self { + factory: Some(factory), + _t: PhantomData, + } + } +} + +impl ServiceFactory

for ServiceFactoryWrapper +where + T: HttpServiceFactory

, +{ + fn register(&mut self, config: &mut AppConfig

) { + if let Some(item) = self.factory.take() { + item.register(config) + } + } +} + pub struct ServiceRequest

{ req: HttpRequest, payload: Payload

, diff --git a/tests/test_server.rs b/tests/test_server.rs index d28f207c1..ebe968fa5 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -40,7 +40,8 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ fn test_body() { let mut srv = TestServer::new(|| { h1::H1Service::new( - App::new().resource("/", |r| r.route(web::to(|| Response::Ok().body(STR)))), + App::new() + .service(web::resource("/").route(web::to(|| Response::Ok().body(STR)))), ) }); @@ -59,7 +60,7 @@ fn test_body_gzip() { h1::H1Service::new( App::new() .middleware(middleware::Compress::new(ContentEncoding::Gzip)) - .resource("/", |r| r.route(web::to(|| Response::Ok().body(STR)))), + .service(web::resource("/").route(web::to(|| Response::Ok().body(STR)))), ) }); @@ -87,9 +88,10 @@ fn test_body_gzip_large() { h1::H1Service::new( App::new() .middleware(middleware::Compress::new(ContentEncoding::Gzip)) - .resource("/", |r| { - r.route(web::to(move || Response::Ok().body(data.clone()))) - }), + .service( + web::resource("/") + .route(web::to(move || Response::Ok().body(data.clone()))), + ), ) }); @@ -120,9 +122,10 @@ fn test_body_gzip_large_random() { h1::H1Service::new( App::new() .middleware(middleware::Compress::new(ContentEncoding::Gzip)) - .resource("/", |r| { - r.route(web::to(move || Response::Ok().body(data.clone()))) - }), + .service( + web::resource("/") + .route(web::to(move || Response::Ok().body(data.clone()))), + ), ) }); @@ -147,13 +150,11 @@ fn test_body_chunked_implicit() { h1::H1Service::new( App::new() .middleware(middleware::Compress::new(ContentEncoding::Gzip)) - .resource("/", |r| { - r.route(web::get().to(move || { - Response::Ok().streaming(once(Ok::<_, Error>( - Bytes::from_static(STR.as_ref()), - ))) - })) - }), + .service(web::resource("/").route(web::get().to(move || { + Response::Ok().streaming(once(Ok::<_, Error>(Bytes::from_static( + STR.as_ref(), + )))) + }))), ) }); @@ -181,13 +182,11 @@ fn test_body_br_streaming() { h1::H1Service::new( App::new() .middleware(middleware::Compress::new(ContentEncoding::Br)) - .resource("/", |r| { - r.route(web::to(move || { - Response::Ok().streaming(once(Ok::<_, Error>( - Bytes::from_static(STR.as_ref()), - ))) - })) - }), + .service(web::resource("/").route(web::to(move || { + Response::Ok().streaming(once(Ok::<_, Error>(Bytes::from_static( + STR.as_ref(), + )))) + }))), ) }); @@ -208,9 +207,9 @@ fn test_body_br_streaming() { #[test] fn test_head_binary() { let mut srv = TestServer::new(move || { - h1::H1Service::new(App::new().resource("/", |r| { - r.route(web::head().to(move || Response::Ok().content_length(100).body(STR))) - })) + h1::H1Service::new(App::new().service(web::resource("/").route( + web::head().to(move || Response::Ok().content_length(100).body(STR)), + ))) }); let request = srv.head().finish().unwrap(); @@ -230,14 +229,14 @@ fn test_head_binary() { #[test] fn test_no_chunking() { let mut srv = TestServer::new(move || { - h1::H1Service::new(App::new().resource("/", |r| { - r.route(web::to(move || { + h1::H1Service::new(App::new().service(web::resource("/").route(web::to( + move || { Response::Ok() .no_chunking() .content_length(STR.len() as u64) .streaming(once(Ok::<_, Error>(Bytes::from_static(STR.as_ref())))) - })) - })) + }, + )))) }); let request = srv.get().finish().unwrap(); @@ -256,7 +255,9 @@ fn test_body_deflate() { h1::H1Service::new( App::new() .middleware(middleware::Compress::new(ContentEncoding::Deflate)) - .resource("/", |r| r.route(web::to(move || Response::Ok().body(STR)))), + .service( + web::resource("/").route(web::to(move || Response::Ok().body(STR))), + ), ) }); @@ -281,7 +282,9 @@ fn test_body_brotli() { h1::H1Service::new( App::new() .middleware(middleware::Compress::new(ContentEncoding::Br)) - .resource("/", |r| r.route(web::to(move || Response::Ok().body(STR)))), + .service( + web::resource("/").route(web::to(move || Response::Ok().body(STR))), + ), ) });