use std::future::Future; use std::pin::Pin; use std::rc::Rc; use std::task::{Context, Poll}; use actix_http::{http::Method, Error}; use actix_service::{Service, ServiceFactory}; use futures::future::{ok, Either, FutureExt, LocalBoxFuture, Ready}; 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< dyn Service< Request = Req, Response = Res, Error = Error, Future = LocalBoxFuture<'static, Result>, >, >; type BoxedRouteNewService = Box< dyn ServiceFactory< Config = (), Request = Req, Response = Res, Error = Error, InitError = (), Service = BoxedRouteService, Future = LocalBoxFuture<'static, Result, ()>>, >, >; /// 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, guards: Rc>>, } impl Route { /// Create new route which matches any request. pub fn new() -> Route { Route { service: Box::new(RouteNewService::new(Extract::new(Handler::new(|| { HttpResponse::NotFound() })))), guards: Rc::new(Vec::new()), } } pub(crate) fn take_guards(&mut self) -> Vec> { std::mem::replace(Rc::get_mut(&mut self.guards).unwrap(), Vec::new()) } } impl ServiceFactory for Route { type Config = (); 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 = LocalBoxFuture< 'static, Result, ()>, >; #[pin_project::pin_project] pub struct CreateRouteService { #[pin] fut: RouteFuture, guards: Rc>>, } impl Future for CreateRouteService { type Output = Result; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { let this = self.project(); match this.fut.poll(cx)? { Poll::Ready(service) => Poll::Ready(Ok(RouteService { service, guards: this.guards.clone(), })), Poll::Pending => Poll::Pending, } } } pub struct RouteService { service: BoxedRouteService, 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 = LocalBoxFuture<'static, Result>; fn poll_ready(&mut self, cx: &mut Context) -> Poll> { self.service.poll_ready(cx) } fn call(&mut self, req: ServiceRequest) -> Self::Future { self.service.call(req).boxed_local() } } 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 /// use actix_web::{web, http, App}; /// use serde_derive::Deserialize; /// /// #[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(Handler::new(handler)))); 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 actix_web::{web, App, Error}; /// use serde_derive::Deserialize; /// /// #[derive(Deserialize)] /// struct Info { /// username: String, /// } /// /// /// extract path info using serde /// async fn index(info: web::Path) -> Result<&'static str, Error> { /// 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: Future + 'static, U: Responder + 'static, { self.service = Box::new(RouteNewService::new(Extract::new(AsyncHandler::new( handler, )))); self } } struct RouteNewService where T: ServiceFactory, { service: T, } impl RouteNewService where T: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = (Error, ServiceRequest), >, T::Future: 'static, T::Service: 'static, ::Future: 'static, { pub fn new(service: T) -> Self { RouteNewService { service } } } impl ServiceFactory for RouteNewService where T: ServiceFactory< Config = (), Request = ServiceRequest, Response = ServiceResponse, Error = (Error, ServiceRequest), >, T::Future: 'static, T::Service: 'static, ::Future: 'static, { type Config = (); type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type InitError = (); type Service = BoxedRouteService; type Future = LocalBoxFuture<'static, Result>; fn new_service(&self, _: &()) -> Self::Future { self.service .new_service(&()) .map(|result| match result { Ok(service) => { let service: BoxedRouteService<_, _> = Box::new(RouteServiceWrapper { service }); Ok(service) } Err(_) => Err(()), }) .boxed_local() } } struct RouteServiceWrapper { service: T, } 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 = LocalBoxFuture<'static, Result>; fn poll_ready(&mut self, cx: &mut Context) -> Poll> { self.service.poll_ready(cx).map_err(|(e, _)| e) } fn call(&mut self, req: ServiceRequest) -> Self::Future { // let mut fut = self.service.call(req); self.service .call(req) .map(|res| match res { Ok(res) => Ok(res), Err((err, req)) => Ok(req.error_response(err)), }) .boxed_local() // match fut.poll() { // Poll::Ready(Ok(res)) => Either::Left(ok(res)), // Poll::Ready(Err((e, req))) => Either::Left(ok(req.error_response(e))), // Poll::Pending => Either::Right(Box::new(fut.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 bytes::Bytes; use futures::Future; use serde_derive::Serialize; use tokio_timer::delay_for; use crate::http::{Method, StatusCode}; use crate::test::{block_on, call_service, init_service, read_body, TestRequest}; use crate::{error, web, App, Error, HttpResponse}; #[derive(Serialize, PartialEq, Debug)] struct MyObject { name: String, } #[test] fn test_route() { block_on(async { 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(|| { async { delay_for(Duration::from_millis(100)).await; HttpResponse::Created() } })) .route(web::delete().to_async(|| { async { delay_for(Duration::from_millis(100)).await; Err::(error::ErrorBadRequest("err")) } })), ) .service(web::resource("/json").route(web::get().to_async(|| { async { delay_for(Duration::from_millis(25)).await; web::Json(MyObject { name: "test".to_string(), }) } }))), ) .await; let req = TestRequest::with_uri("/test") .method(Method::GET) .to_request(); let resp = call_service(&mut srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/test") .method(Method::POST) .to_request(); let resp = call_service(&mut srv, req).await; assert_eq!(resp.status(), StatusCode::CREATED); let req = TestRequest::with_uri("/test") .method(Method::PUT) .to_request(); let resp = call_service(&mut srv, req).await; assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_uri("/test") .method(Method::DELETE) .to_request(); let resp = call_service(&mut srv, req).await; assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_uri("/test") .method(Method::HEAD) .to_request(); let resp = call_service(&mut srv, req).await; assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); let req = TestRequest::with_uri("/json").to_request(); let resp = call_service(&mut srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let body = read_body(resp).await; assert_eq!(body, Bytes::from_static(b"{\"name\":\"test\"}")); }) } }