This commit entirely rewrites Rocket's URI parsing routines and overhauls the 'uri!' macro resolving all known issues and removing any potential limitations for compile-time URI creation. This commit: * Introduces a new 'Reference' URI variant for URI-references. * Modifies 'Redirect' to accept 'TryFrom<Reference>'. * Introduces a new 'Asterisk' URI variant for parity. * Allows creation of any URI type from a string literal via 'uri!'. * Enables dynamic/static prefixing/suffixing of route URIs in 'uri!'. * Unifies 'Segments' and 'QuerySegments' into one generic 'Segments'. * Consolidates URI formatting types/traits into a 'uri::fmt' module. * Makes APIs more symmetric across URI types. It also includes the following less-relevant changes: * Implements 'FromParam' for a single-segment 'PathBuf'. * Adds 'FileName::is_safe()'. * No longer reparses upstream request URIs. Resolves #842. Resolves #853. Resolves #998.
24 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:
# #[macro_use] extern crate rocket;
# fn main() {}
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:
# #[macro_use] extern crate rocket;
# fn main() {}
use rocket::response::content;
#[get("/")]
fn json() -> content::Json<&'static str> {
content::Json("{ \"hi\": \"world\" }")
}
! warning: This is not the same as the Json
in rocket_contrib
!
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,
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
andself.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
and Status
are
themselves headers, you can also dynamically set the content-type and status by
simply including fields of these types.
For more on using the Responder
derive, 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, 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:
# #[macro_use] extern crate rocket;
# fn main() {}
# use std::path::{Path, PathBuf};
use rocket::response::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::response::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. You can find
many of these responders in the response
module and rocket_contrib
library. 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.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 Stream
s.
A stream can be created from any async Stream
or AsyncRead
type, or via
generator syntax using the stream!
macro and its typed equivalents.
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::{self, Duration};
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 = time::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.
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:
# #[macro_use] extern crate rocket;
# #[macro_use] extern crate rocket_contrib;
# fn main() {}
use serde::Serialize;
use rocket_contrib::json::Json;
#[derive(Serialize)]
struct Task { /* .. */ }
#[get("/todo")]
fn todo() -> Json<Task> {
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 serialization example 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:
# #[macro_use] extern crate rocket;
# #[macro_use] extern crate rocket_contrib;
# fn main() {}
use rocket_contrib::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.
For a template to be renderable, 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_contrib::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 both Tera and Handlebars
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
toT
&mut T
toT
String
to&str
&str
to&Path
&str
toPathBuf
T
toForm<T>
The following conversions only apply to path parts:
T
toOption<T>
T
toResult<T, E>
The following conversions are implemented only in query parts:
Option<T>
toResult<T, E>
(for anyE
)Result<T, E>
toOption<T>
(for anyE
)
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.