From 9bbf9dafe4275891082b5ae7fd8f012d083c3d46 Mon Sep 17 00:00:00 2001 From: David Laban Date: Sat, 16 Nov 2019 11:39:25 +0000 Subject: [PATCH 01/14] copy simple_async_handlers as a base --- .../simple_async_handlers_await/Cargo.toml | 17 ++ .../simple_async_handlers_await/README.md | 65 +++++++ .../simple_async_handlers_await/src/main.rs | 180 ++++++++++++++++++ 3 files changed, 262 insertions(+) create mode 100644 examples/handlers/simple_async_handlers_await/Cargo.toml create mode 100644 examples/handlers/simple_async_handlers_await/README.md create mode 100644 examples/handlers/simple_async_handlers_await/src/main.rs diff --git a/examples/handlers/simple_async_handlers_await/Cargo.toml b/examples/handlers/simple_async_handlers_await/Cargo.toml new file mode 100644 index 000000000..33a242534 --- /dev/null +++ b/examples/handlers/simple_async_handlers_await/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "gotham_examples_handlers_simple_async_handlers" +description = "An example that does asynchronous work before responding" +version = "0.0.0" +authors = ["David Laban "] +publish = false + +[dependencies] +gotham = { path = "../../../gotham" } +gotham_derive = { path = "../../../gotham_derive" } + +hyper = "0.12" +mime = "0.3" +futures = "0.1" +serde = "1.0" +serde_derive = "1.0" +tokio = "0.1" diff --git a/examples/handlers/simple_async_handlers_await/README.md b/examples/handlers/simple_async_handlers_await/README.md new file mode 100644 index 000000000..c06af72b4 --- /dev/null +++ b/examples/handlers/simple_async_handlers_await/README.md @@ -0,0 +1,65 @@ +# Async Request Handlers + +The idea of async handlers has already been introduced by the post_handler example in +[Request Data](../request_data), which waits for the POST body asyncronously, and resolves +the response future once it has processed the body. + +This example contains a pair of endpoints that sleep for a number of seconds, +in different ways. Note that we never call `std::thread::sleep` in this example, +so none of our request handlers block the thread from handling other requests +in parallel. Instead, in each case, we return a future, which will resolve when +the requested time has elapsed, and cause Gotham to respond to the http request. + +The approach of using futures to track the status of long-running operations +is significantly lower overhead than that of spawning a new thread per request. +In our case, the long-running operations are sleeps, but in +[Async Handlers](../async_handlers), the long-running operations are http +requests. + +You will often find that most of the time that a web server spends dealing +with a web request, it is waiting on another service (e.g. a database, or an +external api). If you can track these operations using futures, you should end +up with a very lightweight and performant web server. On the other hand, if you +find yourself doing lots of CPU/memory intensive operations on the web server, +then futures are probably not going to help your performance, and you might be +better off spawning a new thread per request. + +## Running + +From the `examples/handlers/async_handlers` directory: + +``` +Terminal 1: + Compiling gotham_examples_handlers_simple_async_handlers v0.0.0 (file:///.../gotham/examples/handlers/simple_async_handlers) + Finished dev [unoptimized + debuginfo] target(s) in 8.19 secs + Running `.../gotham/target/debug/gotham_examples_handlers_simple_async_handlers` +Listening for requests at http://127.0.0.1:7878 +sleep for 5 seconds once: starting +sleep for 5 seconds once: finished +sleep for one second 5 times: starting +sleep for one second 5 times: finished + +Terminal 2: +$ curl 'http://127.0.0.1:7878/sleep?seconds=5' +slept for 5 seconds +$ curl 'http://127.0.0.1:7878/loop?seconds=5' +slept for 1 seconds +slept for 1 seconds +slept for 1 seconds +slept for 1 seconds +slept for 1 seconds +``` + +## License + +Licensed under your option of: + +* [MIT License](../../LICENSE-MIT) +* [Apache License, Version 2.0](../../LICENSE-APACHE) + +## Community + +The following policies guide participation in our project and our community: + +* [Code of conduct](../../CODE_OF_CONDUCT.md) +* [Contributing](../../CONTRIBUTING.md) diff --git a/examples/handlers/simple_async_handlers_await/src/main.rs b/examples/handlers/simple_async_handlers_await/src/main.rs new file mode 100644 index 000000000..4317b2dfc --- /dev/null +++ b/examples/handlers/simple_async_handlers_await/src/main.rs @@ -0,0 +1,180 @@ +//! A basic example showing the request components + +extern crate futures; +extern crate gotham; +#[macro_use] +extern crate gotham_derive; +extern crate hyper; +extern crate mime; +extern crate serde; +#[macro_use] +extern crate serde_derive; +extern crate tokio; + +use futures::{stream, Future, Stream}; +use std::time::{Duration, Instant}; + +use hyper::StatusCode; + +use gotham::handler::{HandlerError, HandlerFuture, IntoHandlerError}; +use gotham::helpers::http::response::create_response; +use gotham::router::builder::DefineSingleRoute; +use gotham::router::builder::{build_simple_router, DrawRoutes}; +use gotham::router::Router; +use gotham::state::{FromState, State}; + +use tokio::timer::Delay; + +type SleepFuture = Box, Error = HandlerError> + Send>; + +#[derive(Deserialize, StateData, StaticResponseExtender)] +struct QueryStringExtractor { + seconds: u64, +} + +/// Sneaky hack to make tests take less time. Nothing to see here ;-). +#[cfg(not(test))] +fn get_duration(seconds: u64) -> Duration { + Duration::from_secs(seconds) +} +#[cfg(test)] +fn get_duration(seconds: u64) -> Duration { + Duration::from_millis(seconds) +} +/// All this function does is return a future that resolves after a number of +/// seconds, with a Vec that tells you how long it slept for. +/// +/// Note that it does not block the thread from handling other requests, because +/// it returns a `Future`, which will be managed by the tokio reactor, and +/// called back once the timeout has expired. +/// +/// Vec is chosen because it is one of the things that you need to resolve +/// a HandlerFuture and respond to a request. +/// +/// Most things that you call to access remote services (e.g databases and +/// web apis) can be coerced into returning futures that yield useful data, +/// so the patterns that you learn in this example should be applicable to +/// real world problems. +fn sleep(seconds: u64) -> SleepFuture { + let when = Instant::now() + get_duration(seconds); + let delay = Delay::new(when) + .map_err(|e| panic!("timer failed; err={:?}", e)) + .and_then(move |_| { + Ok(format!("slept for {} seconds\n", seconds) + .as_bytes() + .to_vec()) + }); + + Box::new(delay) +} + +/// This handler sleeps for the requested number of seconds, using the `sleep()` +/// helper method, above. +fn sleep_handler(mut state: State) -> Box { + let seconds = QueryStringExtractor::take_from(&mut state).seconds; + println!("sleep for {} seconds once: starting", seconds); + + // Here, we call our helper function that returns a future. + let sleep_future = sleep(seconds); + + // Here, we convert the future from `sleep()` into the form that Gotham expects. + // We have to use .then() rather than .and_then() because we need to coerce both + // the success and error cases into the right shape. + // `state` is moved in, so that we can return it, and we convert any errors + // that we have into the form that Hyper expects, using the helper from + // IntoHandlerError. + Box::new(sleep_future.then(move |result| match result { + Ok(data) => { + let res = create_response(&state, StatusCode::OK, mime::TEXT_PLAIN, data); + println!("sleep for {} seconds once: finished", seconds); + Ok((state, res)) + } + Err(err) => Err((state, err.into_handler_error())), + })) +} + +/// This example uses a `future::Stream` to implement a `for` loop. It calls sleep(1) +/// as many times as needed to make the requested duration. +/// +/// https://github.com/alexcrichton/futures-await has a more readable syntax for +/// async for loops, if you are using nightly Rust. +fn loop_handler(mut state: State) -> Box { + let seconds = QueryStringExtractor::take_from(&mut state).seconds; + println!("sleep for one second {} times: starting", seconds); + + // Here, we create a stream of Ok(_) that's as long as we need, and use fold + // to loop over it asyncronously, accumulating the return values from sleep(). + let sleep_future: SleepFuture = Box::new(stream::iter_ok(0..seconds).fold( + Vec::new(), + move |mut accumulator, _| { + // Do the sleep(), and append the result to the accumulator so that it can + // be returned. + sleep(1).and_then(move |body| { + accumulator.extend(body); + Ok(accumulator) + }) + }, + )); + + // This bit is the same as the bit in the first example. + Box::new(sleep_future.then(move |result| match result { + Ok(data) => { + let res = create_response(&state, StatusCode::OK, mime::TEXT_PLAIN, data); + println!("sleep for one second {} times: finished", seconds); + Ok((state, res)) + } + Err(err) => Err((state, err.into_handler_error())), + })) +} + +/// Create a `Router`. +fn router() -> Router { + build_simple_router(|route| { + route + .get("/sleep") + .with_query_string_extractor::() + .to(sleep_handler); + route + .get("/loop") + .with_query_string_extractor::() + .to(loop_handler); + }) +} + +/// Start a server and use a `Router` to dispatch requests. +pub fn main() { + let addr = "127.0.0.1:7878"; + println!("Listening for requests at http://{}", addr); + gotham::start(addr, router()) +} + +#[cfg(test)] +mod tests { + use gotham::test::TestServer; + + use super::*; + + fn assert_returns_ok(url_str: &str, expected_response: &str) { + let test_server = TestServer::new(router()).unwrap(); + let response = test_server.client().get(url_str).perform().unwrap(); + + assert_eq!(response.status(), StatusCode::OK); + assert_eq!( + &String::from_utf8(response.read_body().unwrap()).unwrap(), + expected_response + ); + } + + #[test] + fn sleep_says_how_long_it_slept_for() { + assert_returns_ok("http://localhost/sleep?seconds=2", "slept for 2 seconds\n"); + } + + #[test] + fn loop_breaks_the_time_into_one_second_sleeps() { + assert_returns_ok( + "http://localhost/loop?seconds=2", + "slept for 1 seconds\nslept for 1 seconds\n", + ); + } +} From bf2bca8a3d11819c756e9a57914d6eac0a3f84f3 Mon Sep 17 00:00:00 2001 From: David Laban Date: Sat, 16 Nov 2019 11:45:21 +0000 Subject: [PATCH 02/14] .await version of the simple_async_handlers example --- Cargo.toml | 1 + examples/handlers/README.md | 1 + .../handlers/simple_async_handlers/README.md | 3 + .../simple_async_handlers_await/Cargo.toml | 10 +- .../simple_async_handlers_await/README.md | 31 ++-- .../simple_async_handlers_await/src/main.rs | 143 ++++++++++++------ 6 files changed, 115 insertions(+), 74 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2315b9fc9..fe3efb2f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ members = [ "examples/handlers/request_data", "examples/handlers/stateful", "examples/handlers/simple_async_handlers", + "examples/handlers/simple_async_handlers_await", "examples/handlers/async_handlers", "examples/handlers/form_urlencoded", "examples/handlers/multipart", diff --git a/examples/handlers/README.md b/examples/handlers/README.md index 486ed38ed..fa98ba9a8 100644 --- a/examples/handlers/README.md +++ b/examples/handlers/README.md @@ -18,6 +18,7 @@ We recommend reviewing our handler examples in the order shown below: 1. [Request Data](request_data) - Accessing common request information 1. [Stateful Handlers](stateful) - Keeping state in a handler 1. [Simple Async Handlers](simple_async_handlers) - Async Request Handlers 101 +1. [Simple Async Handlers (.await version)](simple_async_handlers_await) - Request Handlers that use async/.await 1. [Async Handlers](async_handlers) - More complicated async request handlers ## Help diff --git a/examples/handlers/simple_async_handlers/README.md b/examples/handlers/simple_async_handlers/README.md index c06af72b4..b0993ca1c 100644 --- a/examples/handlers/simple_async_handlers/README.md +++ b/examples/handlers/simple_async_handlers/README.md @@ -24,6 +24,9 @@ find yourself doing lots of CPU/memory intensive operations on the web server, then futures are probably not going to help your performance, and you might be better off spawning a new thread per request. +If you came here looking for an example that uses async/.await, please read +[Async Request Handlers (.await version)](../simple_async_handlers). + ## Running From the `examples/handlers/async_handlers` directory: diff --git a/examples/handlers/simple_async_handlers_await/Cargo.toml b/examples/handlers/simple_async_handlers_await/Cargo.toml index 33a242534..adb7666ee 100644 --- a/examples/handlers/simple_async_handlers_await/Cargo.toml +++ b/examples/handlers/simple_async_handlers_await/Cargo.toml @@ -1,9 +1,10 @@ [package] -name = "gotham_examples_handlers_simple_async_handlers" +name = "gotham_examples_handlers_simple_async_handlers_await" description = "An example that does asynchronous work before responding" version = "0.0.0" authors = ["David Laban "] publish = false +edition = "2018" [dependencies] gotham = { path = "../../../gotham" } @@ -11,7 +12,12 @@ gotham_derive = { path = "../../../gotham_derive" } hyper = "0.12" mime = "0.3" -futures = "0.1" +# We need two versions of the futures library. One that matches what Gotham +# understands, and one that matches what .await understands. Stolen from: +# https://rust-lang-nursery.github.io/futures-rs/blog/2019/04/18/compatibility-layer.html +# (although they call the old version futures01 and we call it legacy_futures) +legacy_futures = {package = "futures", version = "0.1"} +futures = {package = "futures", version = "0.3.1", features = ["compat"]} serde = "1.0" serde_derive = "1.0" tokio = "0.1" diff --git a/examples/handlers/simple_async_handlers_await/README.md b/examples/handlers/simple_async_handlers_await/README.md index c06af72b4..ee8f9eb10 100644 --- a/examples/handlers/simple_async_handlers_await/README.md +++ b/examples/handlers/simple_async_handlers_await/README.md @@ -1,28 +1,15 @@ -# Async Request Handlers +# Async Request Handlers (.await version) The idea of async handlers has already been introduced by the post_handler example in [Request Data](../request_data), which waits for the POST body asyncronously, and resolves -the response future once it has processed the body. - -This example contains a pair of endpoints that sleep for a number of seconds, -in different ways. Note that we never call `std::thread::sleep` in this example, -so none of our request handlers block the thread from handling other requests -in parallel. Instead, in each case, we return a future, which will resolve when -the requested time has elapsed, and cause Gotham to respond to the http request. - -The approach of using futures to track the status of long-running operations -is significantly lower overhead than that of spawning a new thread per request. -In our case, the long-running operations are sleeps, but in -[Async Handlers](../async_handlers), the long-running operations are http -requests. - -You will often find that most of the time that a web server spends dealing -with a web request, it is waiting on another service (e.g. a database, or an -external api). If you can track these operations using futures, you should end -up with a very lightweight and performant web server. On the other hand, if you -find yourself doing lots of CPU/memory intensive operations on the web server, -then futures are probably not going to help your performance, and you might be -better off spawning a new thread per request. +the response future once it has processed the body. The combinator-based version +of this example can be found at [Async Request Handlers](../simple_async_handlers). + +This example has exactly the same behavior and API as the combinator-based version, +and it can be used as a reference when converting your code to use async/await. +It also leaves the versions of gotham, tokio and hyper the same, and uses the +compatibility helpers from the `futures` crate to convert things at the +interface boundaries. ## Running diff --git a/examples/handlers/simple_async_handlers_await/src/main.rs b/examples/handlers/simple_async_handlers_await/src/main.rs index 4317b2dfc..65ea91cb9 100644 --- a/examples/handlers/simple_async_handlers_await/src/main.rs +++ b/examples/handlers/simple_async_handlers_await/src/main.rs @@ -11,7 +11,10 @@ extern crate serde; extern crate serde_derive; extern crate tokio; -use futures::{stream, Future, Stream}; +use futures::compat::Future01CompatExt; +use futures::{FutureExt, TryFutureExt}; +use legacy_futures::Future as LegacyFuture; + use std::time::{Duration, Instant}; use hyper::StatusCode; @@ -25,7 +28,7 @@ use gotham::state::{FromState, State}; use tokio::timer::Delay; -type SleepFuture = Box, Error = HandlerError> + Send>; +type SleepFuture = Box, Error = HandlerError> + Send>; #[derive(Deserialize, StateData, StaticResponseExtender)] struct QueryStringExtractor { @@ -55,6 +58,14 @@ fn get_duration(seconds: u64) -> Duration { /// web apis) can be coerced into returning futures that yield useful data, /// so the patterns that you learn in this example should be applicable to /// real world problems. +/// +/// This function returns a LegacyFuture (a Future from the 0.1.x branch of +/// the `futures` crate rather than a std::future::Future that we can .await). +/// This is partly to keep it the same as the simple_async_handlers example, +/// and partly to show you how to use the .compat() combinators (because you +/// will probably be using them a lot while the ecosystem stabilises). For a +/// better explanation of .compat(), please read this blog post: +/// https://rust-lang-nursery.github.io/futures-rs/blog/2019/04/18/compatibility-layer.html fn sleep(seconds: u64) -> SleepFuture { let when = Instant::now() + get_duration(seconds); let delay = Delay::new(when) @@ -71,60 +82,92 @@ fn sleep(seconds: u64) -> SleepFuture { /// This handler sleeps for the requested number of seconds, using the `sleep()` /// helper method, above. fn sleep_handler(mut state: State) -> Box { - let seconds = QueryStringExtractor::take_from(&mut state).seconds; - println!("sleep for {} seconds once: starting", seconds); - - // Here, we call our helper function that returns a future. - let sleep_future = sleep(seconds); - - // Here, we convert the future from `sleep()` into the form that Gotham expects. - // We have to use .then() rather than .and_then() because we need to coerce both - // the success and error cases into the right shape. - // `state` is moved in, so that we can return it, and we convert any errors - // that we have into the form that Hyper expects, using the helper from - // IntoHandlerError. - Box::new(sleep_future.then(move |result| match result { - Ok(data) => { - let res = create_response(&state, StatusCode::OK, mime::TEXT_PLAIN, data); - println!("sleep for {} seconds once: finished", seconds); - Ok((state, res)) + let async_block_future = async move { + let seconds = QueryStringExtractor::take_from(&mut 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. + let sleep_future = sleep(seconds).compat(); + + // 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. + let result = 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. + match result { + Ok(data) => { + let res = create_response(&state, StatusCode::OK, mime::TEXT_PLAIN, data); + println!("sleep for {} seconds once: finished", seconds); + Ok((state, res)) + } + Err(err) => Err((state, err.into_handler_error())), } - Err(err) => Err((state, err.into_handler_error())), - })) + }; + // Here, we convert the new-style future produced by the async block into + // an old-style future that gotham can understand. There are a couple of + // layers of boxes, which is a bit sad, but these will go away once the + // ecosystem settles and we can return std::future::Future from Handler + // functions. Think of it as a temporary wart. + Box::new(async_block_future.boxed().compat()) } -/// This example uses a `future::Stream` to implement a `for` loop. It calls sleep(1) -/// as many times as needed to make the requested duration. +/// It calls sleep(1) as many times as needed to make the requested duration. /// -/// https://github.com/alexcrichton/futures-await has a more readable syntax for -/// async for loops, if you are using nightly Rust. +/// Notice how much easier it is to read than the version in +/// `simple_async_handlers`. fn loop_handler(mut state: State) -> Box { - let seconds = QueryStringExtractor::take_from(&mut state).seconds; - println!("sleep for one second {} times: starting", seconds); - - // Here, we create a stream of Ok(_) that's as long as we need, and use fold - // to loop over it asyncronously, accumulating the return values from sleep(). - let sleep_future: SleepFuture = Box::new(stream::iter_ok(0..seconds).fold( - Vec::new(), - move |mut accumulator, _| { - // Do the sleep(), and append the result to the accumulator so that it can - // be returned. - sleep(1).and_then(move |body| { - accumulator.extend(body); - Ok(accumulator) - }) - }, - )); - - // This bit is the same as the bit in the first example. - Box::new(sleep_future.then(move |result| match result { - Ok(data) => { - let res = create_response(&state, StatusCode::OK, mime::TEXT_PLAIN, data); - println!("sleep for one second {} times: finished", seconds); - Ok((state, res)) + let async_block_future = async move { + let seconds = QueryStringExtractor::take_from(&mut state).seconds; + println!("sleep for one second {} times: starting", seconds); + + // We can't use the ? operator in the outermost async block, because we + // need to need to return ownership of the State object back to gotham. + // I quite like using ?, so I often find myself writing `async {}.await` + // to get around this problem when I'm feeling lazy. Think of this + // self-awaiting-block as a bit like a try block. + // + // In real code, you probably shouldn't be writing business logic in + // your Handler functions anyway. Instead, you should be + // unpacking everything you need from State in the Handler function + // and then calling your business logic with only the dependencies that + // they need. That way your business logic can use new-style futures + // and ? as much as it likes, and you will only need to update your + // handler functions (which don't contain any business logic) when you + // upgrade your gotham. + let result = async { + // The code within this block reads exactly like syncronous code. + // This is the style that you should aim to write your business + // logic in. + let mut accumulator = Vec::new(); + for _ in 0..seconds { + let body = sleep(1).compat().await?; + accumulator.extend(body) + } + // ? does type coercion for us, so we need to use a turbofish to + // tell the compiler that we have a HandlerError. See this section + // of the rust async book for more details: + // https://rust-lang.github.io/async-book/07_workarounds/03_err_in_async_blocks.html + Ok::<_, HandlerError>(accumulator) + } + .await; + + // This bit is the same boilerplate as the bit in the first example. + // Nothing to see here. + match result { + Ok(data) => { + let res = create_response(&state, StatusCode::OK, mime::TEXT_PLAIN, data); + println!("sleep for one second {} times: finished", seconds); + Ok((state, res)) + } + Err(err) => Err((state, err.into_handler_error())), } - Err(err) => Err((state, err.into_handler_error())), - })) + }; + Box::new(async_block_future.boxed().compat()) } /// Create a `Router`. From 7d7258a3f74ca3503b8937298376e7fc3103ce5c Mon Sep 17 00:00:00 2001 From: David Laban Date: Sat, 16 Nov 2019 12:06:59 +0000 Subject: [PATCH 03/14] make docstrings easier to read --- examples/handlers/simple_async_handlers_await/src/main.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/handlers/simple_async_handlers_await/src/main.rs b/examples/handlers/simple_async_handlers_await/src/main.rs index 65ea91cb9..a378d6883 100644 --- a/examples/handlers/simple_async_handlers_await/src/main.rs +++ b/examples/handlers/simple_async_handlers_await/src/main.rs @@ -60,11 +60,12 @@ fn get_duration(seconds: u64) -> Duration { /// real world problems. /// /// This function returns a LegacyFuture (a Future from the 0.1.x branch of -/// the `futures` crate rather than a std::future::Future that we can .await). +/// the `futures` crate, rather than a std::future::Future that we can .await). /// This is partly to keep it the same as the simple_async_handlers example, /// and partly to show you how to use the .compat() combinators (because you -/// will probably be using them a lot while the ecosystem stabilises). For a -/// better explanation of .compat(), please read this blog post: +/// will probably be using them a lot while the ecosystem stabilises). +/// +/// For a better explanation of .compat(), please read this blog post: /// https://rust-lang-nursery.github.io/futures-rs/blog/2019/04/18/compatibility-layer.html fn sleep(seconds: u64) -> SleepFuture { let when = Instant::now() + get_duration(seconds); From a960ef0bfbd3faf075eef1e9318d0a4c48e8631d Mon Sep 17 00:00:00 2001 From: David Laban Date: Sat, 30 Nov 2019 20:02:03 +0000 Subject: [PATCH 04/14] rustfmt fix --- examples/handlers/simple_async_handlers_await/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/handlers/simple_async_handlers_await/src/main.rs b/examples/handlers/simple_async_handlers_await/src/main.rs index a378d6883..03a3ec160 100644 --- a/examples/handlers/simple_async_handlers_await/src/main.rs +++ b/examples/handlers/simple_async_handlers_await/src/main.rs @@ -64,7 +64,7 @@ fn get_duration(seconds: u64) -> Duration { /// This is partly to keep it the same as the simple_async_handlers example, /// and partly to show you how to use the .compat() combinators (because you /// will probably be using them a lot while the ecosystem stabilises). -/// +/// /// For a better explanation of .compat(), please read this blog post: /// https://rust-lang-nursery.github.io/futures-rs/blog/2019/04/18/compatibility-layer.html fn sleep(seconds: u64) -> SleepFuture { From 9cba316f5e88b27751cadc2ac4e70631821f750d Mon Sep 17 00:00:00 2001 From: David Laban Date: Mon, 20 Jan 2020 20:25:17 +0000 Subject: [PATCH 05/14] make it compile after rebase --- .../simple_async_handlers_await/Cargo.toml | 12 ++-- .../simple_async_handlers_await/src/main.rs | 67 +++++++------------ 2 files changed, 29 insertions(+), 50 deletions(-) diff --git a/examples/handlers/simple_async_handlers_await/Cargo.toml b/examples/handlers/simple_async_handlers_await/Cargo.toml index adb7666ee..6101145e7 100644 --- a/examples/handlers/simple_async_handlers_await/Cargo.toml +++ b/examples/handlers/simple_async_handlers_await/Cargo.toml @@ -10,14 +10,14 @@ edition = "2018" gotham = { path = "../../../gotham" } gotham_derive = { path = "../../../gotham_derive" } -hyper = "0.12" -mime = "0.3" +hyper = "0.13.1" +mime = "0.3.16" # We need two versions of the futures library. One that matches what Gotham # understands, and one that matches what .await understands. Stolen from: # https://rust-lang-nursery.github.io/futures-rs/blog/2019/04/18/compatibility-layer.html # (although they call the old version futures01 and we call it legacy_futures) legacy_futures = {package = "futures", version = "0.1"} -futures = {package = "futures", version = "0.3.1", features = ["compat"]} -serde = "1.0" -serde_derive = "1.0" -tokio = "0.1" +futures = { package = "futures", version = "0.3.1", features = ["compat"] } +serde = "1.0.104" +serde_derive = "1.0.104" +tokio = "0.2.9" diff --git a/examples/handlers/simple_async_handlers_await/src/main.rs b/examples/handlers/simple_async_handlers_await/src/main.rs index 03a3ec160..8d413ffa6 100644 --- a/examples/handlers/simple_async_handlers_await/src/main.rs +++ b/examples/handlers/simple_async_handlers_await/src/main.rs @@ -11,10 +11,8 @@ extern crate serde; extern crate serde_derive; extern crate tokio; -use futures::compat::Future01CompatExt; -use futures::{FutureExt, TryFutureExt}; -use legacy_futures::Future as LegacyFuture; - +use futures::prelude::*; +use std::pin::Pin; use std::time::{Duration, Instant}; use hyper::StatusCode; @@ -26,9 +24,9 @@ use gotham::router::builder::{build_simple_router, DrawRoutes}; use gotham::router::Router; use gotham::state::{FromState, State}; -use tokio::timer::Delay; +use tokio::time::delay_until; -type SleepFuture = Box, Error = HandlerError> + Send>; +type SleepFuture = Pin> + Send>>; #[derive(Deserialize, StateData, StaticResponseExtender)] struct QueryStringExtractor { @@ -58,70 +56,51 @@ fn get_duration(seconds: u64) -> Duration { /// web apis) can be coerced into returning futures that yield useful data, /// so the patterns that you learn in this example should be applicable to /// real world problems. -/// -/// This function returns a LegacyFuture (a Future from the 0.1.x branch of -/// the `futures` crate, rather than a std::future::Future that we can .await). -/// This is partly to keep it the same as the simple_async_handlers example, -/// and partly to show you how to use the .compat() combinators (because you -/// will probably be using them a lot while the ecosystem stabilises). -/// -/// For a better explanation of .compat(), please read this blog post: -/// https://rust-lang-nursery.github.io/futures-rs/blog/2019/04/18/compatibility-layer.html fn sleep(seconds: u64) -> SleepFuture { let when = Instant::now() + get_duration(seconds); - let delay = Delay::new(when) - .map_err(|e| panic!("timer failed; err={:?}", e)) - .and_then(move |_| { - Ok(format!("slept for {} seconds\n", seconds) - .as_bytes() - .to_vec()) - }); - - Box::new(delay) + let delay = delay_until(when.into()).map(move |_| { + format!("slept for {} seconds\n", seconds) + .as_bytes() + .to_vec() + }); + + delay.boxed() } /// This handler sleeps for the requested number of seconds, using the `sleep()` /// helper method, above. -fn sleep_handler(mut state: State) -> Box { +fn sleep_handler(mut state: State) -> Pin> { let async_block_future = async move { let seconds = QueryStringExtractor::take_from(&mut 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. - let sleep_future = sleep(seconds).compat(); + 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. - let result = sleep_future.await; + 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. - match result { - Ok(data) => { - let res = create_response(&state, StatusCode::OK, mime::TEXT_PLAIN, data); - println!("sleep for {} seconds once: finished", seconds); - Ok((state, res)) - } - Err(err) => Err((state, err.into_handler_error())), - } + let res = create_response(&state, StatusCode::OK, mime::TEXT_PLAIN, data); + println!("sleep for {} seconds once: finished", seconds); + Ok((state, res)) }; - // Here, we convert the new-style future produced by the async block into - // an old-style future that gotham can understand. There are a couple of - // layers of boxes, which is a bit sad, but these will go away once the - // ecosystem settles and we can return std::future::Future from Handler - // functions. Think of it as a temporary wart. - Box::new(async_block_future.boxed().compat()) + // Here, we move the future from the async block onto the heap so that + // gotham can use it later. + async_block_future.boxed() } /// It calls sleep(1) as many times as needed to make the requested duration. /// /// Notice how much easier it is to read than the version in /// `simple_async_handlers`. -fn loop_handler(mut state: State) -> Box { +fn loop_handler(mut state: State) -> Pin> { let async_block_future = async move { let seconds = QueryStringExtractor::take_from(&mut state).seconds; println!("sleep for one second {} times: starting", seconds); @@ -146,7 +125,7 @@ fn loop_handler(mut state: State) -> Box { // logic in. let mut accumulator = Vec::new(); for _ in 0..seconds { - let body = sleep(1).compat().await?; + let body = sleep(1).await; accumulator.extend(body) } // ? does type coercion for us, so we need to use a turbofish to @@ -168,7 +147,7 @@ fn loop_handler(mut state: State) -> Box { Err(err) => Err((state, err.into_handler_error())), } }; - Box::new(async_block_future.boxed().compat()) + async_block_future.boxed() } /// Create a `Router`. From 122015a2e946d6e136ae44bb9a0ff08d0fb2f3ae Mon Sep 17 00:00:00 2001 From: David Laban Date: Mon, 20 Jan 2020 20:40:10 +0000 Subject: [PATCH 06/14] simplify example now we're not using ? --- .../simple_async_handlers_await/src/main.rs | 60 ++++++------------- 1 file changed, 18 insertions(+), 42 deletions(-) diff --git a/examples/handlers/simple_async_handlers_await/src/main.rs b/examples/handlers/simple_async_handlers_await/src/main.rs index 8d413ffa6..f003d76b1 100644 --- a/examples/handlers/simple_async_handlers_await/src/main.rs +++ b/examples/handlers/simple_async_handlers_await/src/main.rs @@ -15,9 +15,9 @@ use futures::prelude::*; use std::pin::Pin; use std::time::{Duration, Instant}; -use hyper::StatusCode; +use hyper::{Body, StatusCode}; -use gotham::handler::{HandlerError, HandlerFuture, IntoHandlerError}; +use gotham::handler::HandlerFuture; use gotham::helpers::http::response::create_response; use gotham::router::builder::DefineSingleRoute; use gotham::router::builder::{build_simple_router, DrawRoutes}; @@ -105,47 +105,23 @@ fn loop_handler(mut state: State) -> Pin> { let seconds = QueryStringExtractor::take_from(&mut state).seconds; println!("sleep for one second {} times: starting", seconds); - // We can't use the ? operator in the outermost async block, because we - // need to need to return ownership of the State object back to gotham. - // I quite like using ?, so I often find myself writing `async {}.await` - // to get around this problem when I'm feeling lazy. Think of this - // self-awaiting-block as a bit like a try block. - // - // In real code, you probably shouldn't be writing business logic in - // your Handler functions anyway. Instead, you should be - // unpacking everything you need from State in the Handler function - // and then calling your business logic with only the dependencies that - // they need. That way your business logic can use new-style futures - // and ? as much as it likes, and you will only need to update your - // handler functions (which don't contain any business logic) when you - // upgrade your gotham. - let result = async { - // The code within this block reads exactly like syncronous code. - // This is the style that you should aim to write your business - // logic in. - let mut accumulator = Vec::new(); - for _ in 0..seconds { - let body = sleep(1).await; - accumulator.extend(body) - } - // ? does type coercion for us, so we need to use a turbofish to - // tell the compiler that we have a HandlerError. See this section - // of the rust async book for more details: - // https://rust-lang.github.io/async-book/07_workarounds/03_err_in_async_blocks.html - Ok::<_, HandlerError>(accumulator) - } - .await; - - // This bit is the same boilerplate as the bit in the first example. - // Nothing to see here. - match result { - Ok(data) => { - let res = create_response(&state, StatusCode::OK, mime::TEXT_PLAIN, data); - println!("sleep for one second {} times: finished", seconds); - Ok((state, res)) - } - Err(err) => Err((state, err.into_handler_error())), + // The code within this block reads exactly like syncronous code. + // This is the style that you should aim to write your business + // logic in. + let mut accumulator = Vec::new(); + for _ in 0..seconds { + let body = sleep(1).await; + accumulator.extend(body) } + + let res = create_response( + &state, + StatusCode::OK, + mime::TEXT_PLAIN, + Body::from(accumulator), + ); + println!("sleep for one second {} times: finished", seconds); + Ok((state, res)) }; async_block_future.boxed() } From 60f883175886549528570fafa7fad65d5736a173 Mon Sep 17 00:00:00 2001 From: David Laban Date: Mon, 20 Jan 2020 22:04:34 +0000 Subject: [PATCH 07/14] POC router.to_async() --- .../simple_async_handlers_await/src/main.rs | 96 +++++++++---------- gotham/src/handler/mod.rs | 6 +- gotham/src/router/builder/single.rs | 18 +++- 3 files changed, 65 insertions(+), 55 deletions(-) diff --git a/examples/handlers/simple_async_handlers_await/src/main.rs b/examples/handlers/simple_async_handlers_await/src/main.rs index f003d76b1..e3b1e3b0e 100644 --- a/examples/handlers/simple_async_handlers_await/src/main.rs +++ b/examples/handlers/simple_async_handlers_await/src/main.rs @@ -17,7 +17,7 @@ use std::time::{Duration, Instant}; use hyper::{Body, StatusCode}; -use gotham::handler::HandlerFuture; +use gotham::handler::HandlerResult; use gotham::helpers::http::response::create_response; use gotham::router::builder::DefineSingleRoute; use gotham::router::builder::{build_simple_router, DrawRoutes}; @@ -69,61 +69,53 @@ fn sleep(seconds: u64) -> SleepFuture { /// This handler sleeps for the requested number of seconds, using the `sleep()` /// helper method, above. -fn sleep_handler(mut state: State) -> Pin> { - let async_block_future = async move { - let seconds = QueryStringExtractor::take_from(&mut 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. - 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. - 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); - println!("sleep for {} seconds once: finished", seconds); - Ok((state, res)) - }; - // Here, we move the future from the async block onto the heap so that - // gotham can use it later. - async_block_future.boxed() +async fn sleep_handler(mut state: State) -> HandlerResult { + let seconds = QueryStringExtractor::take_from(&mut 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. + 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. + 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); + println!("sleep for {} seconds once: finished", seconds); + Ok((state, res)) } /// It calls sleep(1) as many times as needed to make the requested duration. /// /// Notice how much easier it is to read than the version in /// `simple_async_handlers`. -fn loop_handler(mut state: State) -> Pin> { - let async_block_future = async move { - let seconds = QueryStringExtractor::take_from(&mut state).seconds; - println!("sleep for one second {} times: starting", seconds); - - // The code within this block reads exactly like syncronous code. - // This is the style that you should aim to write your business - // logic in. - let mut accumulator = Vec::new(); - for _ in 0..seconds { - let body = sleep(1).await; - accumulator.extend(body) - } - - let res = create_response( - &state, - StatusCode::OK, - mime::TEXT_PLAIN, - Body::from(accumulator), - ); - println!("sleep for one second {} times: finished", seconds); - Ok((state, res)) - }; - async_block_future.boxed() +async fn loop_handler(mut state: State) -> HandlerResult { + let seconds = QueryStringExtractor::take_from(&mut state).seconds; + println!("sleep for one second {} times: starting", seconds); + + // The code within this block reads exactly like syncronous code. + // This is the style that you should aim to write your business + // logic in. + let mut accumulator = Vec::new(); + for _ in 0..seconds { + let body = sleep(1).await; + accumulator.extend(body) + } + + let res = create_response( + &state, + StatusCode::OK, + mime::TEXT_PLAIN, + Body::from(accumulator), + ); + println!("sleep for one second {} times: finished", seconds); + Ok((state, res)) } /// Create a `Router`. @@ -132,11 +124,11 @@ fn router() -> Router { route .get("/sleep") .with_query_string_extractor::() - .to(sleep_handler); + .to_async(sleep_handler); route .get("/loop") .with_query_string_extractor::() - .to(loop_handler); + .to_async(loop_handler); }) } diff --git a/gotham/src/handler/mod.rs b/gotham/src/handler/mod.rs index 948ca4c3f..1a4f452a0 100644 --- a/gotham/src/handler/mod.rs +++ b/gotham/src/handler/mod.rs @@ -23,12 +23,14 @@ pub mod assets; pub use self::error::{HandlerError, IntoHandlerError}; +/// 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 trait objects returned by `HandlerService`. /// /// When the `Future` resolves to an error, the `(State, HandlerError)` value is used to generate /// an appropriate HTTP error response. -pub type HandlerFuture = - dyn Future), (State, HandlerError)>> + Send; +pub type HandlerFuture = dyn Future + Send; /// A `Handler` is an asynchronous function, taking a `State` value which represents the request /// and related runtime state, and returns a future which resolves to a response. diff --git a/gotham/src/router/builder/single.rs b/gotham/src/router/builder/single.rs index 8f9f3ba03..ba02e3e19 100644 --- a/gotham/src/router/builder/single.rs +++ b/gotham/src/router/builder/single.rs @@ -4,7 +4,7 @@ use std::panic::RefUnwindSafe; use crate::extractor::{PathExtractor, QueryStringExtractor}; use crate::handler::assets::{DirHandler, FileHandler, FileOptions, FilePathExtractor}; -use crate::handler::{Handler, NewHandler}; +use crate::handler::{Handler, HandlerError, NewHandler}; use crate::pipeline::chain::PipelineHandleChain; use crate::router::builder::{ ExtendRouteMatcher, ReplacePathExtractor, ReplaceQueryStringExtractor, SingleRouteBuilder, @@ -12,6 +12,10 @@ use crate::router::builder::{ use crate::router::route::dispatch::DispatcherImpl; use crate::router::route::matcher::RouteMatcher; use crate::router::route::{Delegation, Extractors, RouteImpl}; +use crate::state::State; +use core::future::Future; +use futures::FutureExt; +use hyper::Response; /// 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 @@ -106,6 +110,18 @@ pub trait DefineSingleRoute { where H: Handler + RefUnwindSafe + Copy + Send + Sync + 'static; + /// Similar to `to`, but accepts an `async fn` + fn to_async(self, handler: F) + where + Self: Sized, + F: (FnOnce(State) -> Fut) + RefUnwindSafe + Copy + Send + Sync + 'static, + Fut: Future), (State, HandlerError)>> + + Send + + 'static, + Self: Sized, + { + self.to_new_handler(move || Ok(move |s: State| handler(s).boxed())) + } /// Directs the route to the given `NewHandler`. This gives more control over how `Handler` /// values are constructed. /// From 6ef8666ab4c2236405c05d0c262dfc72346860a3 Mon Sep 17 00:00:00 2001 From: David Laban Date: Mon, 20 Jan 2020 22:25:18 +0000 Subject: [PATCH 08/14] Import macros explicitly like it's 2020 --- .../simple_async_handlers_await/src/main.rs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/examples/handlers/simple_async_handlers_await/src/main.rs b/examples/handlers/simple_async_handlers_await/src/main.rs index e3b1e3b0e..e29e30ec9 100644 --- a/examples/handlers/simple_async_handlers_await/src/main.rs +++ b/examples/handlers/simple_async_handlers_await/src/main.rs @@ -1,16 +1,5 @@ //! A basic example showing the request components -extern crate futures; -extern crate gotham; -#[macro_use] -extern crate gotham_derive; -extern crate hyper; -extern crate mime; -extern crate serde; -#[macro_use] -extern crate serde_derive; -extern crate tokio; - use futures::prelude::*; use std::pin::Pin; use std::time::{Duration, Instant}; @@ -23,6 +12,8 @@ use gotham::router::builder::DefineSingleRoute; use gotham::router::builder::{build_simple_router, DrawRoutes}; use gotham::router::Router; use gotham::state::{FromState, State}; +use gotham_derive::{StateData, StaticResponseExtender}; +use serde_derive::Deserialize; use tokio::time::delay_until; From 6294e75450c6245f19ce958fdab83708151bb760 Mon Sep 17 00:00:00 2001 From: David Laban Date: Tue, 21 Jan 2020 18:43:18 +0000 Subject: [PATCH 09/14] review comments --- examples/handlers/simple_async_handlers_await/Cargo.toml | 8 +------- examples/handlers/simple_async_handlers_await/src/main.rs | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/examples/handlers/simple_async_handlers_await/Cargo.toml b/examples/handlers/simple_async_handlers_await/Cargo.toml index 6101145e7..df20d0f35 100644 --- a/examples/handlers/simple_async_handlers_await/Cargo.toml +++ b/examples/handlers/simple_async_handlers_await/Cargo.toml @@ -10,14 +10,8 @@ edition = "2018" gotham = { path = "../../../gotham" } gotham_derive = { path = "../../../gotham_derive" } -hyper = "0.13.1" mime = "0.3.16" -# We need two versions of the futures library. One that matches what Gotham -# understands, and one that matches what .await understands. Stolen from: -# https://rust-lang-nursery.github.io/futures-rs/blog/2019/04/18/compatibility-layer.html -# (although they call the old version futures01 and we call it legacy_futures) -legacy_futures = {package = "futures", version = "0.1"} -futures = { package = "futures", version = "0.3.1", features = ["compat"] } +futures = "0.3.1" serde = "1.0.104" serde_derive = "1.0.104" tokio = "0.2.9" diff --git a/examples/handlers/simple_async_handlers_await/src/main.rs b/examples/handlers/simple_async_handlers_await/src/main.rs index e29e30ec9..3d0a1449f 100644 --- a/examples/handlers/simple_async_handlers_await/src/main.rs +++ b/examples/handlers/simple_async_handlers_await/src/main.rs @@ -4,7 +4,7 @@ use futures::prelude::*; use std::pin::Pin; use std::time::{Duration, Instant}; -use hyper::{Body, StatusCode}; +use gotham::hyper::{Body, StatusCode}; use gotham::handler::HandlerResult; use gotham::helpers::http::response::create_response; From 3940d4909f20a6fb73d3149f6a1122341c0290da Mon Sep 17 00:00:00 2001 From: David Laban Date: Tue, 21 Jan 2020 18:45:42 +0000 Subject: [PATCH 10/14] remove irrelevant bit of readme --- examples/handlers/simple_async_handlers_await/README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/handlers/simple_async_handlers_await/README.md b/examples/handlers/simple_async_handlers_await/README.md index ee8f9eb10..dd13ad5d8 100644 --- a/examples/handlers/simple_async_handlers_await/README.md +++ b/examples/handlers/simple_async_handlers_await/README.md @@ -7,9 +7,6 @@ of this example can be found at [Async Request Handlers](../simple_async_handler This example has exactly the same behavior and API as the combinator-based version, and it can be used as a reference when converting your code to use async/await. -It also leaves the versions of gotham, tokio and hyper the same, and uses the -compatibility helpers from the `futures` crate to convert things at the -interface boundaries. ## Running From 448fc39081fc969d0f12c9a97cdcdc40e24bb6eb Mon Sep 17 00:00:00 2001 From: David Laban Date: Tue, 21 Jan 2020 19:10:03 +0000 Subject: [PATCH 11/14] add example to to_async, and fix type soup --- gotham/src/router/builder/single.rs | 54 ++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/gotham/src/router/builder/single.rs b/gotham/src/router/builder/single.rs index ba02e3e19..8e6e7ca6a 100644 --- a/gotham/src/router/builder/single.rs +++ b/gotham/src/router/builder/single.rs @@ -4,7 +4,7 @@ use std::panic::RefUnwindSafe; use crate::extractor::{PathExtractor, QueryStringExtractor}; use crate::handler::assets::{DirHandler, FileHandler, FileOptions, FilePathExtractor}; -use crate::handler::{Handler, HandlerError, NewHandler}; +use crate::handler::{Handler, HandlerResult, NewHandler}; use crate::pipeline::chain::PipelineHandleChain; use crate::router::builder::{ ExtendRouteMatcher, ReplacePathExtractor, ReplaceQueryStringExtractor, SingleRouteBuilder, @@ -15,7 +15,6 @@ use crate::router::route::{Delegation, Extractors, RouteImpl}; use crate::state::State; use core::future::Future; use futures::FutureExt; -use hyper::Response; /// 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 @@ -111,14 +110,53 @@ pub trait DefineSingleRoute { H: Handler + RefUnwindSafe + Copy + Send + Sync + 'static; /// Similar to `to`, but accepts an `async fn` - fn to_async(self, handler: F) + /// + /// # Examples + /// + /// ```rust + /// # extern crate gotham; + /// # extern crate hyper; + /// # + /// # use hyper::{Body, Response, StatusCode}; + /// # use gotham::handler::HandlerResult; + /// # 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: State) -> HandlerResult { + /// // Handler implementation elided. + /// # Ok((state, Response::builder().status(StatusCode::ACCEPTED).body(Body::empty()).unwrap())) + /// } + /// # + /// # 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(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::ACCEPTED); + /// # } + /// ``` + fn to_async(self, handler: H) where Self: Sized, - F: (FnOnce(State) -> Fut) + RefUnwindSafe + Copy + Send + Sync + 'static, - Fut: Future), (State, HandlerError)>> - + Send - + 'static, - Self: Sized, + H: (FnOnce(State) -> Fut) + RefUnwindSafe + Copy + Send + Sync + 'static, + Fut: Future + Send + 'static, { self.to_new_handler(move || Ok(move |s: State| handler(s).boxed())) } From 51630b6b5e309a8122d4c3978f1c73503718fb5c Mon Sep 17 00:00:00 2001 From: David Laban Date: Tue, 21 Jan 2020 19:15:45 +0000 Subject: [PATCH 12/14] make deps more similar to combinator version --- examples/handlers/simple_async_handlers_await/Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/handlers/simple_async_handlers_await/Cargo.toml b/examples/handlers/simple_async_handlers_await/Cargo.toml index df20d0f35..cf7b1ca30 100644 --- a/examples/handlers/simple_async_handlers_await/Cargo.toml +++ b/examples/handlers/simple_async_handlers_await/Cargo.toml @@ -10,8 +10,8 @@ edition = "2018" gotham = { path = "../../../gotham" } gotham_derive = { path = "../../../gotham_derive" } -mime = "0.3.16" +mime = "0.3" futures = "0.3.1" -serde = "1.0.104" -serde_derive = "1.0.104" +serde = "1.0" +serde_derive = "1.0" tokio = "0.2.9" From 9e8c30a8cc51a178282cb3bb0bccaab45b05c935 Mon Sep 17 00:00:00 2001 From: David Laban Date: Sat, 22 Feb 2020 09:41:36 +0000 Subject: [PATCH 13/14] separate interface from implementation --- gotham/src/router/builder/single.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/gotham/src/router/builder/single.rs b/gotham/src/router/builder/single.rs index 8e6e7ca6a..371fb1fb9 100644 --- a/gotham/src/router/builder/single.rs +++ b/gotham/src/router/builder/single.rs @@ -156,10 +156,7 @@ pub trait DefineSingleRoute { where Self: Sized, H: (FnOnce(State) -> Fut) + RefUnwindSafe + Copy + Send + Sync + 'static, - Fut: Future + Send + 'static, - { - self.to_new_handler(move || Ok(move |s: State| handler(s).boxed())) - } + Fut: Future + Send + 'static; /// Directs the route to the given `NewHandler`. This gives more control over how `Handler` /// values are constructed. /// @@ -525,6 +522,15 @@ where self.to_new_handler(move || Ok(handler)) } + fn to_async(self, handler: H) + where + Self: Sized, + H: (FnOnce(State) -> Fut) + RefUnwindSafe + Copy + Send + Sync + 'static, + Fut: Future + Send + 'static, + { + self.to_new_handler(move || Ok(move |s: State| handler(s).boxed())) + } + fn to_new_handler(self, new_handler: NH) where NH: NewHandler + 'static, From 702e7cf369e915c13d34bbe8d59cc3f781bfb4a1 Mon Sep 17 00:00:00 2001 From: David Laban Date: Wed, 20 May 2020 18:54:14 +0100 Subject: [PATCH 14/14] fix link spotted by msrd0 --- examples/handlers/simple_async_handlers/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/handlers/simple_async_handlers/README.md b/examples/handlers/simple_async_handlers/README.md index b0993ca1c..e7acb56fa 100644 --- a/examples/handlers/simple_async_handlers/README.md +++ b/examples/handlers/simple_async_handlers/README.md @@ -25,7 +25,7 @@ then futures are probably not going to help your performance, and you might be better off spawning a new thread per request. If you came here looking for an example that uses async/.await, please read -[Async Request Handlers (.await version)](../simple_async_handlers). +[Async Request Handlers (.await version)](../simple_async_handlers_await). ## Running