knowledge/technology/dev/programming/frameworks/Rocket.md
2024-07-08 09:09:36 +02:00

92 KiB
Raw Blame History

obj website rev
concept https://rocket.rs 2024-06-19

Rocket.rs

Rocket provides primitives to build web servers and applications with Rust.

Lifecycle

Rocket's main task is to listen for incoming web requests, dispatch the request to the application code, and return a response to the client. We call the process that goes from request to response the "lifecycle". We summarize the lifecycle as the following sequence of steps:

  1. Routing
    Rocket parses an incoming HTTP request into native structures that your code operates on indirectly. Rocket determines which request handler to invoke by matching against route attributes declared in your application.
  2. Validation
    Rocket validates the incoming request against types and guards present in the matched route. If validation fails, Rocket forwards the request to the next matching route or calls an error handler.
  3. Processing
    The request handler associated with the route is invoked with validated arguments. This is the main business logic of an application. Processing completes by returning a Response.
  4. Response
    The returned Response is processed. Rocket generates the appropriate HTTP response and sends it to the client. This completes the lifecycle. Rocket continues listening for requests, restarting the lifecycle for each incoming request.

Routing

Rocket applications are centered around routes and handlers. A route is a combination of:

  • A set of parameters to match an incoming request against.
  • A handler to process the request and return a response.

handler is simply a function that takes an arbitrary number of arguments and returns any arbitrary type.

The parameters to match against include static paths, dynamic paths, path segments, forms, query strings, request format specifiers, and body data. Rocket uses attributes, which look like function decorators in other languages, to make declaring routes easy. Routes are declared by annotating a function, the handler, with the set of parameters to match against. A complete route declaration looks like this:

#[get("/world")]              // <- route attribute
fn world() -> &'static str {  // <- request handler
    "hello, world!"
}

Mounting

Before Rocket can dispatch requests to a route, the route needs to be mounted:

rocket::build().mount("/hello", routes![world]);

The mount method takes as input:

  1. base path to namespace a list of routes under, here, /hello.
  2. A list of routes via the routes! macro: here, routes![world], with multiple routes: routes![a, b, c].

This creates a new Rocket instance via the build function and mounts the world route to the /hello base path, making Rocket aware of the route. GET requests to /hello/world will be directed to the world function.

The mount method, like all other builder methods on Rocket, can be chained any number of times, and routes can be reused by mount points:

rocket::build()
    .mount("/hello", routes![world])
    .mount("/hi", routes![world]);

By mounting world to both /hello and /hi, requests to "/hello/world" and "/hi/world" will be directed to the world function.

 > Note: In many cases, the base path will simply be "/".
 

Launching

Rocket begins serving requests after being launched, which starts a multi-threaded asynchronous server and dispatches requests to matching routes as they arrive.

There are two mechanisms by which a Rocket can be launched. The first and preferred approach is via the #[launch] route attribute, which generates a main function that sets up an async runtime and starts the server. With #[launch], our complete Hello, world! application looks like:

#[macro_use] extern crate rocket;

#[get("/world")]
fn world() -> &'static str {
    "Hello, world!"
}

#[launch]
fn rocket() -> _ {
    rocket::build().mount("/hello", routes![world])
}

Requests

Together, a route attribute and function signature specify what must be true about a request in order for the route's handler to be called. You've already seen an example of this in action:

#[get("/world")]
fn handler() { /* .. */ }

This route indicates that it only matches against GET requests to the /world route. Rocket ensures that this is the case before handler is called. Of course, you can do much more than specify the method and path of a request. Among other things, you can ask Rocket to automatically validate:

  • The type of a dynamic path segment.
  • The type of several dynamic path segments.
  • The type of incoming body data.
  • The types of query strings, forms, and form values.
  • The expected incoming or outgoing format of a request.
  • Any arbitrary, user-defined security or validation policies.

The route attribute and function signature work in tandem to describe these validations. Rocket's code generation takes care of actually validating the properties. This section describes how to ask Rocket to validate against all of these properties and more.

Methods

A Rocket route attribute can be any one of getputpostdeleteheadpatch, or options, each corresponding to the HTTP method to match against. For example, the following attribute will match against POST requests to the root path:

#[post("/")]

The grammar for these attributes is defined formally in the route API docs.

HEAD Requests

Rocket handles HEAD requests automatically when there exists a GET route that would otherwise match. It does this by stripping the body from the response, if there is one. You can also specialize the handling of a HEAD request by declaring a route for it; Rocket won't interfere with HEAD requests your application explicitly handles.

Dynamic Paths

You can declare path segments as dynamic by using angle brackets around variable names in a route's path. For example, if we want to say Hello! to anything, not just the world, we can declare a route like so:

#[get("/hello/<name>")]
fn hello(name: &str) -> String {
    format!("Hello, {}!", name)
}

If we were to mount the path at the root (.mount("/", routes![hello])), then any request to a path with two non-empty segments, where the first segment is hello, will be dispatched to the hello route. For example, if we were to visit /hello/John, the application would respond with Hello, John!.

Any number of dynamic path segments are allowed. A path segment can be of any type, including your own, as long as the type implements the FromParam trait. We call these types parameter guards. Rocket implements FromParam for many of the standard library types, as well as a few special Rocket types. For the full list of provided implementations, see the FromParam API docs. Here's a more complete route to illustrate varied usage:

#[get("/hello/<name>/<age>/<cool>")]
fn hello(name: &str, age: u8, cool: bool) -> String {
    if cool {
        format!("You're a cool {} year old, {}!", age, name)
    } else {
        format!("{}, we need to talk about your coolness.", name)
    }
}

Multiple Segments

You can also match against multiple segments by using <param..> in a route path. The type of such parameters, known as segments guards, must implement FromSegments. A segments guard must be the final component of a path: any text after a segments guard will result in a compile-time error.

As an example, the following route matches against all paths that begin with /page:

use std::path::PathBuf;

#[get("/page/<path..>")]
fn get_page(path: PathBuf) { /* ... */ }

The path after /page/ will be available in the path parameter, which may be empty for paths that are simply /page/page//page//, and so on. The FromSegments implementation for PathBuf ensures that path cannot lead to path traversal attacks. With this, a safe and secure static file server can be implemented in just 4 lines:

use std::path::{Path, PathBuf};
use rocket::fs::NamedFile;

#[get("/<file..>")]
async fn files(file: PathBuf) -> Option<NamedFile> {
    NamedFile::open(Path::new("static/").join(file)).await.ok()
}

Rocket makes it even easier to serve static files!

If you need to serve static files from your Rocket application, consider using FileServer, which makes it as simple as:

use rocket::fs::FileServer;

#[launch]
fn rocket() -> _ {
    rocket::build()
         // serve files from `/www/static` at path `/public`
        .mount("/public", FileServer::from("/www/static"))
}

Ignored Segments

A component of a route can be fully ignored by using <_>, and multiple components can be ignored by using <_..>. In other words, the wildcard name _ is a dynamic parameter name that ignores that dynamic parameter. An ignored parameter must not appear in the function argument list. A segment declared as <_> matches anything in a single segment while segments declared as <_..> match any number of segments with no conditions.

As an example, the foo_bar route below matches any GET request with a 3-segment URI that starts with /foo/ and ends with /bar. The everything route below matches every GET request.

#[get("/foo/<_>/bar")]
fn foo_bar() -> &'static str {
    "Foo _____ bar!"
}

#[get("/<_..>")]
fn everything() -> &'static str {
    "Hey, you're here."
}

Request Guards

Request guards are one of Rocket's most powerful instruments. As the name might imply, a request guard protects a handler from being called erroneously based on information contained in an incoming request. More specifically, a request guard is a type that represents an arbitrary validation policy. The validation policy is implemented through the FromRequest trait. Every type that implements FromRequest is a request guard.

Request guards appear as inputs to handlers. An arbitrary number of request guards can appear as arguments in a route handler. Rocket will automatically invoke the FromRequest implementation for request guards before calling the handler. Rocket only dispatches requests to a handler when all of its guards pass.

