Rocket

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit 3903ffdb471d82f10c885ef10991cbe1fb4d7dfe
parent 9ef0b731c8bc26d4100aac0e93c24513781b36ad
Author: Sergio Benitez <sb@sergio.bz>
Date:   Mon, 22 Oct 2018 14:47:35 -0700

Update guide in full for current 0.4.0-dev.

Diffstat:
Msite/guide/0-introduction.md | 52++++++++++++++++++++++++++++++++--------------------
Msite/guide/1-quickstart.md | 8++++++++
Msite/guide/2-getting-started.md | 30+++++++++++++++++-------------
Msite/guide/3-overview.md | 66+++++++++++++++++++++++++++++++-----------------------------------
Msite/guide/4-requests.md | 454++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Msite/guide/5-responses.md | 122++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msite/guide/6-state.md | 26++++++++++++++++++++------
Msite/guide/7-fairings.md | 21++++++++++++---------
Msite/guide/8-testing.md | 22++++++++++++++--------
Msite/guide/9-configuration.md | 11+++++++++--
Msite/guide/index.md | 11+++++------
Msite/index.toml | 46+++++++++++++++++++++++++++-------------------
Msite/overview.toml | 32++++++++++++++++----------------
13 files changed, 589 insertions(+), 312 deletions(-)

