Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async borrowing (continuation of #449) #450

Merged
merged 15 commits into from
Sep 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 11 additions & 14 deletions examples/handlers/simple_async_handlers_await/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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<impl IntoResponse, HandlerError> {
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<impl IntoResponse, HandlerError>` directly
// where the success type can be anything implementing `IntoResponse`
// (including a `Response<Body>`)
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.
Expand Down Expand Up @@ -115,7 +112,7 @@ fn router() -> Router {
route
.get("/sleep")
.with_query_string_extractor::<QueryStringExtractor>()
.to_async(sleep_handler);
.to_async_borrowing(sleep_handler);
route
.get("/loop")
.with_query_string_extractor::<QueryStringExtractor>()
Expand Down
3 changes: 3 additions & 0 deletions gotham/src/handler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Body>), (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<Response<Body>, 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
Expand Down
113 changes: 112 additions & 1 deletion gotham/src/router/builder/single.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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<Box<HandlerFuture>>;
}

pub trait AsyncHandlerFn<'a> {
type Res: IntoResponse + 'static;
type Fut: std::future::Future<Output = Result<Self::Res, HandlerError>> + 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<Output = Result<R, HandlerError>> + Send + 'a,
{
type Res = R;
type Fut = Fut;
fn call(self, state: &'a mut State) -> Fut {
self(state)
}
}

impl<F, R> 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<Box<HandlerFuture>> {
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.
Expand Down Expand Up @@ -157,6 +204,62 @@ pub trait DefineSingleRoute {
Self: Sized,
H: (FnOnce(State) -> Fut) + RefUnwindSafe + Copy + Send + Sync + 'static,
Fut: Future<Output = HandlerResult> + 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<impl IntoResponse, HandlerError> {
/// 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<F>(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.
///
Expand Down Expand Up @@ -531,6 +634,14 @@ where
self.to_new_handler(move || Ok(move |s: State| handler(s).boxed()))
}

fn to_async_borrowing<F>(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<NH>(self, new_handler: NH)
where
NH: NewHandler + 'static,
Expand Down