Rocket/site/guide/fairings.md

7.9 KiB

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.

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:

rocket::ignite()
    .attach(req_fairing)
    .attach(res_fairing)
    .launch();

Once a fairing is attached, Rocket will execute its callbacks at the appropiate time.

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 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.

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);
    }));

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.

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.

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.

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.