diff --git a/examples/handlers/simple_async_handlers_await/src/main.rs b/examples/handlers/simple_async_handlers_await/src/main.rs index 3d0a1449f..567b48e09 100644 --- a/examples/handlers/simple_async_handlers_await/src/main.rs +++ b/examples/handlers/simple_async_handlers_await/src/main.rs @@ -6,7 +6,7 @@ use std::time::{Duration, Instant}; use gotham::hyper::{Body, StatusCode}; -use gotham::handler::HandlerResult; +use gotham::handler::{HandlerError, HandlerResult, IntoResponse}; use gotham::helpers::http::response::create_response; use gotham::router::builder::DefineSingleRoute; use gotham::router::builder::{build_simple_router, DrawRoutes}; @@ -60,26 +60,23 @@ fn sleep(seconds: u64) -> SleepFuture { /// This handler sleeps for the requested number of seconds, using the `sleep()` /// helper method, above. -async fn sleep_handler(mut state: State) -> HandlerResult { - let seconds = QueryStringExtractor::take_from(&mut state).seconds; +async fn sleep_handler(state: &mut State) -> Result { + let seconds = QueryStringExtractor::borrow_from(state).seconds; println!("sleep for {} seconds once: starting", seconds); - // Here, we call the sleep function and turn its old-style future into - // a new-style future. Note that this step doesn't block: it just sets - // up the timer so that we can use it later. + // Here, we call the sleep function. Note that this step doesn't block: + // it just sets up the timer so that we can use it later. let sleep_future = sleep(seconds); // Here is where the serious sleeping happens. We yield execution of // this block until sleep_future is resolved. - // The Ok("slept for x seconds") value is stored in result. + // The "slept for x seconds" value is stored in data. let data = sleep_future.await; - // Here, we convert the result from `sleep()` into the form that Gotham - // expects: `state` is owned by this block so we need to return it. - // We also convert any errors that we have into the form that Hyper - // expects, using the helper from IntoHandlerError. - let res = create_response(&state, StatusCode::OK, mime::TEXT_PLAIN, data); + // We return a `Result` directly + // where the success type can be anything implementing `IntoResponse` + // (including a `Response`) println!("sleep for {} seconds once: finished", seconds); - Ok((state, res)) + Ok((StatusCode::OK, mime::TEXT_PLAIN, data)) } /// It calls sleep(1) as many times as needed to make the requested duration. @@ -115,7 +112,7 @@ fn router() -> Router { route .get("/sleep") .with_query_string_extractor::() - .to_async(sleep_handler); + .to_async_borrowing(sleep_handler); route .get("/loop") .with_query_string_extractor::() diff --git a/gotham/src/handler/mod.rs b/gotham/src/handler/mod.rs index 27a118e2f..6928dc2a1 100644 --- a/gotham/src/handler/mod.rs +++ b/gotham/src/handler/mod.rs @@ -27,6 +27,9 @@ pub use self::error::{HandlerError, MapHandlerError, MapHandlerErrorFuture}; /// A type alias for the results returned by async fns that can be passed to to_async. pub type HandlerResult = std::result::Result<(State, Response), (State, HandlerError)>; +/// A type alias for the results returned by async fns that can be passed to to_async_borrowing. +pub type SimpleHandlerResult = std::result::Result, HandlerError>; + /// A type alias for the trait objects returned by `HandlerService`. /// /// When the `Future` resolves to an error, the `(State, HandlerError)` value is used to generate diff --git a/gotham/src/router/builder/single.rs b/gotham/src/router/builder/single.rs index b241bd2af..55e241a99 100644 --- a/gotham/src/router/builder/single.rs +++ b/gotham/src/router/builder/single.rs @@ -1,10 +1,13 @@ use hyper::Body; use std::panic::RefUnwindSafe; +use std::pin::Pin; use crate::extractor::{PathExtractor, QueryStringExtractor}; use crate::handler::assets::{DirHandler, FileHandler, FileOptions, FilePathExtractor}; -use crate::handler::{Handler, HandlerResult, NewHandler}; +use crate::handler::{ + Handler, HandlerError, HandlerFuture, HandlerResult, IntoResponse, NewHandler, +}; use crate::pipeline::chain::PipelineHandleChain; use crate::router::builder::{ ExtendRouteMatcher, ReplacePathExtractor, ReplaceQueryStringExtractor, SingleRouteBuilder, @@ -16,6 +19,50 @@ use crate::state::State; use core::future::Future; use futures::FutureExt; +pub trait HandlerMarker { + fn call_and_wrap(self, state: State) -> Pin>; +} + +pub trait AsyncHandlerFn<'a> { + type Res: IntoResponse + 'static; + type Fut: std::future::Future> + Send + 'a; + fn call(self, arg: &'a mut State) -> Self::Fut; +} + +impl<'a, Fut, R, F> AsyncHandlerFn<'a> for F +where + F: FnOnce(&'a mut State) -> Fut, + R: IntoResponse + 'static, + Fut: std::future::Future> + Send + 'a, +{ + type Res = R; + type Fut = Fut; + fn call(self, state: &'a mut State) -> Fut { + self(state) + } +} + +impl HandlerMarker for F +where + R: IntoResponse + 'static, + for<'a> F: AsyncHandlerFn<'a, Res = R> + Send + 'static, +{ + fn call_and_wrap(self, mut state: State) -> Pin> { + async move { + let fut = self.call(&mut state); + let result = fut.await; + match result { + Ok(data) => { + let response = data.into_response(&state); + Ok((state, response)) + } + Err(err) => Err((state, err)), + } + } + .boxed() + } +} + /// Describes the API for defining a single route, after determining which request paths will be /// dispatched here. The API here uses chained function calls to build and add the route into the /// `RouterBuilder` which created it. @@ -157,6 +204,62 @@ pub trait DefineSingleRoute { Self: Sized, H: (FnOnce(State) -> Fut) + RefUnwindSafe + Copy + Send + Sync + 'static, Fut: Future + Send + 'static; + + /// Directs the route to the given `async fn`, passing `State` to it by mutable reference. + /// + /// Note that, as of Rust 1.46.0, this does not work for closures due to + /// [rust-lang/rust#70263](https://github.com/rust-lang/rust/issues/70263). + /// + /// On the other hand, one can easily use the `?` operator for error handling + /// in these async functions. + /// + /// # Examples + /// + /// ```rust + /// # extern crate gotham; + /// # extern crate hyper; + /// # + /// # use hyper::StatusCode; + /// # use gotham::handler::{HandlerError, IntoResponse, MapHandlerError}; + /// # use gotham::state::State; + /// # use gotham::router::Router; + /// # use gotham::router::builder::*; + /// # use gotham::pipeline::new_pipeline; + /// # use gotham::pipeline::single::*; + /// # use gotham::middleware::session::NewSessionMiddleware; + /// # use gotham::test::TestServer; + /// # + /// async fn my_handler(_state: &mut State) -> Result { + /// let flavors = std::fs::read("coffee-flavors.txt") + /// .map_err_with_status(StatusCode::IM_A_TEAPOT)?; + /// Ok(flavors) + /// } + /// # + /// # fn router() -> Router { + /// # let (chain, pipelines) = single_pipeline( + /// # new_pipeline().add(NewSessionMiddleware::default()).build() + /// # ); + /// + /// build_router(chain, pipelines, |route| { + /// route.get("/request/path").to_async_borrowing(my_handler); + /// }) + /// # + /// # } + /// # + /// # fn main() { + /// # let test_server = TestServer::new(router()).unwrap(); + /// # let response = test_server.client() + /// # .get("https://example.com/request/path") + /// # .perform() + /// # .unwrap(); + /// # assert_eq!(response.status(), StatusCode::IM_A_TEAPOT); + /// # } + /// ``` + fn to_async_borrowing(self, handler: F) + where + Self: Sized, + F: HandlerMarker + Copy + Send + Sync + RefUnwindSafe + 'static; + /// Directs the route to the given `NewHandler`. This gives more control over how `Handler` /// values are constructed. /// @@ -531,6 +634,14 @@ where self.to_new_handler(move || Ok(move |s: State| handler(s).boxed())) } + fn to_async_borrowing(self, handler: F) + where + Self: Sized, + F: HandlerMarker + Copy + Send + Sync + RefUnwindSafe + 'static, + { + self.to_new_handler(move || Ok(move |state: State| handler.call_and_wrap(state))) + } + fn to_new_handler(self, new_handler: NH) where NH: NewHandler + 'static,