-
Notifications
You must be signed in to change notification settings - Fork 125
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add example of middleware with async-await usage (#577)
- Loading branch information
1 parent
7efda5e
commit 097822e
Showing
4 changed files
with
236 additions
and
0 deletions.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
[package] | ||
name = "gotham_examples_middleware_introduction_await" | ||
description = "Introduces the Middleware and Pipeline concepts provided by the Gotham web framework with async-await" | ||
version = "0.0.0" | ||
publish = false | ||
edition = "2018" | ||
|
||
[dependencies] | ||
gotham = { path = "../../../gotham" } |
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,53 @@ | ||
# Middleware and Pipelines introduction | ||
|
||
Introduces the `Middleware` and `Pipelines` concepts provided by the | ||
Gotham web framework. Shows how to use async-await syntax in middlewares. | ||
|
||
## Running | ||
|
||
From the `examples/middleware/introduction_await` directory: | ||
|
||
``` | ||
Terminal 1: | ||
$ cargo run | ||
Compiling gotham_examples_middleware_introduction_await v0.0.0 (file://.../gotham/examples/middleware/introduction_await) | ||
Finished dev [unoptimized + debuginfo] target(s) in 3.57s | ||
Running `.../gotham/target/debug/gotham_examples_middleware_introduction_await` | ||
Listening for requests at http://127.0.0.1:7878 | ||
Terminal 2: | ||
$ curl -v http://localhost:7878/ | ||
* Trying 127.0.0.1... | ||
* TCP_NODELAY set | ||
* Connected to localhost (127.0.0.1) port 7878 (#0) | ||
> GET / HTTP/1.1 | ||
> Host: localhost:7878 | ||
> User-Agent: curl/7.54.1 | ||
> Accept: */* | ||
> | ||
< HTTP/1.1 200 OK | ||
< Content-Length: 0 | ||
< X-Request-ID: 22d760ad-a7d2-4cf1-accf-fb192230d0ea | ||
< X-Frame-Options: DENY | ||
< X-XSS-Protection: 1; mode=block | ||
< X-Content-Type-Options: nosniff | ||
< X-User-Agent: Supplied: curl/7.54.1, Supported: true | ||
< X-Runtime-Microseconds: 147 | ||
< Date: Mon, 19 Feb 2018 10:29:41 GMT | ||
< | ||
* Connection #0 to host localhost left intact | ||
``` | ||
|
||
## 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) |
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,173 @@ | ||
//! Introduces the Middleware and Pipeline concepts provided by the Gotham web framework. | ||
|
||
use std::pin::Pin; | ||
|
||
use gotham::handler::HandlerFuture; | ||
use gotham::helpers::http::response::create_empty_response; | ||
use gotham::hyper::header::{HeaderMap, USER_AGENT}; | ||
use gotham::hyper::{Body, Response, StatusCode}; | ||
use gotham::middleware::{Middleware, NewMiddleware}; | ||
use gotham::pipeline::{new_pipeline, single_pipeline}; | ||
use gotham::router::builder::*; | ||
use gotham::router::Router; | ||
use gotham::state::{FromState, State, StateData}; | ||
|
||
/// A simple struct which holds an identifier for the user agent which made the request. | ||
/// | ||
/// It is created by our Middleware and then accessed via `state` by both our Middleware and Handler. | ||
#[derive(StateData)] | ||
pub struct ExampleMiddlewareData { | ||
pub user_agent: String, | ||
pub supported: bool, | ||
} | ||
|
||
/// A struct that can act as a Gotham web framework middleware. | ||
/// | ||
/// The key requirements for struct to act as a Middleware are: | ||
/// | ||
/// 1. That the struct implements the `gotham::middleware::NewMiddleware` trait which allows | ||
/// the Gotham web framework to create a new instance of your middleware to service every | ||
/// request. In many cases, as we're doing here, this can simply be derived. | ||
/// 2. That the struct implements the `gotham::middleware::Middleware` trait as we're doing | ||
/// next. | ||
#[derive(Clone, NewMiddleware)] | ||
pub struct ExampleMiddleware; | ||
|
||
/// Implementing `gotham::middleware::Middleware` allows the logic that you want your Middleware to | ||
/// provided to be correctly executed by the Gotham web framework Router. | ||
/// | ||
/// As shown here Middleware can make changes to the environment both before and after the handler^ | ||
/// for the route is executed. | ||
/// | ||
/// ^Later examples will show how Middlewares in a pipeline can work with each other in a similar | ||
/// manner. | ||
impl Middleware for ExampleMiddleware { | ||
fn call<Chain>(self, mut state: State, chain: Chain) -> Pin<Box<HandlerFuture>> | ||
where | ||
Chain: FnOnce(State) -> Pin<Box<HandlerFuture>>, | ||
{ | ||
let user_agent = match HeaderMap::borrow_from(&state).get(USER_AGENT) { | ||
Some(ua) => ua.to_str().unwrap().to_string(), | ||
None => "None".to_string(), | ||
}; | ||
|
||
// Prior to letting Request handling proceed our middleware creates some new data and adds | ||
// it to `state`. | ||
state.put(ExampleMiddlewareData { | ||
user_agent, | ||
supported: false, | ||
}); | ||
|
||
// We're finished working on the Request, so allow other components to continue processing | ||
// the Request. | ||
// | ||
// Alternatively we could elect to not call chain and return a Response we've created if we | ||
// want to prevent any further processing from occuring on the Request. | ||
let inner_future = chain(state); | ||
|
||
// Once a Response is generated by another part of the application, in this example's case | ||
// the middleware_reliant_handler function, we want to do some more work. | ||
// | ||
// Here we use async-await syntax to add new actions after handling request. | ||
let f = async move { | ||
let (state, mut response) = inner_future.await?; | ||
let headers = response.headers_mut(); | ||
let data = ExampleMiddlewareData::borrow_from(&state); | ||
|
||
// All our middleware does is add a header to the Response generated by our handler. | ||
headers.insert( | ||
"X-User-Agent", | ||
format!( | ||
"Supplied: {}, Supported: {}", | ||
data.user_agent, data.supported | ||
) | ||
.parse() | ||
.unwrap(), | ||
); | ||
|
||
Ok((state, response)) | ||
}; | ||
|
||
Box::pin(f) | ||
} | ||
} | ||
|
||
/// The handler which is invoked for all requests to "/". | ||
/// | ||
/// This handler expects that `ExampleMiddleware` has already been executed by Gotham before | ||
/// it is invoked. As a result of that middleware being run our handler trusts that it must | ||
/// have placed data into state that we can perform operations on. | ||
pub fn middleware_reliant_handler(mut state: State) -> (State, Response<Body>) { | ||
{ | ||
let data = ExampleMiddlewareData::borrow_mut_from(&mut state); | ||
|
||
// Mark any kind of web client as supported. A trival example but it highlights the | ||
// interaction that is possible between Middleware and Handlers via state. | ||
data.supported = true; | ||
}; | ||
|
||
// Finally we create a basic Response to complete our handling of the Request. | ||
let res = create_empty_response(&state, StatusCode::OK); | ||
(state, res) | ||
} | ||
|
||
/// Create a `Router` | ||
fn router() -> Router { | ||
// Within the Gotham web framework Middleware is added to and referenced from a Pipeline. | ||
// | ||
// A pipeline can consist of multiple Middleware types and guarantees to call them all in the | ||
// ordering which is established by successive calls to the `add` method. | ||
// | ||
// A pipeline is considered complete once the build method is called and can no longer | ||
// be modified. | ||
// | ||
// The Gotham web framework supports multiple Pipelines and even Pipelines containing Pipelines. | ||
// However, as shown here, many applications will get sufficent power and flexibility | ||
// from a `single_pipeline` which we've provided specific API assitance for. | ||
let (chain, pipelines) = single_pipeline(new_pipeline().add(ExampleMiddleware).build()); | ||
|
||
// Notice we've switched from build_simple_router which has been present in all our examples up | ||
// until this point. Under the hood build_simple_router has simply been creating an empty | ||
// set of Pipelines on your behalf. | ||
// | ||
// Now that we're creating and populating our own Pipelines we'll switch to using | ||
// build_router directly. | ||
// | ||
// Tip: Use build_simple_router for as long as you can. Switching to build_router is simple once | ||
// do need to introduce Pipelines and Middleware. | ||
build_router(chain, pipelines, |route| { | ||
route.get("/").to(middleware_reliant_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()).unwrap(); | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use gotham::test::TestServer; | ||
|
||
#[test] | ||
fn ensure_middleware_and_handler_collaborate() { | ||
let test_server = TestServer::new(router()).unwrap(); | ||
let response = test_server | ||
.client() | ||
.get("http://localhost") | ||
.with_header(USER_AGENT, "TestServer/0.0.0".parse().unwrap()) | ||
.perform() | ||
.unwrap(); | ||
|
||
assert_eq!(response.status(), StatusCode::OK); | ||
|
||
// Ensure Middleware has set a header after our handler generated the Response. | ||
assert_eq!( | ||
response.headers().get("X-User-Agent").unwrap(), | ||
"Supplied: TestServer/0.0.0, Supported: true" | ||
); | ||
} | ||
} |