mirror of https://github.com/rwf2/Rocket.git
Add a fairings guide.
This commit is contained in:
parent
05944c01af
commit
ba6d87a0e8
|
@ -0,0 +1,205 @@
|
|||
# Fairings
|
||||
|
||||
Fairings are Rocket's approach to structured middleware. They allow for
|
||||
interposition at various points in the application and request/response
|
||||
lifecycle through callbacks issued by Rocket.
|
||||
|
||||
## Overview
|
||||
|
||||
A _fairing_ is any type that implements the [`Fairing`] trait. The `Fairing`
|
||||
trait is composed of methods that represent callbacks that Rocket will run at
|
||||
requested points in a program. Through these methods, fairings can rewrite or
|
||||
record information about requests and responses as well as perform actions when
|
||||
a Rocket application launches.
|
||||
|
||||
[`Fairing`]: https://api.rocket.rs/rocket/fairing/trait.Fairing.html
|
||||
|
||||
### Attaching
|
||||
|
||||
For a fairing to be active, it must first be _attached_ through the the
|
||||
[`attach`] method on a [`Rocket`] instance. For instance, to attach fairings
|
||||
named `req_fairing` and `res_fairing` to a new Rocket instance, you might write:
|
||||
|
||||
```rust
|
||||
rocket::ignite()
|
||||
.attach(req_fairing)
|
||||
.attach(res_fairing)
|
||||
.launch();
|
||||
```
|
||||
|
||||
Once a fairing is attached, Rocket will execute its callbacks at the appropiate
|
||||
time.
|
||||
|
||||
[`attach`]: https://api.rocket.rs/rocket/struct.Rocket.html#method.attach
|
||||
[`Rocket`]: https://api.rocket.rs/rocket/struct.Rocket.html
|
||||
|
||||
### Callbacks
|
||||
|
||||
A fairing can implement any combination of the following four callbacks:
|
||||
|
||||
* **Attach**
|
||||
|
||||
An attach callback is called when a fairing is first attached via the
|
||||
[`attach`](https://api.rocket.rs/rocket/struct.Rocket.html#method.attach)
|
||||
method. An attach callback can arbitrarily modify the `Rocket` instance
|
||||
being constructed and optionally abort launch.
|
||||
|
||||
* **Launch**
|
||||
|
||||
A launch callback is called immediately before the Rocket application has
|
||||
launched. A launch callback can inspect the `Rocket` instance being
|
||||
launched.
|
||||
|
||||
* **Request**
|
||||
|
||||
A request callback is called just after a request is received. A request
|
||||
callback can modify the request at will and peek into the incoming data. It
|
||||
may not, however, abort or respond directly to the request; these issues are
|
||||
better handled via request guards or via response callbacks.
|
||||
|
||||
* **Response**
|
||||
|
||||
A response callback is called when a response is ready to be sent to the
|
||||
client. A response callback can modify the response at will. For example, a
|
||||
response callback can provide a default response when the user fails to
|
||||
handle the request by checking for 404 responses.
|
||||
|
||||
|
||||
### Execution Order
|
||||
|
||||
Fairings are executed in the order in which they are attached: the first
|
||||
attached fairing has its callbacks executed before all others. Because fairing
|
||||
callbacks may not be commutative, the order in which fairings are attached may
|
||||
be significant.
|
||||
|
||||
### Ad-Hoc Fairings
|
||||
|
||||
For simple occasions, implementing the `Fairing` trait can be cumbersome. This
|
||||
is why Rocket provides the [`AdHoc`] type, which creates a fairing from a simple
|
||||
function or clusure.
|
||||
|
||||
Using the `AdHoc` type is easy: simply call the `on_attach`, `on_launch`,
|
||||
`on_request`, or `on_response` constructors to create an `AdHoc` structure from
|
||||
a function or closure. Then, attach the structure to a `Rocket` instance. Rocket
|
||||
takes care of the rest.
|
||||
|
||||
As an example, the code below creates a `Rocket` instance with two attached
|
||||
ad-hoc fairings. The first, a launch fairing, simply prints a message indicating
|
||||
that the application is about to the launch. The second, a request fairing,
|
||||
changes the method of all requests to `PUT`.
|
||||
|
||||
```rust
|
||||
use rocket::fairing::AdHoc;
|
||||
use rocket::http::Method;
|
||||
|
||||
rocket::ignite()
|
||||
.attach(AdHoc::on_launch(|_| {
|
||||
println!("Rocket is about to launch! Exciting!");
|
||||
}))
|
||||
.attach(AdHoc::on_request(|req, _| {
|
||||
req.set_method(Method::Put);
|
||||
}));
|
||||
```
|
||||
|
||||
[`AdHoc`]: https://api.rocket.rs/rocket/fairing/enum.AdHoc.html
|
||||
|
||||
## Considerations
|
||||
|
||||
Fairings are a large hammer that can easily be abused and misused. If you
|
||||
are considering writing a `Fairing` implementation, first consider if it is
|
||||
appropriate to do so. While middleware is often the best solution to some
|
||||
problems in other frameworks, it is often a suboptimal solution in Rocket.
|
||||
This is because Rocket provides richer mechanisms such as [request guards]
|
||||
and [data guards] that can be used to accomplish the same objective in a
|
||||
cleaner, more composable, and more robust manner.
|
||||
|
||||
As a general rule of thumb, only _globally applicable actions_ should be
|
||||
implemented via fairings. For instance, you should _not_ use a fairing to
|
||||
implement authentication or authorization (preferring to use a [request
|
||||
guard] instead) _unless_ the authentication or authorization applies to the
|
||||
entire application. On the other hand, you _should_ use a fairing to record
|
||||
timing and/or usage statistics or to implement global security policies.
|
||||
|
||||
[request guard]: https://api.rocket.rs/rocket/request/trait.FromRequest.html
|
||||
[request guards]: https://api.rocket.rs/rocket/request/trait.FromRequest.html
|
||||
[data guards]: https://api.rocket.rs/rocket/data/trait.FromData.html
|
||||
|
||||
## Implementing
|
||||
|
||||
A fairing must implement the [`Fairing`] trait. A `Fairing` implementation has
|
||||
one required method: [`info`], which returns an [`Info`] structure. This
|
||||
structure is used by Rocket to assign a name to the `Fairing` and determine
|
||||
which callbacks to actually issue on the `Fairing`. A `Fairing` can also
|
||||
implement any of the available callbacks: [`on_attach`], [`on_launch`],
|
||||
[`on_request`], and [`on_response`].
|
||||
|
||||
[`Info`]: https://api.rocket.rs/rocket/fairing/struct.Info.html
|
||||
[`info`]: https://api.rocket.rs/rocket/fairing/trait.Fairing.html#tymethod.info
|
||||
[`on_attach`]: https://api.rocket.rs/rocket/fairing/trait.Fairing.html#method.on_attach
|
||||
[`on_launch`]: https://api.rocket.rs/rocket/fairing/trait.Fairing.html#method.on_launch
|
||||
[`on_request`]: https://api.rocket.rs/rocket/fairing/trait.Fairing.html#method.on_request
|
||||
[`on_response`]: https://api.rocket.rs/rocket/fairing/trait.Fairing.html#method.on_response
|
||||
|
||||
### Restrictions
|
||||
|
||||
A `Fairing` must be `Send + Sync + 'static`. This means that the fairing must be
|
||||
sendable across thread boundaries (`Send`), thread-safe (`Sync`), and have only
|
||||
`'static` references, if any (`'static`). Note that these bounds _do not_
|
||||
prohibit a `Fairing` from holding state: the state need simply be thread-safe
|
||||
and statically available or heap allocated.
|
||||
|
||||
## Example
|
||||
|
||||
Imagine that we want to record the number of `GET` and `POST` requests that our
|
||||
application has received. While we could do this with request guards and managed
|
||||
state, it would require us to annotate every `GET` and `POST` request with
|
||||
custom types, polluting handler signatures. Instead, we can create a simple
|
||||
fairing that acts globally.
|
||||
|
||||
The `Counter` fairing below records the number of all `GET` and `POST` requests
|
||||
received. It makes these counts available at a special `'/counts'` path.
|
||||
|
||||
```rust
|
||||
struct Counter {
|
||||
get: AtomicUsize,
|
||||
post: AtomicUsize,
|
||||
}
|
||||
|
||||
impl Fairing for Counter {
|
||||
fn info(&self) -> Info {
|
||||
Info {
|
||||
name: "GET/POST Counter",
|
||||
kind: Kind::Request | Kind::Response
|
||||
}
|
||||
}
|
||||
|
||||
fn on_request(&self, request: &mut Request, _: &Data) {
|
||||
match request.method() {
|
||||
Method::Get => self.get.fetch_add(1, Ordering::Relaxed),
|
||||
Method::Post => self.post.fetch_add(1, Ordering::Relaxed),
|
||||
_ => return
|
||||
}
|
||||
}
|
||||
|
||||
fn on_response(&self, request: &Request, response: &mut Response) {
|
||||
// Don't change a successful user's response, ever.
|
||||
if response.status() != Status::NotFound {
|
||||
return
|
||||
}
|
||||
|
||||
if request.method() == Method::Get && request.uri().path() == "/counts" {
|
||||
let get_count = self.get.load(Ordering::Relaxed);
|
||||
let post_count = self.post.load(Ordering::Relaxed);
|
||||
let body = format!("Get: {}\nPost: {}", get_count, post_count);
|
||||
|
||||
response.set_status(Status::Ok);
|
||||
response.set_header(ContentType::Plain);
|
||||
response.set_sized_body(Cursor::new(body));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For brevity, imports are not shown. The complete example can be found in the
|
||||
[`Fairing`
|
||||
documentation](https://api.rocket.rs/rocket/fairing/trait.Fairing.html#example).
|
Loading…
Reference in New Issue