For instance, the following dummy handler makes use of three request guards, AB, and C. An input can be identified as a request guard if it is not named in the route attribute.

#[get("/<param>")]
fn index(param: isize, a: A, b: B, c: C) { /* ... */ }

Request guards always fire in left-to-right declaration order. In the example above, the order will be A followed by B followed by C. Errors are short-circuiting; if one guard fails, the remaining are not attempted. To learn more about request guards and implementing them, see the FromRequest documentation.

Custom Guards

You can implement FromRequest for your own types. For instance, to protect a sensitive route from running unless an ApiKey is present in the request headers, you might create an ApiKey type that implements FromRequest and then use it as a request guard:

#[get("/sensitive")]
fn sensitive(key: ApiKey) { /* .. */ }

You might also implement FromRequest for an AdminUser type that authenticates an administrator using incoming cookies. Then, any handler with an AdminUser or ApiKey type in its argument list is assured to only be invoked if the appropriate conditions are met. Request guards centralize policies, resulting in a simpler, safer, and more secure applications.

Cookies

A reference to a CookieJar is an important, built-in request guard: it allows you to get, set, and remove cookies. Because &CookieJar is a request guard, an argument of its type can simply be added to a handler:

use rocket::http::CookieJar;

#[get("/")]
fn index(cookies: &CookieJar<'_>) -> Option<String> {
    cookies.get("message").map(|crumb| format!("Message: {}", crumb.value()))
}

This results in the incoming request's cookies being accessible from the handler. The example above retrieves a cookie named message. Cookies can also be set and removed using the CookieJar guard. The cookies example on GitHub illustrates further use of the CookieJar type to get and set cookies, while the CookieJar documentation contains complete usage information.

Private Cookies

Cookies added via the CookieJar::add() method are set in the clear. In other words, the value set is visible to the client. For sensitive data, Rocket provides private cookies. Private cookies are similar to regular cookies except that they are encrypted using authenticated encryption, a form of encryption which simultaneously provides confidentiality, integrity, and authenticity. Thus, private cookies cannot be inspected, tampered with, or manufactured by clients. If you prefer, you can think of private cookies as being signed and encrypted.

Support for private cookies must be manually enabled via the secrets crate feature:

## in Cargo.toml
rocket = { version = "0.5.1", features = ["secrets"] }

The API for retrieving, adding, and removing private cookies is identical except that most methods are suffixed with _private. These methods are: get_privateadd_private, and remove_private. An example of their usage is below:

use rocket::http::{Cookie, CookieJar};
use rocket::response::{Flash, Redirect};

/// Retrieve the user's ID, if any.
#[get("/user_id")]
fn user_id(cookies: &CookieJar<'_>) -> Option<String> {
    cookies.get_private("user_id")
        .map(|crumb| format!("User ID: {}", crumb.value()))
}

/// Remove the `user_id` cookie.
#[post("/logout")]
fn logout(cookies: &CookieJar<'_>) -> Flash<Redirect> {
    cookies.remove_private("user_id");
    Flash::success(Redirect::to("/"), "Successfully logged out.")
}

Secret Key

To encrypt private cookies, Rocket uses the 256-bit key specified in the secret_key configuration parameter. When compiled in debug mode, a fresh key is generated automatically. In release mode, Rocket requires you to set a secret key if the secrets feature is enabled. Failure to do so results in a hard error at launch time. The value of the parameter may either be a 256-bit base64 or hex string or a 32-byte slice.

Generating a string suitable for use as a secret_key configuration value is usually done through tools like openssl. Using openssl, a 256-bit base64 key can be generated with the command openssl rand -base64 32.

For more information on configuration, see Configuration.

Body Data

Body data processing, like much of Rocket, is type directed. To indicate that a handler expects body data, annotate it with data = "<param>", where param is an argument in the handler. The argument's type must implement the FromData trait. It looks like this, where T is assumed to implement FromData:

#[post("/", data = "<input>")]
fn new(input: T) { /* .. */ }

Any type that implements FromData is also known as a data guard.

JSON

The Json<T> guard deserializes body data as JSON. The only condition is that the generic type T implements the Deserialize trait from serde.

use rocket::serde::{Deserialize, json::Json};

#[derive(Deserialize)]
#[serde(crate = "rocket::serde")]
struct Task<'r> {
    description: &'r str,
    complete: bool
}

#[post("/todo", data = "<task>")]
fn new(task: Json<Task<'_>>) { /* .. */ }

JSON support requires enabling Rocket's json feature flag.

Rocket intentionally places JSON support, as well support for other data formats and features, behind feature flags. See the api docs for a list of available features. The json feature can be enabled in the Cargo.toml:

rocket = { version = "0.5.1", features = ["json"] }

Temporary Files

The TempFile data guard streams data directly to a temporary file which can then be persisted. It makes accepting file uploads trivial:

use rocket::fs::TempFile;

#[post("/upload", format = "plain", data = "<file>")]
async fn upload(mut file: TempFile<'_>) -> std::io::Result<()> {
    file.persist_to(permanent_location).await
}

Streaming

Sometimes you just want to handle incoming data directly. For example, you might want to stream the incoming data to some sink. Rocket makes this as simple as possible via the Data type:

use rocket::tokio;

use rocket::data::{Data, ToByteUnit};

#[post("/debug", data = "<data>")]
async fn debug(data: Data<'_>) -> std::io::Result<()> {
    // Stream at most 512KiB all of the body data to stdout.
    data.open(512.kibibytes())
        .stream_to(tokio::io::stdout())
        .await?;

    Ok(())
}

The route above accepts any POST request to the /debug path. At most 512KiB of the incoming is streamed out to stdout. If the upload fails, an error response is returned.

Forms

Forms are one of the most common types of data handled in web applications, and Rocket makes handling them easy. Rocket supports both multipart and x-www-form-urlencoded forms out of the box, enabled by the Form data guard and derivable FromForm trait.

Say your application is processing a form submission for a new todo Task. The form contains two fields: complete, a checkbox, and type, a text field. You can easily handle the form request in Rocket as follows:

use rocket::form::Form;

#[derive(FromForm)]
struct Task<'r> {
    complete: bool,
    r#type: &'r str,
}

#[post("/todo", data = "<task>")]
fn new(task: Form<Task<'_>>) { /* .. */ }

Multipart

Multipart forms are handled transparently, with no additional effort. Most FromForm types can parse themselves from the incoming data stream. For example, here's a form and route that accepts a multipart file upload using TempFile:

use rocket::form::Form;
use rocket::fs::TempFile;

#[derive(FromForm)]
struct Upload<'r> {
    save: bool,
    file: TempFile<'r>,
}

