Rocket/site/guide/requests.md

379 lines
15 KiB
Markdown

# Requests
If all we could do was match against static paths like `"/world"`, Rocket
wouldn't be much fun. Of course, Rocket allows you to match against just about
any information in an incoming request. This section describes the available
options and their effect on the application.
## Methods
A Rocket route attribute can be any one of `get`, `put`, `post`, `delete`,
`head`, `patch`, or `options`, each corresponding to the HTTP method to match
against. For example, the following attribute will match against `POST` requests
to the root path:
```rust
#[post("/")]
```
The grammar for these routes is defined formally in the
[rocket_codegen](https://api.rocket.rs/rocket_codegen/) API docs.
Rocket handles `HEAD` requests automatically when there exists a `GET` route
that would otherwise match. It does this by stripping the body from the
response, if there is one. You can also specialize the handling of a `HEAD`
request by declaring a route for it; Rocket won't interfere with `HEAD` requests
your application handles.
Because browsers only send `GET` and `POST` requests, Rocket _reinterprets_
requests under certain conditions. If a `POST` request contains a body of
`Content-Type: application/x-www-form-urlencoded`, and the form's **first**
field has the name `_method` and a valid HTTP method as its value, that field's
value is used as the method for the incoming request. This allows Rocket
applications to submit non-`POST` forms. The [todo
example](https://github.com/SergioBenitez/Rocket/tree/v0.2.6/examples/todo/static/index.html.tera#L47)
makes use of this feature to submit `PUT` and `DELETE` requests from a web form.
## Format
When receiving data, you can specify the Content-Type the route matches against
via the `format` route parameter. The parameter is a string of the Content-Type
expected. For example, to match `application/json` data, a route can be declared
as:
```rust
#[post("/user", format = "application/json", data = "<user>")]
fn new_user(user: JSON<User>) -> T { ... }
```
Note the `format` parameter in the `post` attribute. The `data` parameter is
described later in the [data](#data) section.
## Dynamic Paths
You can declare path segments as dynamic by using angle brackets around variable
names in a route's path. For example, if we wanted to say _Hello!_ to anything,
not just the world, we could declare a route and handler like so:
```rust
#[get("/hello/<name>")]
fn hello(name: &str) -> String {
format!("Hello, {}!", name)
}
```
If we were to mount the path at the root (`.mount("/", routes![hello])`), then
any request to a path with two non-empty segments, where the first segment is
`hello`, will be dispatched to the `hello` route. For example, if we were to
visit `/hello/John`, the application would respond with `Hello, John!`.
You can have any number of dynamic path segments, and the type of the path
segment can be any type that implements the [FromParam
trait](https://api.rocket.rs/rocket/request/trait.FromParam.html), including
your own! Rocket implements `FromParam` for many of the standard library types,
as well as a few special Rocket types. Here's a somewhat complicated route to
illustrate varied usage:
```rust
#[get("/hello/<name>/<age>/<cool>")]
fn hello(name: &str, age: u8, cool: bool) -> String {
if cool {
format!("You're a cool {} year old, {}!", age, name)
} else {
format!("{}, we need to talk about your coolness.", name)
}
}
```
## Forwarding
In this example above, what if `cool` isn't a `bool`? Or, what if `age` isn't a
`u8`? In this case, the request is _forwarded_ to the next matching route, if
there is any. This continues until a route doesn't forward the request or there
are no remaining routes to try. When there are no remaining matching routes, a
customizable **404 error** is returned.
Routes are tried in increasing _rank_ order. By default, routes with static
paths have a rank of 0 and routes with dynamic paths have a rank of 1. A route's
rank can be manually set with the `rank` route parameter.
To illustrate, consider the following routes:
```rust
#[get("/user/<id>")]
fn user(id: usize) -> T { ... }
#[get("/user/<id>", rank = 2)]
fn user_int(id: isize) -> T { ... }
#[get("/user/<id>", rank = 3)]
fn user_str(id: &str) -> T { ... }
```
Notice the `rank` parameters in `user_int` and `user_str`. If we run this
application with the routes mounted at the root, requests to `/user/<id>` will
be routed as follows:
1. The `user` route matches first. If the string at the `<id>` position is an
unsigned integer, then the `user` handler is called. If it is not, then the
request is forwarded to the next matching route: `user_int`.
2. The `user_int` route matches next. If `<id>` is a signed integer,
`user_int` is called. Otherwise, the request is forwarded.
3. The `user_str` route matches last. Since `<id>` is a always string, the
route always matches. The `user_str` handler is called.
Forwards can be _caught_ by using a `Result` or `Option` type. For example, if
the type of `id` in the `user` function was `Result<usize, &str>`, then `user`
would never forward. An `Ok` variant would indicate that `<id>` was a valid
`usize`, while an `Err` would indicate that `<id>` was not a `usize`. The
`Err`'s value would contain the string that failed to parse as a `usize`.
By the way, if you were to omit the `rank` parameter in the `user_str` or
`user_int` routes, Rocket would emit a warning indicating that the routes
_collide_, or can match against similar incoming requests. The `rank` parameter
resolves this collision.
## Dynamic Segments
You can also match against multiple segments by using `<param..>` in the route
path. The type of such parameters, known as _segments_ parameters, can be any
that implements
[FromSegments](https://api.rocket.rs/rocket/request/trait.FromSegments.html).
Segments parameters must be the final component of the path: any text after a
segments parameter in a path will result in a compile-time error.
As an example, the following route matches against all paths that begin with
`/page/`:
```rust
#[get("/page/<path..>")]
fn get_page(path: PathBuf) -> T { ... }
```
The path after `/page/` will be available in the `path` parameter. The
`FromSegments` implementation for `PathBuf` ensures that `path` cannot lead to
[path traversal attacks](https://www.owasp.org/index.php/Path_Traversal). With
this, a safe and secure static file server can be implemented in 4 lines:
```rust
#[get("/<file..>")]
fn files(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(Path::new("static/").join(file)).ok()
}
```
## Request Guards
Sometimes we need data associated with a request that isn't a direct input.
Headers and cookies are a good example of this: they simply tag along for the
ride. Rocket makes retrieving and validating such information easy: simply add
any number of parameters to the request handler with types that implement the
[FromRequest](https://api.rocket.rs/rocket/request/trait.FromRequest.html)
trait. If the data can be retrieved from the incoming request and validated, the
handler is called. If it cannot, the handler isn't called, and the request is
forwarded or terminated. In this way, these parameters act as _guards_: they
protect the request handler from being called erroneously.
For example, to retrieve cookies and the Content-Type header from a request, we
can declare a route as follows:
```rust
#[get("/")]
fn index(cookies: &Cookies, content: ContentType) -> String { ... }
```
The [cookies example on
GitHub](https://github.com/SergioBenitez/Rocket/tree/v0.2.6/examples/cookies)
illustrates how to use the `Cookies` type to get and set cookies.
You can implement `FromRequest` for your own types. For instance, to protect a
`sensitive` route from running unless an `APIKey` is present in the request
headers, you might create an `APIKey` type that implements `FromRequest` and use
it as a request guard:
```rust
#[get("/sensitive")]
fn sensitive(key: APIKey) -> &'static str { ... }
```
You might also implement `FromRequest` for an `AdminUser` type that validates
that the cookies in the incoming request authenticate an administrator. Then,
any handler with an `AdminUser` or `APIKey` type in its argument list is assured
to only be invoked if the appropriate conditions are met. Request guards
centralize policies, resulting in a simpler, safer, and more secure
applications.
## Data
At some point, your web application will need to process body data, and Rocket
makes it as simple as possible. Data processing, like much of Rocket, is type
directed. To indicate that a handler expects data, annotate it with a `data =
"<param>"` parameter, where `param` is an argument in the handler. The
argument's type must implement the
[FromData](https://api.rocket.rs/rocket/data/trait.FromData.html) trait. It
looks like this, where `T: FromData`:
```rust
#[post("/", data = "<input>")]
fn new(input: T) -> String { ... }
```
### Forms
Forms are the most common type of data handled in web applications, and Rocket
makes handling them easy. Say your application is processing a form submission
for a new todo `Task`. The form contains two fields: `complete`, a checkbox, and
`description`, a text field. You can easily handle the form request in Rocket
as follows:
```rust
#[derive(FromForm)]
struct Task {
complete: bool,
description: String,
}
#[post("/todo", data = "<task>")]
fn new(task: Form<Task>) -> String { ... }
```
The `Form` type implements the `FromData` trait as long as its generic parameter
implements the
[FromForm](https://api.rocket.rs/rocket/request/trait.FromForm.html) trait. In
the example, we've derived the `FromForm` trait automatically for the `Task`
structure. `FromForm` can be derived for any structure whose fields implement
[FromFormValue](https://api.rocket.rs/rocket/request/trait.FromFormValue.html).
If a `POST /todo` request arrives, the form data will automatically be parsed
into the `Task` structure. If the data that arrives isn't of the correct
Content-Type, the request is forwarded. If the data doesn't parse or is simply
invalid, a customizable `400 Bad Request` error is returned. As before, a
forward or failure can be caught by using the `Option` and `Result` types.
Fields of forms can be easily validated via implementations of the
`FromFormValue` trait. For example, if you'd like to verify that some user is
over some age in a form, then you might define a new `AdultAge` type, use it as
a field in a form structure, and implement `FromFormValue` so that it only
validates integers over that age. If a form is submitted with a bad age,
Rocket won't call a handler requiring a valid form for that structure. You can
use `Option` or `Result` types for fields to catch parse failures.
The [forms](https://github.com/SergioBenitez/Rocket/tree/v0.2.6/examples/forms)
and [forms kitchen
sink](https://github.com/SergioBenitez/Rocket/tree/v0.2.6/examples/form_kitchen_sink)
examples on GitHub provide further illustrations.
### JSON
Handling JSON data is no harder: simply use the
[JSON](https://api.rocket.rs/rocket_contrib/struct.JSON.html) type:
```rust
#[derive(Deserialize)]
struct Task {
description: String,
complete: bool
}
#[post("/todo", data = "<task>")]
fn new(task: JSON<Task>) -> String { ... }
```
The only condition is that the generic type to `JSON` implements the
`Deserialize` trait. See the [JSON example on
GitHub](https://github.com/SergioBenitez/Rocket/tree/v0.2.6/examples/json) for a
complete example.
### Streaming
Sometimes you just want to handle the incoming data directly. For example, you
might want to stream the incoming data out to a file. Rocket makes this as
simple as possible via the
[Data](https://api.rocket.rs/rocket/data/struct.Data.html) type:
```rust
#[post("/upload", format = "text/plain", data = "<data>")]
fn upload(data: Data) -> io::Result<Plain<String>> {
data.stream_to_file("/tmp/upload.txt").map(|n| Plain(n.to_string()))
}
```
The route above accepts any `POST` request to the `/upload` path with
`Content-Type` `text/plain` The incoming data is streamed out to
`tmp/upload.txt` file, and the number of bytes written is returned as a plain
text response if the upload succeeds. If the upload fails, an error response is
returned. The handler above is complete. It really is that simple! See the
[GitHub example
code](https://github.com/SergioBenitez/Rocket/tree/v0.2.6/examples/raw_upload)
for the full crate.
## Query Strings
Query strings are handled similarly to `POST` forms. A query string can be
parsed into any structure that implements the `FromForm` trait. They are matched
against by appending a `?` followed by a dynamic parameter `<param>` to the
path.
For instance, say you change your mind and decide to use query strings instead
of `POST` forms for new todo tasks in the previous forms example, reproduced
below:
```rust
#[derive(FromForm)]
struct Task { .. }
#[post("/todo", data = "<task>")]
fn new(task: Form<Task>) -> String { ... }
```
Rocket makes the transition simple: simply declare `<task>` as a query parameter
as follows:
```rust
#[get("/todo?<task>")]
fn new(task: Task) -> String { ... }
```
Rocket will parse the query string into the `Task` structure automatically by
matching the structure field names to the query parameters. If the parse fails,
the request is forwarded to the next matching route. To catch parse failures,
you can use `Option` or `Result` as the type of the field to catch errors for.
See [the GitHub
example](https://github.com/SergioBenitez/Rocket/tree/v0.2.6/examples/query_params)
for a complete illustration.
## Error Catchers
When Rocket wants to return an error page to the client, Rocket invokes the
_catcher_ for that error. A catcher is like a route, except it only handles
errors. Catchers are declared via the `error` attribute, which takes a single
integer corresponding to the HTTP status code to catch. For instance, to declare
a catcher for **404** errors, you'd write:
```rust
#[error(404)]
fn not_found(req: &Request) -> String { }
```
As with routes, Rocket needs to know about a catcher before it is used to handle
errors. The process is similar to mounting: call the `catch` method with a list
of catchers via the `errors!` macro. The invocation to add the **404** catcher
declared above looks like this:
```rust
rocket::ignite().catch(errors![not_found])
```
Unlike request handlers, error handlers can only take 0, 1, or 2 parameters of
types [Request](https://api.rocket.rs/rocket/struct.Request.html) and/or
[Error](https://api.rocket.rs/rocket/enum.Error.html). At present, the `Error`
type is not particularly useful, and so it is often omitted. The
[error catcher
example](https://github.com/SergioBenitez/Rocket/tree/v0.2.6/examples/errors) on
GitHub illustrates their use in full.
Rocket has a default catcher for all of the standard HTTP error codes including
**404**, **500**, and more.