Expand description
The middleware and handler system module.
It’s highly inspired by The Iron middleware & handler system and intended to work with latest Hyper 0.14
.
The middleware and handler system are the fundamental building blocks for handling HTTP requests and generating responses with Hyper.
§Handlers
A Handler
will produce a Response
given a Request
. Most handlers are
functions or closures that accept a &mut Request
as an argument and return
an Result
containing a Response
. An Result
is returned instead of
directly returning a Response
in order to indicate a possibility of
failure (e.g. database timeout).
Here’s an example of a Handler
:
use hyper_middleware::{async_trait, Handler, Request, Response, Result, Body};
struct Application {}
#[async_trait]
impl Handler for Application {
async fn handle(&self, req: &mut Request) -> Result<Response> {
Ok(Response::builder().body(Body::from("¡Hola!")).unwrap())
}
}
§Middleware
In situations involving more complex logic, it may be desirable to transform
Request
s passed to a Handler
or altering Response
s sent to the
clients. For example, an authorization step could only allow requests sent
by authorized users to be passed to a Handler
and respond to all other
requests with a 403 status code. To faciliate such use cases, Iron’s
middleware system allows Handler
s to be extended by defining middleware,
which will perform transformations.
There are three types of middleware:
- A
BeforeMiddleware
alters aRequest
. It can be useful for handling control flow (e.g. routing and authorization). - An
AroundMiddleware
wraps aHandler
, changing both theResponse
passed to theHandler
and the returnedResponse
. - An
AfterMiddleware
performsResponse
post-processing. It can be used for editing headers or loggingResponse
s, but it should not be used for changing the body of aResponse
.
See the documentation for each middleware for more details.
§Defining the middleware pipeline
the Middlewares
chain is a Handler
that wraps another Handler
. It is used to attach
middleware to the wrapped Handler
using a link
method corresponding to
each type of middleware. A sample middleware pipeline is shown below:
use hyper::Server;
use hyper_middleware::{async_trait, Handler, BeforeMiddleware, Body, Middlewares, Request, Response, Result, Service};
struct Application {}
#[async_trait]
impl Handler for Application {
async fn handle(&self, req: &mut Request) -> Result<Response> {
let mut resp = Response::new(Body::from("¡Hola!"));
resp.headers_mut().insert(
hyper::header::CONTENT_TYPE,
"text/html; charset=utf-8".parse().unwrap(),
);
Ok(resp)
}
}
struct RequestLoggingMiddleware {}
#[async_trait]
impl BeforeMiddleware for RequestLoggingMiddleware {
async fn before(&self, req: &mut Request) -> Result {
println!("{:?}", req);
Ok(())
}
}
#[tokio::main(flavor = "multi_thread")]
async fn main() -> Result {
let mut middlewares = Middlewares::new(Application {});
// Plug in the custom middleware(s)
middlewares.link_before(RequestLoggingMiddleware {});
let addr = ([127, 0, 0, 1], 8080).into();
let service = Service::new(middlewares);
let server = Server::bind(&addr).serve(service);
println!("Listening on http://{}", addr);
// server.await?;
Ok(())
}
§The Request Handling Flow
A diagram modeling the entire middleware system process is shown below:
[b] = BeforeMiddleware
[a] = AfterMiddleware
[[h]] = AroundMiddleware
[h] = Handler
With no errors, the flow looks like:
[b] -> [b] -> [b] -> [[[[h]]]] -> [a] -> [a] -> [a] -> [a]
A request first travels through all BeforeMiddleware
, then a Response
is
generated by the Handler
, which can be an arbitrary nesting of
AroundMiddleware
, then all AfterMiddleware
are called with both the
Request
and Response
. After all AfterMiddleware
have been fired, the
response is written back to the client.
Iron’s error handling system is pragmatic and focuses on tracking two pieces of information for error receivers (other middleware):
- The cause of the error
- The result (what to do about) the error.
The cause of the error is represented simply by the error itself, and the result of the error, representing the action to take in response to the error, is a complete Response, which will be sent at the end of the error flow.
When an error is thrown in Iron by any middleware or handler returning an
Err
variant with an Error
, the flow of the Request
switches to the
error flow, which proceeds to just call the catch
method of middleware and
sidesteps the Handler
entirely, since there is already a Response
in the
error.
A Request
can exit the error flow by returning an Ok from any of the catch
methods. This resumes the flow at the middleware immediately following the
middleware which handled the error. It is impossible to “go back” to an
earlier middleware that was skipped.
Generally speaking, returning a 5xx
error code means that the error flow
should be entered by raising an explicit error. Dealing with 4xx
errors is
trickier, since the server may not want to recognize an error that is
entirely the clients fault; handling of 4xx
error codes is up to to each
application and middleware author.
Middleware authors should be cognizant that their middleware may be skipped
during the error flow. Anything that must be done to each Request
or
Response
should be run during both the normal and error flow by
implementing the catch
method to also do the necessary action.
Structs§
- Middlewares
- The middleware chain which can append other middlewares.
Traits§
- After
Middleware AfterMiddleware
are fired after aHandler
is called inside of theMiddlewares
chain.- Around
Middleware AroundMiddleware
are used to wrap and replace theHandler
in theMiddlewares
chain.- Before
Middleware BeforeMiddleware
are fired before aHandler
is called inside of a Middlewares.- Handler
Handler
s are responsible for handling requests by creatingResponse
s from thoseRequest
s.