#[post("/upload", data = "<upload>")]
fn upload_form(upload: Form<Upload<'_>>) { /* .. */ }

Parsing Strategy

Rocket's FromForm parsing is lenient by default: a Form<T> will parse successfully from an incoming form even if it contains extra, duplicate, or missing fields. Extras or duplicates are ignored -- no validation or parsing of the fields occurs -- and missing fields are filled with defaults when available. To change this behavior and make form parsing strict, use the Form<Strict<T>> data type, which emits errors if there are any extra or missing fields, irrespective of defaults.

You can use a Form<Strict<T>> anywhere you'd use a Form<T>. Its generic parameter is also required to implement FromForm. For instance, we can simply replace Form<T> with Form<Strict<T>> above to get strict parsing:

use rocket::form::{Form, Strict};

#[post("/todo", data = "<task>")]
fn new(task: Form<Strict<Task<'_>>>) { /* .. */ }

Strict can also be used to make individual fields strict while keeping the overall structure and remaining fields lenient:

#[derive(FromForm)]
struct Input {
    required: Strict<bool>,
    uses_default: bool
}

#[post("/", data = "<input>")]
fn new(input: Form<Input>) { /* .. */ }

Lenient is the lenient analog to Strict, which forces parsing to be lenient. Form is lenient by default, so a Form<Lenient<T>> is redundant, but Lenient can be used to overwrite a strict parse as lenient: Option<Lenient<T>>.

Defaults

A form guard may specify a default value to use when a field is missing. The default value is used only when parsing is lenient. When strict, all errors, including missing fields, are propagated directly.

Some types with defaults include bool, which defaults to false, useful for checkboxes, Option<T>, which defaults to None, and form::Result, which defaults to Err(Missing) or otherwise collects errors in an Err of Errors<'_>. Defaulting guards can be used just like any other form guard:

use rocket::form::{self, Errors};

#[derive(FromForm)]
struct MyForm<'v> {
    maybe_string: Option<&'v str>,
    ok_or_error: form::Result<'v, Vec<&'v str>>,
    here_or_false: bool,
}

The default can be overridden or unset using the #[field(default = expr)] field attribute. If expr is not literally None, the parameter sets the default value of the field to be expr.into(). If expr is None, the parameter unsets the default value of the field, if any.

#[derive(FromForm)]
struct MyForm {
    // Set the default value to be `"hello"`.
    //
    // Note how an `&str` is automatically converted into a `String`.
    #[field(default = "hello")]
    greeting: String,
    // Remove the default value of `false`, requiring all parses of `MyForm`
    // to contain an `is_friendly` field.
    #[field(default = None)]
    is_friendly: bool,
}

See the FromForm derive documentation for full details on the default attribute parameter as well documentation on the more expressive default_with parameter option.

Field Renaming

By default, Rocket matches the name of an incoming form field to the name of a structure field. While this behavior is typical, it may also be desired to use different names for form fields and struct fields while still parsing as expected. You can ask Rocket to look for a different form field for a given structure field by using one or more #[field(name = "name")] or #[field(name = uncased("name")] field annotation. The uncased variant case-insensitively matches field names.

As an example, say that you're writing an application that receives data from an external service. The external service POSTs a form with a field named first-Name which you'd like to write as first_name in Rust. Such a form structure can be written as:

#[derive(FromForm)]
struct External<'r> {
    #[field(name = "first-Name")]
    first_name: &'r str
}

If you want to accept both firstName case-insensitively as well as first_name case-sensitively, you'll need to use two annotations:

#[derive(FromForm)]
struct External<'r> {
    #[field(name = uncased("firstName"))]
    #[field(name = "first_name")]
    first_name: &'r str
}

This will match any casing of firstName including FirstNamefirstnameFIRSTname, and so on, but only match exactly on first_name.

If instead you wanted to match any of first-namefirst_name or firstName, in each instance case-insensitively, you would write:

#[derive(FromForm)]
struct External<'r> {
    #[field(name = uncased("first-name"))]
    #[field(name = uncased("first_name"))]
    #[field(name = uncased("firstname"))]
    first_name: &'r str
}

Cased and uncased renamings can be mixed and matched, and any number of renamings is allowed. Rocket will emit an error at compile-time if field names conflict, preventing ambiguous parsing at runtime.

Ad-Hoc Validation

Fields of forms can be easily ad-hoc validated via the #[field(validate)] attribute. As an example, consider a form field age: u16 which we'd like to ensure is greater than 21. The following structure accomplishes this:

#[derive(FromForm)]
struct Person {
    #[field(validate = range(21..))]
    age: u16
}

The expression range(21..) is a call to form::validate::range. Rocket passes a borrow of the attributed field, here self.age, as the first parameter to the function call. The rest of the fields are pass as written in the expression.

Any function in the form::validate module can be called, and other fields of the form can be passed in by using self.$field where $field is the name of the field in the structure. You can also apply more than one validation to a field by using multiple attributes. For example, the following form validates that the value of the field confirm is equal to the value of the field value and that it doesn't contain no:

#[derive(FromForm)]
struct Password<'r> {
    #[field(name = "password")]
    value: &'r str,
    #[field(validate = eq(self.value))]
    #[field(validate = omits("no"))]
    confirm: &'r str,
}

In reality, the expression after validate = can be any expression as long as it evaluates to a value of type Result<(), Errors<'_>> (aliased by form::Result), where an Ok value means that validation was successful while an Err of Errors<'_> indicates the error(s) that occurred. For instance, if you wanted to implement an ad-hoc Luhn validator for credit-card-like numbers, you might write:

use rocket::time::Date;
use rocket::form::{self, Error};

#[derive(FromForm)]
struct CreditCard {
    #[field(validate = luhn(self.cvv, &self.expiration))]
    number: u64,
    #[field(validate = range(..9999))]
    cvv: u16,
    expiration: Date,
}

fn luhn<'v>(number: &u64, cvv: u16, exp: &Date) -> form::Result<'v, ()> {
    if !valid {
        Err(Error::validation("invalid credit card number"))?;
    }

    Ok(())
}

If a field's validation doesn't depend on other fields (validation is local), it is validated prior to those fields that do. For CreditCardcvv and expiration will be validated prior to number.

Wrapping Validators

If a particular validation is applied in more than once place, prefer creating a type that encapsulates and represents the validated value. For example, if your application often validates age fields, consider creating a custom Age form guard that always applies the validation:

#[derive(FromForm)]
#[field(validate = range(18..150))]
struct Age(u16);

This approach is also useful when a custom validator already exists in some other form. For instance, the following example leverages try_with and an existing FromStr implementation on a Token type to validate a string:

use std::str::FromStr;

#[derive(FromForm)]
#[field(validate = try_with(|s| Token::from_str(s)))]
struct Token<'r>(&'r str);

Query Strings

Query strings are URL-encoded forms that appear in the URL of a request. Query parameters are declared like path parameters but otherwise handled like regular URL-encoded form fields.

Static Parameters

A request matches a route iff its query string contains all of the static parameters in the route's query string. A route with a static parameter param (any UTF-8 text string) in a query will only match requests with that exact path segment in its query string.

 This is truly an iff!

Only the static parameters in query route string affect routing. Dynamic parameters are allowed to be missing by default.

For example, the route below will match requests with path / and at least the query segments hello and cat=♥:

#[get("/?hello&cat=♥")]
fn cats() -> &'static str {
    "Hello, kittens!"
}

// The following GET requests match `cats`. `%E2%99%A5` is encoded `♥`.
"/?cat=%E2%99%A5&hello"
"/?hello&cat=%E2%99%A5"
"/?dogs=amazing&hello&there&cat=%E2%99%A5"

Dynamic Parameters

A single dynamic parameter of <param> acts identically to a form field declared as param. In particular, Rocket will expect the query form to contain a field with key param and push the shifted field to the param type. As with forms, default values are used when parsing fails. The example below illustrates this with a single value name, a collection color, a nested form person, and an other value that will default to None:

#[derive(Debug, PartialEq, FromFormField)]
enum Color {
    Red,
    Blue,
    Green
}

#[derive(Debug, PartialEq, FromForm)]
struct Pet<'r> {
  name: &'r str,
  age: usize,
}

#[derive(Debug, PartialEq, FromForm)]
struct Person<'r> {
  pet: Pet<'r>,
}

#[get("/?<name>&<color>&<person>&<other>")]
fn hello(name: &str, color: Vec<Color>, person: Person<'_>, other: Option<usize>) {
    assert_eq!(name, "George");
    assert_eq!(color, [Color::Red, Color::Green, Color::Green, Color::Blue]);
    assert_eq!(other, None);
    assert_eq!(person, Person {
      pet: Pet { name: "Fi Fo Alex", age: 1 }
    });
}

// A request with these query segments matches as above.
name=George&\
color=red&\
color=green&\
person.pet.name=Fi+Fo+Alex&\
color=green&\
person.pet.age=1&\
color=blue&\
extra=yes\

Note that, like forms, parsing is field-ordering insensitive and lenient by default.

Trailing Parameter

