Skip to content

Latest commit

 

History

History
846 lines (654 loc) · 28.4 KB

06-responses.md

File metadata and controls

846 lines (654 loc) · 28.4 KB

+++ summary = "generating responses and using typed URIs" +++

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

Responders compose by wrapping (encapsulating) other responders. Naturally, we call these wrapping responders. A wrapping responder takes a response from an existing responder R, modifies it, and then returns it. They typically have the following shape:

struct WrappingResponder<R>(R);

Examples include Responders in the status module, which override the status code of the wrapped Responder's response, and Responders in the content module, which override the Content-Type.

As a concrete example, the Accepted wrapping responder sets the status to 202 - Accepted. It can be used as follows:

# #[macro_use] extern crate rocket;
# fn main() {}

use rocket::response::status;

#[post("/<id>")]
fn new(id: usize) -> status::Accepted<String> {
    // Modifies the response generated by `String` to have a status of `202`.
    status::Accepted(format!("id: '{}'", id))
}

Because wrapping responders are themselves responders, they too can be wrapped. This is what allows responders to compose, enabling powerful combinations of simple primitives to form more powerful abstractions. For example, combining the content::RawJson and status::Custom responders allows creating a response with a Content-Type of JSON and an arbitrary status code like 418 I'm a teapot from an &'static str:

# #[macro_use] extern crate rocket;
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\" }"))
}

! warning: This is not the same as serde::json::Json!

! note: There are simpler alternatives for setting a status and content-types.

The built-in (Status, R) and (ContentType, R) wrapping responders also override the Status and Content-Type of responses, respectively, and may be simpler alternatives:

# #[macro_use] extern crate rocket;
use rocket::http::{Status, ContentType};

