use std::cell::RefCell; use std::marker::PhantomData; use std::rc::Rc; use actix_http::{http::Method, Error, Extensions, Response}; use actix_service::{NewService, Service}; use futures::{Async, Future, IntoFuture, Poll}; use crate::data::RouteData; use crate::extract::FromRequest; use crate::guard::{self, Guard}; use crate::handler::{AsyncFactory, AsyncHandler, Extract, Factory, Handler}; use crate::responder::Responder; use crate::service::{ServiceRequest, ServiceResponse}; use crate::HttpResponse; type BoxedRouteService = Box< Service< Request = Req, Response = Res, Error = Error, Future = Box>, >, >; type BoxedRouteNewService = Box< NewService< Request = Req, Response = Res, Error = Error, InitError = (), Service = BoxedRouteService, Future = Box, Error = ()>>, >, >; /// Resource route definition /// /// Route uses builder-like pattern for configuration. /// If handler is not explicitly set, default *404 Not Found* handler is used. pub struct Route

{ service: BoxedRouteNewService, ServiceResponse>, guards: Rc>>, data: Option, data_ref: Rc>>>, } impl Route

{ /// Create new route which matches any request. pub fn new() -> Route

{ let data_ref = Rc::new(RefCell::new(None)); Route { service: Box::new(RouteNewService::new( Extract::new(data_ref.clone()).and_then( Handler::new(HttpResponse::NotFound).map_err(|_| panic!()), ), )), guards: Rc::new(Vec::new()), data: None, data_ref, } } pub(crate) fn finish(mut self) -> Self { *self.data_ref.borrow_mut() = self.data.take().map(Rc::new); 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

{ type Request = ServiceRequest

; type Response = ServiceResponse; type Error = Error; type InitError = (); type Service = RouteService

; type Future = CreateRouteService

; fn new_service(&self, _: &()) -> Self::Future { CreateRouteService { fut: self.service.new_service(&()), guards: self.guards.clone(), } } } type RouteFuture

= Box< Future, ServiceResponse>, Error = ()>, >; pub struct CreateRouteService

{ fut: RouteFuture

, guards: Rc>>, } impl

Future for CreateRouteService

{ type Item = RouteService

; type Error = (); fn poll(&mut self) -> Poll { match self.fut.poll()? { Async::Ready(service) => Ok(Async::Ready(RouteService { service, guards: self.guards.clone(), })), Async::NotReady => Ok(Async::NotReady), } } } pub struct RouteService

{ service: BoxedRouteService, ServiceResponse>, guards: Rc>>, } impl

RouteService

{ pub fn check(&self, req: &mut ServiceRequest

) -> bool { for f in self.guards.iter() { if !f.check(req.head()) { return false; } } true } } impl

Service for RouteService

{ type Request = ServiceRequest

; type Response = ServiceResponse; type Error = Error; type Future = Box>; fn poll_ready(&mut self) -> Poll<(), Self::Error> { self.service.poll_ready() } fn call(&mut self, req: ServiceRequest

) -> Self::Future { self.service.call(req) } } impl Route

{ /// Add method guard to the route. /// /// ```rust /// # use actix_web::*; /// # fn main() { /// 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 { Rc::get_mut(&mut self.guards) .unwrap() .push(Box::new(guard::Method(method))); self } /// Add guard to the route. /// /// ```rust /// # use actix_web::*; /// # fn main() { /// 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 { Rc::get_mut(&mut self.guards).unwrap().push(Box::new(f)); self } /// Set handler function, use request extractors for parameters. /// /// ```rust /// #[macro_use] extern crate serde_derive; /// use actix_web::{web, http, App}; /// /// #[derive(Deserialize)] /// struct Info { /// username: String, /// } /// /// /// extract path info using serde /// fn index(info: web::Path) -> String { /// format!("Welcome {}!", info.username) /// } /// /// fn main() { /// let app = App::new().service( /// web::resource("/{username}/index.html") // <- define path parameters /// .route(web::get().to(index)) // <- register handler /// ); /// } /// ``` /// /// It is possible to use multiple extractors for one handler function. /// /// ```rust /// # use std::collections::HashMap; /// # use serde_derive::Deserialize; /// use actix_web::{web, App}; /// /// #[derive(Deserialize)] /// struct Info { /// username: String, /// } /// /// /// extract path info using serde /// fn index(path: web::Path, query: web::Query>, body: web::Json) -> String { /// format!("Welcome {}!", path.username) /// } /// /// fn main() { /// let app = App::new().service( /// web::resource("/{username}/index.html") // <- define path parameters /// .route(web::get().to(index)) /// ); /// } /// ``` pub fn to(mut self, handler: F) -> Route

where F: Factory + 'static, T: FromRequest

+ 'static, R: Responder + 'static, { self.service = Box::new(RouteNewService::new( Extract::new(self.data_ref.clone()) .and_then(Handler::new(handler).map_err(|_| panic!())), )); self } /// Set async handler function, use request extractors for parameters. /// This method has to be used if your handler function returns `impl Future<>` /// /// ```rust /// # use futures::future::ok; /// #[macro_use] extern crate serde_derive; /// use actix_web::{web, App, Error}; /// use futures::Future; /// /// #[derive(Deserialize)] /// struct Info { /// username: String, /// } /// /// /// extract path info using serde /// fn index(info: web::Path) -> impl Future { /// ok("Hello World!") /// } /// /// fn main() { /// let app = App::new().service( /// web::resource("/{username}/index.html") // <- define path parameters /// .route(web::get().to_async(index)) // <- register async handler /// ); /// } /// ``` #[allow(clippy::wrong_self_convention)] pub fn to_async(mut self, handler: F) -> Self where F: AsyncFactory, T: FromRequest

+ 'static, R: IntoFuture + 'static, R::Item: Into, R::Error: Into, { self.service = Box::new(RouteNewService::new( Extract::new(self.data_ref.clone()) .and_then(AsyncHandler::new(handler).map_err(|_| panic!())), )); self } /// Provide route specific data. This method allows to add extractor /// configuration or specific state available via `RouteData` extractor. /// /// ```rust /// use actix_web::{web, App}; /// /// /// extract text data from request /// fn index(body: String) -> String { /// format!("Body {}!", body) /// } /// /// fn main() { /// let app = App::new().service( /// web::resource("/index.html").route( /// web::get() /// // limit size of the payload /// .data(web::PayloadConfig::new(4096)) /// // register handler /// .to(index) /// )); /// } /// ``` pub fn data(mut self, data: C) -> Self { if self.data.is_none() { self.data = Some(Extensions::new()); } self.data.as_mut().unwrap().insert(RouteData::new(data)); self } } struct RouteNewService where T: NewService, Error = (Error, ServiceRequest

)>, { service: T, _t: PhantomData

, } impl RouteNewService where T: NewService< Request = ServiceRequest

, Response = ServiceResponse, Error = (Error, ServiceRequest

), >, T::Future: 'static, T::Service: 'static, ::Future: 'static, { pub fn new(service: T) -> Self { RouteNewService { service, _t: PhantomData, } } } impl NewService for RouteNewService where T: NewService< Request = ServiceRequest

, Response = ServiceResponse, Error = (Error, ServiceRequest

), >, T::Future: 'static, T::Service: 'static, ::Future: 'static, { type Request = ServiceRequest

; type Response = ServiceResponse; type Error = Error; type InitError = (); type Service = BoxedRouteService, Self::Response>; type Future = Box>; fn new_service(&self, _: &()) -> Self::Future { Box::new( self.service .new_service(&()) .map_err(|_| ()) .and_then(|service| { let service: BoxedRouteService<_, _> = Box::new(RouteServiceWrapper { service, _t: PhantomData, }); Ok(service) }), ) } } struct RouteServiceWrapper { service: T, _t: PhantomData

, } impl Service for RouteServiceWrapper where T::Future: 'static, T: Service< Request = ServiceRequest

, Response = ServiceResponse, Error = (Error, ServiceRequest

), >, { type Request = ServiceRequest

; type Response = ServiceResponse; type Error = Error; type Future = Box>; fn poll_ready(&mut self) -> Poll<(), Self::Error> { self.service.poll_ready().map_err(|(e, _)| e) } fn call(&mut self, req: ServiceRequest

) -> Self::Future { Box::new(self.service.call(req).then(|res| match res { Ok(res) => Ok(res), Err((err, req)) => Ok(req.error_response(err)), })) } } #[cfg(test)] mod tests { use std::time::Duration; use futures::Future; use tokio_timer::sleep; use crate::http::{Method, StatusCode}; use crate::test::{call_success, init_service, TestRequest}; use crate::{error, web, App, HttpResponse}; #[test] fn test_route() { let mut srv = init_service( App::new().service( web::resource("/test") .route(web::get().to(|| HttpResponse::Ok())) .route(web::put().to(|| { Err::(error::ErrorBadRequest("err")) })) .route(web::post().to_async(|| { sleep(Duration::from_millis(100)) .then(|_| HttpResponse::Created()) })) .route(web::delete().to_async(|| { sleep(Duration::from_millis(100)).then(|_| { Err::(error::ErrorBadRequest("err")) }) })), ), ); let req = TestRequest::with_uri("/test") .method(Method::GET) .to_request(); let resp = call_success(&mut srv, req); assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/test") .method(Method::POST) .to_request(); let resp = call_success(&mut srv, req); assert_eq!(resp.status(), StatusCode::CREATED); let req = TestRequest::with_uri("/test") .method(Method::PUT) .to_request(); let resp = call_success(&mut srv, req); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_uri("/test") .method(Method::DELETE) .to_request(); let resp = call_success(&mut srv, req); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_uri("/test") .method(Method::HEAD) .to_request(); let resp = call_success(&mut srv, req); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); } }