A trailing dynamic parameter of <param..> collects all of the query segments that don't otherwise match a declared static or dynamic parameter. In other words, the otherwise unmatched segments are pushed, unshifted, to the <param..> type:

use rocket::form::Form;

#[derive(FromForm)]
struct User<'r> {
    name: &'r str,
    active: bool,
}

#[get("/?hello&<id>&<user..>")]
fn user(id: usize, user: User<'_>) {
    assert_eq!(id, 1337);
    assert_eq!(user.name, "Bob Smith");
    assert_eq!(user.active, true);
}

// A request with these query segments matches as above.
hello&\
name=Bob+Smith&\
id=1337&\
active=yes\

Error Catchers

Application processing is fallible. Errors arise from the following sources:

  • A failing guard.
  • A failing responder.
  • A routing failure.

If any of these occur, Rocket returns an error to the client. To generate the error, Rocket invokes the catcher corresponding to the error's status code and scope. Catchers are similar to routes except in that:

  1. Catchers are only invoked on error conditions.
  2. Catchers are declared with the catch attribute.
  3. Catchers are registered with register() instead of mount().
  4. Any modifications to cookies are cleared before a catcher is invoked.
  5. Error catchers cannot invoke guards.
  6. Error catchers should not fail to produce a response.
  7. Catchers are scoped to a path prefix.

To declare a catcher for a given status code, use the catch attribute, which takes a single integer corresponding to the HTTP status code to catch. For instance, to declare a catcher for 404 Not Found errors, you'd write:

use rocket::Request;

#[catch(404)]
fn not_found(req: &Request) { /* .. */ }

Catchers may take zero, one, or two arguments. If the catcher takes one argument, it must be of type &Request. It it takes two, they must be of type Status and &Request, in that order. As with routes, the return type must implement Responder. A concrete implementation may look like:

#[catch(404)]
fn not_found(req: &Request) -> String {
    format!("Sorry, '{}' is not a valid path.", req.uri())
}

Also as with routes, Rocket needs to know about a catcher before it is used to handle errors. The process, known as "registering" a catcher, is similar to mounting a route: call the register() method with a list of catchers via the catchers! macro. The invocation to add the 404 catcher declared above looks like:

fn main() {
    rocket::build().register("/", catchers![not_found]);
}

Scoping

The first argument to register() is a path to scope the catcher under called the catcher's base. A catcher's base determines which requests it will handle errors for. Specifically, a catcher's base must be a prefix of the erroring request for it to be invoked. When multiple catchers can be invoked, the catcher with the longest base takes precedence.

As an example, consider the following application:

#[catch(404)]
fn general_not_found() -> &'static str {
    "General 404"
}

#[catch(404)]
fn foo_not_found() -> &'static str {
    "Foo 404"
}

#[launch]
fn rocket() -> _ {
    rocket::build()
        .register("/", catchers![general_not_found])
        .register("/foo", catchers![foo_not_found])
}

Since there are no mounted routes, all requests will 404. Any request whose path begins with /foo (i.e, GET /fooGET /foo/bar, etc) will be handled by the foo_not_found catcher while all other requests will be handled by the general_not_found catcher.

Default Catchers

default catcher is a catcher that handles all status codes. They are invoked as a fallback if no status-specific catcher is registered for a given error. Declaring a default catcher is done with #[catch(default)] and must similarly be registered with register():

use rocket::Request;
use rocket::http::Status;

#[catch(default)]
fn default_catcher(status: Status, request: &Request) { /* .. */ }

#[launch]
fn rocket() -> _ {
    rocket::build().register("/", catchers![default_catcher])
}

Catchers with longer bases are preferred, even when there is a status-specific catcher. In other words, a default catcher with a longer matching base than a status-specific catcher takes precedence.

Built-In Catcher

Rocket provides a built-in default catcher. It produces HTML or JSON, depending on the value of the Accept header. As such, custom catchers only need to be registered for custom error handling.

Responses

You may have noticed that the return type of a handler appears to be arbitrary, and that's because it is! A value of any type that implements the Responder trait can be returned, including your own. In this section, we describe the Responder trait as well as several useful Responders provided by Rocket. We'll also briefly discuss how to implement your own Responder.

Responder

Types that implement Responder know how to generate a Response from their values. A Response includes an HTTP status, headers, and body. The body may either be fixed-sized or streaming. The given Responder implementation decides which to use. For instance, String uses a fixed-sized body, while File uses a streamed response. Responders may dynamically adjust their responses according to the incoming Request they are responding to.

Wrapping

Before we describe a few responders, we note that it is typical for responders to wrap other responders. That is, responders can be of the following form, where R is some type that implements Responder:

struct WrappingResponder<R>(R);

A wrapping responder modifies the response returned by R before responding with that same response. For instance, Rocket provides Responders in the status module that override the status code of the wrapped Responder. As an example, the Accepted type sets the status to 202 - Accepted. It can be used as follows:

use rocket::response::status;

#[post("/<id>")]
fn new(id: usize) -> status::Accepted<String> {
    status::Accepted(format!("id: '{}'", id))
}

Similarly, the types in the content module can be used to override the Content-Type of a response. For instance, to set the Content-Type of &'static str to JSON, as well as setting the status code to an arbitrary one like 418 I'm a teapot, combine [content::RawJson] with status::Custom:

use rocket::http::Status;
use rocket::response::{content, status};

#[get("/")]
fn json() -> status::Custom<content::RawJson<&'static str>> {
    status::Custom(Status::ImATeapot, content::RawJson("{ \"hi\": \"world\" }"))
}

Errors

Responders may fail instead of generating a response by returning an Err with a status code. When this happens, Rocket forwards the request to the error catcher for that status code.

If an error catcher has been registered for the given status code, Rocket will invoke it. The catcher creates and returns a response to the client. If no error catcher has been registered and the error status code is one of the standard HTTP status code, a default error catcher will be used. Default error catchers return an HTML page with the status code and description. If there is no catcher for a custom status code, Rocket uses the 500 error catcher to return a response.

Custom Responders

The Responder trait documentation details how to implement your own custom responders by explicitly implementing the trait. For most use cases, however, Rocket makes it possible to automatically derive an implementation of Responder. In particular, if your custom responder wraps an existing responder, headers, or sets a custom status or content-type, Responder can be automatically derived:

use rocket::http::{Header, ContentType};

#[derive(Responder)]
#[response(status = 500, content_type = "json")]
struct MyResponder {
    inner: OtherResponder,
    // Override the Content-Type declared above.
    header: ContentType,
    more: Header<'static>,
    #[response(ignore)]
    unrelated: MyType,
}

For the example above, Rocket generates a Responder implementation that:

  • Set the response's status to 500: Internal Server Error.
  • Sets the Content-Type to application/json.
  • Adds the headers self.header and self.more to the response.
  • Completes the response using self.inner.

Note that the first field is used as the inner responder while all remaining fields (unless ignored with #[response(ignore)]) are added as headers to the response. The optional #[response] attribute can be used to customize the status and content-type of the response. Because ContentType is itself a header, you can also dynamically set a content-type by simply including a field of type ContentType. To set an HTTP status dynamically, leverage the (Status, R: Responder) responder:

use rocket::http::{Header, Status};

#[derive(Responder)]
#[response(content_type = "json")]
struct MyResponder {
    inner: (Status, OtherResponder),
    some_header: Header<'static>,
}

You can also use derive Responder for enums, allowing dynamic selection of a responder:

use rocket::http::{ContentType, Header, Status};
use rocket::fs::NamedFile;

#[derive(Responder)]
enum Error {
    #[response(status = 500, content_type = "json")]
    A(String),
    #[response(status = 404)]
    B(NamedFile, ContentType),
    C {
        inner: (Status, Option<String>),
        header: ContentType,
    }
}

For more on using the Responder derive, including details on how to use the derive to define generic responders, see the Responder derive documentation.

Implementations

Rocket implements Responder for many types in Rust's standard library including String&strFileOption, and Result. The Responder documentation describes these in detail, but we briefly cover a few here.

Strings

The Responder implementations for &str and String are straight-forward: the string is used as a sized body, and the Content-Type of the response is set to text/plain. To get a taste for what such a Responder implementation looks like, here's the implementation for String:

use std::io::Cursor;

use rocket::request::Request;
use rocket::response::{self, Response, Responder};
use rocket::http::ContentType;

#[rocket::async_trait]
impl<'r> Responder<'r, 'static> for String {
    fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
        Response::build()
            .header(ContentType::Plain)
            .sized_body(self.len(), Cursor::new(self))
            .ok()
    }
}

