Trait rocket::fairing::Fairing [−][src]
pub trait Fairing: Send + Sync + 'static {
fn info(&self) -> Info;
fn on_attach(&self, rocket: Rocket) -> Result<Rocket, Rocket> { ... }
fn on_launch(&self, rocket: &Rocket) { ... }
fn on_request(&self, request: &mut Request<'_>, data: &Data) { ... }
fn on_response(&self, request: &Request<'_>, response: &mut Response<'_>) { ... }
}
Expand description
Trait implemented by fairings: Rocket’s structured middleware.
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.
Fairing Callbacks
There are four kinds of fairing callbacks: attach, launch, request, and
response. A fairing can request any combination of these callbacks through
the kind
field of the Info
structure returned from the info
method.
Rocket will only invoke the callbacks set in the kind
field.
The four callback kinds are as follows:
-
Attach (
on_attach
)An attach callback, represented by the
Fairing::on_attach()
method, is called when a fairing is first attached viaRocket::attach()
method. The state of theRocket
instance is, at this point, not finalized, as the user may still add additional information to theRocket
instance. As a result, it is unwise to depend on the state of theRocket
instance.An attach callback can arbitrarily modify the
Rocket
instance being constructed. It returnsOk
if it would like launching to proceed nominally andErr
otherwise. If an attach callback returnsErr
, launch will be aborted. All attach callbacks are executed onlaunch
, even if one or more signal a failure. -
Launch (
on_launch
)A launch callback, represented by the
Fairing::on_launch()
method, is called immediately before the Rocket application has launched. At this point, Rocket has opened a socket for listening but has not yet begun accepting connections. A launch callback can inspect theRocket
instance being launched. -
Request (
on_request
)A request callback, represented by the
Fairing::on_request()
method, is called just after a request is received, immediately after pre-processing the request with method changes due to_method
form fields. At this point, Rocket has parsed the incoming HTTP request intoRequest
andData
structures but has not routed the request. A request callback can modify the request at will andData::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. Any modifications to a request are persisted and can potentially alter how a request is routed. = -
Response (
on_response
)A response callback, represented by the
Fairing::on_response()
method, is called when a response is ready to be sent to the client. At this point, Rocket has completed all routing, including to error catchers, and has generated the would-be final response. 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. Note that a givenRequest
may have changed betweenon_request
andon_response
invocations. Apart from any change made by other fairings, Rocket sets the method forHEAD
requests toGET
if there is no matchingHEAD
handler for that request. Additionally, Rocket will automatically strip the body forHEAD
requests after response fairings have run.
Implementing
A Fairing
implementation has one required method: info
. A Fairing
can also implement any of the available callbacks: on_attach
, on_launch
,
on_request
, and on_response
. A Fairing
must set the appropriate
callback kind in the kind
field of the returned Info
structure from
info
for a callback to actually be called by Rocket.
Fairing Info
Every Fairing
must implement the info
method, which returns an
Info
structure. This structure is used by Rocket to:
-
Assign a name to the
Fairing
.This is the
name
field, which can be any arbitrary string. Name your fairing something illustrative. The name will be logged during the application’s launch procedures. -
Determine which callbacks to actually issue on the
Fairing
.This is the
kind
field of typeKind
. This field is a bitset that represents the kinds of callbacks the fairing wishes to receive. Rocket will only invoke the callbacks that are flagged in this set.Kind
structures can beor
d together to represent any combination of kinds of callbacks. For instance, to request launch and response callbacks, return akind
field with the valueKind::Launch | Kind::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.
use std::io::Cursor;
use std::sync::atomic::{AtomicUsize, Ordering};
use rocket::{Request, Data, Response};
use rocket::fairing::{Fairing, Info, Kind};
use rocket::http::{Method, ContentType, Status};
#[derive(Default)]
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) {
if request.method() == Method::Get {
self.get.fetch_add(1, Ordering::Relaxed);
} else if request.method() == Method::Post {
self.post.fetch_add(1, Ordering::Relaxed);
}
}
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));
}
}
}
Request-Local State
Fairings can use request-local state to persist or carry data between requests and responses, or to pass data to a request guard.
As an example, the following fairing uses request-local state to time
requests, setting an X-Response-Time
header on all responses with the
elapsed time. It also exposes the start time of a request via a StartTime
request guard.
/// Fairing for timing requests.
pub struct RequestTimer;
/// Value stored in request-local state.
#[derive(Copy, Clone)]
struct TimerStart(Option<SystemTime>);
impl Fairing for RequestTimer {
fn info(&self) -> Info {
Info {
name: "Request Timer",
kind: Kind::Request | Kind::Response
}
}
/// Stores the start time of the request in request-local state.
fn on_request(&self, request: &mut Request, _: &Data) {
// Store a `TimerStart` instead of directly storing a `SystemTime`
// to ensure that this usage doesn't conflict with anything else
// that might store a `SystemTime` in request-local cache.
request.local_cache(|| TimerStart(Some(SystemTime::now())));
}
/// Adds a header to the response indicating how long the server took to
/// process the request.
fn on_response(&self, request: &Request, response: &mut Response) {
let start_time = request.local_cache(|| TimerStart(None));
if let Some(Ok(duration)) = start_time.0.map(|st| st.elapsed()) {
let ms = duration.as_secs() * 1000 + duration.subsec_millis() as u64;
response.set_raw_header("X-Response-Time", format!("{} ms", ms));
}
}
}
/// Request guard used to retrieve the start time of a request.
#[derive(Copy, Clone)]
pub struct StartTime(pub SystemTime);
// Allows a route to access the time a request was initiated.
impl<'a, 'r> FromRequest<'a, 'r> for StartTime {
type Error = ();
fn from_request(request: &'a Request<'r>) -> request::Outcome<StartTime, ()> {
match *request.local_cache(|| TimerStart(None)) {
TimerStart(Some(time)) => Outcome::Success(StartTime(time)),
TimerStart(None) => Outcome::Failure((Status::InternalServerError, ())),
}
}
}
Required methods
Returns an Info
structure containing the name
and Kind
of this
fairing. The name
can be any arbitrary string. Kind
must be an or
d
set of Kind
variants.
This is the only required method of a Fairing
. All other methods have
no-op default implementations.
Rocket will only dispatch callbacks to this fairing for the kinds in the
kind
field of the returned Info
structure. For instance, if
Kind::Launch | Kind::Request
is used, then Rocket will only call the
on_launch
and on_request
methods of the fairing. Similarly, if
Kind::Response
is used, Rocket will only call the on_response
method
of this fairing.
Example
An info
implementation for MyFairing
: a fairing named “My Custom
Fairing” that is both a launch and response fairing.
use rocket::fairing::{Fairing, Info, Kind};
struct MyFairing;
impl Fairing for MyFairing {
fn info(&self) -> Info {
Info {
name: "My Custom Fairing",
kind: Kind::Launch | Kind::Response
}
}
}
Provided methods
The attach callback. Returns Ok
if launch should proceed and Err
if
launch should be aborted.
This method is called when a fairing is attached if Kind::Attach
is in
the kind
field of the Info
structure for this fairing. The rocket
parameter is the Rocket
instance that is currently being built for
this application.
Default Implementation
The default implementation of this method simply returns Ok(rocket)
.
The launch callback.
This method is called just prior to launching the application if
Kind::Launch
is in the kind
field of the Info
structure for this
fairing. The &Rocket
parameter corresponds to the application that
will be launched.
Default Implementation
The default implementation of this method does nothing.
fn on_request(&self, request: &mut Request<'_>, data: &Data)
fn on_request(&self, request: &mut Request<'_>, data: &Data)
The request callback.
This method is called when a new request is received if Kind::Request
is in the kind
field of the Info
structure for this fairing. The
&mut Request
parameter is the incoming request, and the &Data
parameter is the incoming data in the request.
Default Implementation
The default implementation of this method does nothing.
fn on_response(&self, request: &Request<'_>, response: &mut Response<'_>)
fn on_response(&self, request: &Request<'_>, response: &mut Response<'_>)
The response callback.
This method is called when a response is ready to be issued to a client
if Kind::Response
is in the kind
field of the Info
structure for
this fairing. The &Request
parameter is the request that was routed,
and the &mut Response
parameter is the resulting response.
Default Implementation
The default implementation of this method does nothing.