Skip to content

Commit

Permalink
copy simple_async_handlers as a base
Browse files Browse the repository at this point in the history
  • Loading branch information
alsuren committed Nov 16, 2019
1 parent ff707e6 commit a138306
Show file tree
Hide file tree
Showing 3 changed files with 262 additions and 0 deletions.
17 changes: 17 additions & 0 deletions examples/handlers/simple_async_handlers_await/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>"]
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"
65 changes: 65 additions & 0 deletions examples/handlers/simple_async_handlers_await/README.md
Original file line number Diff line number Diff line change
@@ -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)
180 changes: 180 additions & 0 deletions examples/handlers/simple_async_handlers_await/src/main.rs
Original file line number Diff line number Diff line change
@@ -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<dyn Future<Item = Vec<u8>, 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<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::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<HandlerFuture> {
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<HandlerFuture> {
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::<QueryStringExtractor>()
.to(sleep_handler);
route
.get("/loop")
.with_query_string_extractor::<QueryStringExtractor>()
.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",
);
}
}

0 comments on commit a138306

Please sign in to comment.