Because of these implementations, you can directly return an &str or String type from a handler:

#[get("/string")]
fn handler() -> &'static str {
    "Hello there! I'm a string!"
}

Option

Option is a wrapping responder: an Option<T> can only be returned when T implements Responder. If the Option is Some, the wrapped responder is used to respond to the client. Otherwise, an error of 404 - Not Found is returned to the client.

This implementation makes Option a convenient type to return when it is not known until process-time whether content exists. For example, because of Option, we can implement a file server that returns a 200 when a file is found and a 404 when a file is not found in just 4, idiomatic lines:

use rocket::fs::NamedFile;

#[get("/<file..>")]
async fn files(file: PathBuf) -> Option<NamedFile> {
    NamedFile::open(Path::new("static/").join(file)).await.ok()
}

Result

Result is another wrapping responder: a Result<T, E> can only be returned when T implements Responder and E implements Responder.

The wrapped Responder in Ok or Err, whichever it might be, is used to respond to the client. This means that the responder can be chosen dynamically at run-time, and two different kinds of responses can be used depending on the circumstances. Revisiting our file server, for instance, we might wish to provide more feedback to the user when a file isn't found. We might do this as follows:

use rocket::fs::NamedFile;
use rocket::response::status::NotFound;

#[get("/<file..>")]
async fn files(file: PathBuf) -> Result<NamedFile, NotFound<String>> {
    let path = Path::new("static/").join(file);
    NamedFile::open(&path).await.map_err(|e| NotFound(e.to_string()))
}

Rocket Responders

Some of Rocket's best features are implemented through responders. Among these are:

  • NamedFile - Streams a file to the client; automatically sets the Content-Type based on the file's extension.
  • Redirect - Redirects the client to a different URI.
  • content - Contains types that override the Content-Type of a response.
  • status - Contains types that override the status code of a response.
  • Flash - Sets a "flash" cookie that is removed when accessed.
  • Json - Automatically serializes values into JSON.
  • MsgPack - Automatically serializes values into MessagePack.
  • Template - Renders a dynamic template using handlebars or Tera.

Async Streams

The stream responders allow serving potentially infinite async Streams. A stream can be created from any async Stream or AsyncRead type, or via generator syntax using the stream! macro and its typed equivalents. Streams are the building blocks for unidirectional real-time communication. For instance, the chat example uses an EventStream to implement a real-time, multi-room chat application using Server-Sent Events (SSE).

The simplest version creates a ReaderStream from a single AsyncRead type. For example, to stream from a TCP connection, we might write:

use std::io;
use std::net::SocketAddr;

use rocket::tokio::net::TcpStream;
use rocket::response::stream::ReaderStream;

#[get("/stream")]
async fn stream() -> io::Result<ReaderStream![TcpStream]> {
    let addr = SocketAddr::from(([127, 0, 0, 1], 9999));
    let stream = TcpStream::connect(addr).await?;
    Ok(ReaderStream::one(stream))
}

Streams can also be created using generator syntax. The following example returns an infinite TextStream that produces one "hello" every second:

use rocket::tokio::time::{Duration, interval};
use rocket::response::stream::TextStream;

/// Produce an infinite series of `"hello"`s, one per second.
#[get("/infinite-hellos")]
fn hello() -> TextStream![&'static str] {
    TextStream! {
        let mut interval = interval(Duration::from_secs(1));
        loop {
            yield "hello";
            interval.tick().await;
        }
    }
}

See the stream docs for full details on creating streams including notes on how to detect and handle graceful shutdown requests.

WebSockets

Enabled by Rocket's support for HTTP connection upgrades, the official rocket_ws crate implements first-class support for WebSockets. Working with rocket_ws to implement an echo server looks like this:

use rocket_ws::{WebSocket, Stream};

#[get("/echo")]
fn echo_compose(ws: WebSocket) -> Stream!['static] {
    ws.stream(|io| io)
}

As with async streams, rocket_ws also supports using generator syntax for WebSocket messages:

use rocket_ws::{WebSocket, Stream};

#[get("/echo")]
fn echo_stream(ws: WebSocket) -> Stream!['static] {
    Stream! { ws =>
        for await message in ws {
            yield message?;
        }
    }
}

For complete usage details, see the rocket_ws documentation.

JSON

The Json responder in allows you to easily respond with well-formed JSON data: simply return a value of type Json<T> where T is the type of a structure to serialize into JSON. The type T must implement the Serialize trait from serde, which can be automatically derived.

As an example, to respond with the JSON value of a Task structure, we might write:

use rocket::serde::{Serialize, json::Json};

#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
struct Task { /* .. */ }

#[get("/todo")]
fn todo() -> Json<Task> {
    Json(Task { /* .. */ })
}

You must enable Rocket's json crate feature to use the Json type.

Templates

Rocket has first-class templating support that works largely through a Template responder in the rocket_dyn_templates contrib library. To render a template named "index", for instance, you might return a value of type Template as follows:

use rocket_dyn_templates::Template;

#[get("/")]
fn index() -> Template {
    let context = /* object-like value */;
    Template::render("index", &context)
}

Templates are rendered with the render method. The method takes in the name of a template and a context to render the template with. The context can be any type that implements Serialize and serializes into an Object value, such as structs, HashMaps, and others.

You can also use context! to create ad-hoc templating contexts without defining a new type:

use rocket_dyn_templates::Template;

#[get("/")]
fn index() -> Template {
    Template::render("index", context! {
        foo: 123,
    })
}

To render a template, it must first be registered. The Template fairing automatically registers all discoverable templates when attached. The Fairings sections of the guide provides more information on fairings. To attach the template fairing, simply call .attach(Template::fairing()) on an instance of Rocket as follows:

use rocket_dyn_templates::Template;

#[launch]
fn rocket() -> _ {
    rocket::build()
        .mount("/", routes![/* .. */])
        .attach(Template::fairing())
}

Rocket discovers templates in the configurable template_dir directory. Templating support in Rocket is engine agnostic. The engine used to render a template depends on the template file's extension. For example, if a file ends with .hbs, Handlebars is used, while if a file ends with .tera, Tera is used.

Live Reloading

When your application is compiled in debug mode (without the --release flag passed to cargo), templates are automatically reloaded when they are modified on supported platforms. This means that you don't need to rebuild your application to observe template changes: simply refresh! In release builds, reloading is disabled.

Typed URIs

Rocket's uri! macro allows you to build URIs to routes in your application in a robust, type-safe, and URI-safe manner. Type or route parameter mismatches are caught at compile-time, and changes to route URIs are automatically reflected in the generated URIs.

The uri! macro returns an Origin structure with the URI of the supplied route interpolated with the given values. Each value passed into uri! is rendered in its appropriate place in the URI using the UriDisplay implementation for the value's type. The UriDisplay implementation ensures that the rendered value is URI-safe.

Note that Origin implements Into<Uri> (and by extension, TryInto<Uri>), so it can be converted into a Uri using .into() as needed and passed into methods such as Redirect::to().

For example, given the following route:

#[get("/<id>/<name>?<age>")]
fn person(id: Option<usize>, name: &str, age: Option<u8>) { /* .. */ }

URIs to person can be created as follows:

// with unnamed parameters, in route path declaration order
let mike = uri!(person(101, "Mike Smith", Some(28)));
assert_eq!(mike.to_string(), "/101/Mike%20Smith?age=28");

