-
Notifications
You must be signed in to change notification settings - Fork 125
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
router.to_async(), simple async/.await examples #281
Merged
Merged
Changes from 13 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
9bbf9da
copy simple_async_handlers as a base
alsuren bf2bca8
.await version of the simple_async_handlers example
alsuren 7d7258a
make docstrings easier to read
alsuren a960ef0
rustfmt fix
alsuren 9cba316
make it compile after rebase
alsuren 122015a
simplify example now we're not using ?
alsuren 60f8831
POC router.to_async()
alsuren 6ef8666
Import macros explicitly like it's 2020
alsuren 6294e75
review comments
alsuren 3940d49
remove irrelevant bit of readme
alsuren 448fc39
add example to to_async, and fix type soup
alsuren 51630b6
make deps more similar to combinator version
alsuren 9e8c30a
separate interface from implementation
alsuren 702e7cf
fix link spotted by msrd0
alsuren File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
[package] | ||
name = "gotham_examples_handlers_simple_async_handlers_await" | ||
description = "An example that does asynchronous work before responding" | ||
version = "0.0.0" | ||
authors = ["David Laban <alsuren@gmail.com>"] | ||
publish = false | ||
edition = "2018" | ||
|
||
[dependencies] | ||
gotham = { path = "../../../gotham" } | ||
gotham_derive = { path = "../../../gotham_derive" } | ||
|
||
mime = "0.3" | ||
futures = "0.3.1" | ||
serde = "1.0" | ||
serde_derive = "1.0" | ||
tokio = "0.2.9" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
# 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. 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. | ||
|
||
## 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) |
162 changes: 162 additions & 0 deletions
162
examples/handlers/simple_async_handlers_await/src/main.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
//! A basic example showing the request components | ||
|
||
use futures::prelude::*; | ||
use std::pin::Pin; | ||
use std::time::{Duration, Instant}; | ||
|
||
use gotham::hyper::{Body, StatusCode}; | ||
|
||
use gotham::handler::HandlerResult; | ||
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 gotham_derive::{StateData, StaticResponseExtender}; | ||
use serde_derive::Deserialize; | ||
|
||
use tokio::time::delay_until; | ||
|
||
type SleepFuture = Pin<Box<dyn Future<Output = Vec<u8>> + 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<u8> 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<u8> 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_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. | ||
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`. | ||
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`. | ||
fn router() -> Router { | ||
build_simple_router(|route| { | ||
route | ||
.get("/sleep") | ||
.with_query_string_extractor::<QueryStringExtractor>() | ||
.to_async(sleep_handler); | ||
route | ||
.get("/loop") | ||
.with_query_string_extractor::<QueryStringExtractor>() | ||
.to_async(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", | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this link is incorrect as it points to itself if I'm not mistaken
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good spot. Thanks.