Rewrite the 'Responses' guide for 0.3.

This commit is contained in:
Sergio Benitez 2017-07-04 02:37:21 -07:00
parent 436bd99376
commit a61977befc
1 changed files with 218 additions and 118 deletions

View File

@ -1,82 +1,69 @@
# 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](https://api.rocket.rs/rocket/response/trait.Responder.html) trait
can be returned, including your own.
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`]: https://api.rocket.rs/rocket/response/trait.Responder.html
## Responder
Types that implement `Responder` know how to generate a
[Response](https://api.rocket.rs/rocket/response/struct.Response.html) from
their values. A `Response` includes the HTTP status, headers, and body of the
response. Rocket implements `Responder` for many built-in types including
`String`, `&str`, `File`, `Option`, `Result`, and others. Rocket also provides
custom types, such as
[Content](https://api.rocket.rs/rocket/response/struct.Content.html) and
[Flash](https://api.rocket.rs/rocket/response/struct.Flash.html), which you can
find in the [response](https://api.rocket.rs/rocket/response/index.html) module.
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.
The body of a `Response` 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 streaming body.
[`Response`]: https://api.rocket.rs/rocket/response/struct.Response.html
### Wrapping
Responders can _wrap_ other responders. That is, responders can be of the
following form, where `R: Responder`:
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`:
```rust
struct WrappingResponder<R>(R);
```
When this is the case, the wrapping responder will modify the response returned
by `R` in some way before responding itself. For instance, to override the
status code of some response, you can use the types in the [status
module](https://api.rocket.rs/rocket/response/status/index.html). In particular,
to set the status code of a response for a `String` to **202 Accepted**, you can
return a type of `status::Accepted<String>`:
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](https://api.rocket.rs/rocket/response/status/index.html) 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:
```rust
#[get("/")]
fn accept() -> status::Accepted<String> {
status::Accepted("I accept!".to_string())
use rocket::response::status;
#[post("/<id>")]
fn new(id: usize) -> status::Accepted<String> {
let url = "http://example.com/resource.json";
status::Created(url.into(), Some(format!("id: '{}'", id)))
}
```
By default, the `String` responder sets the status to **200**. By using the
`Accepted` type however, The client will receive an HTTP response with status
code **202**.
Similarly, the types in the [content
Similarly, the types in the [`content`
module](https://api.rocket.rs/rocket/response/content/index.html) can be used to
override the Content-Type of the response. For instance, to set the Content-Type
of some `&'static str` to JSON, you can use the
[content::JSON](https://api.rocket.rs/rocket/response/content/struct.JSON.html)
type as follows:
override the Content-Type of a response. For instance, to set the Content-Type
an `&'static str` to JSON, you can use the [`content::JSON`] type as follows:
```rust
use rocket::response::content;
#[get("/")]
fn json() -> content::JSON<&'static str> {
content::JSON("{ 'hi': 'world' }")
}
```
### Result
[`Accepted`]: https://api.rocket.rs/rocket/response/status/struct.Accepted.html
[`content::JSON`]: https://api.rocket.rs/rocket/response/content/struct.JSON.html
`Result` is one of the most commonly used responders. Returning a `Result` means
one of two things. If the error type implements `Responder`, the `Ok` or `Err`
value will be used, whichever the variant is. If the error type does _not_
implement `Responder`, the error is printed to the console, and the request is
forwarded to the **500** error catcher.
### Option
`Option` is another commonly used responder. If the `Option` is `Some`, the
wrapped responder is used to respond to the client. Otherwise, the request is
forwarded to the **404** error catcher.
## Errors
### 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
@ -87,16 +74,13 @@ 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
returns 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.
### Failure
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](https://api.rocket.rs/rocket/response/struct.Failure.html)
type. For instance, to forward to the catcher for **406 Not Acceptable**, you
using the [`Failure`](https://api.rocket.rs/rocket/response/struct.Failure.html)
type. For instance, to forward to the catcher for **406 - Not Acceptable**, you
would write:
```rust
@ -106,75 +90,114 @@ fn just_fail() -> Failure {
}
```
## JSON
## `std` Implementations
Responding with JSON data is simple: return a value of type
[JSON](https://api.rocket.rs/rocket_contrib/struct.JSON.html). For example, to
respond with the JSON value of the `Task` structure from previous examples, we
would write:
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.
### `&str` and `String`
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`:
```rust
#[derive(Serialize)]
struct Task { ... }
#[get("/todo")]
fn todo() -> JSON<Task> { ... }
```
The generic type in `JSON` must implement `Serialize`. The `JSON` type
serializes the structure into JSON, sets the Content-Type to JSON, and emits the
serialization in a fixed-sized body. If serialization fails, the request is
forwarded to the **500** error catcher.
For a complete example, see the [JSON example on
GitHub](https://github.com/SergioBenitez/Rocket/tree/v0.2.8/examples/json).
## Templates
Rocket has built-in support for templating. To respond with a rendered template,
ensure that you are using
[`Template::fairing()`](https://api.rocket.rs/rocket_contrib/struct.Template.html#method.fairing)
and then simply return a
[Template](https://api.rocket.rs/rocket_contrib/struct.Template.html) type.
```rust
#[get("/")]
fn index() -> Template {
let context = /* object-like value */;
Template::render("index", &context)
}
fn main() {
rocket::ignite()
.mount("/", routes![index])
.attach(Template::fairing())
.launch();
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()
}
}
```
Templates are rendered with the `render` method. The method takes in the name of
a template and a context to render the template with. Rocket searches for a
template with that name in the configurable `template_dir` configuration
parameter, which defaults to `templates/`. 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.
Because of these implementations, you can directly return an `&str` or `String`
type from a handler:
The context can be any type that implements `Serialize` and serializes to an
`Object` value, such as structs, `HashMaps`, and others. The
[Template](https://api.rocket.rs/rocket_contrib/struct.Template.html) API
documentation contains more information about templates, while the [Handlebars
Templates example on
GitHub](https://github.com/SergioBenitez/Rocket/tree/v0.2.8/examples/handlebars_templates)
is a fully composed application that makes use of Handlebars templates.
```rust
#[get("/string")]
fn handler() -> &'static str {
"Hello there! I'm a string!"
}
```
## Streaming
### `Option<T>` **where** `T: Responder`
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](https://api.rocket.rs/rocket/response/struct.Stream.html)
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:
`Option` is _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:
```rust
#[get("/<file..>")]
fn files(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(Path::new("static/").join(file)).ok()
}
```
### `Result<T, E>` **where** `E: Debug`, `E: Responder`
`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. Revisting 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:
```rust
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 arbitrary `Read`er type.
* [`status`] - Contains types that override the status code of a response.
* [`Flash`] - Sets a "flash" cookie that is removed when accessed.
[`status`]: https://api.rocket.rs/rocket/response/status/index.html
[`response`]: https://api.rocket.rs/rocket/response/index.html
[`NamedFile`]: https://api.rocket.rs/rocket/response/struct.NamedFile.html
[`Content`]: https://api.rocket.rs/rocket/response/struct.Content.html
[`Redirect`]: https://api.rocket.rs/rocket/response/struct.Redirect.html
[`Stream`]: https://api.rocket.rs/rocket/response/struct.Stream.html
[`Flash`]: https://api.rocket.rs/rocket/response/struct.Flash.html
### 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](https://api.rocket.rs/rocket/response/struct.Stream.html) 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:
```rust
#[get("/stream")]
@ -184,4 +207,81 @@ fn stream() -> io::Result<Stream<UnixStream>> {
```
Rocket takes care of the rest.
[`rocket_contrib`]: https://api.rocket.rs/rocket_contrib/index.html
### 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.
An an example, to respond with the JSON value of a `Task` structure, we might
write:
```rust
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.
[`JSON`]: https://api.rocket.rs/rocket_contrib/struct.JSON.html
[`Serialize`]: https://docs.serde.rs/serde/trait.Serialize.html
[`serde`]: https://docs.serde.rs/serde/
[JSON example on GitHub]: https://github.com/SergioBenitez/Rocket/tree/v0.2.8/examples/json
### 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:
```rust
#[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.
For templates to be properly registered, the template fairing must be attached
to the instance of Rocket. Fairings are explained in the next section. To attach
the template fairing, simply call `.attach(Template::fairing())` on an instance
of `Rocket` as follows:
```rust
fn main() {
rocket::ignite()
.mount("/", routes![...])
.attach(Template::fairing());
}
```
The [`Template`] API
documentation contains more information about templates, while the [Handlebars
Templates example on
GitHub](https://github.com/SergioBenitez/Rocket/tree/v0.2.8/examples/handlebars_templates)
is a fully composed application that makes use of Handlebars templates.
[`Template`]: https://api.rocket.rs/rocket_contrib/struct.Template.html