// with named parameters, order irrelevant
let mike = uri!(person(name = "Mike", id = 101, age = Some(28)));
assert_eq!(mike.to_string(), "/101/Mike?age=28");
let mike = uri!(person(id = 101, age = Some(28), name = "Mike"));
assert_eq!(mike.to_string(), "/101/Mike?age=28");

// with a specific mount-point
let mike = uri!("/api", person(id = 101, name = "Mike", age = Some(28)));
assert_eq!(mike.to_string(), "/api/101/Mike?age=28");

// with optional (defaultable) query parameters ignored
let mike = uri!(person(101, "Mike", _));
assert_eq!(mike.to_string(), "/101/Mike");
let mike = uri!(person(id = 101, name = "Mike", age = _));
assert_eq!(mike.to_string(), "/101/Mike");

Rocket informs you of any mismatched parameters at compile-time:

error: `person` route uri expects 3 parameters but 1 was supplied
 --> examples/uri/main.rs:7:26
  |
7 |     let x = uri!(person("Mike Smith"));
  |                         ^^^^^^^^^^^^
  |
  = note: expected parameters: id: Option <usize>, name: &str, age: Option <u8>

Rocket also informs you of any type errors at compile-time:

 --> examples/uri/src/main.rs:7:31
  |
7 |     let x = uri!(person(id = "10", name = "Mike Smith", age = Some(10)));
  |                              ^^^^ `FromUriParam<Path, &str>` is not implemented for `usize`

We recommend that you use uri! exclusively when constructing URIs to your routes.

Ignorables

As illustrated in the previous above, query parameters can be ignored using _ in place of an expression in a uri! invocation. The corresponding type in the route URI must implement Ignorable. Ignored parameters are not interpolated into the resulting Origin. Path parameters are not ignorable.

DerivingUriDisplay

The UriDisplay trait can be derived for custom types. For types that appear in the path part of a URI, derive using UriDisplayPath; for types that appear in the query part of a URI, derive using UriDisplayQuery.

As an example, consider the following form structure and route:

use rocket::form::Form;

#[derive(FromForm, UriDisplayQuery)]
struct UserDetails<'r> {
    age: Option<usize>,
    nickname: &'r str,
}

#[post("/user/<id>?<details..>")]
fn add_user(id: usize, details: UserDetails) { /* .. */ }

By deriving using UriDisplayQuery, an implementation of UriDisplay<Query> is automatically generated, allowing for URIs to add_user to be generated using uri!:

let link = uri!(add_user(120, UserDetails { age: Some(20), nickname: "Bob".into() }));
assert_eq!(link.to_string(), "/user/120?age=20&nickname=Bob");

State

Many web applications have a need to maintain state. This can be as simple as maintaining a counter for the number of visits or as complex as needing to access job queues and multiple databases. Rocket provides the tools to enable these kinds of interactions in a safe and simple manner.

Managed State

The enabling feature for maintaining state is managed state. Managed state, as the name implies, is state that Rocket manages for your application. The state is managed on a per-type basis: Rocket will manage at most one value of a given type.

The process for using managed state is simple:

  1. Call manage on the Rocket instance corresponding to your application with the initial value of the state.
  2. Add a &State<T> type to any request handler, where T is the type of the value passed into manage.

 All managed state must be thread-safe.

Because Rocket automatically parallelizes your application, handlers can concurrently access managed state. As a result, managed state must be thread-safe. Thanks to Rust, this condition is checked at compile-time by ensuring that the type of values you store in managed state implement Send + Sync.

Adding State

To instruct Rocket to manage state for your application, call the manage method on an instance of Rocket. For example, to ask Rocket to manage a HitCount structure with an internal AtomicUsize with an initial value of 0, we can write the following:

use std::sync::atomic::AtomicUsize;

struct HitCount {
    count: AtomicUsize
}

rocket::build().manage(HitCount { count: AtomicUsize::new(0) });

The manage method can be called any number of times as long as each call refers to a value of a different type. For instance, to have Rocket manage both a HitCount value and a Config value, we can write:

rocket::build()
    .manage(HitCount { count: AtomicUsize::new(0) })
    .manage(Config::from(user_input));

Retrieving State

State that is being managed by Rocket can be retrieved via the &State type: a request guard for managed state. To use the request guard, add a &State<T> type to any request handler, where T is the type of the managed state. For example, we can retrieve and respond with the current HitCount in a count route as follows:

use rocket::State;

#[get("/count")]
fn count(hit_count: &State<HitCount>) -> String {
    let current_count = hit_count.count.load(Ordering::Relaxed);
    format!("Number of visits: {}", current_count)
}

You can retrieve more than one &State type in a single route as well:

#[get("/state")]
fn state(hit_count: &State<HitCount>, config: &State<Config>) { /* .. */ }

If you request a &State<T> for a T that is not managed, Rocket will refuse to start your application. This prevents what would have been an unmanaged state runtime error. Unmanaged state is detected at runtime through sentinels, so there are limitations. If a limitation is hit, Rocket still won't call the offending route. Instead, Rocket will log an error message and return a 500 error to the client.

Within Guards

Because State is itself a request guard, managed state can be retrieved from another request guard's implementation using either Request::guard() or Rocket::state(). In the following code example, the Item request guard retrieves MyConfig from managed state using both methods:

use rocket::State;
use rocket::request::{self, Request, FromRequest};
use rocket::outcome::IntoOutcome;
use rocket::http::Status;

struct Item<'r>(&'r str);

#[rocket::async_trait]
impl<'r> FromRequest<'r> for Item<'r> {
    type Error = ();

    async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, ()> {
        // Using `State` as a request guard. Use `inner()` to get an `'r`.
        let outcome = request.guard::<&State<MyConfig>>().await
            .map(|my_config| Item(&my_config.user_val));

        // Or alternatively, using `Rocket::state()`:
        let outcome = request.rocket().state::<MyConfig>()
            .map(|my_config| Item(&my_config.user_val))
            .or_forward(Status::InternalServerError);

        outcome
    }
}

Fairings

Fairings are Rocket's approach to structured middleware. With fairings, your application can hook into the request lifecycle to record or rewrite information about incoming requests and outgoing responses.

Overview

Any type that implements the Fairing trait is a fairing. Fairings hook into Rocket's request lifecycle, receiving callbacks for events such as incoming requests and outgoing responses. Rocket passes information about these events to the fairing; the fairing can do what it wants with the information. This includes rewriting requests or responses, recording information about the event, or doing nothing at all.

Rockets fairings are a lot like middleware from other frameworks, but they bear a few key distinctions:

  • Fairings cannot terminate or respond to an incoming request directly.
  • Fairings cannot inject arbitrary, non-request data into a request.
  • Fairings can prevent an application from launching.
  • Fairings can inspect and modify the application's configuration.

If you are familiar with middleware from other frameworks, you may find yourself reaching for fairings instinctively. Before doing so, remember that Rocket provides a rich set of mechanisms such as request guards and data guards that can be used to solve problems in a clean, composable, and robust manner.

As a general rule of thumb, only globally applicable actions should be effected through fairings. 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 all or the overwhelming majority of the application. On the other hand, you should use a fairing to record timing and usage statistics or to enforce global security policies.

Attaching

Fairings are registered with Rocket via the attach method on a Rocket instance. Only when a fairing is attached will its callbacks fire. As an example, the following snippet attached two fairings, req_fairing and res_fairing, to a new Rocket instance:

#[launch]
fn rocket() -> _ {
    rocket::build()
        .attach(req_fairing)
        .attach(res_fairing)
}

Fairings are executed in the order in which they are attached: the first attached fairing has its callbacks executed before all others. A fairing can be attached any number of times. Except for singleton fairings, all attached instances are polled at runtime. Fairing callbacks may not be commutative; the order in which fairings are attached may be significant.

Callbacks