diff --git a/site/guide/0-introduction.md b/site/guide/0-introduction.md @@ -5,38 +5,50 @@ being a more flexible, friendly medley of [Rails](http://rubyonrails.org), [Flask](http://flask.pocoo.org/), [Bottle](http://bottlepy.org/docs/dev/index.html), and [Yesod](http://www.yesodweb.com/). We prefer to think of Rocket as something -new. Rocket aims to be fast, easy, and flexible. It also aims to be _fun_, and +new. Rocket aims to be fast, easy, and flexible while offerring guaranteed +safety and security where it can. Importantly, Rocket also aims to be _fun_, and it accomplishes this by ensuring that you write as little code as needed to -accomplish your task. This guide introduces you to the core, intermediate, and -advanced concepts of Rocket. After reading this guide, you should find yourself -being _very_ productive with Rocket. +accomplish your task. + +This guide introduces you to the core, intermediate, and advanced concepts of +Rocket. After reading this guide, you should find yourself being very +productive with Rocket. ## Audience Readers are assumed to have a good grasp of the Rust programming language. Readers new to Rust are encouraged to read the [Rust Book](https://doc.rust-lang.org/book/). This guide also assumes a basic -understanding of web application fundamentals, such as routing and HTTP. +understanding of web application fundamentals, such as routing and HTTP. Mozilla +provides a good overview of these concepts in their [MDN web docs]. + +[MDN web docs]: https://developer.mozilla.org/en-US/docs/Web/HTTP ## Foreword Rocket's design is centered around three core philosophies: - * **Function declaration and parameter types should contain all the necessary - information to validate and process a request.** This immediately prohibits - APIs where request state is retrieved from a global context. As a result, - request handling is **self-contained** in Rocket: handlers are regular - functions with regular arguments. - - * **All request handling information should be typed.** Because the web and - HTTP are themselves untyped (or _stringly_ typed, as some call it), this - means that something or someone has to convert strings to native types. - Rocket does this for you with zero programming overhead. - - * **Decisions should not be forced.** Templates, serialization, sessions, and - just about everything else are all pluggable, optional components. While - Rocket has official support and libraries for each of these, they are - completely optional and swappable. + * **Security, correctness, and developer experience are paramount.** + + The path of least resistance should lead you to the most secure, correct web + application, though security and correctness should not come at the cost of + a degraded developer experience. Rocket is easy to use while taking great + measures to ensure that your application is secure and correct without + cognitive overhead. + + * **All request handling information should be typed and self-contained.** + + Because the web and HTTP are themselves untyped (or _stringly_ typed, as + some call it), this means that something or someone has to convert strings + to native types. Rocket does this for you with zero programming overhead. + What's more, Rocket's request handling is **self-contained** with zero + global state: handlers are regular functions with regular arguments. + + * **Decisions should not be forced.** + + Templates, serialization, sessions, and just about everything else are all + pluggable, optional components. While Rocket has official support and + libraries for each of these, they are completely optional and swappable. These three ideas dictate Rocket's interface, and you will find all of them embedded in Rocket's core features. diff --git a/site/guide/1-quickstart.md b/site/guide/1-quickstart.md @@ -22,3 +22,11 @@ cargo run There are numerous examples in the `examples/` directory. They can all be run with `cargo run`. + +! note + + The examples' `Cargo.toml` files will point to the locally cloned `rocket` + libraries. When copying the examples for your own use, you should modify the + `Cargo.toml` files as explained in the [Getting Started] guide. + +[Getting Started]: ../getting-started diff --git a/site/guide/2-getting-started.md b/site/guide/2-getting-started.md @@ -28,15 +28,13 @@ Rocket project by running the following command in the directory: rustup override set nightly ``` -### Minimum Nightly +! warning: Rocket requires the _latest_ version of Rust nightly. -Rocket always requires the _latest_ version of Rust nightly. If your Rocket -application suddenly stops building, ensure you're using the latest version of -Rust nightly and Rocket by updating your toolchain and dependencies with: + If your Rocket application suddenly stops building, ensure you're using the + latest version of Rust nightly and Rocket by updating your toolchain and + dependencies with: -```sh -rustup update && cargo update -``` + `rustup update && cargo update` ## Hello, world! @@ -48,13 +46,11 @@ cargo new hello-rocket --bin cd hello-rocket ``` -Now, add Rocket and its code generation facilities as dependencies of your -project by ensuring your `Cargo.toml` contains the following: +Now, add Rocket as a dependency in your `Cargo.toml`: ``` [dependencies] -rocket = "0.3.6" -rocket_codegen = "0.3.6" +rocket = "0.4.0-dev" ``` Modify `src/main.rs` so that it contains the code for the Rocket `Hello, world!` @@ -85,13 +81,21 @@ run`. You should see the following: => address: localhost => port: 8000 => log: normal - => workers: [core count * 2] + => workers: 24 => secret key: generated => limits: forms = 32KiB + => keep-alive: 5s => tls: disabled 🛰 Mounting '/': - => GET / + => GET / (hello) 🚀 Rocket has launched from http://localhost:8000 ``` Visit `http://localhost:8000` to see your first Rocket application in action! + +! tip: Don't like colors or emoji? + + You can disable colors and emoji by setting the `ROCKET_CLI_COLORS` + environment variable to `0` or `off` when running a Rocket binary: + + `ROCKET_CLI_COLORS=off cargo run` diff --git a/site/guide/3-overview.md b/site/guide/3-overview.md @@ -1,10 +1,10 @@ # Overview -Rocket provides primitives to build web servers and applications with Rust: the -rest is up to you. In short, Rocket provides routing, pre-processing of -requests, and post-processing of responses. Your application code instructs -Rocket on what to pre-process and post-process and fills the gaps between -pre-processing and post-processing. +Rocket provides primitives to build web servers and applications with Rust: +Rocket provides routing, pre-processing of requests, and post-processing of +responses; the rest is up to you. Your application code instructs Rocket on what +to pre-process and post-process and fills the gaps between pre-processing and +post-processing. ## Lifecycle @@ -76,27 +76,24 @@ constructing routes. ## Mounting -Before Rocket can dispatch requests to a route, the route needs to be _mounted_. -Mounting a route is like namespacing it. Routes are mounted via the `mount` -method on a `Rocket` instance. A `Rocket` instance is typically created with the -`rocket::ignite()` static method. - -The `mount` method takes: - - 1. A path to namespace a list of routes under, - 2. A list of route handlers through the `routes!` macro, tying Rocket's code - generation to your application. - -For instance, to mount the `world` route we declared above, we can write the -following: +Before Rocket can dispatch requests to a route, the route needs to be _mounted_: ```rust -rocket::ignite().mount("/hello", routes![world]); +fn main() { + rocket::ignite().mount("/hello", routes![world]); +} ``` +The `mount` method takes as input: + + 1. A _base_ path to namespace a list of routes under. + 2. A list of routes via the `routes!` macro. + This creates a new `Rocket` instance via the `ignite` function and mounts the -`world` route to the `"/hello"` path. As a result, `GET` requests to the -`"/hello/world"` path will be directed to the `world` function. +`world` route to the `"/hello"` path, making Rocket aware of the route. `GET` +requests to `"/hello/world"` will be directed to the `world` function. + +! note: In many cases, the base path will simply be `"/"`. ### Namespacing @@ -121,7 +118,7 @@ fn main() { This occurs because the `routes!` macro implicitly converts the route's name into the name of a structure generated by Rocket's code generation. The solution -is to name the route by a module path instead: +is to refer to the route using a namespaced path instead: ```rust rocket::ignite().mount("/hello", routes![other::world]); @@ -153,10 +150,10 @@ fn main() { ``` Note the `#![feature]` line: this tells Rust that we're opting in to compiler -features vailable in the nightly release channel. This line must be in the crate -root, typically `main.rs`. We've also imported the `rocket` crate and all of its -macros into our namespace via `#[macro_use] extern crate rocket`. Finally, we -call the `launch` method in the `main` function. +features available in the nightly release channel. This line **must** be in the +crate root, typically `main.rs`. We've also imported the `rocket` crate and all +of its macros into our namespace via `#[macro_use] extern crate rocket`. +Finally, we call the `launch` method in the `main` function. Running the application, the console shows: @@ -165,21 +162,20 @@ Running the application, the console shows: => address: localhost => port: 8000 => log: normal - => workers: [logical cores * 2] + => workers: 24 => secret key: generated => limits: forms = 32KiB + => keep-alive: 5s => tls: disabled -🛰 Mounting '/hello': - => GET /hello/world +🛰 Mounting '/': + => GET / (hello) 🚀 Rocket has launched from http://localhost:8000 ``` -If we visit `localhost:8000/hello/world`, we see `Hello, world!`, exactly as -we expected. +If we visit `localhost:8000/hello/world`, we see `Hello, world!`, exactly as we +expected. A version of this example's complete crate, ready to `cargo run`, can be found -on -[GitHub](@example/hello_world). -You can find dozens of other complete examples, spanning all of Rocket's -features, in the [GitHub examples +on [GitHub](@example/hello_world). You can find dozens of other complete +examples, spanning all of Rocket's features, in the [GitHub examples directory](@example/). diff --git a/site/guide/4-requests.md b/site/guide/4-requests.md @@ -1,6 +1,6 @@ # Requests -Together, a route's attribute and function signature specify what must be true +Together, a [`route`] attribute and function signature specify what must be true about a request in order for the route's handler to be called. You've already seen an example of this in action: @@ -15,8 +15,8 @@ course, you can do much more than specify the method and path of a request. Among other things, you can ask Rocket to automatically validate: * The type of a dynamic path segment. - * The type of _many_ dynamic path segments. - * The type of incoming data. + * The type of _several_ dynamic path segments. + * The type of incoming body data. * The types of query strings, forms, and form values. * The expected incoming or outgoing format of a request. * Any arbitrary, user-defined security or validation policies. @@ -26,6 +26,8 @@ validations. Rocket's code generation takes care of actually validating the properties. This section describes how to ask Rocket to validate against all of these properties and more. +[`route`]: @api/rocket_codegen/attr.route.html + ## Methods A Rocket route attribute can be any one of `get`, `put`, `post`, `delete`, @@ -38,7 +40,7 @@ to the root path: ``` The grammar for these attributes is defined formally in the -[`rocket_codegen`](@api/rocket_codegen/) API docs. +[`rocket_codegen`](@api/rocket_codegen/attr.route.html) API docs. ### HEAD Requests @@ -46,20 +48,20 @@ 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. +your application explicitly handles. ### Reinterpreting Because browsers can only send `GET` and `POST` requests, Rocket _reinterprets_ request methods under certain conditions. If a `POST` request contains a body of -`Content-Type: application/x-www-form-urlencoded`, and the form's **first** +`Content-Type: application/x-www-form-urlencoded` and the form's **first** field has the name `_method` and a valid HTTP method name as its value (such as `"PUT"`), 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](@example/todo/static/index.html.tera#L47) makes use of this feature to submit `PUT` and `DELETE` requests from a web form. -## Dynamic Segments +## 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 want to say _Hello!_ to anything, @@ -79,10 +81,10 @@ visit `/hello/John`, the application would respond with `Hello, John!`. Any number of dynamic path segments are allowed. A path segment can be of any type, including your own, as long as the type implements the [`FromParam`] -trait. Rocket implements `FromParam` for many of the standard library types, as -well as a few special Rocket types. For the full list of supplied -implementations, see the [`FromParam` API docs]. Here's a more complete route to -illustrate varied usage: +trait. We call these types _parameter guards_. Rocket implements `FromParam` for +many of the standard library types, as well as a few special Rocket types. For +the full list of provided implementations, see the [`FromParam` API docs]. +Here's a more complete route to illustrate varied usage: ```rust #[get("/hello/<name>/<age>/<cool>")] @@ -98,28 +100,70 @@ fn hello(name: String, age: u8, cool: bool) -> String { [`FromParam`]: @api/rocket/request/trait.FromParam.html [`FromParam` API docs]: @api/rocket/request/trait.FromParam.html -### Raw Strings +! note: Rocket types _raw_ strings separately from decoded strings. + + You may have noticed an unfamiliar [`RawStr`] type in the code example above. + This is a special type, provided by Rocket, that represents an unsanitized, + unvalidated, and undecoded raw string from an HTTP message. It exists to + separate validated string inputs, represented by types such as `String`, + `&str`, and `Cow<str>`, from unvalidated inputs, represented by `&RawStr`. It + also provides helpful methods to convert the unvalidated string into a + validated one. + + Because `&RawStr` implements [`FromParam`], it can be used as the type of a + dynamic segment, as in the example above, where the value refers to a + potentially undecoded string. By contrast, a `String` is guaranteed to be + decoded. Which you should use depends on whether you want direct but + potentially unsafe access to the string (`&RawStr`), or safe access to the + string at the cost of an allocation (`String`). -You may have noticed an unfamiliar [`RawStr`] type in the code example above. -This is a special type, provided by Rocket, that represents an unsanitized, -unvalidated, and undecoded raw string from an HTTP message. It exists to -separate validated string inputs, represented by types such as `String`, `&str`, -and `Cow<str>` types, from unvalidated inputs, represented by `&RawStr`. It -provides helpful methods to convert the unvalidated string into a validated one. + [`RawStr`]: @api/rocket/http/struct.RawStr.html + +### Multiple Segments + +You can also match against multiple segments by using `<param..>` in a route +path. The type of such parameters, known as _segments guards_, must implement +[`FromSegments`]. A segments guard must be the final component of a path: any +text after a segments guard will result in a compile-time error. -Because `&RawStr` implements [`FromParam`], it can be used as the type of a -dynamic segment, as in the example above. When used as the type of a dynamic -segment, a `RawStr` points to a potentially undecoded string. By contrast, a -`String` is guaranteed to be decoded. Which you should use depends on whether -you want direct but potentially unsafe access to the string (`&RawStr`), or safe -access to the string at the cost of an allocation (`String`). +As an example, the following route matches against all paths that begin with +`/page/`: -[`RawStr`]: @api/rocket/http/struct.RawStr.html +```rust +use std::path::PathBuf; + +#[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() +} +``` + +! tip: Rocket makes it even _easier_ to serve static files! + + If you need to serve static files from your Rocket application, consider using + the [`StaticFiles`] custom handler from [`rocket_contrib`], which makes it as + simple as: + + `rocket.mount("/public", StaticFiles::from("/static")` + +[`rocket_contrib`]: @api/rocket_contrib/ +[`StaticFiles`]: @api/rocket_contrib/serve/struct.StaticFiles.html +[`FromSegments`]: @api/rocket/request/trait.FromSegments.html ## Forwarding -Let's take a closer look at the route attribute and signature pair from the last -example: +Let's take a closer look at the route attribute and signature pair from a +previous example: ```rust #[get("/hello/<name>/<age>/<cool>")] @@ -133,9 +177,9 @@ there are no remaining routes to try. When there are no remaining routes, a customizable **404 error** is returned. Routes are attempted in increasing _rank_ order. Rocket chooses a default -ranking from -4 to -1, detailed in the next section, for all routes, but a -route's rank can also be manually set with the `rank` attribute. To illustrate, -consider the following routes: +ranking from -6 to -1, detailed in the next section, but a route's rank can also +be manually set with the `rank` attribute. To illustrate, consider the following +routes: ```rust #[get("/user/<id>")] @@ -168,6 +212,11 @@ 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`. +! tip: It's not just forwards that can be caught! + + In general, when any guard fails for any reason, including parameter guards, + you can use an `Option` or `Result` type in its place to catch the failure. + By the way, if you were to omit the `rank` parameter in the `user_str` or `user_int` routes, Rocket would emit an error and abort launch, indicating that the routes _collide_, or can match against similar incoming requests. The `rank` @@ -181,89 +230,79 @@ precedence) while routes with dynamic paths and without query strings have higher ranks (lower precedence). The table below describes the default ranking of a route given its properties. -| static path | query string | rank | example | -| ------------- | -------------- | ------ | ------------------- | -| yes | yes | -4 | `/hello?world=true` | -| yes | no | -3 | `/hello` | -| no | yes | -2 | `/<hi>?world=true` | -| no | no | -1 | `/<hi>` | - -## Multiple Segments - -You can also match against multiple segments by using `<param..>` in a route -path. The type of such parameters, known as _segments_ parameters, must -implement [`FromSegments`]. Segments parameters must be the final component of a -path: any text after a segments parameter will result in a compile-time error. - -As an example, the following route matches against all paths that begin with -`/page/`: +| static path | query | rank | example | +|-------------|---------------|------|---------------------| +| yes | partly static | -6 | `/hello?world=true` | +| yes | fully dynamic | -5 | `/hello/?<world>` | +| yes | none | -4 | `/hello` | +| no | partly static | -3 | `/<hi>?world=true` | +| no | fully dynamic | -2 | `/<hi>?<world>` | +| no | none | -1 | `/<hi>` | -```rust -#[get("/page/<path..>")] -fn get_page(path: PathBuf) -> T { ... } -``` +## Query Strings -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: +Query segments can be declared static or dynamic in much the same way as path +segments: ```rust -#[get("/<file..>")] -fn files(file: PathBuf) -> Option<NamedFile> { - NamedFile::open(Path::new("static/").join(file)).ok() +#[get("/hello?wave&<name>")] +fn hello(name: &RawStr) -> String { + format!("Hello, {}!", name.as_str()) } ``` -[`FromSegments`]: @api/rocket/request/trait.FromSegments.html +The `hello` route above matches any `GET` request to `/hello` that has at least +one query key of `name` and a query segment of `wave` in any order, ignoring any +extra query segments. The value of the `name` query parameter is used as the +value of the `name` function argument. For instance, a request to +`/hello?wave&name=John` would return `Hello, John!`. Other requests that would +result in the same response include: -## Format + * `/hello?name=John&wave` (reordered) + * `/hello?name=John&wave&id=123` (extra segments) + * `/hello?id=123&name=John&wave` (reordered, extra segments) + * `/hello?name=Bob&name=John&wave` (last value taken) -A route can specify the data format it is willing to accept or respond with -using the `format` route parameter. The value of the parameter is a string -identifying an HTTP media type. For instance, for JSON data, the string -`application/json` can be used. +Any number of dynamic query segments are allowed. A query segment can be of any +type, including your own, as long as the type implements the [`FromFormValue`] +trait. -When a route indicates a payload-supporting method (`PUT`, `POST`, `DELETE`, and -`PATCH`), the `format` route parameter instructs Rocket to check against the -`Content-Type` header of the incoming request. Only requests where the -`Content-Type` header matches the `format` parameter will match to the route. +[`FromFormValue`]: @api/rocket/request/trait.FromFormValue.html -As an example, consider the following route: +### Multiple Segments -```rust -#[post("/user", format = "application/json", data = "<user>")] -fn new_user(user: Json<User>) -> T { ... } -``` +As with paths, you can also match against multiple segments in a query by using +`<param..>`. The type of such parameters, known as _query guards_, must +implement the [`FromQuery`] trait. Query guards must be the final component of a +query: any text after a query parameter will result in a compile-time error. -The `format` parameter in the `post` attribute declares that only incoming -requests with `Content-Type: application/json` will match `new_user`. (The -`data` parameter is described in the next section.) Shorthand is also supported -for the most common `format` arguments. Instead of using the full Content-Type, -`format = "application/json"`, you can also write shorthands like `format = -"json"`. For a full list of available shorthands, see the -[`ContentType::parse_flexible()`] documentation. +A query guard validates all otherwise unmatched (by static or dynamic query +parameters) query segments. While you can implement [`FromQuery`] yourself, most +use cases will be handled by using the [`Form`] or [`LenientForm`] query guard. +The [Forms](#forms) section explains using these types in detail. In short, +these types allow you to use a structure with named fields to automatically +validate query/form parameters: -When a route indicates a non-payload-supporting method (`HEAD`, `OPTIONS`, and, -these purposes, `GET`) the `format` route parameter instructs Rocket to check -against the `Accept` header of the incoming request. Only requests where the -preferred media type in the `Accept` header matches the `format` parameter will -match to the route. +```rust +use rocket::request::Form; -As an example, consider the following route: +#[derive(FromForm)] +struct User { + name: String, + account: usize, +} -```rust -#[get("/user/<id>", format = "json")] -fn user(id: usize) -> Json<User> { ... } +#[get("/item?<id>&<user..>")] +fn item(id: usize, user: Form<User>) { /* ... */ } ``` -The `format` parameter in the `get` attribute declares that only incoming -requests with `application/json` as the preferred media type in the `Accept` -header will match `user`. If instead the route had been declared as `post`, -Rocket would match the `format` against the `Content-Type` header of the -incoming response. +For a request to `/item?id=100&name=sandal&account=400`, the `item` route above +sets `id` to `100` and `user` to `User { name: "sandal", account: 400 }`. -[`ContentType::parse_flexible()`]: @api/rocket/http/struct.ContentType.html#method.parse_flexible +For more query handling examples, see [the `query_params` +example](@example/query_params). + +[`FromQuery`]: @api/rocket/request/trait.FromQuery.html ## Request Guards @@ -280,9 +319,9 @@ invoke the [`FromRequest`] implementation for request guards before calling the handler. Rocket only dispatches requests to a handler when all of its guards pass. -As an example, the following dummy handler makes use of three request guards, +For instance, the following dummy handler makes use of three request guards, `A`, `B`, and `C`. An input can be identified as a request guard if it is not -named in the route attribute. This is why `param` is not a request guard. +named in the route attribute. ```rust #[get("/<param>")] @@ -316,6 +355,46 @@ authenticates an administrator using incoming cookies. Then, any handler with an if the appropriate conditions are met. Request guards centralize policies, resulting in a simpler, safer, and more secure applications. +### Guard Transparency + +When a request guard type can only be created through its [`FromRequest`] +implementation, and the type is not `Copy`, the existence of a request guard +value provides a _type-level proof_ that the current request has been validated +against an arbitrary policy. This provides powerful means of protecting your +application against access-control violations by requiring data accessing +methods to _witness_ a proof of authorization via a request guard. We call the +notion of using a request guard as a witness _guard transparency_. + +As a concrete example, the following application has a function, +`health_records`, that returns all of the health records in a database. Because +health records are sensitive information, they should only be accessible by +super users. The `SuperUser` request guard authenticates and authorizes a super +user, and its `FromRequest` implementation is the only means by which a +`SuperUser` can be constructed. By declaring the `health_records` function as +follows, access control violations against health records are guaranteed to be +prevented at _compile-time_: + +```rust +fn health_records(user: &SuperUser) -> Records { ... } +``` + +The reasoning is as follows: + + 1. The `health_records` function requires an `&SuperUser` type. + 2. The only constructor for a `SuperUser` type is `FromRequest`. + 3. Only Rocket can provide an active `&Request` to construct via `FromRequest`. + 4. Thus, there must be a `Request` authorizing a `SuperUser` to call + `health_records`. + +! note + + At the expense of a lifetime parameter in the guard type, guarantees can be + made even stronger by tying the lifetime of the `Request` passed to + `FromRequest` to the request guard, ensuring that the guard value always + corresponds to an _active_ request. + +We recommend leveraging request guard transparency for _all_ data accesses. + ### Forwarding Guards Request guards and forwarding are a powerful combination for enforcing policies. @@ -482,13 +561,60 @@ guards: fn good(custom: Custom, cookies: Cookies) { .. } ``` +## Format + +A route can specify the data format it is willing to accept or respond with by +using the `format` route parameter. The value of the parameter is a string +identifying an HTTP media type or a shorthand variant. For instance, for JSON +data, the string `application/json` of simply `json` can be used. + +When a route indicates a payload-supporting method (`PUT`, `POST`, `DELETE`, and +`PATCH`), the `format` route parameter instructs Rocket to check against the +`Content-Type` header of the incoming request. Only requests where the +`Content-Type` header matches the `format` parameter will match to the route. + +As an example, consider the following route: + +```rust +#[post("/user", format = "application/json", data = "<user>")] +fn new_user(user: Json<User>) -> T { ... } +``` + +The `format` parameter in the `post` attribute declares that only incoming +requests with `Content-Type: application/json` will match `new_user`. (The +`data` parameter is described in the next section.) Shorthand is also supported +for the most common `format` arguments. Instead of using the full Content-Type, +`format = "application/json"`, you can also write shorthands like `format = +"json"`. For a full list of available shorthands, see the +[`ContentType::parse_flexible()`] documentation. + +When a route indicates a non-payload-supporting method (`HEAD`, `OPTIONS`, and, +these purposes, `GET`) the `format` route parameter instructs Rocket to check +against the `Accept` header of the incoming request. Only requests where the +preferred media type in the `Accept` header matches the `format` parameter will +match to the route. + +As an example, consider the following route: + +```rust +#[get("/user/<id>", format = "json")] +fn user(id: usize) -> Json<User> { ... } +``` + +The `format` parameter in the `get` attribute declares that only incoming +requests with `application/json` as the preferred media type in the `Accept` +header will match `user`. If instead the route had been declared as `post`, +Rocket would match the `format` against the `Content-Type` header of the +incoming response. + +[`ContentType::parse_flexible()`]: @api/rocket/http/struct.ContentType.html#method.parse_flexible + ## Body Data -At some point, your web application will need to process body data. Data -processing, like much of Rocket, is type directed. To indicate that a handler -expects data, annotate it with `data = "<param>"`, where `param` is an argument -in the handler. The argument's type must implement the [`FromData`] trait. It -looks like this, where `T: FromData`: +Body data processing, like much of Rocket, is type directed. To indicate that a +handler expects body data, annotate it with `data = "<param>"`, where `param` is +an argument in the handler. The argument's type must implement the [`FromData`] +trait. It looks like this, where `T` is assumed to implement `FromData`: ```rust #[post("/", data = "<input>")] @@ -501,11 +627,11 @@ Any type that implements [`FromData`] is also known as _data guard_. ### 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: +Forms are one of the most common types 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)] @@ -518,21 +644,23 @@ struct Task { fn new(task: Form<Task>) -> String { ... } ``` -The `Form` type implements the `FromData` trait as long as its generic parameter -implements the [`FromForm`] 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`]. 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` or `422 - Unprocessable Entity` error is returned. As before, a -forward or failure can be caught by using the `Option` and `Result` types: +The [`Form`] type implements the `FromData` trait as long as its generic +parameter implements the [`FromForm`] 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`]. 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` or `422 - Unprocessable Entity` error is +returned. As before, a forward or failure can be caught by using the `Option` +and `Result` types: ```rust #[post("/todo", data = "<task>")] fn new(task: Option<Form<Task>>) -> String { ... } ``` +[`Form`]: @api/rocket/request/struct.Form.html [`FromForm`]: @api/rocket/request/trait.FromForm.html [`FromFormValue`]: @api/rocket/request/trait.FromFormValue.html @@ -629,14 +757,30 @@ struct Person { } ``` -The [forms validation](@example/form_validation) -and [forms kitchen sink](@example/form_kitchen_sink) -examples on GitHub provide further illustrations. +The `FromFormValue` trait can also be derived for enums with nullary fields: + +```rust +#[derive(FromFormValue)] +enum MyValue { + First, + Second, + Third, +} +``` + +The derive generates an implementation of the `FromFormValue` trait for the +decorated enum. The implementation returns successfully when the form value +matches, case insensitively, the stringified version of a variant's name, +returning an instance of said variant. + +The [form validation](@example/form_validation) and [form kitchen +sink](@example/form_kitchen_sink) examples provide further illustrations. ### JSON Handling JSON data is no harder: simply use the -[`Json`](@api/rocket_contrib/json/struct.Json.html) type: +[`Json`](@api/rocket_contrib/json/struct.Json.html) type from +[`rocket_contrib`]: ```rust #[derive(Deserialize)] @@ -676,74 +820,27 @@ 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](@example/raw_upload) for the full crate. -## Query Strings - -Query strings are handled just like forms. A query string can be parsed into any -structure that implements the `FromForm` trait. They are matched against by -appending a `?` to the path followed by a static query string or a dynamic -parameter `<param>`. +! warning: You should _always_ set limits when reading incoming data. -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: + To prevent DoS attacks, you should limit the amount of data you're willing to + accept. The [`take()`] reader adapter makes doing this easy: + `data.open().take(LIMIT)`. -```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. Parse failures can be -captured on a per-field or per-form basis. - -To catch failures on a per-field basis, use a type of `Option` or `Result` for -the given field: - -```rust -#[derive(FromForm)] -struct Task<'r> { - description: Result<String, &'r RawStr>, - complete: Option<bool> -} -``` - -To catch failures on a per-form basis, change the type of the query string -target to either `Option` or `Result`: - -```rust -#[get("/todo?<task>")] -fn new(task: Option<Task>) { ... } -``` - -For a concrete illustration on how to handle query parameters, see [the -`query_params` -example](@example/query_params). + [`take()`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.take ## Error Catchers Routing may fail for a variety of reasons. These include: - * A [request guard](#request-guards) returns `Failure`. + * A guard fails. * A handler returns a [`Responder`](../responses/#responder) that fails. - * No matching route was found. + * No routes matched. If any of these conditions occur, Rocket returns an error to the client. To do so, Rocket invokes the _catcher_ corresponding to the error's status code. A catcher is like a route, except it only handles errors. Rocket provides default catchers for all of the standard HTTP error codes. To override a default -catcher, or declare a catcher for a custom status code, use the `catch` +catcher, or declare a catcher for a custom status code, use the [`catch`] attribute, which takes a single integer corresponding to the HTTP status code to catch. For instance, to declare a catcher for `404 Not Found` errors, you'd write: @@ -765,8 +862,8 @@ fn not_found(req: &Request) -> String { Also as with routes, Rocket needs to know about a catcher before it is used to handle errors. The process, known as "registering" a catcher, is similar to -mounting a route: call the `register` method with a list of catchers via the -`catchers!` macro. The invocation to add the **404** catcher declared above +mounting a route: call the [`register()`] method with a list of catchers via the +[`catchers!`] macro. The invocation to add the **404** catcher declared above looks like: ```rust @@ -777,4 +874,7 @@ Unlike route request handlers, catchers take exactly zero or one parameter. If the catcher takes a parameter, it must be of type [`&Request`] The [error catcher example](@example/errors) on GitHub illustrates their use in full. -[`&Request]: @api/rocket/struct.Request.html +[`catch`]: @api/rocket_codegen/attr.catch.html +[`register()`]: @api/rocket/struct.Rocket.html#method.register +[`catchers!`]: @api/rocket_codegen/macro.catchers.html +[`&Request`]: @api/rocket/struct.Request.html diff --git a/site/guide/5-responses.md b/site/guide/5-responses.md @@ -58,6 +58,8 @@ fn json() -> content::Json<&'static str> { } ``` +! warning: This is _not_ the same as the [`Json`] in [`rocket_contrib`]! + [`Accepted`]: @api/rocket/response/status/struct.Accepted.html [`content::Json`]: @api/rocket/response/content/struct.Json.html @@ -82,12 +84,54 @@ type. For instance, to forward to the catcher for **406 - Not Acceptable**, you would write: ```rust +use rocket::response::Failure; + #[get("/")] fn just_fail() -> Failure { Failure(Status::NotAcceptable) } ``` +## 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: + +```rust +#[derive(Responder)] +#[response(status = 500, content_type = "json")] +struct MyResponder { + inner: OtherResponder, + header: SomeHeader, + more: YetAnotherHeader, + #[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` and `self.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 `#[responder]` 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. + +[`Responder` derive]: @api/rocket_codegen/derive.Responder.html + ## Implementations Rocket implements `Responder` for many types in Rust's standard library @@ -170,7 +214,8 @@ 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: +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 @@ -179,6 +224,9 @@ many of these responders in the [`response`] module. Among these are: * [`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. + * [`Json`] - Automatically serializes values into JSON. + * [`MsgPack`] - Automatically serializes values into MessagePack. + * [`Template`] - Renders a dynamic template using handlebars or Tera. [`status`]: @api/rocket/response/status/ [`response`]: @api/rocket/response/ @@ -187,6 +235,7 @@ many of these responders in the [`response`] module. Among these are: [`Redirect`]: @api/rocket/response/struct.Redirect.html [`Stream`]: @api/rocket/response/struct.Stream.html [`Flash`]: @api/rocket/response/struct.Flash.html +[`MsgPack`]: @api/rocket_contrib/msgpack/struct.MsgPack.html ### Streaming @@ -287,3 +336,74 @@ fully composed application that makes use of Handlebars templates. [`Template`]: @api/rocket_contrib/templates/struct.Template.html [configurable]: ../configuration/#extras + +## Typed URIs + +Rocket's [`uri!`] macro allows you to build URIs routes in your application in a +robust, type-safe, and URI-safe manner. Type or route parameter mismatches are +caught at compile-time. + +The `uri!` returns an [`Origin`] structure with the URI of the supplied route +interpolated with the given values. 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: + +```rust +#[get("/person/<name>/<age>")] +fn person(name: String, age: u8) -> String { + format!("Hello {}! You're {} years old.", name, age) +} +``` + +URIs to `person` can be created as follows: + +```rust +// with unnamed parameters, in route path declaration order +let mike = uri!(person: "Mike Smith", 28); +assert_eq!(mike.path(), "/person/Mike%20Smith/28"); + +// with named parameters, order irrelevant +let mike = uri!(person: name = "Mike", age = 28); +let mike = uri!(person: age = 28, name = "Mike"); +assert_eq!(mike.path(), "/person/Mike/28"); + +// with a specific mount-point +let mike = uri!("/api", person: name = "Mike", age = 28); +assert_eq!(mike.path(), "/api/person/Mike/28"); +``` + +Rocket informs you of any mismatched parameters at compile-time: + +```rust +error: person route uri expects 2 parameter but 1 was supplied + --> src/main.rs:21:19 + | +21 | uri!(person: "Mike Smith"); + | ^^^^^^^^^^^^ + | + = note: expected parameter: age: u8 +``` + +Rocket also informs you of any type errors at compile-time: + +```rust +error: the trait bound u8: rocket::http::uri::FromUriParam<&str> is not satisfied + --> src/main:25:23 + | +25 | uri!(person: age = "ten", name = "Mike"); + | ^^^^^ FromUriParam<&str> is not implemented for u8 + | + = note: required by rocket::http::uri::FromUriParam::from_uri_param +``` + +We recommend that you use `uri!` exclusively when constructing URIs to your +routes. + +See the [`uri!`] documentation for more usage details. + +[`Origin`]: @api/rocket/http/uri/struct.Origin.html +[`Uri`]: @api/rocket/http/uri/enum.Uri.html +[`Redirect::to()`]: @api/rocket/response/struct.Redirect.html#method.to +[`uri!`]: @api/rocket_codegen/macro.uri.html diff --git a/site/guide/6-state.md b/site/guide/6-state.md @@ -19,6 +19,14 @@ The process for using managed state is simple: 2. Add a `State<T>` type to any request handler, where `T` is the type of the value passed into `manage`. +! note: All managed state must be thread-safe. + + Because Rocket automatically multithreads your application, handlers can to + concurrently access managed state. As a result, managed state must be + thread-safe. Thanks to Rust, this condition is checked at compile-time by + ensuring that the type of values you store in managed state implement `Send` + + `Sync`. + ### Adding State To instruct Rocket to manage state for your application, call the @@ -28,6 +36,8 @@ structure with an internal `AtomicUsize` with an initial value of `0`, we can write the following: ```rust +use std::sync::atomic::AtomicUsize; + struct HitCount { count: AtomicUsize } @@ -55,6 +65,8 @@ the managed state. For example, we can retrieve and respond with the current `HitCount` in a `count` route as follows: ```rust +use rocket::State; + #[get("/count")] fn count(hit_count: State<HitCount>) -> String { let current_count = hit_count.count.load(Ordering::Relaxed); @@ -69,9 +81,11 @@ You can retrieve more than one `State` type in a single route as well: fn state(hit_count: State<HitCount>, config: State<Config>) -> T { ... } ``` -If you request a `State<T>` for a `T` that is not `managed`, Rocket won't call -the offending route. Instead, Rocket will log an error message and return a -**500** error to the client. +! warning + + If you request a `State<T>` for a `T` that is not `managed`, Rocket won't call + the offending route. Instead, Rocket will log an error message and return a + **500** error to the client. You can find a complete example using the `HitCount` structure in the [state example on GitHub](@example/state) and learn more about the [`manage` @@ -94,7 +108,7 @@ fn from_request(req: &'a Request<'r>) -> request::Outcome<T, ()> { [`Request::guard()`]: @api/rocket/struct.Request.html#method.guard -### Request-Local State +## Request-Local State While managed state is *global* and available application-wide, request-local state is *local* to a given request, carried along with the request, and dropped @@ -132,8 +146,8 @@ impl<'a, 'r> FromRequest<'a, 'r> for RequestId { Note that, without request-local state, it would not be possible to: - 1. Associate a piece of data, here an ID, directly with a request. - 2. Ensure that a value is generated at most once per request. + 1. Associate a piece of data, here an ID, directly with a request. + 2. Ensure that a value is generated at most once per request. For more examples, see the [`FromRequest` request-local state] documentation, which uses request-local state to cache expensive authentication and diff --git a/site/guide/7-fairings.md b/site/guide/7-fairings.md @@ -9,9 +9,9 @@ about incoming requests and outgoing responses. Any type that implements the [`Fairing`] trait is a _fairing_. Fairings hook into Rocket's request lifecycle, receiving callbacks for events such as incoming requests and outgoing responses. Rocket passes information about these events to -the fairing, and the fairing can do what it wants with the information. This -includes rewriting data when applicable, recording information about the event -or data, or doing nothing at all. +the fairing; the fairing can do what it wants with the information. This +includes rewriting requests or responses, recording information about the event, +or doing nothing at all. Rocket’s fairings are a lot like middleware from other frameworks, but they bear a few key distinctions: @@ -26,12 +26,15 @@ reaching for fairings instinctively. Before doing so, remember that Rocket provides a rich set of mechanisms such as [request guards] and [data guards] that can be used to solve problems in a clean, composable, and robust manner. -As a general rule of thumb, only _globally applicable_ actions should be -effected through fairings. You should _not_ use a fairing to implement -authentication or authorization (preferring to use a [request guard] instead) -_unless_ the authentication or authorization applies to all or most of the -application. On the other hand, you _should_ use a fairing to record timing and -usage statistics or to enforce global security policies. +! warning + + As a general rule of thumb, only _globally applicable_ actions should be + effected through fairings. You should **_not_** use a fairing to implement + authentication or authorization (preferring to use a [request guard] instead) + _unless_ the authentication or authorization applies to all or the + overwhelming majority application. On the other hand, you _should_ use a + fairing to record timing and usage statistics or to enforce global security + policies. [`Fairing`]: @api/rocket/fairing/trait.Fairing.html [request guard]: ../requests/#request-guards diff --git a/site/guide/8-testing.md b/site/guide/8-testing.md @@ -194,14 +194,20 @@ ROCKET_CODEGEN_DEBUG=1 cargo build During compilation, you should see output like: ```rust -Emitting item: -fn rocket_route_fn_hello<'_b>( - __req: &'_b ::rocket::Request, - __data: ::rocket::Data -) -> ::rocket::handler::Outcome<'_b> { - let responder = hello(); - ::rocket::handler::Outcome::from(__req, responder) -} +note: emitting Rocket code generation debug output + --> examples/hello_world/src/main.rs:7:1 + | +7 | #[get("/")] + | ^^^^^^^^^^^ + | + = note: + fn rocket_route_fn_hello<'_b>( + __req: &'_b ::rocket::Request, + __data: ::rocket::Data + ) -> ::rocket::handler::Outcome<'_b> { + let responder = hello(); + ::rocket::handler::Outcome::from(__req, responder) + } ``` This corresponds to the facade request handler Rocket has generated for the diff --git a/site/guide/9-configuration.md b/site/guide/9-configuration.md @@ -35,12 +35,13 @@ $ sudo ROCKET_ENV=staging cargo run => address: 0.0.0.0 => port: 8000 => log: normal - => workers: [logical cores * 2] + => workers: 24 => secret key: generated => limits: forms = 32KiB + => keep-alive: 5s => tls: disabled 🛰 Mounting '/': - => GET / + => GET / (hello) 🚀 Rocket has launched from http://0.0.0.0:8000 ``` @@ -64,6 +65,7 @@ value: address = "localhost" port = 8000 workers = [number of cpus * 2] +keep_alive = 5 log = "normal" secret_key = [randomly generated at launch] limits = { forms = 32768 } @@ -72,6 +74,7 @@ limits = { forms = 32768 } address = "0.0.0.0" port = 8000 workers = [number of cpus * 2] +keep_alive = 5 log = "normal" secret_key = [randomly generated at launch] limits = { forms = 32768 } @@ -80,6 +83,7 @@ limits = { forms = 32768 } address = "0.0.0.0" port = 8000 workers = [number of cpus * 2] +keep_alive = 5 log = "critical" secret_key = [randomly generated at launch] limits = { forms = 32768 } @@ -262,6 +266,9 @@ results in `Rocket.toml` and environment variables being ignored. ## Configuring TLS +! warning: Rocket's built-in TLS is **not** considered ready for production use. + It is intended for development use _only_. + Rocket includes built-in, native support for TLS >= 1.2 (Transport Layer Security). In order for TLS support to be enabled, Rocket must be compiled with the `"tls"` feature. To do this, add the `"tls"` feature to the `rocket` diff --git a/site/guide/index.md b/site/guide/index.md @@ -2,11 +2,11 @@ Welcome to Rocket! -This is the official guide. It is designed to serve as a starting point to -writing web applications with Rocket and Rust. The guide is also designed to be -a reference for experienced Rocket developers. This guide is conversational in -tone. For concise and purely technical documentation, see the [API -documentation](@api). +This is the official guide for Rocket v0.4. It is designed to serve as a +starting point to writing web applications with Rocket and Rust. The guide is +also designed to be a reference for experienced Rocket developers. This guide is +conversational in tone. For purely technical documentation with examples, see +the [API documentation](@api). The guide is split into several sections, each with a focus on a different aspect of Rocket. The sections are: @@ -43,4 +43,3 @@ web IRC client](https://kiwiirc.com/client/irc.mozilla.org/#rocket). You can learn more about IRC via Mozilla's [Getting Started with IRC](https://developer.mozilla.org/en-US/docs/Mozilla/QA/Getting_Started_with_IRC) guide. - diff --git a/site/index.toml b/site/index.toml @@ -80,7 +80,7 @@ code = ''' #[post("/", data = "<task>")] fn new(task: Form<Task>) -> Flash<Redirect> { - if task.get().description.is_empty() { + if task.description.is_empty() { Flash::error(Redirect::to("/"), "Cannot be empty.") } else { Flash::success(Redirect::to("/"), "Task added.") @@ -106,9 +106,9 @@ code = ''' } #[put("/<id>", data = "<message>")] - fn update(id: ID, message: Json<Message>) -> JsonValue { - if DB.contains_key(&id) { - DB.insert(id, &message.contents); + fn update(db: &Db, id: Id, message: Json<Message>) -> JsonValue { + if db.contains_key(&id) { + db.insert(id, &message.contents); json!({ "status": "ok" }) } else { json!({ "status": "error" }) @@ -132,7 +132,7 @@ text = ''' [[bottom_features]] title = 'Templating' -text = "Rocket makes rendering templates a breeze with built-in templating support." +text = "Rocket makes templating a breeze with built-in templating support." image = 'templating-icon' url = 'guide/responses/#templates' button = 'Learn More' @@ -166,27 +166,35 @@ color = 'yellow' margin = -3 [[bottom_features]] -title = 'Query Strings' -text = "Handling query strings and parameters is type-safe and easy in Rocket." -image = 'query-icon' -url = 'guide/requests/#query-strings' -button = 'Learn More' -color = 'orange' -margin = -3 - -[[bottom_features]] title = 'Testing Library' text = "Unit test your applications with ease using the built-in testing library." image = 'testing-icon' url = 'guide/testing#testing' button = 'Learn More' +color = 'orange' + +[[bottom_features]] +title = 'Typed URIs' +text = "Rocket typechecks route URIs for you so you never mistype a URI again." +image = 'ship-icon' +url = 'guide/responses/#typed-uris' +button = 'Learn More' color = 'green' +margin = -20 + +# [[bottom_features]] +# title = 'Query Strings' +# text = "Handling query strings and parameters is type-safe and easy in Rocket." +# image = 'query-icon' +# url = 'guide/requests/#query-strings' +# button = 'Learn More' +# color = 'red' +# margin = -3 -# Blocked on Hyper/OpenSSL. # [[bottom_features]] -# title = 'Signed Sessions' -# text = "Safe, secure, signed sessions are built-in to Rocket so your users can stay safe." +# title = 'Private Cookies' +# text = "Safe, secure, private cookies are built-in so your users can stay safe." # image = 'sessions-icon' -# url = '/overview' +# url = 'guide/requests/#private-cookies' # button = 'Learn More' -# color = 'green' +# color = 'purple' diff --git a/site/overview.toml b/site/overview.toml @@ -63,7 +63,7 @@ the parameter in the request handler: ```rust #[post("/login", data = "<user_form>")] fn login(user_form: Form<UserLogin>) -> String { - // Use `user_form`, return a String. + format!("Hello, {}!", user_form.name) } ``` @@ -143,7 +143,7 @@ look like: ```rust rocket::ignite() .mount("/base", routes![index, another]) - .launch() + .launch(); ``` The `mount` call takes a base path and a set of routes via the `routes!` macro. @@ -182,11 +182,11 @@ fn new_user(admin: AdminUser, new_user: Form<User>) -> T { For the `new_user` handler above to be called, the following conditions must hold: -* The request method must be `POST`. -* The request path must be `/user`. -* The request must contain `data` in its body. -* The request metadata must authenticate an `AdminUser`. -* The request body must be a form that parses into a `User` struct. + * The request method must be `POST`. + * The request path must be `/user`. + * The request must contain `data` in its body. + * The request metadata must authenticate an `AdminUser`. + * The request body must be a form that parses into a `User` struct. ''' [[steps]] @@ -217,13 +217,13 @@ If the function above is used as a handler, for instance, then the type `T` must implement `Responder`. Rocket provides many useful responder types out of the box. They include: - * `Json<T>`: Serializes the structure T into JSON and returns it to - the client. - * `Template`: Renders a template file and returns it to the client. - * `Redirect`: Returns a properly formatted HTTP redirect. - * `NamedFile`: Streams a given file to the client with the - Content-Type taken from the file’s extension. - * `Stream`: Streams data to the client from an arbitrary `Read` value. - * Many Primitive Types: `String`, `&str`, `File`, `Option`, `Result`, and - others all implement the `Responder` trait. + * `Json<T>`: Serializes the structure T into JSON and returns it to + the client. + * `Template`: Renders a template file and returns it to the client. + * `Redirect`: Returns a properly formatted HTTP redirect. + * `NamedFile`: Streams a given file to the client with the + Content-Type taken from the file’s extension. + * `Stream`: Streams data to the client from an arbitrary `Read` value. + * Many Primitive Types: `String`, `&str`, `File`, `Option`, `Result`, and + others all implement the `Responder` trait. '''