11 KiB
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 Responder
s 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 Responder
s 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(Some(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, you can use the content::Json
type
as follows:
use rocket::response::content;
#[get("/")]
fn json() -> content::Json<&'static str> {
content::Json("{ 'hi': 'world' }")
}
Errors
Responders may fail; they need not always generate a response. Instead, they
can return an Err
with a given status code. When this happens, Rocket forwards
the request to the error catcher for the
given 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.
While not encouraged, you can also forward a request to a catcher manually by
using the Failure
type. For instance, to forward to the catcher for 406 - Not Acceptable, you
would write:
#[get("/")]
fn just_fail() -> Failure {
Failure(Status::NotAcceptable)
}
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
:
impl Responder<'static> for String {
fn respond_to(self, _: &Request) -> Result<Response<'static>, Status> {
Response::build()
.header(ContentType::Plain)
.sized_body(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, a 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:
#[get("/<file..>")]
fn files(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(Path::new("static/").join(file)).ok()
}
Result
Result
is a special kind of wrapping responder: its functionality depends on
whether the error type E
implements Responder
.
When the error type 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::response::status::NotFound;
#[get("/<file..>")]
fn files(file: PathBuf) -> Result<NamedFile, NotFound<String>> {
let path = Path::new("static/").join(file);
NamedFile::open(&path).map_err(|_| NotFound(format!("Bad path: {}", path)))
}
If the error type E
does not implement Responder
, then the error is simply
logged to the console, using its Debug
implementation, and a 500
error is
returned to the client.
Rocket Responders
Some of Rocket's best features are implemented through responders. You can find
many of these responders in the response
module. Among these are:
Content
- Used to override the Content-Type of a response.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.Stream
- Streams a response to a client from an arbitraryRead
er type.status
- Contains types that override the status code of a response.Flash
- Sets a "flash" cookie that is removed when accessed.
Streaming
The Stream
type deserves special attention. When a large amount of data needs
to be sent to the client, it is better to stream the data to the client to avoid
consuming large amounts of memory. Rocket provides the Stream
type, making
this easy. The Stream
type can be created from any Read
type. For example,
to stream from a local Unix stream, we might write:
#[get("/stream")]
fn stream() -> io::Result<Stream<UnixStream>> {
UnixStream::connect("/path/to/my/socket").map(|s| Stream::from(s))
}
JSON
The JSON
responder in rocket_contrib
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_contrib::Json;
#[derive(Serialize)]
struct Task { ... }
#[get("/todo")]
fn todo() -> Json<Task> { ... }
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 JSON example on GitHub provides further illustration.
Templates
Rocket includes built-in templating support that works largely through a
Template
responder in rocket_contrib
. To render a template named "index",
for instance, you might return a value of type Template
as follows:
#[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.
Rocket searches for a template with the given name 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.
When your application is compiled in debug
mode (without the --release
flag
passed to cargo
), templates are automatically reloaded when they are modified.
This means that you don't need to rebuild your application to observe template
changes: simply refresh! In release builds, reloading is disabled.
For templates to be properly registered, the template fairing must be attached
to the instance of Rocket. 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:
fn main() {
rocket::ignite()
.mount("/", routes![...])
.attach(Template::fairing());
}
The Template
API documentation contains more information about templates,
including how to customize a template engine to add custom helpers and filters.
The Handlebars Templates example on GitHub is a
fully composed application that makes use of Handlebars templates.