There are five events for which Rocket issues fairing callbacks. Each of these events is briefly described below and in details in the Fairing trait docs:

  • Ignite (on_ignite)

    An ignite callback is called during ignition An ignite callback can arbitrarily modify the Rocket instance being built. They are commonly used to parse and validate configuration values, aborting on bad configurations, and inserting the parsed value into managed state for later retrieval.

  • Liftoff (on_liftoff)

    A liftoff callback is called immediately after a Rocket application has launched. A liftoff callback can inspect the Rocket instance being launched. A liftoff callback can be a convenient hook for launching services related to the Rocket application being launched.

  • Request (on_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 (on_response)

    A response callback is called when a response is ready to be sent to the client. A response callback can modify part or all of the response. As such, a response fairing can be used to provide a response when the greater application fails by rewriting 404 responses as desired. As another example, response fairings can also be used to inject headers into all outgoing responses.

  • Shutdown (on_shutdown)

    A shutdown callback is called when shutdown is triggered. At this point, graceful shutdown has commenced but not completed; no new requests are accepted but the application may still be actively serving existing requests. All registered shutdown fairings are run concurrently; resolution of all fairings is awaited before resuming shutdown.

Implementing

Recall that a fairing is any type that implements 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 the set of callbacks the fairing is registering for. A Fairing can implement any of the available callbacks: on_igniteon_liftoffon_requeston_response, and on_shutdown. Each callback has a default implementation that does absolutely nothing.

Requirements

A type implementing Fairing is required to 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

As an example, 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 code for a Counter fairing below implements exactly this. The fairing receives a request callback, where it increments a counter on each GET and POST request. It also receives a response callback, where it responds to unrouted requests to the /counts path by returning the recorded number of counts.

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

struct Counter {
    get: AtomicUsize,
    post: AtomicUsize,
}

#[rocket::async_trait]
impl Fairing for Counter {
    // This is a request and response fairing named "GET/POST Counter".
    fn info(&self) -> Info {
        Info {
            name: "GET/POST Counter",
            kind: Kind::Request | Kind::Response
        }
    }

    // Increment the counter for `GET` and `POST` requests.
    async fn on_request(&self, request: &mut Request<'_>, _: &mut Data<'_>) {
        match request.method() {
            Method::Get => self.get.fetch_add(1, Ordering::Relaxed),
            Method::Post => self.post.fetch_add(1, Ordering::Relaxed),
            _ => return
        };
    }

    async fn on_response<'r>(&self, request: &'r Request<'_>, response: &mut Response<'r>) {
        // Don't change a successful user's response, ever.
        if response.status() != Status::NotFound {
            return
        }

        // Rewrite the response to return the current counts.
        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(body.len(), Cursor::new(body));
        }
    }
}

The complete example can be found in the Fairing documentation.

Ad-Hoc Fairings

For simpler cases, implementing the Fairing trait can be cumbersome. This is why Rocket provides the AdHoc type, which creates a fairing from a simple function or closure. Using the AdHoc type is easy: simply call the on_igniteon_liftoffon_requeston_response, or on_shutdown constructors on AdHoc to create a fairing from a function or closure.

As an example, the code below creates a Rocket instance with two attached ad-hoc fairings. The first, a liftoff fairing named "Liftoff Printer", prints a message indicating that the application has launched. The second named "Put Rewriter", a request fairing, rewrites the method of all requests to be PUT.

use rocket::fairing::AdHoc;
use rocket::http::Method;

rocket::build()
    .attach(AdHoc::on_liftoff("Liftoff Printer", |_| Box::pin(async move {
        println!("...annnddd we have liftoff!");
    })))
    .attach(AdHoc::on_request("Put Rewriter", |req, _| Box::pin(async move {
        req.set_method(Method::Put);
    })))
    .attach(AdHoc::on_shutdown("Shutdown Printer", |_| Box::pin(async move {
        println!("...shutdown has commenced!");
    })));

Testing

Every application should be well tested and understandable. Rocket provides the tools to perform unit and integration tests. It also provides a means to inspect code generated by Rocket.

Local Dispatching

Rocket applications are tested by dispatching requests to a local instance of Rocket. The local module contains all of the structures necessary to do so. In particular, it contains a Client structure that is used to create LocalRequest structures that can be dispatched against a given Rocket instance. Usage is straightforward:

  1. Construct a Rocket instance that represents the application.
let rocket = rocket::build();
  1. Construct a Client using the Rocket instance.
let client = Client::tracked(rocket).unwrap();
  1. Construct requests using the Client instance.
let req = client.get("/");
  1. Dispatch the request to retrieve the response.
let response = req.dispatch();

Validating Responses

dispatch of a LocalRequest returns a LocalResponse which can be inspected for validity. During testing, the response is usually validated against expected properties. These includes things like the response HTTP status, the inclusion of headers, and expected body data.

LocalResponse type provides methods to ease this sort of validation. We list a few below:

  • status: returns the HTTP status in the response.
  • content_type: returns the Content-Type header in the response.
  • headers: returns a map of all of the headers in the response.
  • into_string: reads the body data into a String.
  • into_bytes: reads the body data into a Vec<u8>.
  • into_json: deserializes the body data on-the-fly as JSON.
  • into_msgpack: deserializes the body data on-the-fly as MessagePack.

These methods are typically used in combination with the assert_eq! or assert! macros as follows:

use rocket::http::{ContentType, Status};

let mut response = client.get(uri!(hello)).dispatch();

assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::Plain));
assert!(response.headers().get_one("X-Special").is_some());
assert_eq!(response.into_string().unwrap(), "Expected Body");

Testing "Hello, world!"

To solidify an intuition for how Rocket applications are tested, we walk through how to test the "Hello, world!" application below:

#[get("/")]
fn hello() -> &'static str {
    "Hello, world!"
}

#[launch]
fn rocket() -> _ {
    rocket::build().mount("/", routes![hello])
}

Notice that we've separated the creation of the Rocket instance from the launch of the instance. As you'll soon see, this makes testing our application easier, less verbose, and less error-prone.

Setting Up

First, we'll create a test module with the proper imports:

#[cfg(test)]
mod test {
    use super::rocket;
    use rocket::local::blocking::Client;
    use rocket::http::Status;

    #[test]
    fn hello_world() {
        /* .. */
    }
}

You can also move the body of the test module into its own file, say tests.rs, and then import the module into the main file using:

#[cfg(test)] mod tests;

Testing

To test our "Hello, world!" application, we create a Client for our Rocket instance. It's okay to use methods like expect and unwrap during testing: we want our tests to panic when something goes wrong.

let client = Client::tracked(rocket()).expect("valid rocket instance");

Then, we create a new GET / request and dispatch it, getting back our application's response:

let mut response = client.get(uri!(hello)).dispatch();

Finally, we ensure that the response contains the information we expect it to. Here, we want to ensure two things:

  1. The status is 200 OK.
  2. The body is the string "Hello, world!".

We do this by checking the Response object directly:

use rocket::http::{ContentType, Status};

assert_eq!(response.status(), Status::Ok);
assert_eq!(response.into_string(), Some("Hello, world!".into()));

That's it! Altogether, this looks like:

#[get("/")]
fn hello() -> &'static str {
    "Hello, world!"
}

#[launch]
fn rocket() -> Rocket<Build> {
    rocket::build().mount("/", routes![hello])
}

#[cfg(test)]
mod test {
    use super::rocket;
    use rocket::local::blocking::Client;
    use rocket::http::Status;

    #[test]
    fn hello_world() {
        let client = Client::tracked(rocket()).expect("valid rocket instance");
        let mut response = client.get(uri!(super::hello)).dispatch();
        assert_eq!(response.status(), Status::Ok);
        assert_eq!(response.into_string().unwrap(), "Hello, world!");
    }
}

The tests can be run with cargo test. You can find the full source code to this example on GitHub.

Asynchronous Testing

