From a61977befc0543cc1a3c6fd9c164c688d4804efb Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Tue, 4 Jul 2017 02:37:21 -0700 Subject: [PATCH] Rewrite the 'Responses' guide for 0.3. --- site/guide/responses.md | 336 ++++++++++++++++++++++++++-------------- 1 file changed, 218 insertions(+), 118 deletions(-) diff --git a/site/guide/responses.md b/site/guide/responses.md index 39195b26..2826946f 100644 --- a/site/guide/responses.md +++ b/site/guide/responses.md @@ -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); ``` -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`: +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 { - status::Accepted("I accept!".to_string()) +use rocket::response::status; + +#[post("/")] +fn new(id: usize) -> status::Accepted { + 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 { ... } -``` - -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, 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` **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` 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("/")] +fn files(file: PathBuf) -> Option { + NamedFile::open(Path::new("static/").join(file)).ok() +} +``` + +### `Result` **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("/")] +fn files(file: PathBuf) -> Result> { + 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> { ``` -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` 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 { ... } +``` + +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