#[get("/")]
fn json() -> (Status, (ContentType, &'static str)) {
    (Status::ImATeapot, (ContentType::JSON, "{ \"hi\": \"world\" }"))
}

Composition through wrapping is useful when you want to change one or two properties of an existing response. For more complex use-cases, instead consider deriving a custom responder:

# #[macro_use] extern crate rocket;

#[derive(Responder)]
#[response(status = 418, content_type = "json")]
struct RawTeapotJson(&'static str);

#[get("/")]
fn json() -> RawTeapotJson {
    RawTeapotJson("{ \"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.

Status

While not encouraged, you can also forward a request to a catcher manually by returning a Status directly. For instance, to forward to the catcher for 406: Not Acceptable, you would write:

# #[macro_use] extern crate rocket;
# fn main() {}

use rocket::http::Status;

#[get("/")]
fn just_fail() -> Status {
    Status::NotAcceptable
}

The response generated by Status depends on the status code itself. As indicated above, for error status codes (in range [400, 599]), Status forwards to the corresponding error catcher. The table below summarizes responses generated by Status for these and other codes:

Status Code Range Response
[400, 599] Forwards to catcher for given status.
100, [200, 205] Empty with given status.
All others. Invalid. Errors to 500 catcher.

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:

# #[macro_use] extern crate rocket;
# fn main() {}

use rocket::http::{Header, ContentType};
# type OtherResponder = ();
# type MyType = u8;

#[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:

# #[macro_use] extern crate rocket;
# fn main() {}

use rocket::http::{Header, Status};
# type OtherResponder = ();

#[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:

# #[macro_use] extern crate rocket;
# fn main() {}

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, &str, File, Option, 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:

# #[macro_use] extern crate rocket;
# fn main() {}

use std::io::Cursor;

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

# struct String(std::string::String);
#[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))
            # */
            # .sized_body(self.0.len(), Cursor::new(self.0))
            .ok()
    }
}

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

# #[macro_use] extern crate rocket;
# fn main() {}
#[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:

# #[macro_use] extern crate rocket;
# fn main() {}

# 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()
}

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:

# #[macro_use] extern crate rocket;
# fn main() {}

# use std::path::{Path, PathBuf};
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, Tera or MiniJinja.

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 rocket::*;
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::get;
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::get;
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::get;
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:

# #[macro_use] extern crate rocket;

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

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

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

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

The Json type serializes the structure into JSON, sets the Content-Type to JSON, and emits the serialized data in a fixed-sized body. If serialization fails, a 500 - Internal Server Error is returned.

The serialization example provides further illustration.

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:

# #[macro_use] extern crate rocket;
# fn main() {}

use rocket_dyn_templates::Template;

#[get("/")]
fn index() -> Template {
    # /*
    let context = /* object-like value */;
    # */ let context = ();
    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:

# #[macro_use] extern crate rocket;
# #[macro_use] extern crate rocket_dyn_templates;
# fn main() {}

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:

# #[macro_use] extern crate rocket;

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.

! note: The name of the template does not include its extension.

For a template file named index.html.tera, call render("index") and use the name "index" in templates, i.e, extends "index" or extends "base" for base.html.tera.

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.

The Template API documentation contains more information about templates, including how to customize a template engine to add custom helpers and filters. The templating example uses Tera, Handlebars and MiniJinja templating to implement the same application.

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:

# #[macro_use] extern crate rocket;
# fn main() {}

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

URIs to person can be created as follows:

# #[macro_use] extern crate rocket;

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

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

Deriving UriDisplay

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:

# #[macro_use] extern crate rocket;
# fn main() {}

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!:

# #[macro_use] extern crate rocket;

# 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) { /* .. */ }

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

Typed URI Parts

The Part trait categorizes types that mark a part of the URI as either a Path or a Query. Said another way, types that implement Part are marker types that represent a part of a URI at the type-level. Traits such as UriDisplay and FromUriParam bound a generic parameter by Part: P: Part. This creates two instances of each trait: UriDisplay<Query> and UriDisplay<Path>, and FromUriParam<Query> and FromUriParam<Path>.

As the names might imply, the Path version of the traits is used when displaying parameters in the path part of the URI while the Query version is used when displaying parameters in the query part of the URI. These distinct versions of the traits exist exactly to differentiate, at the type-level, where in the URI a value is to be written to, allowing for type safety in the face of differences between the two locations. For example, while it is valid to use a value of None in the query part, omitting the parameter entirely, doing so is not valid in the path part. By differentiating in the type system, both of these conditions can be enforced appropriately through distinct implementations of FromUriParam<Path> and FromUriParam<Query>.

This division has an effect on how the uri! macro can be invoked. In query parts, for a route type of Option<T>, you must supply a type of Option, Result, or an ignored _ to the uri! invocation. By contrast, you cannot supply such a type in the path part. This ensures that a valid URI is always generated.

# #[macro_use] extern crate rocket;

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

// Note that `id` is `Option<usize>` in the route, but `id` in `uri!` _cannot_
// be an `Option`. `age`, on the other hand, _must_ be an `Option` (or `Result`
// or `_`) as its in the query part and is allowed to be ignored.
let mike = uri!(person(id = 101, name = "Mike", age = Some(28)));
assert_eq!(mike.to_string(), "/101/Mike?age=28");

Conversions

FromUriParam is used to perform a conversion for each value passed to uri! before it is displayed with UriDisplay. If a T: FromUriParam<P, S> implementation exists for a type T for part URI part P, then a value of type S can be used in uri! macro for a route URI parameter declared with a type of T in part P. For example, the following implementation, provided by Rocket, allows an &str to be used in a uri! invocation for route URI parameters declared as String:

# use rocket::http::uri::fmt::{FromUriParam, Part};
# struct S;
# type String = S;
impl<'a, P: Part> FromUriParam<P, &'a str> for String {
    type Target = &'a str;
#   fn from_uri_param(s: &'a str) -> Self::Target { "hi" }
}

Other conversions to be aware of are:

  • &T to T
  • &mut T to T
  • String to &str
  • &str to &Path
  • &str to PathBuf
  • T to Form<T>

The following conversions only apply to path parts:

  • T to Option<T>
  • T to Result<T, E>

The following conversions are implemented only in query parts:

  • Option<T> to Result<T, E> (for any E)
  • Result<T, E> to Option<T> (for any E)

Conversions are transitive. That is, a conversion from A -> B and a conversion B -> C implies a conversion from A -> C. For instance, a value of type &str can be supplied when a value of type Option<PathBuf> is expected:

# #[macro_use] extern crate rocket;

use std::path::PathBuf;

#[get("/person/<id>/<details..>")]
fn person(id: usize, details: Option<PathBuf>) { /* .. */ }

uri!(person(id = 100, details = "a/b/c"));

See the FromUriParam documentation for further details.