Skip to content

Commit

Permalink
Merge branch 'master' into improve-error-handling
Browse files Browse the repository at this point in the history
  • Loading branch information
msrd0 committed Jun 15, 2020
2 parents 170ea14 + 77c563c commit f2fe29d
Show file tree
Hide file tree
Showing 22 changed files with 677 additions and 74 deletions.
8 changes: 2 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,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",
Expand All @@ -75,15 +76,10 @@ members = [
"examples/example_contribution_template/name",

# TODO: Re-enable when tokio-tungstenite is updated
# "examples/websocket",
"examples/websocket",

# finalizer
"examples/finalizers/",


]

[patch.crates-io]
gotham = { path = "gotham" }
gotham_derive = { path = "gotham_derive" }
borrow-bag = { path = "misc/borrow_bag" }
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ We do acknowledge that sometimes the choices we've made for the Gotham web
framework may not suit the needs of all projects. If that is the case for your
project there are alternative Rust web frameworks you might like to consider:

1. [Actix-Web](https://github.com/actix/actix-web)
1. [Conduit](https://github.com/conduit-rust/conduit)
1. [Nickel](https://github.com/nickel-org/nickel.rs)
1. [Rocket](https://github.com/SergioBenitez/Rocket)
Expand Down
2 changes: 1 addition & 1 deletion examples/cookies/introduction/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ edition = "2018"
gotham = { path = "../../../gotham" }

mime = "0.3"
cookie = "0.13"
cookie = "0.14"
1 change: 1 addition & 0 deletions examples/handlers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions examples/handlers/simple_async_handlers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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_await).

## Running

From the `examples/handlers/async_handlers` directory:
Expand Down
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_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"
49 changes: 49 additions & 0 deletions examples/handlers/simple_async_handlers_await/README.md
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 examples/handlers/simple_async_handlers_await/src/main.rs
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",
);
}
}
50 changes: 48 additions & 2 deletions examples/path/globs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Terminal 2:
> Host: localhost:7878
> User-Agent: curl/7.54.0
> Accept: */*
>
>
< HTTP/1.1 200 OK
< Content-Length: 39
< Content-Type: text/plain
Expand All @@ -34,13 +34,59 @@ Terminal 2:
< X-Content-Type-Options: nosniff
< X-Runtime-Microseconds: 165
< Date: Mon, 19 Mar 2018 22:17:17 GMT
<
<
Got 4 parts:
heads
shoulders
knees
* Connection #0 to host localhost left intact
toes
curl -vvv http://localhost:7878/middle/heads/shoulders/knees/toes/foobar
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 7878 (#0)
> GET /middle/heads/shoulders/knees/toes/foobar HTTP/1.1
> Host: localhost:7878
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< x-request-id: 8449a2ed-2b00-4fbf-98af-63e17d65a345
< content-type: text/plain
< content-length: 39
< date: Sat, 23 May 2020 09:00:40 GMT
<
Got 4 parts:
heads
shoulders
knees
* Connection #0 to host localhost left intact
toes
curl -vvv http://localhost:7878/multi/heads/shoulders/foobar/knees/toes
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 7878 (#0)
> GET /multi/heads/shoulders/foobar/knees/toes HTTP/1.1
> Host: localhost:7878
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< x-request-id: 4cbcf782-9dbb-4fcd-b12e-55661d3309a4
< content-type: text/plain
< content-length: 72
< date: Sat, 23 May 2020 09:09:51 GMT
<
Got 2 parts for top:
heads
shoulders
Got 2 parts for bottom:
knees
* Connection #0 to host localhost left intact
toes
```

## License
Expand Down
Loading

0 comments on commit f2fe29d

Please sign in to comment.