use std::marker::PhantomData; use futures::future::{Future, ok, err}; use error::Error; use httprequest::HttpRequest; use httpresponse::HttpResponse; /// Trait defines object that could be registered as route handler #[allow(unused_variables)] pub trait Handler: 'static { /// The type of value that handler will return. type Result: Responder; /// Handle request fn handle(&mut self, req: HttpRequest) -> Self::Result; } /// Trait implemented by types that generate responses for clients. /// /// Types that implement this trait can be used as the return type of a handler. pub trait Responder { /// The associated item which can be returned. type Item: Into; /// The associated error which can be returned. type Error: Into; /// Convert itself to `Reply` or `Error`. fn respond_to(self, req: HttpRequest) -> Result; } /// Combines two different responder types into a single type /// /// ```rust /// # extern crate actix_web; /// # extern crate futures; /// # use futures::future::Future; /// use actix_web::AsyncResponder; /// use futures::future::result; /// use actix_web::{Either, Error, HttpRequest, HttpResponse, httpcodes}; /// /// type RegisterResult = Either>>; /// /// /// fn index(req: HttpRequest) -> RegisterResult { /// if is_a_variant() { // <- choose variant A /// Either::A( /// httpcodes::HttpBadRequest.with_body("Bad data")) /// } else { /// Either::B( // <- variant B /// result(HttpResponse::Ok() /// .content_type("text/html") /// .body(format!("Hello!")) /// .map_err(|e| e.into())).responder()) /// } /// } /// # fn is_a_variant() -> bool { true } /// # fn main() {} /// ``` #[derive(Debug)] pub enum Either { /// First branch of the type A(A), /// Second branch of the type B(B), } impl Responder for Either where A: Responder, B: Responder { type Item = Reply; type Error = Error; fn respond_to(self, req: HttpRequest) -> Result { match self { Either::A(a) => match a.respond_to(req) { Ok(val) => Ok(val.into()), Err(err) => Err(err.into()), }, Either::B(b) => match b.respond_to(req) { Ok(val) => Ok(val.into()), Err(err) => Err(err.into()), }, } } } #[doc(hidden)] /// Convenience trait that convert `Future` object into `Boxed` future pub trait AsyncResponder: Sized { fn responder(self) -> Box>; } impl AsyncResponder for F where F: Future + 'static, I: Responder + 'static, E: Into + 'static, { fn responder(self) -> Box> { Box::new(self) } } /// Handler for Fn() impl Handler for F where F: Fn(HttpRequest) -> R + 'static, R: Responder + 'static { type Result = R; fn handle(&mut self, req: HttpRequest) -> R { (self)(req) } } /// Represents response process. pub struct Reply(ReplyItem); pub(crate) enum ReplyItem { Message(HttpResponse), Future(Box>), } impl Reply { /// Create async response #[inline] pub fn async(fut: F) -> Reply where F: Future + 'static { Reply(ReplyItem::Future(Box::new(fut))) } /// Send response #[inline] pub fn response>(response: R) -> Reply { Reply(ReplyItem::Message(response.into())) } #[inline] pub(crate) fn into(self) -> ReplyItem { self.0 } #[cfg(test)] pub(crate) fn as_response(&self) -> Option<&HttpResponse> { match self.0 { ReplyItem::Message(ref resp) => Some(resp), _ => None, } } } impl Responder for Reply { type Item = Reply; type Error = Error; fn respond_to(self, _: HttpRequest) -> Result { Ok(self) } } impl Responder for HttpResponse { type Item = Reply; type Error = Error; #[inline] fn respond_to(self, _: HttpRequest) -> Result { Ok(Reply(ReplyItem::Message(self))) } } impl From for Reply { #[inline] fn from(resp: HttpResponse) -> Reply { Reply(ReplyItem::Message(resp)) } } impl> Responder for Result { type Item = ::Item; type Error = Error; fn respond_to(self, req: HttpRequest) -> Result { match self { Ok(val) => match val.respond_to(req) { Ok(val) => Ok(val), Err(err) => Err(err.into()), }, Err(err) => Err(err.into()), } } } impl> From> for Reply { #[inline] fn from(res: Result) -> Self { match res { Ok(val) => val, Err(err) => Reply(ReplyItem::Message(err.into().into())), } } } impl> From> for Reply { #[inline] fn from(res: Result) -> Self { match res { Ok(val) => Reply(ReplyItem::Message(val)), Err(err) => Reply(ReplyItem::Message(err.into().into())), } } } impl From>> for Reply { #[inline] fn from(fut: Box>) -> Reply { Reply(ReplyItem::Future(fut)) } } /// Convenience type alias pub type FutureResponse = Box>; impl Responder for Box> where I: Responder + 'static, E: Into + 'static { type Item = Reply; type Error = Error; #[inline] fn respond_to(self, req: HttpRequest) -> Result { let fut = self.map_err(|e| e.into()) .then(move |r| { match r.respond_to(req) { Ok(reply) => match reply.into().0 { ReplyItem::Message(resp) => ok(resp), _ => panic!("Nested async replies are not supported"), }, Err(e) => err(e), } }); Ok(Reply::async(fut)) } } /// Trait defines object that could be registered as resource route pub(crate) trait RouteHandler: 'static { fn handle(&mut self, req: HttpRequest) -> Reply; } /// Route handler wrapper for Handler pub(crate) struct WrapHandler where H: Handler, R: Responder, S: 'static, { h: H, s: PhantomData, } impl WrapHandler where H: Handler, R: Responder, S: 'static, { pub fn new(h: H) -> Self { WrapHandler{h, s: PhantomData} } } impl RouteHandler for WrapHandler where H: Handler, R: Responder + 'static, S: 'static, { fn handle(&mut self, req: HttpRequest) -> Reply { let req2 = req.without_state(); match self.h.handle(req).respond_to(req2) { Ok(reply) => reply.into(), Err(err) => Reply::response(err.into()), } } } /// Async route handler pub(crate) struct AsyncHandler where H: Fn(HttpRequest) -> F + 'static, F: Future + 'static, R: Responder + 'static, E: Into + 'static, S: 'static, { h: Box, s: PhantomData, } impl AsyncHandler where H: Fn(HttpRequest) -> F + 'static, F: Future + 'static, R: Responder + 'static, E: Into + 'static, S: 'static, { pub fn new(h: H) -> Self { AsyncHandler{h: Box::new(h), s: PhantomData} } } impl RouteHandler for AsyncHandler where H: Fn(HttpRequest) -> F + 'static, F: Future + 'static, R: Responder + 'static, E: Into + 'static, S: 'static, { fn handle(&mut self, req: HttpRequest) -> Reply { let req2 = req.without_state(); let fut = (self.h)(req) .map_err(|e| e.into()) .then(move |r| { match r.respond_to(req2) { Ok(reply) => match reply.into().0 { ReplyItem::Message(resp) => ok(resp), _ => panic!("Nested async replies are not supported"), }, Err(e) => err(e), } }); Reply::async(fut) } }