You may have noticed the use of a "blocking" API in these examples, even though Rocket is an async web framework. In most situations, the blocking testing API is easier to use and should be preferred. However, when concurrent execution of two or more requests is required for the server to make progress, you will need the more flexible asynchronous API; the blocking API is not capable of dispatching multiple requests simultaneously. While synthetic, the async_required testing example uses an async barrier to demonstrate such a case. For more information, see the rocket::local and rocket::local::asynchronous documentation.

Configuration

Rocket's configuration system is flexible. Based on Figment, it allows you to configure your application the way you want while also providing with a sensible set of defaults.

Overview

Rocket's configuration system is based on Figment's Providers, types which provide configuration data. Rocket's Config and Config::figment(), as well as Figment's Toml and Json, are some examples of providers. Providers can be combined into a single Figment provider from which any configuration structure that implements Deserialize can be extracted.

Rocket expects to be able to extract a Config structure from the provider it is configured with. This means that no matter which configuration provider Rocket is asked to use, it must be able to read the following configuration values:

key kind description debug/release default
address IpAddr IP address to serve on 127.0.0.1
port u16 Port to serve on. 8000
workers* usize Number of threads to use for executing futures. cpu core count
max_blocking* usize Limit on threads to start for blocking tasks. 512
ident stringfalse If and how to identify via the Server header. "Rocket"
ip_header stringfalse IP header to inspect to get client's real IP. "X-Real-IP"
keep_alive u32 Keep-alive timeout seconds; disabled when 0. 5
log_level LogLevel Max level to log. (off/normal/debug/critical) normal/critical
cli_colors bool Whether to use colors and emoji when logging. true
secret_key SecretKey Secret key for signing and encrypting values. None
tls TlsConfig TLS configuration, if any. None
limits Limits Streaming read size limits. Limits::default()
limits.$name &str/uint Read limit for $name. form = "32KiB"
ctrlc bool Whether ctrl-c initiates a server shutdown. true
shutdown* Shutdown Graceful shutdown configuration. Shutdown::default()
  • Note: the workersmax_blocking, and shutdown.force configuration parameters are only read from the default provider.

Profiles

Configurations can be arbitrarily namespaced by Profiles. Rocket's Config and Config::figment() providers automatically set the configuration profile to "debug" when compiled in "debug" mode and "release" when compiled in release mode, but you can arbitrarily name and set profiles to your desire. For example, with the default provider, you can set the selected profile via ROCKET_PROFILE. This results in Rocket preferring the values in the ROCKET_PROFILE profile.

In addition to any profiles you declare, there are two meta-profiles, default and global, which can be used to provide values that apply to all profiles. Values provided in a default profile are used as fall-back values when the selected profile doesn't contain a requested value, while values in the global profile supplant any values with the same name in any profile.

Default Provider

Rocket's default configuration provider is Config::figment(); this is the provider that's used when calling rocket::build().

The default figment reads from and merges, at a per-key level, the following sources in ascending priority order:

  1. Config::default(), which provides default values for all parameters.
  2. Rocket.toml or TOML file path in ROCKET_CONFIG environment variable.
  3. ROCKET_ prefixed environment variables.

The selected profile is the value of the ROCKET_PROFILE environment variable, or if it is not set, "debug" when compiled in debug mode and "release" when compiled in release mode. With the exception of log_level, which changes from normal in debug to critical in release, all of the default configuration values are the same in all profiles. What's more, all configuration values have defaults, so no configuration is needed to get started.

As a result of Config::figment(), without any effort, Rocket can be configured via a Rocket.toml file and/or via environment variables, the latter of which take precedence over the former.

Rocket.toml

Rocket searches for Rocket.toml or the filename in a ROCKET_CONFIG environment variable starting at the current working directory. If it is not found, the parent directory, its parent, and so on, are searched until the file is found or the root is reached. If the path set in ROCKET_CONFIG is absolute, no such search occurs and the set path is used directly.

The file is assumed to be nested, so each top-level key declares a profile and its values the value for the profile. The following is an example of what such a file might look like:

## defaults for _all_ profiles
[default]
address = "0.0.0.0"
limits = { form = "64 kB", json = "1 MiB" }

## set only when compiled in debug mode, i.e, `cargo build`
[debug]
port = 8000
## only the `json` key from `default` will be overridden; `form` will remain
limits = { json = "10MiB" }

## set only when the `nyc` profile is selected
[nyc]
port = 9001

## set only when compiled in release mode, i.e, `cargo build --release`
[release]
port = 9999
ip_header = false
secret_key = "hPrYyЭRiMyµ5sBB1π+CMæ1køFsåqKvBiQJxBVHQk="

The following is a Rocket.toml file with all configuration options set for demonstration purposes. You do not and should not set a value for configuration options needlessly, preferring to use the default value when sensible.

[default]
address = "127.0.0.1"
port = 8000
workers = 16
max_blocking = 512
keep_alive = 5
ident = "Rocket"
ip_header = "X-Real-IP" # set to `false` to disable
log_level = "normal"
temp_dir = "/tmp"
cli_colors = true
secret_key = "hPrYyЭRiMyµ5sBB1π+CMæ1køFsåqKvBiQJxBVHQk="

[default.limits]
form = "64 kB"
json = "1 MiB"
msgpack = "2 MiB"
"file/jpg" = "5 MiB"

[default.tls]
certs = "path/to/cert-chain.pem"
key = "path/to/key.pem"

[default.shutdown]
ctrlc = true
signals = ["term", "hup"]
grace = 5
mercy = 5

Environment Variables

Rocket reads all environment variable names prefixed with ROCKET_ using the string after the _ as the name of a configuration value as the value of the parameter as the value itself. Environment variables take precedence over values in Rocket.toml. Values are parsed as loose form of TOML syntax. Consider the following examples:

ROCKET_FLOAT=3.14
ROCKET_ARRAY=[1,"b",3.14]
ROCKET_STRING=Hello
ROCKET_STRING="Hello There"

ROCKET_KEEP_ALIVE=1
ROCKET_IDENT=Rocket
ROCKET_IDENT="Hello Rocket"
ROCKET_IDENT=false
ROCKET_TLS={certs="abc",key="foo/bar"}
ROCKET_LIMITS={form="64 KiB"}

Deployment

In a containerization environment, you are responsible for writing a Dockerfile or Containerfile which you provide to an application platform.

Below you'll find an example of a Dockerfile that:

  • Builds the application with the latest stable Rust compiler.
  • Uses --mount=type=cache to avoid recompiling dependencies.
  • Uses a second stage to create a slim (~100MiB), ready-to-deploy image with only what's needed.
  • Bundles all of an application's assets in the container.
FROM docker.io/rust:1-slim-bookworm AS build

## cargo package name: customize here or provide via --build-arg
ARG pkg=rocket-app

WORKDIR /build

COPY . .

RUN --mount=type=cache,target=/build/target \
    --mount=type=cache,target=/usr/local/cargo/registry \
    --mount=type=cache,target=/usr/local/cargo/git \
    set -eux; \
    cargo build --release; \
    objcopy --compress-debug-sections target/release/$pkg ./main

################################################################################
FROM docker.io/debian:bookworm-slim

WORKDIR /app

## copy the main binary
COPY --from=build /build/main ./

## copy runtime assets which may or may not exist
COPY --from=build /build/Rocket.tom[l] ./static
COPY --from=build /build/stati[c] ./static
COPY --from=build /build/template[s] ./templates

## ensure the container listens globally on port 8080
ENV ROCKET_ADDRESS=0.0.0.0
ENV ROCKET_PORT=8080

CMD ./main

You will need to modify the pkg ARG or provide it via the command-line:

docker build --build-arg pkg=cargo_package_name -t app  .

You may also need to make the following changes:

  • Add/remove/modify ENV variables as needed.
  • Modify the expected target/release/$pkg directory.
  • Add more assets to COPY to the final image.

Finally, we recommend the following .dockerignore file to avoid copying unnecessary artifacts:

target
.cargo
**/*.sh
**/*.tar.gz