Rocket

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

commit 26ee13278264a181319005c9db8d9e79a84ccfe9
parent 1c0f2d41a705ea38fe7f7994b129461d4a4a6195
Author: Sergio Benitez <sb@sergio.bz>
Date:   Mon, 15 Oct 2018 22:50:35 -0700

Update site content for restructure.

Diffstat:
Msite/README.md | 35+++++++++++++++++++++++++++--------
Dsite/guide.md | 46----------------------------------------------
Rsite/guide/introduction.md -> site/guide/0-introduction.md | 0
Asite/guide/1-quickstart.md | 24++++++++++++++++++++++++
Asite/guide/10-pastebin.md | 406+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asite/guide/11-conclusion.md | 26++++++++++++++++++++++++++
Rsite/guide/getting-started.md -> site/guide/2-getting-started.md | 0
Asite/guide/3-overview.md | 185+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asite/guide/4-requests.md | 780+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asite/guide/5-responses.md | 289++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asite/guide/6-state.md | 256+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asite/guide/7-fairings.md | 213+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asite/guide/8-testing.md | 208+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asite/guide/9-configuration.md | 295+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsite/guide/conclusion.md | 28----------------------------
Dsite/guide/configuration.md | 295-------------------------------------------------------------------------------
Dsite/guide/fairings.md | 215-------------------------------------------------------------------------------
Asite/guide/index.md | 46++++++++++++++++++++++++++++++++++++++++++++++
Dsite/guide/overview.md | 185-------------------------------------------------------------------------------
Dsite/guide/pastebin.md | 407-------------------------------------------------------------------------------
Dsite/guide/quickstart.md | 24------------------------
Dsite/guide/requests.md | 782-------------------------------------------------------------------------------
Dsite/guide/responses.md | 291------------------------------------------------------------------------------
Dsite/guide/state.md | 258-------------------------------------------------------------------------------
Dsite/guide/testing.md | 209-------------------------------------------------------------------------------
Msite/index.toml | 20++++++++++----------
Msite/news/2017-02-06-version-0.2.md | 24+++++++++++-------------
Msite/news/2017-07-14-version-0.3.md | 70+++++++++++++++++++++++++++++++++++-----------------------------------
Rsite/news.toml -> site/news/index.toml | 0
Msite/overview.toml | 24++++++++++++------------
30 files changed, 2823 insertions(+), 2818 deletions(-)

diff --git a/site/README.md b/site/README.md @@ -7,20 +7,39 @@ website](https://rocket.rs). This directory contains the following: - * `index.toml` - Source data for the index (`/`). - * `news.toml` - Source data for the news page (`/news`). - * `overview.toml` - Source data for the overview page (`/overview`). - * `guide.md` - Index page for the [Rocket Programming Guide] (`/guide`). - * `news/*.md` - News articles linked to from `news.toml`. + * `index.toml` - Source data for the index. + * `overview.toml` - Source data for the overview page (`overview/`). + * `news/index.toml` - Source data for the news page (`news/`). + * `news/*.md` - News articles linked to from `news/index.toml`. * `guide/*.md` - Guide pages linked to from `guide.md`. [Rocket Programming Guide]: https://rocket.rs/guide/ ### Guide Links -Cross-linking to pages in the guide is accomplished via absolute links rooted at -`/guide/`. To link to the page whose source is at `guide/page.md`, for instance, -link to `/guide/page`. +Cross-linking guide pages is accomplished via relative links. Outside of the +index, this is: `../{page}#anchor`. For instance, to link to the **Quickstart > +Running Examples** page, use `../quickstart#running-examples`. + +### Aliases + +Aliases are shorthand URLs that start with `@` (e.g, `@api`). They are used +throughout the guide to simplify versioning URLs to Rocket's source code and the +Rocket API. They are replaced at build time with a URL prefix. At present, the +following aliases are available, where `${version}` is Rocket's version string +at the time of compilation: + + * `@example`: https://github.com/SergioBenitez/Rocket/tree/${version}/examples + * `@github`: https://github.com/SergioBenitez/Rocket/tree/${version} + * `@api`: https://api.rocket.rs/${version} + +For example, to link to `Rocket::launch()`, you might write: + +```md +Launch an instance of your application using the [`launch()`] method. + +[`launch()`]: @api/rocket/struct.Rocket.html#method.launch +``` ## License diff --git a/site/guide.md b/site/guide.md @@ -1,46 +0,0 @@ -# The Rocket Programming Guide - -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](https://api.rocket.rs). - -The guide is split into several sections, each with a focus on a different -aspect of Rocket. The sections are: - - - **[Introduction](introduction/):** introduces Rocket and its philosophy. - - **[Quickstart](quickstart/):** presents the minimal steps necessary to - run your first Rocket application. - - **[Getting Started](getting-started/):** a gentle introduction to getting - your first Rocket application running. - - **[Overview](overview/):** describes the core concepts of Rocket. - - **[Requests](requests/):** discusses handling requests: control-flow, - parsing, and validating. - - **[Responses](responses/):** discusses generating responses. - - **[State](state/):** how to manage state in a Rocket application. - - **[Fairings](fairings/):** provides an overview of Rocket's structured - middleware. - - **[Testing](testing/):** how to unit and integration test a Rocket - application. - - **[Configuration](configuration/):** how to configure a Rocket application. - - **[Pastebin](pastebin/):** a tutorial on how to create a pastebin with - Rocket. - - **[Conclusion](conclusion/):** concludes the guide and discusses next steps - for learning. - -## Getting Help - -The official community support channels are the `#rocket` IRC channel on the -[Mozilla IRC Server](https://wiki.mozilla.org/IRC) at `irc.mozilla.org` and the -bridged [Rocket room on -Matrix](https://riot.im/app/#/room/#mozilla_#rocket:matrix.org). If you're not -familiar with IRC, we recommend chatting through [Matrix via -Riot](https://riot.im/app/#/room/#mozilla_#rocket:matrix.org) or via the [Kiwi -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/guide/introduction.md b/site/guide/0-introduction.md diff --git a/site/guide/1-quickstart.md b/site/guide/1-quickstart.md @@ -0,0 +1,24 @@ +# Quickstart + +Before you can start writing a Rocket application, you'll need a **nightly** +version of Rust installed. We recommend you use [rustup](https://rustup.rs/) to +install or configure such a version. If you don't have Rust installed and would +like extra guidance doing so, see the [getting started](../getting-started) +section. + +## Running Examples + +The absolute fastest way to start experimenting with Rocket is to clone the +Rocket repository and run the included examples in the `examples/` directory. +For instance, the following set of commands runs the `hello_world` example: + +```sh +git clone https://github.com/SergioBenitez/Rocket +cd Rocket +git checkout v0.4.0-dev +cd examples/hello_world +cargo run +``` + +There are numerous examples in the `examples/` directory. They can all be run +with `cargo run`. diff --git a/site/guide/10-pastebin.md b/site/guide/10-pastebin.md @@ -0,0 +1,406 @@ +# Pastebin + +To give you a taste of what a real Rocket application looks like, this section +of the guide is a tutorial on how to create a Pastebin application in Rocket. A +pastebin is a simple web application that allows users to upload a text document +and later retrieve it via a special URL. They're often used to share code +snippets, configuration files, and error logs. In this tutorial, we'll build a +simple pastebin service that allows users to upload a file from their terminal. +The service will respond back with a URL to the uploaded file. + +## Finished Product + +A souped-up, completed version of the application you're about to build is +deployed live at [paste.rs](https://paste.rs). Feel free to play with the +application to get a feel for how it works. For example, to upload a text +document named `test.txt`, you can do: + +```sh +curl --data-binary @test.txt https://paste.rs/ +# => https://paste.rs/IYu +``` + +The finished product is composed of the following routes: + + * index: **GET /** - returns a simple HTML page with instructions about how + to use the service + * upload: **POST /** - accepts raw data in the body of the request and + responds with a URL of a page containing the body's content + * retrieve: **GET /&lt;id>** - retrieves the content for the paste with id + `<id>` + +## Getting Started + +Let's get started! First, create a fresh Cargo binary project named +`rocket-pastebin`: + +```rust +cargo new --bin rocket-pastebin +cd rocket-pastebin +``` + +Then add the usual Rocket dependencies to the `Cargo.toml` file: + +```toml +[dependencies] +rocket = "0.4.0-dev" +rocket_codegen = "0.4.0-dev" +``` + +And finally, create a skeleton Rocket application to work off of in +`src/main.rs`: + +```rust +#![feature(proc_macro_hygiene, decl_macro)] + +#[macro_use] extern crate rocket; + +fn main() { + rocket::ignite().launch(); +} +``` + +Ensure everything works by running the application: + +```sh +cargo run +``` + +At this point, we haven't declared any routes or handlers, so visiting any page +will result in Rocket returning a **404** error. Throughout the rest of the +tutorial, we'll create the three routes and accompanying handlers. + +## Index + +The first route we'll create is the `index` route. This is the page users will +see when they first visit the service. As such, the route should field requests +of the form `GET /`. We declare the route and its handler by adding the `index` +function below to `src/main.rs`: + +```rust +#[get("/")] +fn index() -> &'static str { + " + USAGE + + POST / + + accepts raw data in the body of the request and responds with a URL of + a page containing the body's content + + GET /<id> + + retrieves the content for the paste with id `<id>` + " +} +``` + +This declares the `index` route for requests to `GET /` as returning a static +string with the specified contents. Rocket will take the string and return it as +the body of a fully formed HTTP response with `Content-Type: text/plain`. You +can read more about how Rocket formulates responses at the [API documentation +for the Responder + trait](@api/rocket/response/trait.Responder.html). + +Remember that routes first need to be mounted before Rocket dispatches requests +to them. To mount the `index` route, modify the main function so that it reads: + +```rust +fn main() { + rocket::ignite().mount("/", routes![index]).launch(); +} +``` + +You should now be able to `cargo run` the application and visit the root path +(`/`) to see the text being displayed. + +## Uploading + +The most complicated aspect of the pastebin, as you might imagine, is handling +upload requests. When a user attempts to upload a pastebin, our service needs to +generate a unique ID for the upload, read the data, write it out to a file or +database, and then return a URL with the ID. We'll take each of these one step +at a time, beginning with generating IDs. + +### Unique IDs + +Generating a unique and useful ID is an interesting topic, but it is outside the +scope of this tutorial. Instead, we simply provide the code for a `PasteID` +structure that represents a _probably_ unique ID. Read through the code, then +copy/paste it into a new file named `paste_id.rs` in the `src/` directory: + +```rust +use std::fmt; +use std::borrow::Cow; + +use rand::{self, Rng}; + +/// Table to retrieve base62 values from. +const BASE62: &'static [u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +/// A _probably_ unique paste ID. +pub struct PasteID<'a>(Cow<'a, str>); + +impl<'a> PasteID<'a> { + /// Generate a _probably_ unique ID with `size` characters. For readability, + /// the characters used are from the sets [0-9], [A-Z], [a-z]. The + /// probability of a collision depends on the value of `size` and the number + /// of IDs generated thus far. + pub fn new(size: usize) -> PasteID<'static> { + let mut id = String::with_capacity(size); + let mut rng = rand::thread_rng(); + for _ in 0..size { + id.push(BASE62[rng.gen::<usize>() % 62] as char); + } + + PasteID(Cow::Owned(id)) + } +} + +impl<'a> fmt::Display for PasteID<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} +``` + +Then, in `src/main.rs`, add the following after `extern crate rocket`: + +```rust +extern crate rand; + +mod paste_id; + +use paste_id::PasteID; +``` + +Finally, add a dependency for the `rand` crate to the `Cargo.toml` file: + +```toml +[dependencies] +# existing Rocket dependencies... +rand = "0.3" +``` + +Then, ensure that your application builds with the new code: + +```sh +cargo build +``` + +You'll likely see many "unused" warnings for the new code we've added: that's +okay and expected. We'll be using the new code soon. + +### Processing + +Believe it or not, the hard part is done! (_whew!_). + +To process the upload, we'll need a place to store the uploaded files. To +simplify things, we'll store the uploads in a directory named `upload/`. Create +an `upload` directory next to the `src` directory: + +```sh +mkdir upload +``` + +For the `upload` route, we'll need to `use` a few items: + +```rust +use std::io; +use std::path::Path; + +use rocket::Data; +use rocket::http::RawStr; +``` + +The [Data](@api/rocket/data/struct.Data.html) structure is key +here: it represents an unopened stream to the incoming request body data. We'll +use it to efficiently stream the incoming request to a file. + +### Upload Route + +We're finally ready to write the `upload` route. Before we show you the code, +you should attempt to write the route yourself. Here's a hint: a possible route +and handler signature look like this: + +```rust +#[post("/", data = "<paste>")] +fn upload(paste: Data) -> io::Result<String> +``` + +Your code should: + + 1. Create a new `PasteID` of a length of your choosing. + 2. Construct a filename inside `upload/` given the `PasteID`. + 3. Stream the `Data` to the file with the constructed filename. + 4. Construct a URL given the `PasteID`. + 5. Return the URL to the client. + +Here's our version (in `src/main.rs`): + +```rust +#[post("/", data = "<paste>")] +fn upload(paste: Data) -> io::Result<String> { + let id = PasteID::new(3); + let filename = format!("upload/{id}", id = id); + let url = format!("{host}/{id}\n", host = "http://localhost:8000", id = id); + + // Write the paste out to the file and return the URL. + paste.stream_to_file(Path::new(&filename))?; + Ok(url) +} +``` + +Make sure that the route is mounted at the root path: + +```rust +fn main() { + rocket::ignite().mount("/", routes![index, upload]).launch(); +} +``` + +Test that your route works via `cargo run`. From a separate terminal, upload a +file using `curl`. Then verify that the file was saved to the `upload` directory +with the correct ID: + +```sh +# in the project root +cargo run + +# in a seperate terminal +echo "Hello, world." | curl --data-binary @- http://localhost:8000 +# => http://localhost:8000/eGs + +# back to the terminal running the pastebin +<ctrl-c> # kill running process +ls upload # ensure the upload is there +cat upload/* # ensure that contents are correct +``` + +Note that since we haven't created a `GET /<id>` route, visiting the returned URL +will result in a **404**. We'll fix that now. + +## Retrieving Pastes + +The final step is to create the `retrieve` route which, given an `<id>`, will +return the corresponding paste if it exists. + +Here's a first take at implementing the `retrieve` route. The route below takes +in an `<id>` as a dynamic path element. The handler uses the `id` to construct a +path to the paste inside `upload/`, and then attempts to open the file at that +path, optionally returning the `File` if it exists. Rocket treats a `None` +[Responder](@api/rocket/response/trait.Responder.html#provided-implementations) +as a **404** error, which is exactly what we want to return when the requested +paste doesn't exist. + +```rust +use std::fs::File; +use rocket::http::RawStr; + +#[get("/<id>")] +fn retrieve(id: &RawStr) -> Option<File> { + let filename = format!("upload/{id}", id = id); + File::open(&filename).ok() +} +``` + +Unfortunately, there's a problem with this code. Can you spot the issue? The +[`RawStr`](@api/rocket/http/struct.RawStr.html) type should tip you off! + +The issue is that the _user_ controls the value of `id`, and as a result, can +coerce the service into opening files inside `upload/` that aren't meant to be +opened. For instance, imagine that you later decide that a special file +`upload/_credentials.txt` will store some important, private information. If the +user issues a `GET` request to `/_credentials.txt`, the server will read and +return the `upload/_credentials.txt` file, leaking the sensitive information. +This is a big problem; it's known as the [full path disclosure +attack](https://www.owasp.org/index.php/Full_Path_Disclosure), and Rocket +provides the tools to prevent this and other kinds of attacks from happening. + +To prevent the attack, we need to _validate_ `id` before we use it. Since the +`id` is a dynamic parameter, we can use Rocket's +[FromParam](@api/rocket/request/trait.FromParam.html) trait to +implement the validation and ensure that the `id` is a valid `PasteID` before +using it. We do this by implementing `FromParam` for `PasteID` in +`src/paste_id.rs`, as below: + +```rust +use rocket::request::FromParam; + +/// Returns `true` if `id` is a valid paste ID and `false` otherwise. +fn valid_id(id: &str) -> bool { + id.chars().all(|c| { + (c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9') + }) +} + +/// Returns an instance of `PasteID` if the path segment is a valid ID. +/// Otherwise returns the invalid ID as the `Err` value. +impl<'a> FromParam<'a> for PasteID<'a> { + type Error = &'a RawStr; + + fn from_param(param: &'a RawStr) -> Result<PasteID<'a>, &'a RawStr> { + match valid_id(param) { + true => Ok(PasteID(Cow::Borrowed(param))), + false => Err(param) + } + } +} +``` + +Then, we simply need to change the type of `id` in the handler to `PasteID`. +Rocket will then ensure that `<id>` represents a valid `PasteID` before calling +the `retrieve` route, preventing attacks on the `retrieve` route: + +```rust +#[get("/<id>")] +fn retrieve(id: PasteID) -> Option<File> { + let filename = format!("upload/{id}", id = id); + File::open(&filename).ok() +} +``` + +Note that our `valid_id` function is simplistic and could be improved by, for +example, checking that the length of the `id` is within some known bound or +potentially blacklisting sensitive files as needed. + +The wonderful thing about using `FromParam` and other Rocket traits is that they +centralize policies. For instance, here, we've centralized the policy for valid +`PasteID`s in dynamic parameters. At any point in the future, if other routes +are added that require a `PasteID`, no further work has to be done: simply use +the type in the signature and Rocket takes care of the rest. + +## Conclusion + +That's it! Ensure that all of your routes are mounted and test your application. +You've now written a simple (~75 line!) pastebin in Rocket! There are many +potential improvements to this small application, and we encourage you to work +through some of them to get a better feel for Rocket. Here are some ideas: + + * Add a web form to the `index` where users can manually input new pastes. + Accept the form at `POST /`. Use `format` and/or `rank` to specify which of + the two `POST /` routes should be called. + * Support **deletion** of pastes by adding a new `DELETE /<id>` route. Use + `PasteID` to validate `<id>`. + * **Limit the upload** to a maximum size. If the upload exceeds that size, + return a **206** partial status code. Otherwise, return a **201** created + status code. + * Set the `Content-Type` of the return value in `upload` and `retrieve` to + `text/plain`. + * **Return a unique "key"** after each upload and require that the key is + present and matches when doing deletion. Use one of Rocket's core traits to + do the key validation. + * Add a `PUT /<id>` route that allows a user with the key for `<id>` to + replace the existing paste, if any. + * Add a new route, `GET /<id>/<lang>` that syntax highlights the paste with ID + `<id>` for language `<lang>`. If `<lang>` is not a known language, do no + highlighting. Possibly validate `<lang>` with `FromParam`. + * Use the [`local` module](@api/rocket/local/) to write unit tests for your + pastebin. + * Dispatch a thread before `launch`ing Rocket in `main` that periodically + cleans up idling old pastes in `upload/`. + +You can find the full source code for the [completed pastebin tutorial on +GitHub](@example/pastebin). diff --git a/site/guide/11-conclusion.md b/site/guide/11-conclusion.md @@ -0,0 +1,26 @@ +# Conclusion + +We hope you agree that Rocket is a refreshing take on web frameworks. As with +any software project, Rocket is _alive_. There are always things to improve, and +we're happy to take the best ideas. If you have something in mind, please +[submit an issue](https://github.com/SergioBenitez/Rocket/issues). + +## Getting Help + +If you find yourself having trouble developing Rocket applications, you can get +help via the `#rocket` IRC channel on the [Mozilla IRC +Server](https://wiki.mozilla.org/IRC) at `irc.mozilla.org` and the bridged +[Rocket room on Matrix](https://riot.im/app/#/room/#mozilla_#rocket:matrix.org). +If you're not familiar with IRC, we recommend chatting through [Matrix via +Riot](https://riot.im/app/#/room/#mozilla_#rocket:matrix.org) or via the [Kiwi +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. + +## What's next? + +The best way to learn Rocket is to _build something_. It should be fun and easy, +and there's always someone to help. Alternatively, you can read through the +[Rocket examples](@example) or the [Rocket source code](@github/lib/src). +Whatever you decide to do next, we hope you have a blast! diff --git a/site/guide/getting-started.md b/site/guide/2-getting-started.md diff --git a/site/guide/3-overview.md b/site/guide/3-overview.md @@ -0,0 +1,185 @@ +# 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. + +## Lifecycle + +Rocket's main task is to listen for incoming web requests, dispatch the request +to the application code, and return a response to the client. We call the +process that goes from request to response the "lifecycle". We summarize the +lifecycle as the following sequence of steps: + + 1. **Routing** + + Rocket parses an incoming HTTP request into native structures that your + code operates on indirectly. Rocket determines which request handler to + invoke by matching against route attributes declared in your application. + + 2. **Validation** + + Rocket validates the incoming request against types and guards present in + the matched route. If validation fails, Rocket _forwards_ the request to + the next matching route or calls an _error handler_. + + 3. **Processing** + + The request handler associated with the route is invoked with validated + arguments. This is the main business logic of an application. Processing + completes by returning a `Response`. + + 4. **Response** + + The returned `Response` is processed. Rocket generates the appropriate HTTP + response and sends it to the client. This completes the lifecycle. Rocket + continues listening for requests, restarting the lifecycle for each + incoming request. + +The remainder of this section details the _routing_ phase as well as additional +components needed for Rocket to begin dispatching requests to request handlers. +The sections following describe the request and response phases as well as other +components of Rocket. + +## Routing + +Rocket applications are centered around routes and handlers. A _route_ is a +combination of: + + * A set of parameters to match an incoming request against. + * A handler to process the request and return a response. + +A _handler_ is simply a function that takes an arbitrary number of arguments and +returns any arbitrary type. + +The parameters to match against include static paths, dynamic paths, path +segments, forms, query strings, request format specifiers, and body data. Rocket +uses attributes, which look like function decorators in other languages, to make +declaring routes easy. Routes are declared by annotating a function, the +handler, with the set of parameters to match against. A complete route +declaration looks like this: + +```rust +#[get("/world")] // <- route attribute +fn world() -> &'static str { // <- request handler + "Hello, world!" +} +``` + +This declares the `world` route to match against the static path `"/world"` on +incoming `GET` requests. The `world` route is simple, but additional route +parameters are necessary when building more interesting applications. The +[Requests](../requests) section describes the available options for +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: + +```rust +rocket::ignite().mount("/hello", routes![world]); +``` + +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. + +### Namespacing + +When a route is declared inside a module other than the root, you may find +yourself with unexpected errors when mounting: + +```rust +mod other { + #[get("/world")] + pub fn world() -> &'static str { + "Hello, world!" + } +} + +use other::world; + +fn main() { + // error[E0425]: cannot find value `static_rocket_route_info_for_world` in this scope + rocket::ignite().mount("/hello", routes![world]); +} +``` + +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: + +```rust +rocket::ignite().mount("/hello", routes![other::world]); +``` + +## Launching + +Now that Rocket knows about the route, you can tell Rocket to start accepting +requests via the `launch` method. The method starts up the server and waits for +incoming requests. When a request arrives, Rocket finds the matching route and +dispatches the request to the route's handler. + +We typically call `launch` from the `main` function. Our complete _Hello, +world!_ application thus looks like: + +```rust +#![feature(proc_macro_hygiene, decl_macro)] + +#[macro_use] extern crate rocket; + +#[get("/world")] +fn world() -> &'static str { + "Hello, world!" +} + +fn main() { + rocket::ignite().mount("/hello", routes![world]).launch(); +} +``` + +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. + +Running the application, the console shows: + +```sh +πŸ”§ Configured for development. + => address: localhost + => port: 8000 + => log: normal + => workers: [logical cores * 2] + => secret key: generated + => limits: forms = 32KiB + => tls: disabled +πŸ›° Mounting '/hello': + => GET /hello/world +πŸš€ Rocket has launched from http://localhost:8000 +``` + +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 +directory](@example/). diff --git a/site/guide/4-requests.md b/site/guide/4-requests.md @@ -0,0 +1,780 @@ +# Requests + +Together, a route's 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: + +```rust +#[get("/world")] +fn handler() { .. } +``` + +This route indicates that it only matches against `GET` requests to the `/world` +route. Rocket ensures that this is the case before `handler` is called. Of +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 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. + +The route attribute and function signature work in tandem to describe these +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. + +## 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 attributes is defined formally in the +[`rocket_codegen`](@api/rocket_codegen/) API docs. + +### HEAD Requests + +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. + +### 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** +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 + +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, +not just the world, we can declare a route like so: + +```rust +#[get("/hello/<name>")] +fn hello(name: &RawStr) -> String { + format!("Hello, {}!", name.as_str()) +} +``` + +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!`. + +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: + +```rust +#[get("/hello/<name>/<age>/<cool>")] +fn hello(name: String, 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) + } +} +``` + +[`FromParam`]: @api/rocket/request/trait.FromParam.html +[`FromParam` API docs]: @api/rocket/request/trait.FromParam.html + +### Raw 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>` types, from unvalidated inputs, represented by `&RawStr`. It +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. 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`). + +[`RawStr`]: @api/rocket/http/struct.RawStr.html + +## Forwarding + +Let's take a closer look at the route attribute and signature pair from the last +example: + +```rust +#[get("/hello/<name>/<age>/<cool>")] +fn hello(name: String, age: u8, cool: bool) -> String { ... } +``` + +What if `cool` isn't a `bool`? Or, what if `age` isn't a `u8`? When a parameter +type mismatch occurs, Rocket _forwards_ the request 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 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: + +```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: &RawStr) -> 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, &RawStr>`, 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 an error and abort launch, indicating that +the routes _collide_, or can match against similar incoming requests. The `rank` +parameter resolves this collision. + +### Default Ranking + +If a rank is not explicitly specified, Rocket assigns a default ranking. By +default, routes with static paths and query strings have lower ranks (higher +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/`: + +```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() +} +``` + +[`FromSegments`]: @api/rocket/request/trait.FromSegments.html + +## Format + +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. + +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 + +## Request Guards + +Request guards are one of Rocket's most powerful instruments. As the name might +imply, a request guard protects a handler from being called erroneously based on +information contained in an incoming request. More specifically, a request guard +is a type that represents an arbitrary validation policy. The validation policy +is implemented through the [`FromRequest`] trait. Every type that implements +`FromRequest` is a request guard. + +Request guards appear as inputs to handlers. An arbitrary number of request +guards can appear as arguments in a route handler. Rocket will automatically +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, +`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. + +```rust +#[get("/<param>")] +fn index(param: isize, a: A, b: B, c: C) -> ... { ... } +``` + +Request guards always fire in left-to-right declaration order. In the example +above, the order will be `A` followed by `B` followed by `C`. Failure is +short-circuiting; if one guard fails, the remaining are not attempted. To learn +more about request guards and implementing them, see the [`FromRequest`] +documentation. + +[`FromRequest`]: @api/rocket/request/trait.FromRequest.html +[`Cookies`]: @api/rocket/http/enum.Cookies.html + +### Custom Guards + +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 +then 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 +authenticates an administrator using incoming cookies. 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. + +### Forwarding Guards + +Request guards and forwarding are a powerful combination for enforcing policies. +To illustrate, we consider how a simple authorization system might be +implemented using these mechanisms. + +We start with two request guards: + + * `User`: A regular, authenticated user. + + The `FromRequest` implementation for `User` checks that a cookie identifies + a user and returns a `User` value if so. If no user can be authenticated, + the guard forwards. + + * `AdminUser`: A user authenticated as an administrator. + + The `FromRequest` implementation for `AdminUser` checks that a cookie + identifies an _administrative_ user and returns an `AdminUser` value if so. + If no user can be authenticated, the guard forwards. + +We now use these two guards in combination with forwarding to implement the +following three routes, each leading to an administrative control panel at +`/admin`: + +```rust +#[get("/admin")] +fn admin_panel(admin: AdminUser) -> &'static str { + "Hello, administrator. This is the admin panel!" +} + +#[get("/admin", rank = 2)] +fn admin_panel_user(user: User) -> &'static str { + "Sorry, you must be an administrator to access this page." +} + +#[get("/admin", rank = 3)] +fn admin_panel_redirect() -> Redirect { + Redirect::to("/login") +} +``` + +The three routes above encode authentication _and_ authorization. The +`admin_panel` route only succeeds if an administrator is logged in. Only then is +the admin panel displayed. If the user is not an admin, the `AdminUser` route +will forward. Since the `admin_panel_user` route is ranked next highest, it is +attempted next. This route succeeds if there is _any_ user signed in, and an +authorization failure message is displayed. Finally, if a user isn't signed in, +the `admin_panel_redirect` route is attempted. Since this route has no guards, +it always succeeds. The user is redirected to a log in page. + +## Cookies + +[`Cookies`] is an important, built-in request guard: it allows you to get, set, +and remove cookies. Because `Cookies` is a request guard, an argument of its +type can simply be added to a handler: + +```rust +use rocket::http::Cookies; + +#[get("/")] +fn index(cookies: Cookies) -> Option<String> { + cookies.get("message") + .map(|value| format!("Message: {}", value)) +} +``` + +This results in the incoming request's cookies being accessible from the +handler. The example above retrieves a cookie named `message`. Cookies can also +be set and removed using the `Cookies` guard. The [cookies example] on GitHub +illustrates further use of the `Cookies` type to get and set cookies, while the +[`Cookies`] documentation contains complete usage information. + +[cookies example]: @example/cookies + +### Private Cookies + +Cookies added via the [`Cookies::add()`] method are set _in the clear._ In other +words, the value set is visible by the client. For sensitive data, Rocket +provides _private_ cookies. + +Private cookies are just like regular cookies except that they are encrypted +using authenticated encryption, a form of encryption which simultaneously +provides confidentiality, integrity, and authenticity. This means that private +cookies cannot be inspected, tampered with, or manufactured by clients. If you +prefer, you can think of private cookies as being signed and encrypted. + +The API for retrieving, adding, and removing private cookies is identical except +methods are suffixed with `_private`. These methods are: [`get_private`], +[`add_private`], and [`remove_private`]. An example of their usage is below: + +```rust +/// Retrieve the user's ID, if any. +#[get("/user_id")] +fn user_id(cookies: Cookies) -> Option<String> { + cookies.get_private("user_id") + .map(|cookie| format!("User ID: {}", cookie.value())) +} + +/// Remove the `user_id` cookie. +#[post("/logout")] +fn logout(mut cookies: Cookies) -> Flash<Redirect> { + cookies.remove_private(Cookie::named("user_id")); + Flash::success(Redirect::to("/"), "Successfully logged out.") +} +``` + +[`Cookies::add()`]: @api/rocket/http/enum.Cookies.html#method.add + +### Secret Key + +To encrypt private cookies, Rocket uses the 256-bit key specified in the +`secret_key` configuration parameter. If one is not specified, Rocket will +automatically generate a fresh key. Note, however, that a private cookie can +only be decrypted with the same key with which it was encrypted. As such, it is +important to set a `secret_key` configuration parameter when using private +cookies so that cookies decrypt properly after an application restart. Rocket +emits a warning if an application is run in production without a configured +`secret_key`. + +Generating a string suitable for use as a `secret_key` configuration value is +usually done through tools like `openssl`. Using `openssl`, a 256-bit base64 key +can be generated with the command `openssl rand -base64 32`. + +For more information on configuration, see the [Configuration](../configuration) +section of the guide. + +[`get_private`]: @api/rocket/http/enum.Cookies.html#method.get_private +[`add_private`]: @api/rocket/http/enum.Cookies.html#method.add_private +[`remove_private`]: @api/rocket/http/enum.Cookies.html#method.remove_private + +### One-At-A-Time + +For safety reasons, Rocket currently requires that at most one `Cookies` +instance be active at a time. It's uncommon to run into this restriction, but it +can be confusing to handle if it does crop up. + +If this does happen, Rocket will emit messages to the console that look as +follows: + +``` +=> Error: Multiple `Cookies` instances are active at once. +=> An instance of `Cookies` must be dropped before another can be retrieved. +=> Warning: The retrieved `Cookies` instance will be empty. +``` + +The messages will be emitted when a violating handler is called. The issue can +be resolved by ensuring that two instances of `Cookies` cannot be active at once +due to the offending handler. A common error is to have a handler that uses a +`Cookies` request guard as well as a `Custom` request guard that retrieves +`Cookies`, as so: + +```rust +#[get("/")] +fn bad(cookies: Cookies, custom: Custom) { .. } +``` + +Because the `cookies` guard will fire before the `custom` guard, the `custom` +guard will retrieve an instance of `Cookies` when one already exists for +`cookies`. This scenario can be fixed by simply swapping the order of the +guards: + +```rust +#[get("/")] +fn good(custom: Custom, cookies: Cookies) { .. } +``` + +## 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`: + +```rust +#[post("/", data = "<input>")] +fn new(input: T) -> String { ... } +``` + +Any type that implements [`FromData`] is also known as _data guard_. + +[`FromData`]: @api/rocket/data/trait.FromData.html + +### 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`] 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 { ... } +``` + +[`FromForm`]: @api/rocket/request/trait.FromForm.html +[`FromFormValue`]: @api/rocket/request/trait.FromFormValue.html + +#### Lenient Parsing + +Rocket's `FromForm` parsing is _strict_ by default. In other words, A `Form<T>` +will parse successfully from an incoming form only if the form contains the +exact set of fields in `T`. Said another way, a `Form<T>` will error on missing +and/or extra fields. For instance, if an incoming form contains the fields "a", +"b", and "c" while `T` only contains "a" and "c", the form _will not_ parse as +`Form<T>`. + +Rocket allows you to opt-out of this behavior via the [`LenientForm`] data type. +A `LenientForm<T>` will parse successfully from an incoming form as long as the +form contains a superset of the fields in `T`. Said another way, a +`LenientForm<T>` automatically discards extra fields without error. For +instance, if an incoming form contains the fields "a", "b", and "c" while `T` +only contains "a" and "c", the form _will_ parse as `LenientForm<T>`. + +You can use a `LenientForm` anywhere you'd use a `Form`. Its generic parameter +is also required to implement `FromForm`. For instance, we can simply replace +`Form` with `LenientForm` above to get lenient parsing: + +```rust +#[derive(FromForm)] +struct Task { .. } + +#[post("/todo", data = "<task>")] +fn new(task: LenientForm<Task>) { .. } +``` + +[`LenientForm`]: @api/rocket/request/struct.LenientForm.html + +#### Field Renaming + +By default, Rocket matches the name of an incoming form field to the name of a +structure field. While this behavior is typical, it may also be desired to use +different names for form fields and struct fields while still parsing as +expected. You can ask Rocket to look for a different form field for a given +structure field by using the `#[form(field = "name")]` field annotation. + +As an example, say that you're writing an application that receives data from an +external service. The external service `POST`s a form with a field named `type`. +Since `type` is a reserved keyword in Rust, it cannot be used as the name of a +field. To get around this, you can use field renaming as follows: + +```rust +#[derive(FromForm)] +struct External { + #[form(field = "type")] + api_type: String +} +``` + +Rocket will then match the form field named `type` to the structure field named +`api_type` automatically. + +#### Field Validation + +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: + +```rust +struct AdultAge(usize); + +impl<'v> FromFormValue<'v> for AdultAge { + type Error = &'v RawStr; + + fn from_form_value(form_value: &'v RawStr) -> Result<AdultAge, &'v RawStr> { + match form_value.parse::<usize>() { + Ok(age) if age >= 21 => Ok(AdultAge(age)), + _ => Err(form_value), + } + } +} + +#[derive(FromForm)] +struct Person { + age: AdultAge +} +``` + +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: + +```rust +#[derive(FromForm)] +struct Person { + age: Option<AdultAge> +} +``` + +The [forms validation](@example/form_validation) +and [forms kitchen sink](@example/form_kitchen_sink) +examples on GitHub provide further illustrations. + +### JSON + +Handling JSON data is no harder: simply use the +[`Json`](@api/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 in `Json` implements the +`Deserialize` trait from [Serde](https://github.com/serde-rs/json). See the +[JSON example] on GitHub for a complete example. + +[JSON example]: @example/json + +### Streaming + +Sometimes you just want to handle 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`](@api/rocket/data/struct.Data.html) +type: + +```rust +#[post("/upload", format = "plain", data = "<data>")] +fn upload(data: Data) -> io::Result<String> { + data.stream_to_file("/tmp/upload.txt").map(|n| 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`, 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](@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>`. + +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. 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). + +## Error Catchers + +Routing may fail for a variety of reasons. These include: + + * A [request guard](#request-guards) returns `Failure`. + * A handler returns a [`Responder`](../responses/#responder) that fails. + * No matching route was found. + +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` +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: + +```rust +#[catch(404)] +fn not_found(req: &Request) -> T { .. } +``` + +As with routes, the return type (here `T`) must implement `Responder`. A +concrete implementation may look like: + +```rust +#[catch(404)] +fn not_found(req: &Request) -> String { + format!("Sorry, '{}' is not a valid path.", req.uri()) +} +``` + +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 +looks like: + +```rust +rocket::ignite().register(catchers![not_found]) +``` + +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 diff --git a/site/guide/5-responses.md b/site/guide/5-responses.md @@ -0,0 +1,289 @@ +# Responses + +You may have noticed that the return type of a handler appears to be arbitrary, +and that's because it is! A value of any type that implements the [`Responder`] +trait can be returned, including your own. In this section, we describe the +`Responder` trait as well as several useful `Responder`s provided by Rocket. +We'll also briefly discuss how to implement your own `Responder`. + +[`Responder`]: @api/rocket/response/trait.Responder.html + +## Responder + +Types that implement [`Responder`] know how to generate a [`Response`] from +their values. A `Response` includes an HTTP status, headers, and body. The body +may either be _fixed-sized_ or _streaming_. The given `Responder` implementation +decides which to use. For instance, `String` uses a fixed-sized body, while +`File` uses a streamed response. Responders may dynamically adjust their +responses according to the incoming `Request` they are responding to. + +[`Response`]: @api/rocket/response/struct.Response.html + +### Wrapping + +Before we describe a few responders, we note that it is typical for responders +to _wrap_ other responders. That is, responders can be of the following form, +where `R` is some type that implements `Responder`: + +```rust +struct WrappingResponder<R>(R); +``` + +A wrapping responder modifies the response returned by `R` before responding +with that same response. For instance, Rocket provides `Responder`s in the +[`status` module](@api/rocket/response/status/) 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 +use rocket::response::status; + +#[post("/<id>")] +fn new(id: usize) -> status::Accepted<String> { + status::Accepted(Some(format!("id: '{}'", id))) +} +``` + +Similarly, the types in the [`content` module](@api/rocket/response/content/) +can be used to override the Content-Type of a response. For instance, to set the +Content-Type of `&'static str` to JSON, you can use the [`content::Json`] type +as follows: + +```rust +use rocket::response::content; + +#[get("/")] +fn json() -> content::Json<&'static str> { + content::Json("{ 'hi': 'world' }") +} +``` + +[`Accepted`]: @api/rocket/response/status/struct.Accepted.html +[`content::Json`]: @api/rocket/response/content/struct.Json.html + +### 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 +the request to the [error catcher](../requests/#error-catchers) for the +given status code. + +If an error catcher has been registered for the given status code, Rocket will +invoke it. The catcher creates and returns a response to the client. If no error +catcher has been registered and the error status code is one of the standard +HTTP status code, a default error catcher will be used. Default error catchers +return an HTML page with the status code and description. If there is no catcher +for a custom status code, Rocket uses the **500** error catcher to return a +response. + +While not encouraged, you can also forward a request to a catcher manually by +using the [`Failure`](@api/rocket/response/struct.Failure.html) +type. For instance, to forward to the catcher for **406 - Not Acceptable**, you +would write: + +```rust +#[get("/")] +fn just_fail() -> Failure { + Failure(Status::NotAcceptable) +} +``` + +## Implementations + +Rocket implements `Responder` for many types in Rust's standard library +including `String`, `&str`, `File`, `Option`, and `Result`. The [`Responder`] +documentation describes these in detail, but we briefly cover a few here. + +### Strings + +The `Responder` implementations for `&str` and `String` are straight-forward: +the string is used as a sized body, and the Content-Type of the response is set +to `text/plain`. To get a taste for what such a `Responder` implementation looks +like, here's the implementation for `String`: + +```rust +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() + } +} +``` + +Because of these implementations, you can directly return an `&str` or `String` +type from a handler: + +```rust +#[get("/string")] +fn handler() -> &'static str { + "Hello there! I'm a string!" +} +``` + +### `Option` + +`Option` is a _wrapping_ responder: an `Option<T>` can only be returned when `T` +implements `Responder`. If the `Option` is `Some`, the wrapped responder is used +to respond to the client. Otherwise, a error of **404 - Not Found** is returned +to the client. + +This implementation makes `Option` a convenient type to return when it is not +known until process-time whether content exists. For example, because of +`Option`, we can implement a file server that returns a `200` when a file is +found and a `404` when a file is not found in just 4, idiomatic lines: + +```rust +#[get("/<file..>")] +fn files(file: PathBuf) -> Option<NamedFile> { + NamedFile::open(Path::new("static/").join(file)).ok() +} +``` + +### `Result` + +`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. Revisiting our +file server, for instance, we might wish to provide more feedback to the user +when a file isn't found. We might do this as follows: + +```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`]: @api/rocket/response/status/ +[`response`]: @api/rocket/response/ +[`NamedFile`]: @api/rocket/response/struct.NamedFile.html +[`Content`]: @api/rocket/response/struct.Content.html +[`Redirect`]: @api/rocket/response/struct.Redirect.html +[`Stream`]: @api/rocket/response/struct.Stream.html +[`Flash`]: @api/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`] 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")] +fn stream() -> io::Result<Stream<UnixStream>> { + UnixStream::connect("/path/to/my/socket").map(|s| Stream::from(s)) +} + +``` + +[`rocket_contrib`]: @api/rocket_contrib/ + +### JSON + +The [`JSON`] responder in [`rocket_contrib`] allows you to easily respond with +well-formed JSON data: simply return a value of type `Json<T>` where `T` is the +type of a structure to serialize into JSON. The type `T` must implement the +[`Serialize`] trait from [`serde`], which can be automatically derived. + +As an example, to respond with the JSON value of a `Task` structure, we might +write: + +```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`]: @api/rocket_contrib/struct.Json.html +[`Serialize`]: https://docs.serde.rs/serde/trait.Serialize.html +[`serde`]: https://docs.serde.rs/serde/ +[JSON example on GitHub]: @example/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. + +When your application is compiled in `debug` mode (without the `--release` flag +passed to `cargo`), templates are automatically reloaded when they are modified. +This means that you don't need to rebuild your application to observe template +changes: simply refresh! In release builds, reloading is disabled. + +For templates to be properly registered, the template fairing must be attached +to the instance of Rocket. The [Fairings](../fairings) sections of the guide +provides more information on fairings. To attach the template fairing, simply +call `.attach(Template::fairing())` on an instance of `Rocket` as follows: + +```rust +fn main() { + rocket::ignite() + .mount("/", routes![...]) + .attach(Template::fairing()); +} +``` + +The [`Template`] API documentation contains more information about templates, +including how to customize a template engine to add custom helpers and filters. +The [Handlebars Templates example on GitHub](@example/handlebars_templates) is a +fully composed application that makes use of Handlebars templates. + +[`Template`]: @api/rocket_contrib/struct.Template.html +[configurable]: ../configuration/#extras diff --git a/site/guide/6-state.md b/site/guide/6-state.md @@ -0,0 +1,256 @@ +# State + +Many web applications have a need to maintain state. This can be as simple as +maintaining a counter for the number of visits or as complex as needing to +access job queues and multiple databases. Rocket provides the tools to enable +these kinds of interactions in a safe and simple manner. + +## Managed State + +The enabling feature for maintaining state is _managed state_. Managed state, as +the name implies, is state that Rocket manages for your application. The state +is managed on a per-type basis: Rocket will manage at most one value of a given +type. + +The process for using managed state is simple: + + 1. Call `manage` on the `Rocket` instance corresponding to your application + with the initial value of the state. + 2. Add a `State<T>` type to any request handler, where `T` is the type of the + value passed into `manage`. + +### Adding State + +To instruct Rocket to manage state for your application, call the +[`manage`](@api/rocket/struct.Rocket.html#method.manage) method +on an instance of `Rocket`. For example, to ask Rocket to manage a `HitCount` +structure with an internal `AtomicUsize` with an initial value of `0`, we can +write the following: + +```rust +struct HitCount { + count: AtomicUsize +} + +rocket::ignite().manage(HitCount { count: AtomicUsize::new(0) }); +``` + +The `manage` method can be called any number of times as long as each call +refers to a value of a different type. For instance, to have Rocket manage both +a `HitCount` value and a `Config` value, we can write: + +```rust +rocket::ignite() + .manage(HitCount { count: AtomicUsize::new(0) }) + .manage(Config::from(user_input)); +``` + +### Retrieving State + +State that is being managed by Rocket can be retrieved via the +[`State`](@api/rocket/struct.State.html) type: a [request +guard](../requests/#request-guards) for managed state. To use the request +guard, add a `State<T>` type to any request handler, where `T` is the type of +the managed state. For example, we can retrieve and respond with the current +`HitCount` in a `count` route as follows: + +```rust +#[get("/count")] +fn count(hit_count: State<HitCount>) -> String { + let current_count = hit_count.count.load(Ordering::Relaxed); + format!("Number of visits: {}", current_count) +} +``` + +You can retrieve more than one `State` type in a single route as well: + +```rust +#[get("/state")] +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. + +You can find a complete example using the `HitCount` structure in the [state +example on GitHub](@example/state) and learn more about the [`manage` +method](@api/rocket/struct.Rocket.html#method.manage) and [`State` +type](@api/rocket/struct.State.html) in the API docs. + +### Within Guards + +It can also be useful to retrieve managed state from a `FromRequest` +implementation. To do so, simply invoke `State<T>` as a guard using the +[`Request::guard()`] method. + +```rust +fn from_request(req: &'a Request<'r>) -> request::Outcome<T, ()> { + let hit_count_state = req.guard::<State<HitCount>>()?; + let current_count = hit_count_state.count.load(Ordering::Relaxed); + ... +} +``` + +[`Request::guard()`]: @api/rocket/struct.Request.html#method.guard + +### 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 +once the request is completed. Request-local state can be used whenever a +`Request` is available, such as in a fairing, a request guard, or a responder. + +Request-local state is *cached*: if data of a given type has already been +stored, it will be reused. This is especially useful for request guards that +might be invoked multiple times during routing and processing of a single +request, such as those that deal with authentication. + +As an example, consider the following request guard implementation for +`RequestId` that uses request-local state to generate and expose a unique +integer ID per request: + +```rust +/// A global atomic counter for generating IDs. +static request_id_counter: AtomicUsize = AtomicUsize::new(0); + +/// A type that represents a request's ID. +struct RequestId(pub usize); + +/// Returns the current request's ID, assigning one only as necessary. +impl<'a, 'r> FromRequest<'a, 'r> for RequestId { + fn from_request(request: &'a Request<'r>) -> request::Outcome { + // The closure passed to `local_cache` will be executed at most once per + // request: the first time the `RequestId` guard is used. If it is + // requested again, `local_cache` will return the same value. + Outcome::Success(request.local_cache(|| { + RequestId(request_id_counter.fetch_add(1, Ordering::Relaxed)) + })) + } +} +``` + +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. + +For more examples, see the [`FromRequest`] documentation, which uses +request-local state to cache expensive authentication and authorization +computations, and the [`Fairing`] documentation, which uses request-local state +to implement request timing. + +[`FromRequest`]: @api/rocket/request/trait.FromRequest.htmll#request-local-state +[`Fairing`]: @api/rocket/fairing/trait.Fairing.html#request-local-state + +## Databases + +Rocket includes built-in, ORM-agnostic support for databases. In particular, +Rocket provides a procedural macro that allows you to easily connect your Rocket +application to databases through connection pools. A _database connection pool_ +is a data structure that maintains active database connections for later use in +the application. This implementation of connection pooling support is based on +[`r2d2`] and exposes connections through request guards. Databases are +individually configured through Rocket's regular configuration mechanisms: a +`Rocket.toml` file, environment variables, or procedurally. + +Connecting your Rocket application to a database using this library occurs in +three simple steps: + + 1. Configure the databases in `Rocket.toml`. + 2. Associate a request guard type and fairing with each database. + 3. Use the request guard to retrieve a connection in a handler. + +Presently, Rocket provides built-in support for the following databases: + +| Kind | Driver | `Poolable` Type | Feature | +|----------|-----------------------|--------------------------------|------------------------| +| MySQL | [Diesel] | [`diesel::MysqlConnection`] | `diesel_mysql_pool` | +| MySQL | [`rust-mysql-simple`] | [`mysql::conn`] | `mysql_pool` | +| Postgres | [Diesel] | [`diesel::PgConnection`] | `diesel_postgres_pool` | +| Postgres | [Rust-Postgres] | [`postgres::Connection`] | `postgres_pool` | +| Sqlite | [Diesel] | [`diesel::SqliteConnection`] | `diesel_sqlite_pool` | +| Sqlite | [`Rustqlite`] | [`rusqlite::Connection`] | `sqlite_pool` | +| Neo4j | [`rusted_cypher`] | [`rusted_cypher::GraphClient`] | `cypher_pool` | +| Redis | [`redis-rs`] | [`redis::Connection`] | `redis_pool` | + +[`r2d2`]: https://crates.io/crates/r2d2 +[Diesel]: https://diesel.rs +[`redis::Connection`]: https://docs.rs/redis/0.9.0/redis/struct.Connection.html +[`rusted_cypher::GraphClient`]: https://docs.rs/rusted_cypher/1.1.0/rusted_cypher/graph/struct.GraphClient.html +[`rusqlite::Connection`]: https://docs.rs/rusqlite/0.13.0/rusqlite/struct.Connection.html +[`diesel::SqliteConnection`]: http://docs.diesel.rs/diesel/prelude/struct.SqliteConnection.html +[`postgres::Connection`]: https://docs.rs/postgres/0.15.2/postgres/struct.Connection.html +[`diesel::PgConnection`]: http://docs.diesel.rs/diesel/pg/struct.PgConnection.html +[`mysql::conn`]: https://docs.rs/mysql/14.0.0/mysql/struct.Conn.html +[`diesel::MysqlConnection`]: http://docs.diesel.rs/diesel/mysql/struct.MysqlConnection.html +[`redis-rs`]: https://github.com/mitsuhiko/redis-rs +[`rusted_cypher`]: https://github.com/livioribeiro/rusted-cypher +[`Rustqlite`]: https://github.com/jgallagher/rusqlite +[Rust-Postgres]: https://github.com/sfackler/rust-postgres +[`rust-mysql-simple`]: https://github.com/blackbeam/rust-mysql-simple +[`diesel::PgConnection`]: http://docs.diesel.rs/diesel/pg/struct.PgConnection.html + +### Usage + +To connect your Rocket application to a given database, first identify the +"Kind" and "Driver" in the table that matches your environment. The feature +corresponding to your database type must be enabled. This is the feature +identified in the "Feature" column. For instance, for Diesel-based SQLite +databases, you'd write in `Cargo.toml`: + +```toml +[dependencies.rocket_contrib] +version = "0.4.0-dev" +default-features = false +features = ["diesel_sqlite_pool"] +``` + +Then, in `Rocket.toml` or the equivalent via environment variables, configure +the URL for the database in the `databases` table: + +```toml +[global.databases] +sqlite_logs = { url = "/path/to/database.sqlite" } +``` + +In your application's source code, create a unit-like struct with one internal +type. This type should be the type listed in the "`Poolable` Type" column. Then +decorate the type with the `#[database]` attribute, providing the name of the +database that you configured in the previous step as the only parameter. +Finally, attach the fairing returned by `YourType::fairing()`, which was +generated by the `#[database]` attribute: + +```rust +use rocket_contrib::databases::{database, diesel}; + +#[database("sqlite_logs")] +struct LogsDbConn(diesel::SqliteConnection); + +fn main() { + rocket::ignite() + .attach(LogsDbConn::fairing()) + .launch(); +} +``` + +That's it! Whenever a connection to the database is needed, use your type as a +request guard: + +```rust +impl Logs { + fn by_id(conn: &diesel::SqliteConnection, log_id: usize) -> Result<Logs> { + logs.filter(id.eq(log_id)).load(conn) + } +} + +#[get("/logs/<id>")] +fn get_logs(conn: LogsDbConn, id: usize) -> Result<Logs> { + Logs::by_id(&conn, id) +} +``` + +For more on Rocket's built-in database support, see the +[`rocket_contrib::databases`] module documentation. + +[`rocket_contrib::databases`]: @api/rocket_contrib/databases/index.html diff --git a/site/guide/7-fairings.md b/site/guide/7-fairings.md @@ -0,0 +1,213 @@ +# Fairings + +Fairings are Rocket's approach to structured middleware. With fairings, your +application can hook into the request lifecycle to record or rewrite information +about incoming requests and outgoing responses. + +## Overview + +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. + +Rocket’s fairings are a lot like middleware from other frameworks, but they bear +a few key distinctions: + + * Fairings **cannot** terminate or respond to an incoming request directly. + * Fairings **cannot** inject arbitrary, non-request data into a request. + * Fairings _can_ prevent an application from launching. + * Fairings _can_ inspect and modify the application's configuration. + +If you are familiar with middleware from other frameworks, you may find yourself +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. + +[`Fairing`]: @api/rocket/fairing/trait.Fairing.html +[request guard]: ../requests/#request-guards +[request guards]: ../requests/#request-guards +[data guards]: ../requests/#body-data + +### Attaching + +Fairings are registered with Rocket via the [`attach`] method on a [`Rocket`] +instance. Only when a fairing is attached will its callbacks fire. As an +example, the following snippet attached two fairings, `req_fairing` and +`res_fairing`, to a new Rocket instance: + +```rust +rocket::ignite() + .attach(req_fairing) + .attach(res_fairing) + .launch(); +``` + +[`attach`]: @api/rocket/struct.Rocket.html#method.attach +[`Rocket`]: @api/rocket/struct.Rocket.html + +Fairings are executed in the order in which they are attached: the first +attached fairing has its callbacks executed before all others. Because fairing +callbacks may not be commutative, the order in which fairings are attached may +be significant. + +### Callbacks + +There are four events for which Rocket issues fairing callbacks. Each of these +events is described below: + + * **Attach (`on_attach`)** + + An attach callback is called when a fairing is first attached via the + [`attach`](@api/rocket/struct.Rocket.html#method.attach) method. An attach + callback can arbitrarily modify the `Rocket` instance being constructed and + optionally abort launch. Attach fairings are commonly used to parse and + validate configuration values, aborting on bad configurations, and inserting + the parsed value into managed state for later retrieval. + + * **Launch (`on_launch`)** + + A launch callback is called immediately before the Rocket application has + launched. A launch callback can inspect the `Rocket` instance being + launched. A launch callback can be a convenient hook for launching services + related to the Rocket application being launched. + + * **Request (`on_request`)** + + A request callback is called just after a request is received. A request + callback can modify the request at will and peek into the incoming data. It + may not, however, abort or respond directly to the request; these issues are + better handled via request guards or via response callbacks. + + * **Response (`on_response`)** + + A response callback is called when a response is ready to be sent to the + client. A response callback can modify part or all of the response. As such, + a response fairing can be used to provide a response when the greater + application fails by rewriting **404** responses as desired. As another + example, response fairings can also be used to inject headers into all + outgoing responses. + +## Implementing + +Recall that a fairing is any type that implements the [`Fairing`] trait. A +`Fairing` implementation has one required method: [`info`], which returns an +[`Info`] structure. This structure is used by Rocket to assign a name to the +fairing and determine the set of callbacks the fairing is registering for. A +`Fairing` can implement any of the available callbacks: [`on_attach`], +[`on_launch`], [`on_request`], and [`on_response`]. Each callback has a default +implementation that does absolutely nothing. + +[`Info`]: @api/rocket/fairing/struct.Info.html +[`info`]: @api/rocket/fairing/trait.Fairing.html#tymethod.info +[`on_attach`]: @api/rocket/fairing/trait.Fairing.html#method.on_attach +[`on_launch`]: @api/rocket/fairing/trait.Fairing.html#method.on_launch +[`on_request`]: @api/rocket/fairing/trait.Fairing.html#method.on_request +[`on_response`]: @api/rocket/fairing/trait.Fairing.html#method.on_response + +### Requirements + +A type implementing `Fairing` is required to be `Send + Sync + 'static`. This +means that the fairing must be sendable across thread boundaries (`Send`), +thread-safe (`Sync`), and have only static references, if any (`'static`). Note +that these bounds _do not_ prohibit a `Fairing` from holding state: the state +need simply be thread-safe and statically available or heap allocated. + +### Example + +Imagine that we want to record the number of `GET` and `POST` requests that our +application has received. While we could do this with request guards and managed +state, it would require us to annotate every `GET` and `POST` request with +custom types, polluting handler signatures. Instead, we can create a simple +fairing that acts globally. + +The code for a `Counter` fairing below implements exactly this. The fairing +receives a request callback, where it increments a counter on each `GET` and +`POST` request. It also receives a response callback, where it responds to +unrouted requests to the `/counts` path by returning the recorded number of +counts. + +```rust +struct Counter { + get: AtomicUsize, + post: AtomicUsize, +} + +impl Fairing for Counter { + // This is a request and response fairing named "GET/POST Counter". + fn info(&self) -> Info { + Info { + name: "GET/POST Counter", + kind: Kind::Request | Kind::Response + } + } + + // Increment the counter for `GET` and `POST` requests. + fn on_request(&self, request: &mut Request, _: &Data) { + match request.method() { + Method::Get => self.get.fetch_add(1, Ordering::Relaxed), + Method::Post => self.post.fetch_add(1, Ordering::Relaxed), + _ => return + } + } + + fn on_response(&self, request: &Request, response: &mut Response) { + // Don't change a successful user's response, ever. + if response.status() != Status::NotFound { + return + } + + // Rewrite the response to return the current counts. + if request.method() == Method::Get && request.uri().path() == "/counts" { + let get_count = self.get.load(Ordering::Relaxed); + let post_count = self.post.load(Ordering::Relaxed); + let body = format!("Get: {}\nPost: {}", get_count, post_count); + + response.set_status(Status::Ok); + response.set_header(ContentType::Plain); + response.set_sized_body(Cursor::new(body)); + } + } +} +``` + +For brevity, imports are not shown. The complete example can be found in the +[`Fairing` documentation](@api/rocket/fairing/trait.Fairing.html#example). + +## Ad-Hoc Fairings + +For simple occasions, implementing the `Fairing` trait can be cumbersome. This +is why Rocket provides the [`AdHoc`] type, which creates a fairing from a simple +function or closure. Using the `AdHoc` type is easy: simply call the +`on_attach`, `on_launch`, `on_request`, or `on_response` constructors on `AdHoc` +to create an `AdHoc` structure from a function or closure. + +As an example, the code below creates a `Rocket` instance with two attached +ad-hoc fairings. The first, a launch fairing named "Launch Printer", simply +prints a message indicating that the application is about to the launch. The +second named "Put Rewriter", a request fairing, rewrites the method of all +requests to be `PUT`. + +```rust +use rocket::fairing::AdHoc; +use rocket::http::Method; + +rocket::ignite() + .attach(AdHoc::on_launch("Launch Printer", |_| { + println!("Rocket is about to launch! Exciting! Here we go..."); + })) + .attach(AdHoc::on_request("Put Rewriter", |req, _| { + req.set_method(Method::Put); + })); +``` + +[`AdHoc`]: @api/rocket/fairing/enum.AdHoc.html diff --git a/site/guide/8-testing.md b/site/guide/8-testing.md @@ -0,0 +1,208 @@ +# Testing + +Every application should be well tested and understandable. Rocket provides the +tools to perform unit and integration tests. It also provides a means to inspect +code generated by Rocket. + +## Local Dispatching + +Rocket applications are tested by dispatching requests to a local instance of +`Rocket`. The [`local`] module contains all of the structures necessary to do +so. In particular, it contains a [`Client`] structure that is used to create +[`LocalRequest`] structures that can be dispatched against a given [`Rocket`] +instance. Usage is straightforward: + + 1. Construct a `Rocket` instance that represents the application. + + ```rust + let rocket = rocket::ignite(); + ``` + + 2. Construct a `Client` using the `Rocket` instance. + + ```rust + let client = Client::new(rocket).expect("valid rocket instance"); + ``` + + 3. Construct requests using the `Client` instance. + + ```rust + let req = client.get("/"); + ``` + + 4. Dispatch the request to retrieve the response. + + ```rust + let response = req.dispatch(); + ``` + +[`local`]: @api/rocket/local/index.html +[`Client`]: @api/rocket/local/struct.Client.html +[`LocalRequest`]: @api/rocket/local/struct.LocalRequest.html +[`Rocket`]: @api/rocket/struct.Rocket.html + +## Validating Responses + +A `dispatch` of a `LocalRequest` returns a [`LocalResponse`] which can be used +transparently as a [`Response`] value. During testing, the response is usually +validated against expected properties. These includes things like the response +HTTP status, the inclusion of headers, and expected body data. + +The [`Response`] type provides methods to ease this sort of validation. We list +a few below: + + * [`status`]: returns the HTTP status in the response. + * [`content_type`]: returns the Content-Type header in the response. + * [`headers`]: returns a map of all of the headers in the response. + * [`body_string`]: returns the body data as a `String`. + * [`body_bytes`]: returns the body data as a `Vec<u8>`. + +[`LocalResponse`]: @api/rocket/local/struct.LocalResponse.html +[`Response`]: @api/rocket/struct.Response.html +[`status`]: @api/rocket/struct.Response.html#method.status +[`content_type`]: @api/rocket/struct.Response.html#method.content_type +[`headers`]: @api/rocket/struct.Response.html#method.headers +[`body_string`]: @api/rocket/struct.Response.html#method.body_string +[`body_bytes`]: @api/rocket/struct.Response.html#method.body_bytes + +These methods are typically used in combination with the `assert_eq!` or +`assert!` macros as follows: + +```rust +let rocket = rocket::ignite(); +let client = Client::new(rocket).expect("valid rocket instance"); +let mut response = client.get("/").dispatch(); + +assert_eq!(response.status(), Status::Ok); +assert_eq!(response.content_type(), Some(ContentType::Plain)); +assert!(response.headers().get_one("X-Special").is_some()); +assert_eq!(response.body_string(), Some("Expected Body.".into())); +``` + +## Testing "Hello, world!" + +To solidify an intuition for how Rocket applications are tested, we walk through +how to test the "Hello, world!" application below: + +```rust +#[get("/")] +fn hello() -> &'static str { + "Hello, world!" +} + +fn rocket() -> Rocket { + rocket::ignite().mount("/", routes![hello]) +} + +fn main() { + rocket().launch(); +} +``` + +Notice that we've separated the _creation_ of the `Rocket` instance from the +_launch_ of the instance. As you'll soon see, this makes testing our application +easier, less verbose, and less error-prone. + +### Setting Up + +First, we'll create a `test` module with the proper imports: + +```rust +#[cfg(test)] +mod test { + use super::rocket; + use rocket::local::Client; + use rocket::http::Status; + + #[test] + fn hello_world() { + ... + } +} +``` + +You can also move the body of the `test` module into its own file, say +`tests.rs`, and then import the module into the main file using: + +```rust +#[cfg(test)] mod tests; +``` + +### Testing + +To test our "Hello, world!" application, we first create a `Client` for our +`Rocket` instance. It's okay to use methods like `expect` and `unwrap` during +testing: we _want_ our tests to panic when something goes wrong. + +```rust +let client = Client::new(rocket()).expect("valid rocket instance"); +``` + +Then, we create a new `GET /` request and dispatch it, getting back our +application's response: + +```rust +let mut response = client.get("/").dispatch(); +``` + +Finally, we ensure that the response contains the information we expect it to. +Here, we want to ensure two things: + + 1. The status is `200 OK`. + 2. The body is the string "Hello, world!". + +We do this by checking the `Response` object directly: + +```rust +assert_eq!(response.status(), Status::Ok); +assert_eq!(response.body_string(), Some("Hello, world!".into())); +``` + +That's it! Altogether, this looks like: + +```rust +#[cfg(test)] +mod test { + use super::rocket; + use rocket::local::Client; + use rocket::http::Status; + + #[test] + fn hello_world() { + let client = Client::new(rocket()).expect("valid rocket instance"); + let mut response = client.get("/").dispatch(); + assert_eq!(response.status(), Status::Ok); + assert_eq!(response.body_string(), Some("Hello, world!".into())); + } +} +``` + +The tests can be run with `cargo test`. You can find the full source code to +[this example on GitHub](@example/testing). + +## Codegen Debug + +It can be useful to inspect the code that Rocket's code generation is emitting, +especially when you get a strange type error. To have Rocket log the code that +it is emitting to the console, set the `ROCKET_CODEGEN_DEBUG` environment +variable when compiling: + +```rust +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) +} +``` + +This corresponds to the facade request handler Rocket has generated for the +`hello` route. diff --git a/site/guide/9-configuration.md b/site/guide/9-configuration.md @@ -0,0 +1,295 @@ +# Configuration + +Rocket aims to have a flexible and usable configuration system. Rocket +applications can be configured via a configuration file, through environment +variables, or both. Configurations are separated into three environments: +development, staging, and production. The working environment is selected via an +environment variable. + +## Environment + +At any point in time, a Rocket application is operating in a given +_configuration environment_. There are three such environments: + + * `development` (short: `dev`) + * `staging` (short: `stage`) + * `production` (short: `prod`) + +Without any action, Rocket applications run in the `development` environment for +debug builds and the `production` environment for non-debug builds. The +environment can be changed via the `ROCKET_ENV` environment variable. For +example, to launch an application in the `staging` environment, we can run: + +```sh +ROCKET_ENV=stage cargo run +``` + +Note that you can use the short or long form of the environment name to specify +the environment, `stage` _or_ `staging` here. Rocket tells us the environment we +have chosen and its configuration when it launches: + +```sh +$ sudo ROCKET_ENV=staging cargo run + +πŸ”§ Configured for staging. + => address: 0.0.0.0 + => port: 8000 + => log: normal + => workers: [logical cores * 2] + => secret key: generated + => limits: forms = 32KiB + => tls: disabled +πŸ›° Mounting '/': + => GET / +πŸš€ Rocket has launched from http://0.0.0.0:8000 +``` + +## Rocket.toml + +An optional `Rocket.toml` file can be used to specify the configuration +parameters for each environment. If it is not present, the default configuration +parameters are used. Rocket searches for the file starting at the current +working directory. If it is not found there, Rocket checks the parent directory. +Rocket continues checking parent directories until the root is reached. + +The file must be a series of TOML tables, at most one for each environment, and +an optional "global" table. Each table contains key-value pairs corresponding to +configuration parameters for that environment. If a configuration parameter is +missing, the default value is used. The following is a complete `Rocket.toml` +file, where every standard configuration parameter is specified with the default +value: + +```toml +[development] +address = "localhost" +port = 8000 +workers = [number of cpus * 2] +log = "normal" +secret_key = [randomly generated at launch] +limits = { forms = 32768 } + +[staging] +address = "0.0.0.0" +port = 8000 +workers = [number of cpus * 2] +log = "normal" +secret_key = [randomly generated at launch] +limits = { forms = 32768 } + +[production] +address = "0.0.0.0" +port = 8000 +workers = [number of cpus * 2] +log = "critical" +secret_key = [randomly generated at launch] +limits = { forms = 32768 } +``` + +The `workers` and `secret_key` default parameters are computed by Rocket +automatically; the values above are not valid TOML syntax. When manually +specifying the number of workers, the value should be an integer: `workers = +10`. When manually specifying the secret key, the value should a 256-bit base64 +encoded string. Such a string can be generated using a tool such as openssl: +`openssl rand -base64 32`. + +The "global" pseudo-environment can be used to set and/or override configuration +parameters globally. A parameter defined in a `[global]` table sets, or +overrides if already present, that parameter in every environment. For example, +given the following `Rocket.toml` file, the value of `address` will be +`"1.2.3.4"` in every environment: + +``` +[global] +address = "1.2.3.4" + +[development] +address = "localhost" + +[production] +address = "0.0.0.0" +``` + +## Data Limits + +The `limits` parameter configures the maximum amount of data Rocket will accept +for a given data type. The parameter is a table where each key corresponds to a +data type and each value corresponds to the maximum size in bytes Rocket +should accept for that type. + +By default, Rocket limits forms to 32KiB (32768 bytes). To increase the limit, +simply set the `limits.forms` configuration parameter. For example, to increase +the forms limit to 128KiB globally, we might write: + +```toml +[global.limits] +forms = 131072 +``` + +The `limits` parameter can contain keys and values that are not endemic to +Rocket. For instance, the [`Json`] type reads the `json` limit value to cap +incoming JSON data. You should use the `limits` parameter for your application's +data limits as well. Data limits can be retrieved at runtime via the +[`Request::limits()`] method. + +[`Request::limits()`]: @api/rocket/struct.Request.html#method.limits +[`Json`]: @api/rocket_contrib/struct.Json.html#incoming-data-limits + +## Extras + +In addition to overriding default configuration parameters, a configuration file +can also define values for any number of _extra_ configuration parameters. While +these parameters aren't used by Rocket directly, other libraries, or your own +application, can use them as they wish. As an example, the +[Template](@api/rocket_contrib/struct.Template.html) type +accepts a value for the `template_dir` configuration parameter. The parameter +can be set in `Rocket.toml` as follows: + +```toml +[development] +template_dir = "dev_templates/" + +[production] +template_dir = "prod_templates/" +``` + +This sets the `template_dir` extra configuration parameter to `"dev_templates/"` +when operating in the `development` environment and `"prod_templates/"` when +operating in the `production` environment. Rocket will prepend the `[extra]` tag +to extra configuration parameters when launching: + +```sh +πŸ”§ Configured for development. + => ... + => [extra] template_dir: "dev_templates/" +``` + +To retrieve a custom, extra configuration parameter in your application, we +recommend using an [ad-hoc attach fairing] in combination with [managed state]. +For example, if your application makes use of a custom `assets_dir` parameter: + +[ad-hoc attach fairing]: ../fairings/#ad-hoc-fairings +[managed state]: ../state/#managed-state + +```toml +[development] +assets_dir = "dev_assets/" + +[production] +assets_dir = "prod_assets/" +``` + +The following code will: + + 1. Read the configuration parameter in an ad-hoc `attach` fairing. + 2. Store the parsed parameter in an `AssertsDir` structure in managed state. + 3. Retrieve the parameter in an `assets` route via the `State` guard. + +```rust +struct AssetsDir(String); + +#[get("/<asset..>")] +fn assets(asset: PathBuf, assets_dir: State<AssetsDir>) -> Option<NamedFile> { + NamedFile::open(Path::new(&assets_dir.0).join(asset)).ok() +} + +fn main() { + rocket::ignite() + .mount("/", routes![assets]) + .attach(AdHoc::on_attach("Assets Config", |rocket| { + let assets_dir = rocket.config() + .get_str("assets_dir") + .unwrap_or("assets/") + .to_string(); + + Ok(rocket.manage(AssetsDir(assets_dir))) + })) + .launch(); +} +``` + +## Environment Variables + +All configuration parameters, including extras, can be overridden through +environment variables. To override the configuration parameter `{param}`, use an +environment variable named `ROCKET_{PARAM}`. For instance, to override the +"port" configuration parameter, you can run your application with: + +```sh +ROCKET_PORT=3721 ./your_application + +πŸ”§ Configured for development. + => ... + => port: 3721 +``` + +Environment variables take precedence over all other configuration methods: if +the variable is set, it will be used as the value for the parameter. Variable +values are parsed as if they were TOML syntax. As illustration, consider the +following examples: + +```sh +ROCKET_INTEGER=1 +ROCKET_FLOAT=3.14 +ROCKET_STRING=Hello +ROCKET_STRING="Hello" +ROCKET_BOOL=true +ROCKET_ARRAY=[1,"b",3.14] +ROCKET_DICT={key="abc",val=123} +``` + +## rocket::custom + +Rocket can also be configured using `rocket::custom` and passing configuration options like so: + +```rust +use rocket::config::{Config, Environment}; + +let config = Config::build(Environment::Staging) + .address("1.2.3.4") + .port(9234) + .finalize()?; + +let app = rocket::custom(config, false); +``` + +If Rocket is launched through `rocket::custom` it will ignore `Rocket.toml` and any environment variables present, allowing you to use your own configuration loading code. + +## Configuring TLS + +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` +dependency in your `Cargo.toml` file: + +``` +[dependencies] +rocket = { version = "0.4.0-dev", features = ["tls"] } +``` + +TLS is configured through the `tls` configuration parameter. The value of `tls` +must be a table with two keys: + + * `certs`: _[string]_ a path to a certificate chain in PEM format + * `key`: _[string]_ a path to a private key file in PEM format for the + certificate in `certs` + +The recommended way to specify these parameters is via the `global` environment: + +``` +[global.tls] +certs = "/path/to/certs.pem" +key = "/path/to/key.pem" +``` + +Of course, you can always specify the configuration values per environment: + +``` +[development] +tls = { certs = "/path/to/certs.pem", key = "/path/to/key.pem" } +``` + +Or via environment variables: + +```sh +ROCKET_TLS={certs="/path/to/certs.pem",key="/path/to/key.pem"} cargo run +``` diff --git a/site/guide/conclusion.md b/site/guide/conclusion.md @@ -1,28 +0,0 @@ -# Conclusion - -We hope you agree that Rocket is a refreshing take on web frameworks. As with -any software project, Rocket is _alive_. There are always things to improve, and -we're happy to take the best ideas. If you have something in mind, please -[submit an issue](https://github.com/SergioBenitez/Rocket/issues). - -## Getting Help - -If you find yourself having trouble developing Rocket applications, you can get -help via the `#rocket` IRC channel on the [Mozilla IRC -Server](https://wiki.mozilla.org/IRC) at `irc.mozilla.org` and the bridged -[Rocket room on Matrix](https://riot.im/app/#/room/#mozilla_#rocket:matrix.org). -If you're not familiar with IRC, we recommend chatting through [Matrix via -Riot](https://riot.im/app/#/room/#mozilla_#rocket:matrix.org) or via the [Kiwi -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. - -## What's next? - -The best way to learn Rocket is to _build something_. It should be fun and easy, -and there's always someone to help. Alternatively, you can read through the -[Rocket examples](https://github.com/SergioBenitez/Rocket/tree/v0.4.0-dev/examples) -or the [Rocket source -code](https://github.com/SergioBenitez/Rocket/tree/v0.4.0-dev/lib/src). Whatever you -decide to do next, we hope you have a blast! diff --git a/site/guide/configuration.md b/site/guide/configuration.md @@ -1,295 +0,0 @@ -# Configuration - -Rocket aims to have a flexible and usable configuration system. Rocket -applications can be configured via a configuration file, through environment -variables, or both. Configurations are separated into three environments: -development, staging, and production. The working environment is selected via an -environment variable. - -## Environment - -At any point in time, a Rocket application is operating in a given -_configuration environment_. There are three such environments: - - * `development` (short: `dev`) - * `staging` (short: `stage`) - * `production` (short: `prod`) - -Without any action, Rocket applications run in the `development` environment for -debug builds and the `production` environment for non-debug builds. The -environment can be changed via the `ROCKET_ENV` environment variable. For -example, to launch an application in the `staging` environment, we can run: - -```sh -ROCKET_ENV=stage cargo run -``` - -Note that you can use the short or long form of the environment name to specify -the environment, `stage` _or_ `staging` here. Rocket tells us the environment we -have chosen and its configuration when it launches: - -```sh -$ sudo ROCKET_ENV=staging cargo run - -πŸ”§ Configured for staging. - => address: 0.0.0.0 - => port: 8000 - => log: normal - => workers: [logical cores * 2] - => secret key: generated - => limits: forms = 32KiB - => tls: disabled -πŸ›° Mounting '/': - => GET / -πŸš€ Rocket has launched from http://0.0.0.0:8000 -``` - -## Rocket.toml - -An optional `Rocket.toml` file can be used to specify the configuration -parameters for each environment. If it is not present, the default configuration -parameters are used. Rocket searches for the file starting at the current -working directory. If it is not found there, Rocket checks the parent directory. -Rocket continues checking parent directories until the root is reached. - -The file must be a series of TOML tables, at most one for each environment, and -an optional "global" table. Each table contains key-value pairs corresponding to -configuration parameters for that environment. If a configuration parameter is -missing, the default value is used. The following is a complete `Rocket.toml` -file, where every standard configuration parameter is specified with the default -value: - -```toml -[development] -address = "localhost" -port = 8000 -workers = [number of cpus * 2] -log = "normal" -secret_key = [randomly generated at launch] -limits = { forms = 32768 } - -[staging] -address = "0.0.0.0" -port = 8000 -workers = [number of cpus * 2] -log = "normal" -secret_key = [randomly generated at launch] -limits = { forms = 32768 } - -[production] -address = "0.0.0.0" -port = 8000 -workers = [number of cpus * 2] -log = "critical" -secret_key = [randomly generated at launch] -limits = { forms = 32768 } -``` - -The `workers` and `secret_key` default parameters are computed by Rocket -automatically; the values above are not valid TOML syntax. When manually -specifying the number of workers, the value should be an integer: `workers = -10`. When manually specifying the secret key, the value should a 256-bit base64 -encoded string. Such a string can be generated using a tool such as openssl: -`openssl rand -base64 32`. - -The "global" pseudo-environment can be used to set and/or override configuration -parameters globally. A parameter defined in a `[global]` table sets, or -overrides if already present, that parameter in every environment. For example, -given the following `Rocket.toml` file, the value of `address` will be -`"1.2.3.4"` in every environment: - -``` -[global] -address = "1.2.3.4" - -[development] -address = "localhost" - -[production] -address = "0.0.0.0" -``` - -## Data Limits - -The `limits` parameter configures the maximum amount of data Rocket will accept -for a given data type. The parameter is a table where each key corresponds to a -data type and each value corresponds to the maximum size in bytes Rocket -should accept for that type. - -By default, Rocket limits forms to 32KiB (32768 bytes). To increase the limit, -simply set the `limits.forms` configuration parameter. For example, to increase -the forms limit to 128KiB globally, we might write: - -```toml -[global.limits] -forms = 131072 -``` - -The `limits` parameter can contain keys and values that are not endemic to -Rocket. For instance, the [`Json`] type reads the `json` limit value to cap -incoming JSON data. You should use the `limits` parameter for your application's -data limits as well. Data limits can be retrieved at runtime via the -[`Request::limits()`] method. - -[`Request::limits()`]: https://api.rocket.rs/rocket/struct.Request.html#method.limits -[`Json`]: https://api.rocket.rs/rocket_contrib/struct.Json.html#incoming-data-limits - -## Extras - -In addition to overriding default configuration parameters, a configuration file -can also define values for any number of _extra_ configuration parameters. While -these parameters aren't used by Rocket directly, other libraries, or your own -application, can use them as they wish. As an example, the -[Template](https://api.rocket.rs/rocket_contrib/struct.Template.html) type -accepts a value for the `template_dir` configuration parameter. The parameter -can be set in `Rocket.toml` as follows: - -```toml -[development] -template_dir = "dev_templates/" - -[production] -template_dir = "prod_templates/" -``` - -This sets the `template_dir` extra configuration parameter to `"dev_templates/"` -when operating in the `development` environment and `"prod_templates/"` when -operating in the `production` environment. Rocket will prepend the `[extra]` tag -to extra configuration parameters when launching: - -```sh -πŸ”§ Configured for development. - => ... - => [extra] template_dir: "dev_templates/" -``` - -To retrieve a custom, extra configuration parameter in your application, we -recommend using an [ad-hoc attach fairing] in combination with [managed state]. -For example, if your application makes use of a custom `assets_dir` parameter: - -[ad-hoc attach fairing]: /guide/fairings/#ad-hoc-fairings -[managed state]: /guide/state/#managed-state - -```toml -[development] -assets_dir = "dev_assets/" - -[production] -assets_dir = "prod_assets/" -``` - -The following code will: - - 1. Read the configuration parameter in an ad-hoc `attach` fairing. - 2. Store the parsed parameter in an `AssertsDir` structure in managed state. - 3. Retrieve the parameter in an `assets` route via the `State` guard. - -```rust -struct AssetsDir(String); - -#[get("/<asset..>")] -fn assets(asset: PathBuf, assets_dir: State<AssetsDir>) -> Option<NamedFile> { - NamedFile::open(Path::new(&assets_dir.0).join(asset)).ok() -} - -fn main() { - rocket::ignite() - .mount("/", routes![assets]) - .attach(AdHoc::on_attach("Assets Config", |rocket| { - let assets_dir = rocket.config() - .get_str("assets_dir") - .unwrap_or("assets/") - .to_string(); - - Ok(rocket.manage(AssetsDir(assets_dir))) - })) - .launch(); -} -``` - -## Environment Variables - -All configuration parameters, including extras, can be overridden through -environment variables. To override the configuration parameter `{param}`, use an -environment variable named `ROCKET_{PARAM}`. For instance, to override the -"port" configuration parameter, you can run your application with: - -```sh -ROCKET_PORT=3721 ./your_application - -πŸ”§ Configured for development. - => ... - => port: 3721 -``` - -Environment variables take precedence over all other configuration methods: if -the variable is set, it will be used as the value for the parameter. Variable -values are parsed as if they were TOML syntax. As illustration, consider the -following examples: - -```sh -ROCKET_INTEGER=1 -ROCKET_FLOAT=3.14 -ROCKET_STRING=Hello -ROCKET_STRING="Hello" -ROCKET_BOOL=true -ROCKET_ARRAY=[1,"b",3.14] -ROCKET_DICT={key="abc",val=123} -``` - -## rocket::custom - -Rocket can also be configured using `rocket::custom` and passing configuration options like so: - -```rust -use rocket::config::{Config, Environment}; - -let config = Config::build(Environment::Staging) - .address("1.2.3.4") - .port(9234) - .finalize()?; - -let app = rocket::custom(config, false); -``` - -If Rocket is launched through `rocket::custom` it will ignore `Rocket.toml` and any environment variables present, allowing you to use your own configuration loading code. - -## Configuring TLS - -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` -dependency in your `Cargo.toml` file: - -``` -[dependencies] -rocket = { version = "0.4.0-dev", features = ["tls"] } -``` - -TLS is configured through the `tls` configuration parameter. The value of `tls` -must be a table with two keys: - - * `certs`: _[string]_ a path to a certificate chain in PEM format - * `key`: _[string]_ a path to a private key file in PEM format for the - certificate in `certs` - -The recommended way to specify these parameters is via the `global` environment: - -``` -[global.tls] -certs = "/path/to/certs.pem" -key = "/path/to/key.pem" -``` - -Of course, you can always specify the configuration values per environment: - -``` -[development] -tls = { certs = "/path/to/certs.pem", key = "/path/to/key.pem" } -``` - -Or via environment variables: - -```sh -ROCKET_TLS={certs="/path/to/certs.pem",key="/path/to/key.pem"} cargo run -``` diff --git a/site/guide/fairings.md b/site/guide/fairings.md @@ -1,215 +0,0 @@ -# Fairings - -Fairings are Rocket's approach to structured middleware. With fairings, your -application can hook into the request lifecycle to record or rewrite information -about incoming requests and outgoing responses. - -## Overview - -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. - -Rocket’s fairings are a lot like middleware from other frameworks, but they bear -a few key distinctions: - - * Fairings **cannot** terminate or respond to an incoming request directly. - * Fairings **cannot** inject arbitrary, non-request data into a request. - * Fairings _can_ prevent an application from launching. - * Fairings _can_ inspect and modify the application's configuration. - -If you are familiar with middleware from other frameworks, you may find yourself -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. - -[`Fairing`]: https://api.rocket.rs/rocket/fairing/trait.Fairing.html -[request guard]: /guide/requests/#request-guards -[request guards]: /guide/requests/#request-guards -[data guards]: /guide/requests/#body-data - -### Attaching - -Fairings are registered with Rocket via the [`attach`] method on a [`Rocket`] -instance. Only when a fairing is attached will its callbacks fire. As an -example, the following snippet attached two fairings, `req_fairing` and -`res_fairing`, to a new Rocket instance: - -```rust -rocket::ignite() - .attach(req_fairing) - .attach(res_fairing) - .launch(); -``` - -[`attach`]: https://api.rocket.rs/rocket/struct.Rocket.html#method.attach -[`Rocket`]: https://api.rocket.rs/rocket/struct.Rocket.html - -Fairings are executed in the order in which they are attached: the first -attached fairing has its callbacks executed before all others. Because fairing -callbacks may not be commutative, the order in which fairings are attached may -be significant. - -### Callbacks - -There are four events for which Rocket issues fairing callbacks. Each of these -events is described below: - - * **Attach (`on_attach`)** - - An attach callback is called when a fairing is first attached via the - [`attach`](https://api.rocket.rs/rocket/struct.Rocket.html#method.attach) - method. An attach callback can arbitrarily modify the `Rocket` instance - being constructed and optionally abort launch. Attach fairings are commonly - used to parse and validate configuration values, aborting on bad - configurations, and inserting the parsed value into managed state for later - retrieval. - - * **Launch (`on_launch`)** - - A launch callback is called immediately before the Rocket application has - launched. A launch callback can inspect the `Rocket` instance being - launched. A launch callback can be a convenient hook for launching services - related to the Rocket application being launched. - - * **Request (`on_request`)** - - A request callback is called just after a request is received. A request - callback can modify the request at will and peek into the incoming data. It - may not, however, abort or respond directly to the request; these issues are - better handled via request guards or via response callbacks. - - * **Response (`on_response`)** - - A response callback is called when a response is ready to be sent to the - client. A response callback can modify part or all of the response. As such, - a response fairing can be used to provide a response when the greater - application fails by rewriting **404** responses as desired. As another - example, response fairings can also be used to inject headers into all - outgoing responses. - -## Implementing - -Recall that a fairing is any type that implements the [`Fairing`] trait. A -`Fairing` implementation has one required method: [`info`], which returns an -[`Info`] structure. This structure is used by Rocket to assign a name to the -fairing and determine the set of callbacks the fairing is registering for. A -`Fairing` can implement any of the available callbacks: [`on_attach`], -[`on_launch`], [`on_request`], and [`on_response`]. Each callback has a default -implementation that does absolutely nothing. - -[`Info`]: https://api.rocket.rs/rocket/fairing/struct.Info.html -[`info`]: https://api.rocket.rs/rocket/fairing/trait.Fairing.html#tymethod.info -[`on_attach`]: https://api.rocket.rs/rocket/fairing/trait.Fairing.html#method.on_attach -[`on_launch`]: https://api.rocket.rs/rocket/fairing/trait.Fairing.html#method.on_launch -[`on_request`]: https://api.rocket.rs/rocket/fairing/trait.Fairing.html#method.on_request -[`on_response`]: https://api.rocket.rs/rocket/fairing/trait.Fairing.html#method.on_response - -### Requirements - -A type implementing `Fairing` is required to be `Send + Sync + 'static`. This -means that the fairing must be sendable across thread boundaries (`Send`), -thread-safe (`Sync`), and have only static references, if any (`'static`). Note -that these bounds _do not_ prohibit a `Fairing` from holding state: the state -need simply be thread-safe and statically available or heap allocated. - -### Example - -Imagine that we want to record the number of `GET` and `POST` requests that our -application has received. While we could do this with request guards and managed -state, it would require us to annotate every `GET` and `POST` request with -custom types, polluting handler signatures. Instead, we can create a simple -fairing that acts globally. - -The code for a `Counter` fairing below implements exactly this. The fairing -receives a request callback, where it increments a counter on each `GET` and -`POST` request. It also receives a response callback, where it responds to -unrouted requests to the `/counts` path by returning the recorded number of -counts. - -```rust -struct Counter { - get: AtomicUsize, - post: AtomicUsize, -} - -impl Fairing for Counter { - // This is a request and response fairing named "GET/POST Counter". - fn info(&self) -> Info { - Info { - name: "GET/POST Counter", - kind: Kind::Request | Kind::Response - } - } - - // Increment the counter for `GET` and `POST` requests. - fn on_request(&self, request: &mut Request, _: &Data) { - match request.method() { - Method::Get => self.get.fetch_add(1, Ordering::Relaxed), - Method::Post => self.post.fetch_add(1, Ordering::Relaxed), - _ => return - } - } - - fn on_response(&self, request: &Request, response: &mut Response) { - // Don't change a successful user's response, ever. - if response.status() != Status::NotFound { - return - } - - // Rewrite the response to return the current counts. - if request.method() == Method::Get && request.uri().path() == "/counts" { - let get_count = self.get.load(Ordering::Relaxed); - let post_count = self.post.load(Ordering::Relaxed); - let body = format!("Get: {}\nPost: {}", get_count, post_count); - - response.set_status(Status::Ok); - response.set_header(ContentType::Plain); - response.set_sized_body(Cursor::new(body)); - } - } -} -``` - -For brevity, imports are not shown. The complete example can be found in the -[`Fairing` -documentation](https://api.rocket.rs/rocket/fairing/trait.Fairing.html#example). - -## Ad-Hoc Fairings - -For simple occasions, implementing the `Fairing` trait can be cumbersome. This -is why Rocket provides the [`AdHoc`] type, which creates a fairing from a simple -function or closure. Using the `AdHoc` type is easy: simply call the -`on_attach`, `on_launch`, `on_request`, or `on_response` constructors on `AdHoc` -to create an `AdHoc` structure from a function or closure. - -As an example, the code below creates a `Rocket` instance with two attached -ad-hoc fairings. The first, a launch fairing named "Launch Printer", simply -prints a message indicating that the application is about to the launch. The -second named "Put Rewriter", a request fairing, rewrites the method of all -requests to be `PUT`. - -```rust -use rocket::fairing::AdHoc; -use rocket::http::Method; - -rocket::ignite() - .attach(AdHoc::on_launch("Launch Printer", |_| { - println!("Rocket is about to launch! Exciting! Here we go..."); - })) - .attach(AdHoc::on_request("Put Rewriter", |req, _| { - req.set_method(Method::Put); - })); -``` - -[`AdHoc`]: https://api.rocket.rs/rocket/fairing/enum.AdHoc.html diff --git a/site/guide/index.md b/site/guide/index.md @@ -0,0 +1,46 @@ +# The Rocket Programming Guide + +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). + +The guide is split into several sections, each with a focus on a different +aspect of Rocket. The sections are: + + - **[Introduction](introduction/):** introduces Rocket and its philosophy. + - **[Quickstart](quickstart/):** presents the minimal steps necessary to + run your first Rocket application. + - **[Getting Started](getting-started/):** a gentle introduction to getting + your first Rocket application running. + - **[Overview](overview/):** describes the core concepts of Rocket. + - **[Requests](requests/):** discusses handling requests: control-flow, + parsing, and validating. + - **[Responses](responses/):** discusses generating responses. + - **[State](state/):** how to manage state in a Rocket application. + - **[Fairings](fairings/):** provides an overview of Rocket's structured + middleware. + - **[Testing](testing/):** how to unit and integration test a Rocket + application. + - **[Configuration](configuration/):** how to configure a Rocket application. + - **[Pastebin](pastebin/):** a tutorial on how to create a pastebin with + Rocket. + - **[Conclusion](conclusion/):** concludes the guide and discusses next steps + for learning. + +## Getting Help + +The official community support channels are the `#rocket` IRC channel on the +[Mozilla IRC Server](https://wiki.mozilla.org/IRC) at `irc.mozilla.org` and the +bridged [Rocket room on +Matrix](https://riot.im/app/#/room/#mozilla_#rocket:matrix.org). If you're not +familiar with IRC, we recommend chatting through [Matrix via +Riot](https://riot.im/app/#/room/#mozilla_#rocket:matrix.org) or via the [Kiwi +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/guide/overview.md b/site/guide/overview.md @@ -1,185 +0,0 @@ -# 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. - -## Lifecycle - -Rocket's main task is to listen for incoming web requests, dispatch the request -to the application code, and return a response to the client. We call the -process that goes from request to response the "lifecycle". We summarize the -lifecycle as the following sequence of steps: - - 1. **Routing** - - Rocket parses an incoming HTTP request into native structures that your - code operates on indirectly. Rocket determines which request handler to - invoke by matching against route attributes declared in your application. - - 2. **Validation** - - Rocket validates the incoming request against types and guards present in - the matched route. If validation fails, Rocket _forwards_ the request to - the next matching route or calls an _error handler_. - - 3. **Processing** - - The request handler associated with the route is invoked with validated - arguments. This is the main business logic of an application. Processing - completes by returning a `Response`. - - 4. **Response** - - The returned `Response` is processed. Rocket generates the appropriate HTTP - response and sends it to the client. This completes the lifecycle. Rocket - continues listening for requests, restarting the lifecycle for each - incoming request. - -The remainder of this section details the _routing_ phase as well as additional -components needed for Rocket to begin dispatching requests to request handlers. -The sections following describe the request and response phases as well as other -components of Rocket. - -## Routing - -Rocket applications are centered around routes and handlers. A _route_ is a -combination of: - - * A set of parameters to match an incoming request against. - * A handler to process the request and return a response. - -A _handler_ is simply a function that takes an arbitrary number of arguments and -returns any arbitrary type. - -The parameters to match against include static paths, dynamic paths, path -segments, forms, query strings, request format specifiers, and body data. Rocket -uses attributes, which look like function decorators in other languages, to make -declaring routes easy. Routes are declared by annotating a function, the -handler, with the set of parameters to match against. A complete route -declaration looks like this: - -```rust -#[get("/world")] // <- route attribute -fn world() -> &'static str { // <- request handler - "Hello, world!" -} -``` - -This declares the `world` route to match against the static path `"/world"` on -incoming `GET` requests. The `world` route is simple, but additional route -parameters are necessary when building more interesting applications. The -[Requests](/guide/requests) section describes the available options for -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: - -```rust -rocket::ignite().mount("/hello", routes![world]); -``` - -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. - -### Namespacing - -When a route is declared inside a module other than the root, you may find -yourself with unexpected errors when mounting: - -```rust -mod other { - #[get("/world")] - pub fn world() -> &'static str { - "Hello, world!" - } -} - -use other::world; - -fn main() { - // error[E0425]: cannot find value `static_rocket_route_info_for_world` in this scope - rocket::ignite().mount("/hello", routes![world]); -} -``` - -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: - -```rust -rocket::ignite().mount("/hello", routes![other::world]); -``` - -## Launching - -Now that Rocket knows about the route, you can tell Rocket to start accepting -requests via the `launch` method. The method starts up the server and waits for -incoming requests. When a request arrives, Rocket finds the matching route and -dispatches the request to the route's handler. - -We typically call `launch` from the `main` function. Our complete _Hello, -world!_ application thus looks like: - -```rust -#![feature(proc_macro_hygiene, decl_macro)] - -#[macro_use] extern crate rocket; - -#[get("/world")] -fn world() -> &'static str { - "Hello, world!" -} - -fn main() { - rocket::ignite().mount("/hello", routes![world]).launch(); -} -``` - -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. - -Running the application, the console shows: - -```sh -πŸ”§ Configured for development. - => address: localhost - => port: 8000 - => log: normal - => workers: [logical cores * 2] - => secret key: generated - => limits: forms = 32KiB - => tls: disabled -πŸ›° Mounting '/hello': - => GET /hello/world -πŸš€ Rocket has launched from http://localhost:8000 -``` - -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](https://github.com/SergioBenitez/Rocket/tree/v0.4.0-dev/examples/hello_world). -You can find dozens of other complete examples, spanning all of Rocket's -features, in the [GitHub examples -directory](https://github.com/SergioBenitez/Rocket/tree/v0.4.0-dev/examples/). diff --git a/site/guide/pastebin.md b/site/guide/pastebin.md @@ -1,407 +0,0 @@ -# Pastebin - -To give you a taste of what a real Rocket application looks like, this section -of the guide is a tutorial on how to create a Pastebin application in Rocket. A -pastebin is a simple web application that allows users to upload a text document -and later retrieve it via a special URL. They're often used to share code -snippets, configuration files, and error logs. In this tutorial, we'll build a -simple pastebin service that allows users to upload a file from their terminal. -The service will respond back with a URL to the uploaded file. - -## Finished Product - -A souped-up, completed version of the application you're about to build is -deployed live at [paste.rs](https://paste.rs). Feel free to play with the -application to get a feel for how it works. For example, to upload a text -document named `test.txt`, you can do: - -```sh -curl --data-binary @test.txt https://paste.rs/ -# => https://paste.rs/IYu -``` - -The finished product is composed of the following routes: - - * index: **GET /** - returns a simple HTML page with instructions about how - to use the service - * upload: **POST /** - accepts raw data in the body of the request and - responds with a URL of a page containing the body's content - * retrieve: **GET /&lt;id>** - retrieves the content for the paste with id - `<id>` - -## Getting Started - -Let's get started! First, create a fresh Cargo binary project named -`rocket-pastebin`: - -```rust -cargo new --bin rocket-pastebin -cd rocket-pastebin -``` - -Then add the usual Rocket dependencies to the `Cargo.toml` file: - -```toml -[dependencies] -rocket = "0.4.0-dev" -rocket_codegen = "0.4.0-dev" -``` - -And finally, create a skeleton Rocket application to work off of in -`src/main.rs`: - -```rust -#![feature(proc_macro_hygiene, decl_macro)] - -#[macro_use] extern crate rocket; - -fn main() { - rocket::ignite().launch(); -} -``` - -Ensure everything works by running the application: - -```sh -cargo run -``` - -At this point, we haven't declared any routes or handlers, so visiting any page -will result in Rocket returning a **404** error. Throughout the rest of the -tutorial, we'll create the three routes and accompanying handlers. - -## Index - -The first route we'll create is the `index` route. This is the page users will -see when they first visit the service. As such, the route should field requests -of the form `GET /`. We declare the route and its handler by adding the `index` -function below to `src/main.rs`: - -```rust -#[get("/")] -fn index() -> &'static str { - " - USAGE - - POST / - - accepts raw data in the body of the request and responds with a URL of - a page containing the body's content - - GET /<id> - - retrieves the content for the paste with id `<id>` - " -} -``` - -This declares the `index` route for requests to `GET /` as returning a static -string with the specified contents. Rocket will take the string and return it as -the body of a fully formed HTTP response with `Content-Type: text/plain`. You -can read more about how Rocket formulates responses at the [API documentation -for the Responder - trait](https://api.rocket.rs/rocket/response/trait.Responder.html). - -Remember that routes first need to be mounted before Rocket dispatches requests -to them. To mount the `index` route, modify the main function so that it reads: - -```rust -fn main() { - rocket::ignite().mount("/", routes![index]).launch(); -} -``` - -You should now be able to `cargo run` the application and visit the root path -(`/`) to see the text being displayed. - -## Uploading - -The most complicated aspect of the pastebin, as you might imagine, is handling -upload requests. When a user attempts to upload a pastebin, our service needs to -generate a unique ID for the upload, read the data, write it out to a file or -database, and then return a URL with the ID. We'll take each of these one step -at a time, beginning with generating IDs. - -### Unique IDs - -Generating a unique and useful ID is an interesting topic, but it is outside the -scope of this tutorial. Instead, we simply provide the code for a `PasteID` -structure that represents a _probably_ unique ID. Read through the code, then -copy/paste it into a new file named `paste_id.rs` in the `src/` directory: - -```rust -use std::fmt; -use std::borrow::Cow; - -use rand::{self, Rng}; - -/// Table to retrieve base62 values from. -const BASE62: &'static [u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - -/// A _probably_ unique paste ID. -pub struct PasteID<'a>(Cow<'a, str>); - -impl<'a> PasteID<'a> { - /// Generate a _probably_ unique ID with `size` characters. For readability, - /// the characters used are from the sets [0-9], [A-Z], [a-z]. The - /// probability of a collision depends on the value of `size` and the number - /// of IDs generated thus far. - pub fn new(size: usize) -> PasteID<'static> { - let mut id = String::with_capacity(size); - let mut rng = rand::thread_rng(); - for _ in 0..size { - id.push(BASE62[rng.gen::<usize>() % 62] as char); - } - - PasteID(Cow::Owned(id)) - } -} - -impl<'a> fmt::Display for PasteID<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.0) - } -} -``` - -Then, in `src/main.rs`, add the following after `extern crate rocket`: - -```rust -extern crate rand; - -mod paste_id; - -use paste_id::PasteID; -``` - -Finally, add a dependency for the `rand` crate to the `Cargo.toml` file: - -```toml -[dependencies] -# existing Rocket dependencies... -rand = "0.3" -``` - -Then, ensure that your application builds with the new code: - -```sh -cargo build -``` - -You'll likely see many "unused" warnings for the new code we've added: that's -okay and expected. We'll be using the new code soon. - -### Processing - -Believe it or not, the hard part is done! (_whew!_). - -To process the upload, we'll need a place to store the uploaded files. To -simplify things, we'll store the uploads in a directory named `upload/`. Create -an `upload` directory next to the `src` directory: - -```sh -mkdir upload -``` - -For the `upload` route, we'll need to `use` a few items: - -```rust -use std::io; -use std::path::Path; - -use rocket::Data; -use rocket::http::RawStr; -``` - -The [Data](https://api.rocket.rs/rocket/data/struct.Data.html) structure is key -here: it represents an unopened stream to the incoming request body data. We'll -use it to efficiently stream the incoming request to a file. - -### Upload Route - -We're finally ready to write the `upload` route. Before we show you the code, -you should attempt to write the route yourself. Here's a hint: a possible route -and handler signature look like this: - -```rust -#[post("/", data = "<paste>")] -fn upload(paste: Data) -> io::Result<String> -``` - -Your code should: - - 1. Create a new `PasteID` of a length of your choosing. - 2. Construct a filename inside `upload/` given the `PasteID`. - 3. Stream the `Data` to the file with the constructed filename. - 4. Construct a URL given the `PasteID`. - 5. Return the URL to the client. - -Here's our version (in `src/main.rs`): - -```rust -#[post("/", data = "<paste>")] -fn upload(paste: Data) -> io::Result<String> { - let id = PasteID::new(3); - let filename = format!("upload/{id}", id = id); - let url = format!("{host}/{id}\n", host = "http://localhost:8000", id = id); - - // Write the paste out to the file and return the URL. - paste.stream_to_file(Path::new(&filename))?; - Ok(url) -} -``` - -Make sure that the route is mounted at the root path: - -```rust -fn main() { - rocket::ignite().mount("/", routes![index, upload]).launch(); -} -``` - -Test that your route works via `cargo run`. From a separate terminal, upload a -file using `curl`. Then verify that the file was saved to the `upload` directory -with the correct ID: - -```sh -# in the project root -cargo run - -# in a seperate terminal -echo "Hello, world." | curl --data-binary @- http://localhost:8000 -# => http://localhost:8000/eGs - -# back to the terminal running the pastebin -<ctrl-c> # kill running process -ls upload # ensure the upload is there -cat upload/* # ensure that contents are correct -``` - -Note that since we haven't created a `GET /<id>` route, visiting the returned URL -will result in a **404**. We'll fix that now. - -## Retrieving Pastes - -The final step is to create the `retrieve` route which, given an `<id>`, will -return the corresponding paste if it exists. - -Here's a first take at implementing the `retrieve` route. The route below takes -in an `<id>` as a dynamic path element. The handler uses the `id` to construct a -path to the paste inside `upload/`, and then attempts to open the file at that -path, optionally returning the `File` if it exists. Rocket treats a `None` -[Responder](https://api.rocket.rs/rocket/response/trait.Responder.html#provided-implementations) -as a **404** error, which is exactly what we want to return when the requested -paste doesn't exist. - -```rust -use std::fs::File; -use rocket::http::RawStr; - -#[get("/<id>")] -fn retrieve(id: &RawStr) -> Option<File> { - let filename = format!("upload/{id}", id = id); - File::open(&filename).ok() -} -``` - -Unfortunately, there's a problem with this code. Can you spot the issue? The -[`RawStr`](https://api.rocket.rs/rocket/http/struct.RawStr.html) type should tip -you off! - -The issue is that the _user_ controls the value of `id`, and as a result, can -coerce the service into opening files inside `upload/` that aren't meant to be -opened. For instance, imagine that you later decide that a special file -`upload/_credentials.txt` will store some important, private information. If the -user issues a `GET` request to `/_credentials.txt`, the server will read and -return the `upload/_credentials.txt` file, leaking the sensitive information. -This is a big problem; it's known as the [full path disclosure -attack](https://www.owasp.org/index.php/Full_Path_Disclosure), and Rocket -provides the tools to prevent this and other kinds of attacks from happening. - -To prevent the attack, we need to _validate_ `id` before we use it. Since the -`id` is a dynamic parameter, we can use Rocket's -[FromParam](https://api.rocket.rs/rocket/request/trait.FromParam.html) trait to -implement the validation and ensure that the `id` is a valid `PasteID` before -using it. We do this by implementing `FromParam` for `PasteID` in -`src/paste_id.rs`, as below: - -```rust -use rocket::request::FromParam; - -/// Returns `true` if `id` is a valid paste ID and `false` otherwise. -fn valid_id(id: &str) -> bool { - id.chars().all(|c| { - (c >= 'a' && c <= 'z') - || (c >= 'A' && c <= 'Z') - || (c >= '0' && c <= '9') - }) -} - -/// Returns an instance of `PasteID` if the path segment is a valid ID. -/// Otherwise returns the invalid ID as the `Err` value. -impl<'a> FromParam<'a> for PasteID<'a> { - type Error = &'a RawStr; - - fn from_param(param: &'a RawStr) -> Result<PasteID<'a>, &'a RawStr> { - match valid_id(param) { - true => Ok(PasteID(Cow::Borrowed(param))), - false => Err(param) - } - } -} -``` - -Then, we simply need to change the type of `id` in the handler to `PasteID`. -Rocket will then ensure that `<id>` represents a valid `PasteID` before calling -the `retrieve` route, preventing attacks on the `retrieve` route: - -```rust -#[get("/<id>")] -fn retrieve(id: PasteID) -> Option<File> { - let filename = format!("upload/{id}", id = id); - File::open(&filename).ok() -} -``` - -Note that our `valid_id` function is simplistic and could be improved by, for -example, checking that the length of the `id` is within some known bound or -potentially blacklisting sensitive files as needed. - -The wonderful thing about using `FromParam` and other Rocket traits is that they -centralize policies. For instance, here, we've centralized the policy for valid -`PasteID`s in dynamic parameters. At any point in the future, if other routes -are added that require a `PasteID`, no further work has to be done: simply use -the type in the signature and Rocket takes care of the rest. - -## Conclusion - -That's it! Ensure that all of your routes are mounted and test your application. -You've now written a simple (~75 line!) pastebin in Rocket! There are many -potential improvements to this small application, and we encourage you to work -through some of them to get a better feel for Rocket. Here are some ideas: - - * Add a web form to the `index` where users can manually input new pastes. - Accept the form at `POST /`. Use `format` and/or `rank` to specify which of - the two `POST /` routes should be called. - * Support **deletion** of pastes by adding a new `DELETE /<id>` route. Use - `PasteID` to validate `<id>`. - * **Limit the upload** to a maximum size. If the upload exceeds that size, - return a **206** partial status code. Otherwise, return a **201** created - status code. - * Set the `Content-Type` of the return value in `upload` and `retrieve` to - `text/plain`. - * **Return a unique "key"** after each upload and require that the key is - present and matches when doing deletion. Use one of Rocket's core traits to - do the key validation. - * Add a `PUT /<id>` route that allows a user with the key for `<id>` to - replace the existing paste, if any. - * Add a new route, `GET /<id>/<lang>` that syntax highlights the paste with ID - `<id>` for language `<lang>`. If `<lang>` is not a known language, do no - highlighting. Possibly validate `<lang>` with `FromParam`. - * Use the [`local` module](https://api.rocket.rs/rocket/local/) to write - unit tests for your pastebin. - * Dispatch a thread before `launch`ing Rocket in `main` that periodically - cleans up idling old pastes in `upload/`. - -You can find the full source code for the [completed pastebin tutorial on -GitHub](https://github.com/SergioBenitez/Rocket/tree/v0.4.0-dev/examples/pastebin). diff --git a/site/guide/quickstart.md b/site/guide/quickstart.md @@ -1,24 +0,0 @@ -# Quickstart - -Before you can start writing a Rocket application, you'll need a **nightly** -version of Rust installed. We recommend you use [rustup](https://rustup.rs/) to -install or configure such a version. If you don't have Rust installed and would -like extra guidance doing so, see the [getting started](/guide/getting-started) -section. - -## Running Examples - -The absolute fastest way to start experimenting with Rocket is to clone the -Rocket repository and run the included examples in the `examples/` directory. -For instance, the following set of commands runs the `hello_world` example: - -```sh -git clone https://github.com/SergioBenitez/Rocket -cd Rocket -git checkout v0.4.0-dev -cd examples/hello_world -cargo run -``` - -There are numerous examples in the `examples/` directory. They can all be run -with `cargo run`. diff --git a/site/guide/requests.md b/site/guide/requests.md @@ -1,782 +0,0 @@ -# Requests - -Together, a route's 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: - -```rust -#[get("/world")] -fn handler() { .. } -``` - -This route indicates that it only matches against `GET` requests to the `/world` -route. Rocket ensures that this is the case before `handler` is called. Of -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 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. - -The route attribute and function signature work in tandem to describe these -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. - -## 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 attributes is defined formally in the -[`rocket_codegen`](https://api.rocket.rs/rocket_codegen/) API docs. - -### HEAD Requests - -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. - -### 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** -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](https://github.com/SergioBenitez/Rocket/tree/v0.4.0-dev/examples/todo/static/index.html.tera#L47) -makes use of this feature to submit `PUT` and `DELETE` requests from a web form. - -## Dynamic Segments - -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, -not just the world, we can declare a route like so: - -```rust -#[get("/hello/<name>")] -fn hello(name: &RawStr) -> String { - format!("Hello, {}!", name.as_str()) -} -``` - -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!`. - -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: - -```rust -#[get("/hello/<name>/<age>/<cool>")] -fn hello(name: String, 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) - } -} -``` - -[`FromParam`]: https://api.rocket.rs/rocket/request/trait.FromParam.html -[`FromParam` API docs]: https://api.rocket.rs/rocket/request/trait.FromParam.html - -### Raw 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>` types, from unvalidated inputs, represented by `&RawStr`. It -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. 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`). - -[`RawStr`]: https://api.rocket.rs/rocket/http/struct.RawStr.html - -## Forwarding - -Let's take a closer look at the route attribute and signature pair from the last -example: - -```rust -#[get("/hello/<name>/<age>/<cool>")] -fn hello(name: String, age: u8, cool: bool) -> String { ... } -``` - -What if `cool` isn't a `bool`? Or, what if `age` isn't a `u8`? When a parameter -type mismatch occurs, Rocket _forwards_ the request 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 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: - -```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: &RawStr) -> 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, &RawStr>`, 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 an error and abort launch, indicating that -the routes _collide_, or can match against similar incoming requests. The `rank` -parameter resolves this collision. - -### Default Ranking - -If a rank is not explicitly specified, Rocket assigns a default ranking. By -default, routes with static paths and query strings have lower ranks (higher -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/`: - -```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() -} -``` - -[`FromSegments`]: https://api.rocket.rs/rocket/request/trait.FromSegments.html - -## Format - -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. - -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()`]: https://api.rocket.rs/rocket/http/struct.ContentType.html#method.parse_flexible - -## Request Guards - -Request guards are one of Rocket's most powerful instruments. As the name might -imply, a request guard protects a handler from being called erroneously based on -information contained in an incoming request. More specifically, a request guard -is a type that represents an arbitrary validation policy. The validation policy -is implemented through the [`FromRequest`] trait. Every type that implements -`FromRequest` is a request guard. - -Request guards appear as inputs to handlers. An arbitrary number of request -guards can appear as arguments in a route handler. Rocket will automatically -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, -`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. - -```rust -#[get("/<param>")] -fn index(param: isize, a: A, b: B, c: C) -> ... { ... } -``` - -Request guards always fire in left-to-right declaration order. In the example -above, the order will be `A` followed by `B` followed by `C`. Failure is -short-circuiting; if one guard fails, the remaining are not attempted. To learn -more about request guards and implementing them, see the [`FromRequest`] -documentation. - -[`FromRequest`]: https://api.rocket.rs/rocket/request/trait.FromRequest.html -[`Cookies`]: https://api.rocket.rs/rocket/http/enum.Cookies.html - -### Custom Guards - -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 -then 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 -authenticates an administrator using incoming cookies. 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. - -### Forwarding Guards - -Request guards and forwarding are a powerful combination for enforcing policies. -To illustrate, we consider how a simple authorization system might be -implemented using these mechanisms. - -We start with two request guards: - - * `User`: A regular, authenticated user. - - The `FromRequest` implementation for `User` checks that a cookie identifies - a user and returns a `User` value if so. If no user can be authenticated, - the guard forwards. - - * `AdminUser`: A user authenticated as an administrator. - - The `FromRequest` implementation for `AdminUser` checks that a cookie - identifies an _administrative_ user and returns an `AdminUser` value if so. - If no user can be authenticated, the guard forwards. - -We now use these two guards in combination with forwarding to implement the -following three routes, each leading to an administrative control panel at -`/admin`: - -```rust -#[get("/admin")] -fn admin_panel(admin: AdminUser) -> &'static str { - "Hello, administrator. This is the admin panel!" -} - -#[get("/admin", rank = 2)] -fn admin_panel_user(user: User) -> &'static str { - "Sorry, you must be an administrator to access this page." -} - -#[get("/admin", rank = 3)] -fn admin_panel_redirect() -> Redirect { - Redirect::to("/login") -} -``` - -The three routes above encode authentication _and_ authorization. The -`admin_panel` route only succeeds if an administrator is logged in. Only then is -the admin panel displayed. If the user is not an admin, the `AdminUser` route -will forward. Since the `admin_panel_user` route is ranked next highest, it is -attempted next. This route succeeds if there is _any_ user signed in, and an -authorization failure message is displayed. Finally, if a user isn't signed in, -the `admin_panel_redirect` route is attempted. Since this route has no guards, -it always succeeds. The user is redirected to a log in page. - -## Cookies - -[`Cookies`] is an important, built-in request guard: it allows you to get, set, -and remove cookies. Because `Cookies` is a request guard, an argument of its -type can simply be added to a handler: - -```rust -use rocket::http::Cookies; - -#[get("/")] -fn index(cookies: Cookies) -> Option<String> { - cookies.get("message") - .map(|value| format!("Message: {}", value)) -} -``` - -This results in the incoming request's cookies being accessible from the -handler. The example above retrieves a cookie named `message`. Cookies can also -be set and removed using the `Cookies` guard. The [cookies example] on GitHub -illustrates further use of the `Cookies` type to get and set cookies, while the -[`Cookies`] documentation contains complete usage information. - -[cookies example]: https://github.com/SergioBenitez/Rocket/tree/v0.4.0-dev/examples/cookies - -### Private Cookies - -Cookies added via the [`Cookies::add()`] method are set _in the clear._ In other -words, the value set is visible by the client. For sensitive data, Rocket -provides _private_ cookies. - -Private cookies are just like regular cookies except that they are encrypted -using authenticated encryption, a form of encryption which simultaneously -provides confidentiality, integrity, and authenticity. This means that private -cookies cannot be inspected, tampered with, or manufactured by clients. If you -prefer, you can think of private cookies as being signed and encrypted. - -The API for retrieving, adding, and removing private cookies is identical except -methods are suffixed with `_private`. These methods are: [`get_private`], -[`add_private`], and [`remove_private`]. An example of their usage is below: - -```rust -/// Retrieve the user's ID, if any. -#[get("/user_id")] -fn user_id(cookies: Cookies) -> Option<String> { - cookies.get_private("user_id") - .map(|cookie| format!("User ID: {}", cookie.value())) -} - -/// Remove the `user_id` cookie. -#[post("/logout")] -fn logout(mut cookies: Cookies) -> Flash<Redirect> { - cookies.remove_private(Cookie::named("user_id")); - Flash::success(Redirect::to("/"), "Successfully logged out.") -} -``` - -[`Cookies::add()`]: https://api.rocket.rs/rocket/http/enum.Cookies.html#method.add - -### Secret Key - -To encrypt private cookies, Rocket uses the 256-bit key specified in the -`secret_key` configuration parameter. If one is not specified, Rocket will -automatically generate a fresh key. Note, however, that a private cookie can -only be decrypted with the same key with which it was encrypted. As such, it is -important to set a `secret_key` configuration parameter when using private -cookies so that cookies decrypt properly after an application restart. Rocket -emits a warning if an application is run in production without a configured -`secret_key`. - -Generating a string suitable for use as a `secret_key` configuration value is -usually done through tools like `openssl`. Using `openssl`, a 256-bit base64 key -can be generated with the command `openssl rand -base64 32`. - -For more information on configuration, see the -[Configuration](/guide/configuration) section of the guide. - -[`get_private`]: https://api.rocket.rs/rocket/http/enum.Cookies.html#method.get_private -[`add_private`]: https://api.rocket.rs/rocket/http/enum.Cookies.html#method.add_private -[`remove_private`]: https://api.rocket.rs/rocket/http/enum.Cookies.html#method.remove_private - -### One-At-A-Time - -For safety reasons, Rocket currently requires that at most one `Cookies` -instance be active at a time. It's uncommon to run into this restriction, but it -can be confusing to handle if it does crop up. - -If this does happen, Rocket will emit messages to the console that look as -follows: - -``` -=> Error: Multiple `Cookies` instances are active at once. -=> An instance of `Cookies` must be dropped before another can be retrieved. -=> Warning: The retrieved `Cookies` instance will be empty. -``` - -The messages will be emitted when a violating handler is called. The issue can -be resolved by ensuring that two instances of `Cookies` cannot be active at once -due to the offending handler. A common error is to have a handler that uses a -`Cookies` request guard as well as a `Custom` request guard that retrieves -`Cookies`, as so: - -```rust -#[get("/")] -fn bad(cookies: Cookies, custom: Custom) { .. } -``` - -Because the `cookies` guard will fire before the `custom` guard, the `custom` -guard will retrieve an instance of `Cookies` when one already exists for -`cookies`. This scenario can be fixed by simply swapping the order of the -guards: - -```rust -#[get("/")] -fn good(custom: Custom, cookies: Cookies) { .. } -``` - -## 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`: - -```rust -#[post("/", data = "<input>")] -fn new(input: T) -> String { ... } -``` - -Any type that implements [`FromData`] is also known as _data guard_. - -[`FromData`]: https://api.rocket.rs/rocket/data/trait.FromData.html - -### 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`] 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 { ... } -``` - -[`FromForm`]: https://api.rocket.rs/rocket/request/trait.FromForm.html -[`FromFormValue`]: https://api.rocket.rs/rocket/request/trait.FromFormValue.html - -#### Lenient Parsing - -Rocket's `FromForm` parsing is _strict_ by default. In other words, A `Form<T>` -will parse successfully from an incoming form only if the form contains the -exact set of fields in `T`. Said another way, a `Form<T>` will error on missing -and/or extra fields. For instance, if an incoming form contains the fields "a", -"b", and "c" while `T` only contains "a" and "c", the form _will not_ parse as -`Form<T>`. - -Rocket allows you to opt-out of this behavior via the [`LenientForm`] data type. -A `LenientForm<T>` will parse successfully from an incoming form as long as the -form contains a superset of the fields in `T`. Said another way, a -`LenientForm<T>` automatically discards extra fields without error. For -instance, if an incoming form contains the fields "a", "b", and "c" while `T` -only contains "a" and "c", the form _will_ parse as `LenientForm<T>`. - -You can use a `LenientForm` anywhere you'd use a `Form`. Its generic parameter -is also required to implement `FromForm`. For instance, we can simply replace -`Form` with `LenientForm` above to get lenient parsing: - -```rust -#[derive(FromForm)] -struct Task { .. } - -#[post("/todo", data = "<task>")] -fn new(task: LenientForm<Task>) { .. } -``` - -[`LenientForm`]: https://api.rocket.rs/rocket/request/struct.LenientForm.html - -#### Field Renaming - -By default, Rocket matches the name of an incoming form field to the name of a -structure field. While this behavior is typical, it may also be desired to use -different names for form fields and struct fields while still parsing as -expected. You can ask Rocket to look for a different form field for a given -structure field by using the `#[form(field = "name")]` field annotation. - -As an example, say that you're writing an application that receives data from an -external service. The external service `POST`s a form with a field named `type`. -Since `type` is a reserved keyword in Rust, it cannot be used as the name of a -field. To get around this, you can use field renaming as follows: - -```rust -#[derive(FromForm)] -struct External { - #[form(field = "type")] - api_type: String -} -``` - -Rocket will then match the form field named `type` to the structure field named -`api_type` automatically. - -#### Field Validation - -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: - -```rust -struct AdultAge(usize); - -impl<'v> FromFormValue<'v> for AdultAge { - type Error = &'v RawStr; - - fn from_form_value(form_value: &'v RawStr) -> Result<AdultAge, &'v RawStr> { - match form_value.parse::<usize>() { - Ok(age) if age >= 21 => Ok(AdultAge(age)), - _ => Err(form_value), - } - } -} - -#[derive(FromForm)] -struct Person { - age: AdultAge -} -``` - -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: - -```rust -#[derive(FromForm)] -struct Person { - age: Option<AdultAge> -} -``` - -The [forms validation](https://github.com/SergioBenitez/Rocket/tree/v0.4.0-dev/examples/form_validation) -and [forms kitchen sink](https://github.com/SergioBenitez/Rocket/tree/v0.4.0-dev/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 in `Json` implements the -`Deserialize` trait from [Serde](https://github.com/serde-rs/json). See the -[JSON example] on GitHub for a complete example. - -[JSON example]: https://github.com/SergioBenitez/Rocket/tree/v0.4.0-dev/examples/json - -### Streaming - -Sometimes you just want to handle 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 = "plain", data = "<data>")] -fn upload(data: Data) -> io::Result<String> { - data.stream_to_file("/tmp/upload.txt").map(|n| 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`, 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.4.0-dev/examples/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>`. - -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. 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](https://github.com/SergioBenitez/Rocket/tree/v0.4.0-dev/examples/query_params). - -## Error Catchers - -Routing may fail for a variety of reasons. These include: - - * A [request guard](#request-guards) returns `Failure`. - * A handler returns a [`Responder`](/guide/responses/#responder) that fails. - * No matching route was found. - -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` -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: - -```rust -#[catch(404)] -fn not_found(req: &Request) -> T { .. } -``` - -As with routes, the return type (here `T`) must implement `Responder`. A -concrete implementation may look like: - -```rust -#[catch(404)] -fn not_found(req: &Request) -> String { - format!("Sorry, '{}' is not a valid path.", req.uri()) -} -``` - -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 -looks like: - -```rust -rocket::ignite().register(catchers![not_found]) -``` - -Unlike route request handlers, catchers take exactly zero or one parameters. If -the catcher takes a parameter, it must be of type -[`&Request`](https://api.rocket.rs/rocket/struct.Request.html) The [error -catcher example](https://github.com/SergioBenitez/Rocket/tree/v0.4.0-dev/examples/errors) -on GitHub illustrates their use in full. diff --git a/site/guide/responses.md b/site/guide/responses.md @@ -1,291 +0,0 @@ -# Responses - -You may have noticed that the return type of a handler appears to be arbitrary, -and that's because it is! A value of any type that implements the [`Responder`] -trait can be returned, including your own. In this section, we describe the -`Responder` trait as well as several useful `Responder`s provided by Rocket. -We'll also briefly discuss how to implement your own `Responder`. - -[`Responder`]: https://api.rocket.rs/rocket/response/trait.Responder.html - -## Responder - -Types that implement [`Responder`] know how to generate a [`Response`] from -their values. A `Response` includes an HTTP status, headers, and body. The body -may either be _fixed-sized_ or _streaming_. The given `Responder` implementation -decides which to use. For instance, `String` uses a fixed-sized body, while -`File` uses a streamed response. Responders may dynamically adjust their -responses according to the incoming `Request` they are responding to. - -[`Response`]: https://api.rocket.rs/rocket/response/struct.Response.html - -### Wrapping - -Before we describe a few responders, we note that it is typical for responders -to _wrap_ other responders. That is, responders can be of the following form, -where `R` is some type that implements `Responder`: - -```rust -struct WrappingResponder<R>(R); -``` - -A wrapping responder modifies the response returned by `R` before responding -with that same response. For instance, Rocket provides `Responder`s in the -[`status` module](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 -use rocket::response::status; - -#[post("/<id>")] -fn new(id: usize) -> status::Accepted<String> { - status::Accepted(Some(format!("id: '{}'", id))) -} -``` - -Similarly, the types in the [`content` -module](https://api.rocket.rs/rocket/response/content/index.html) can be used to -override the Content-Type of a response. For instance, to set the Content-Type -of `&'static str` to JSON, you can use the [`content::Json`] type as follows: - -```rust -use rocket::response::content; - -#[get("/")] -fn json() -> content::Json<&'static str> { - content::Json("{ 'hi': 'world' }") -} -``` - -[`Accepted`]: https://api.rocket.rs/rocket/response/status/struct.Accepted.html -[`content::Json`]: https://api.rocket.rs/rocket/response/content/struct.Json.html - -### 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 -the request to the [error catcher](/guide/requests/#error-catchers) for the -given status code. - -If an error catcher has been registered for the given status code, Rocket will -invoke it. The catcher creates and returns a response to the client. If no error -catcher has been registered and the error status code is one of the standard -HTTP status code, a default error catcher will be used. Default error catchers -return an HTML page with the status code and description. If there is no catcher -for a custom status code, Rocket uses the **500** error catcher to return a -response. - -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 -would write: - -```rust -#[get("/")] -fn just_fail() -> Failure { - Failure(Status::NotAcceptable) -} -``` - -## Implementations - -Rocket implements `Responder` for many types in Rust's standard library -including `String`, `&str`, `File`, `Option`, and `Result`. The [`Responder`] -documentation describes these in detail, but we briefly cover a few here. - -### Strings - -The `Responder` implementations for `&str` and `String` are straight-forward: -the string is used as a sized body, and the Content-Type of the response is set -to `text/plain`. To get a taste for what such a `Responder` implementation looks -like, here's the implementation for `String`: - -```rust -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() - } -} -``` - -Because of these implementations, you can directly return an `&str` or `String` -type from a handler: - -```rust -#[get("/string")] -fn handler() -> &'static str { - "Hello there! I'm a string!" -} -``` - -### `Option` - -`Option` is a _wrapping_ responder: an `Option<T>` can only be returned when `T` -implements `Responder`. If the `Option` is `Some`, the wrapped responder is used -to respond to the client. Otherwise, a error of **404 - Not Found** is returned -to the client. - -This implementation makes `Option` a convenient type to return when it is not -known until process-time whether content exists. For example, because of -`Option`, we can implement a file server that returns a `200` when a file is -found and a `404` when a file is not found in just 4, idiomatic lines: - -```rust -#[get("/<file..>")] -fn files(file: PathBuf) -> Option<NamedFile> { - NamedFile::open(Path::new("static/").join(file)).ok() -} -``` - -### `Result` - -`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. Revisiting our -file server, for instance, we might wish to provide more feedback to the user -when a file isn't found. We might do this as follows: - -```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`] 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")] -fn stream() -> io::Result<Stream<UnixStream>> { - UnixStream::connect("/path/to/my/socket").map(|s| Stream::from(s)) -} - -``` - -[`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. - -As 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.4.0-dev/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. - -When your application is compiled in `debug` mode (without the `--release` flag -passed to `cargo`), templates are automatically reloaded when they are modified. -This means that you don't need to rebuild your application to observe template -changes: simply refresh! In release builds, reloading is disabled. - -For templates to be properly registered, the template fairing must be attached -to the instance of Rocket. The [Fairings](/guide/fairings) sections of the guide -provides more information on fairings. To attach the template fairing, simply -call `.attach(Template::fairing())` on an instance of `Rocket` as follows: - -```rust -fn main() { - rocket::ignite() - .mount("/", routes![...]) - .attach(Template::fairing()); -} -``` - -The [`Template`] API documentation contains more information about templates, -including how to customize a template engine to add custom helpers and filters. -The [Handlebars Templates example on -GitHub](https://github.com/SergioBenitez/Rocket/tree/v0.4.0-dev/examples/handlebars_templates) -is a fully composed application that makes use of Handlebars templates. - -[`Template`]: https://api.rocket.rs/rocket_contrib/struct.Template.html -[configurable]: /guide/configuration/#extras diff --git a/site/guide/state.md b/site/guide/state.md @@ -1,258 +0,0 @@ -# State - -Many web applications have a need to maintain state. This can be as simple as -maintaining a counter for the number of visits or as complex as needing to -access job queues and multiple databases. Rocket provides the tools to enable -these kinds of interactions in a safe and simple manner. - -## Managed State - -The enabling feature for maintaining state is _managed state_. Managed state, as -the name implies, is state that Rocket manages for your application. The state -is managed on a per-type basis: Rocket will manage at most one value of a given -type. - -The process for using managed state is simple: - - 1. Call `manage` on the `Rocket` instance corresponding to your application - with the initial value of the state. - 2. Add a `State<T>` type to any request handler, where `T` is the type of the - value passed into `manage`. - -### Adding State - -To instruct Rocket to manage state for your application, call the -[`manage`](https://api.rocket.rs/rocket/struct.Rocket.html#method.manage) method -on an instance of `Rocket`. For example, to ask Rocket to manage a `HitCount` -structure with an internal `AtomicUsize` with an initial value of `0`, we can -write the following: - -```rust -struct HitCount { - count: AtomicUsize -} - -rocket::ignite().manage(HitCount { count: AtomicUsize::new(0) }); -``` - -The `manage` method can be called any number of times as long as each call -refers to a value of a different type. For instance, to have Rocket manage both -a `HitCount` value and a `Config` value, we can write: - -```rust -rocket::ignite() - .manage(HitCount { count: AtomicUsize::new(0) }) - .manage(Config::from(user_input)); -``` - -### Retrieving State - -State that is being managed by Rocket can be retrieved via the -[`State`](https://api.rocket.rs/rocket/struct.State.html) type: a [request -guard](/guide/requests/#request-guards) for managed state. To use the request -guard, add a `State<T>` type to any request handler, where `T` is the type of -the managed state. For example, we can retrieve and respond with the current -`HitCount` in a `count` route as follows: - -```rust -#[get("/count")] -fn count(hit_count: State<HitCount>) -> String { - let current_count = hit_count.count.load(Ordering::Relaxed); - format!("Number of visits: {}", current_count) -} -``` - -You can retrieve more than one `State` type in a single route as well: - -```rust -#[get("/state")] -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. - -You can find a complete example using the `HitCount` structure in the [state -example on -GitHub](https://github.com/SergioBenitez/Rocket/tree/v0.4.0-dev/examples/state) and -learn more about the [`manage` -method](https://api.rocket.rs/rocket/struct.Rocket.html#method.manage) and -[`State` type](https://api.rocket.rs/rocket/struct.State.html) in the API docs. - -### Within Guards - -It can also be useful to retrieve managed state from a `FromRequest` -implementation. To do so, simply invoke `State<T>` as a guard using the -[`Request::guard()`] method. - -```rust -fn from_request(req: &'a Request<'r>) -> request::Outcome<T, ()> { - let hit_count_state = req.guard::<State<HitCount>>()?; - let current_count = hit_count_state.count.load(Ordering::Relaxed); - ... -} -``` - -[`Request::guard()`]: https://api.rocket.rs/rocket/struct.Request.html#method.guard - -### 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 -once the request is completed. Request-local state can be used whenever a -`Request` is available, such as in a fairing, a request guard, or a responder. - -Request-local state is *cached*: if data of a given type has already been -stored, it will be reused. This is especially useful for request guards that -might be invoked multiple times during routing and processing of a single -request, such as those that deal with authentication. - -As an example, consider the following request guard implementation for -`RequestId` that uses request-local state to generate and expose a unique -integer ID per request: - -```rust -/// A global atomic counter for generating IDs. -static request_id_counter: AtomicUsize = AtomicUsize::new(0); - -/// A type that represents a request's ID. -struct RequestId(pub usize); - -/// Returns the current request's ID, assigning one only as necessary. -impl<'a, 'r> FromRequest<'a, 'r> for RequestId { - fn from_request(request: &'a Request<'r>) -> request::Outcome { - // The closure passed to `local_cache` will be executed at most once per - // request: the first time the `RequestId` guard is used. If it is - // requested again, `local_cache` will return the same value. - Outcome::Success(request.local_cache(|| { - RequestId(request_id_counter.fetch_add(1, Ordering::Relaxed)) - })) - } -} -``` - -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. - -For more examples, see the [`FromRequest`] documentation, which uses -request-local state to cache expensive authentication and authorization -computations, and the [`Fairing`] documentation, which uses request-local state -to implement request timing. - -[`FromRequest`]: https://api.rocket.rs/rocket/request/trait.FromRequest.htmll#request-local-state -[`Fairing`]: https://api.rocket.rs/rocket/fairing/trait.Fairing.html#request-local-state - -## Databases - -Rocket includes built-in, ORM-agnostic support for databases. In particular, -Rocket provides a procedural macro that allows you to easily connect your Rocket -application to databases through connection pools. A _database connection pool_ -is a data structure that maintains active database connections for later use in -the application. This implementation of connection pooling support is based on -[`r2d2`] and exposes connections through request guards. Databases are -individually configured through Rocket's regular configuration mechanisms: a -`Rocket.toml` file, environment variables, or procedurally. - -Connecting your Rocket application to a database using this library occurs in -three simple steps: - - 1. Configure the databases in `Rocket.toml`. - 2. Associate a request guard type and fairing with each database. - 3. Use the request guard to retrieve a connection in a handler. - -Presently, Rocket provides built-in support for the following databases: - -| Kind | Driver | `Poolable` Type | Feature | -|----------|-----------------------|--------------------------------|------------------------| -| MySQL | [Diesel] | [`diesel::MysqlConnection`] | `diesel_mysql_pool` | -| MySQL | [`rust-mysql-simple`] | [`mysql::conn`] | `mysql_pool` | -| Postgres | [Diesel] | [`diesel::PgConnection`] | `diesel_postgres_pool` | -| Postgres | [Rust-Postgres] | [`postgres::Connection`] | `postgres_pool` | -| Sqlite | [Diesel] | [`diesel::SqliteConnection`] | `diesel_sqlite_pool` | -| Sqlite | [`Rustqlite`] | [`rusqlite::Connection`] | `sqlite_pool` | -| Neo4j | [`rusted_cypher`] | [`rusted_cypher::GraphClient`] | `cypher_pool` | -| Redis | [`redis-rs`] | [`redis::Connection`] | `redis_pool` | - -[`r2d2`]: https://crates.io/crates/r2d2 -[Diesel]: https://diesel.rs -[`redis::Connection`]: https://docs.rs/redis/0.9.0/redis/struct.Connection.html -[`rusted_cypher::GraphClient`]: https://docs.rs/rusted_cypher/1.1.0/rusted_cypher/graph/struct.GraphClient.html -[`rusqlite::Connection`]: https://docs.rs/rusqlite/0.13.0/rusqlite/struct.Connection.html -[`diesel::SqliteConnection`]: http://docs.diesel.rs/diesel/prelude/struct.SqliteConnection.html -[`postgres::Connection`]: https://docs.rs/postgres/0.15.2/postgres/struct.Connection.html -[`diesel::PgConnection`]: http://docs.diesel.rs/diesel/pg/struct.PgConnection.html -[`mysql::conn`]: https://docs.rs/mysql/14.0.0/mysql/struct.Conn.html -[`diesel::MysqlConnection`]: http://docs.diesel.rs/diesel/mysql/struct.MysqlConnection.html -[`redis-rs`]: https://github.com/mitsuhiko/redis-rs -[`rusted_cypher`]: https://github.com/livioribeiro/rusted-cypher -[`Rustqlite`]: https://github.com/jgallagher/rusqlite -[Rust-Postgres]: https://github.com/sfackler/rust-postgres -[`rust-mysql-simple`]: https://github.com/blackbeam/rust-mysql-simple -[`diesel::PgConnection`]: http://docs.diesel.rs/diesel/pg/struct.PgConnection.html - -### Usage - -To connect your Rocket application to a given database, first identify the -"Kind" and "Driver" in the table that matches your environment. The feature -corresponding to your database type must be enabled. This is the feature -identified in the "Feature" column. For instance, for Diesel-based SQLite -databases, you'd write in `Cargo.toml`: - -```toml -[dependencies.rocket_contrib] -version = "0.4.0-dev" -default-features = false -features = ["diesel_sqlite_pool"] -``` - -Then, in `Rocket.toml` or the equivalent via environment variables, configure -the URL for the database in the `databases` table: - -```toml -[global.databases] -sqlite_logs = { url = "/path/to/database.sqlite" } -``` - -In your application's source code, create a unit-like struct with one internal -type. This type should be the type listed in the "`Poolable` Type" column. Then -decorate the type with the `#[database]` attribute, providing the name of the -database that you configured in the previous step as the only parameter. -Finally, attach the fairing returned by `YourType::fairing()`, which was -generated by the `#[database]` attribute: - -```rust -use rocket_contrib::databases::{database, diesel}; - -#[database("sqlite_logs")] -struct LogsDbConn(diesel::SqliteConnection); - -fn main() { - rocket::ignite() - .attach(LogsDbConn::fairing()) - .launch(); -} -``` - -That's it! Whenever a connection to the database is needed, use your type as a -request guard: - -```rust -impl Logs { - fn by_id(conn: &diesel::SqliteConnection, log_id: usize) -> Result<Logs> { - logs.filter(id.eq(log_id)).load(conn) - } -} - -#[get("/logs/<id>")] -fn get_logs(conn: LogsDbConn, id: usize) -> Result<Logs> { - Logs::by_id(&conn, id) -} -``` - -For more on Rocket's built-in database support, see the -[`rocket_contrib::databases`] module documentation. - -[`rocket_contrib::databases`]: https://api.rocket.rs/rocket_contrib/databases/index.html diff --git a/site/guide/testing.md b/site/guide/testing.md @@ -1,209 +0,0 @@ -# Testing - -Every application should be well tested and understandable. Rocket provides the -tools to perform unit and integration tests. It also provides a means to inspect -code generated by Rocket. - -## Local Dispatching - -Rocket applications are tested by dispatching requests to a local instance of -`Rocket`. The [`local`] module contains all of the structures necessary to do -so. In particular, it contains a [`Client`] structure that is used to create -[`LocalRequest`] structures that can be dispatched against a given [`Rocket`] -instance. Usage is straightforward: - - 1. Construct a `Rocket` instance that represents the application. - - ```rust - let rocket = rocket::ignite(); - ``` - - 2. Construct a `Client` using the `Rocket` instance. - - ```rust - let client = Client::new(rocket).expect("valid rocket instance"); - ``` - - 3. Construct requests using the `Client` instance. - - ```rust - let req = client.get("/"); - ``` - - 4. Dispatch the request to retrieve the response. - - ```rust - let response = req.dispatch(); - ``` - -[`local`]: https://api.rocket.rs/rocket/local/index.html -[`Client`]: https://api.rocket.rs/rocket/local/struct.Client.html -[`LocalRequest`]: https://api.rocket.rs/rocket/local/struct.LocalRequest.html -[`Rocket`]: https://api.rocket.rs/rocket/struct.Rocket.html - -## Validating Responses - -A `dispatch` of a `LocalRequest` returns a [`LocalResponse`] which can be used -transparently as a [`Response`] value. During testing, the response is usually -validated against expected properties. These includes things like the response -HTTP status, the inclusion of headers, and expected body data. - -The [`Response`] type provides methods to ease this sort of validation. We list -a few below: - - * [`status`]: returns the HTTP status in the response. - * [`content_type`]: returns the Content-Type header in the response. - * [`headers`]: returns a map of all of the headers in the response. - * [`body_string`]: returns the body data as a `String`. - * [`body_bytes`]: returns the body data as a `Vec<u8>`. - -[`LocalResponse`]: https://api.rocket.rs/rocket/local/struct.LocalResponse.html -[`Response`]: https://api.rocket.rs/rocket/struct.Response.html -[`status`]: https://api.rocket.rs/rocket/struct.Response.html#method.status -[`content_type`]: https://api.rocket.rs/rocket/struct.Response.html#method.content_type -[`headers`]: https://api.rocket.rs/rocket/struct.Response.html#method.headers -[`body_string`]: https://api.rocket.rs/rocket/struct.Response.html#method.body_string -[`body_bytes`]: https://api.rocket.rs/rocket/struct.Response.html#method.body_bytes - -These methods are typically used in combination with the `assert_eq!` or -`assert!` macros as follows: - -```rust -let rocket = rocket::ignite(); -let client = Client::new(rocket).expect("valid rocket instance"); -let mut response = client.get("/").dispatch(); - -assert_eq!(response.status(), Status::Ok); -assert_eq!(response.content_type(), Some(ContentType::Plain)); -assert!(response.headers().get_one("X-Special").is_some()); -assert_eq!(response.body_string(), Some("Expected Body.".into())); -``` - -## Testing "Hello, world!" - -To solidify an intuition for how Rocket applications are tested, we walk through -how to test the "Hello, world!" application below: - -```rust -#[get("/")] -fn hello() -> &'static str { - "Hello, world!" -} - -fn rocket() -> Rocket { - rocket::ignite().mount("/", routes![hello]) -} - -fn main() { - rocket().launch(); -} -``` - -Notice that we've separated the _creation_ of the `Rocket` instance from the -_launch_ of the instance. As you'll soon see, this makes testing our application -easier, less verbose, and less error-prone. - -### Setting Up - -First, we'll create a `test` module with the proper imports: - -```rust -#[cfg(test)] -mod test { - use super::rocket; - use rocket::local::Client; - use rocket::http::Status; - - #[test] - fn hello_world() { - ... - } -} -``` - -You can also move the body of the `test` module into its own file, say -`tests.rs`, and then import the module into the main file using: - -```rust -#[cfg(test)] mod tests; -``` - -### Testing - -To test our "Hello, world!" application, we first create a `Client` for our -`Rocket` instance. It's okay to use methods like `expect` and `unwrap` during -testing: we _want_ our tests to panic when something goes wrong. - -```rust -let client = Client::new(rocket()).expect("valid rocket instance"); -``` - -Then, we create a new `GET /` request and dispatch it, getting back our -application's response: - -```rust -let mut response = client.get("/").dispatch(); -``` - -Finally, we ensure that the response contains the information we expect it to. -Here, we want to ensure two things: - - 1. The status is `200 OK`. - 2. The body is the string "Hello, world!". - -We do this by checking the `Response` object directly: - -```rust -assert_eq!(response.status(), Status::Ok); -assert_eq!(response.body_string(), Some("Hello, world!".into())); -``` - -That's it! Altogether, this looks like: - -```rust -#[cfg(test)] -mod test { - use super::rocket; - use rocket::local::Client; - use rocket::http::Status; - - #[test] - fn hello_world() { - let client = Client::new(rocket()).expect("valid rocket instance"); - let mut response = client.get("/").dispatch(); - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.body_string(), Some("Hello, world!".into())); - } -} -``` - -The tests can be run with `cargo test`. You can find the full source code to -[this example on -GitHub](https://github.com/SergioBenitez/Rocket/tree/v0.4.0-dev/examples/testing). - -## Codegen Debug - -It can be useful to inspect the code that Rocket's code generation is emitting, -especially when you get a strange type error. To have Rocket log the code that -it is emitting to the console, set the `ROCKET_CODEGEN_DEBUG` environment -variable when compiling: - -```rust -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) -} -``` - -This corresponds to the facade request handler Rocket has generated for the -`hello` route. diff --git a/site/index.toml b/site/index.toml @@ -12,21 +12,21 @@ title = "Type Safe" text = "From request to response Rocket ensures that your types mean something." image = "helmet" button = "Learn More" -url = "/overview/#how-rocket-works" +url = "overview/#how-rocket-works" [[top_features]] title = "Boilerplate Free" text = "Spend your time writing code that really matters, and let Rocket generate the rest." image = "robot-free" button = "See Examples" -url = "/overview/#anatomy-of-a-rocket-application" +url = "overview/#anatomy-of-a-rocket-application" [[top_features]] title = "Easy To Use" text = "Rocket makes extensive use of Rust's code generation tools to provide a clean API." image = "sun" button = "Get Started" -url = "/guide" +url = "guide" margin = 2 [[top_features]] @@ -34,7 +34,7 @@ title = "Extensible" text = "Easily create your own primitives that any Rocket application can use." image = "telescope" button = "See How" -url = "/overview/#anatomy-of-a-rocket-application" +url = "overview/#anatomy-of-a-rocket-application" margin = 9 ############################################################################### @@ -134,7 +134,7 @@ text = ''' title = 'Templating' text = "Rocket makes rendering templates a breeze with built-in templating support." image = 'templating-icon' -url = '/guide/responses/#templates' +url = 'guide/responses/#templates' button = 'Learn More' color = 'blue' @@ -142,7 +142,7 @@ color = 'blue' title = 'Cookies' text = "View, add, or remove cookies, with or without encryption, without hassle." image = 'cookies-icon' -url = '/guide/requests/#cookies' +url = 'guide/requests/#cookies' button = 'Learn More' color = 'purple' margin = -6 @@ -151,7 +151,7 @@ margin = -6 title = 'Streams' text = "Rocket streams all incoming and outgoing data, so size isn't a concern." image = 'streams-icon' -url = '/guide/requests/#streaming' +url = 'guide/requests/#streaming' button = 'Learn More' color = 'red' margin = -29 @@ -160,7 +160,7 @@ margin = -29 title = 'Config Environments' text = "Configure your application your way for development, staging, and production." image = 'config-icon' -url = '/guide/configuration/#environment' +url = 'guide/configuration/#environment' button = 'Learn More' color = 'yellow' margin = -3 @@ -169,7 +169,7 @@ margin = -3 title = 'Query Strings' text = "Handling query strings and parameters is type-safe and easy in Rocket." image = 'query-icon' -url = '/guide/requests/#query-strings' +url = 'guide/requests/#query-strings' button = 'Learn More' color = 'orange' margin = -3 @@ -178,7 +178,7 @@ margin = -3 title = 'Testing Library' text = "Unit test your applications with ease using the built-in testing library." image = 'testing-icon' -url = '/guide/testing#testing' +url = 'guide/testing#testing' button = 'Learn More' color = 'green' diff --git a/site/news/2017-02-06-version-0.2.md b/site/news/2017-02-06-version-0.2.md @@ -46,8 +46,8 @@ state's type in the function signature. It works in two easy steps: value passed into `manage`. Rocket takes care of the rest! `State` works through Rocket's [request -guards](/guide/requests/#request-guards). You can call `manage` any number of -times, as long as each call corresponds to a value of a different type. +guards](../../guide/requests/#request-guards). You can call `manage` any number +of times, as long as each call corresponds to a value of a different type. As a simple example, consider the following "hit counter" example application: @@ -110,16 +110,15 @@ help: maybe add a call to 'manage' here? | ^^^^^^^^^^^^^^^^ ``` -You can read more about managed state in the [guide](/guide/state/), the API -docs for -[manage](https://api.rocket.rs/rocket/struct.Rocket.html#method.manage), and the -API docs for [State](https://api.rocket.rs/rocket/struct.State.html). +You can read more about managed state in the [guide](../../guide/state/), the +API docs for [manage](@api/rocket/struct.Rocket.html#method.manage), and the API +docs for [State](@api/rocket/struct.State.html). ### Unmounted Routes Lint A common mistake that new Rocketeers make is forgetting to -[mount](/guide/overview/#mounting) declared routes. In Rocket v0.2, Rocket adds -a _lint_ that results in a compile-time warning for unmounted routes. As a +[mount](../../guide/overview/#mounting) declared routes. In Rocket v0.2, Rocket +adds a _lint_ that results in a compile-time warning for unmounted routes. As a simple illustration, consider the canonical "Hello, world!" Rocket application below, and note that we've forgotten to mount the `hello` route: @@ -157,8 +156,7 @@ help: maybe add a call to 'mount' here? The lint can be disabled selectively per route by adding an `#[allow(unmounted_route)]` annotation to a given route declaration. It can also be disabled globally by adding `#![allow(unmounted_route)]`. You can read more -about this lint in the [codegen -documentation](https://api.rocket.rs/rocket_codegen/index.html). +about this lint in the [codegen documentation](@api/rocket_codegen/index.html). ### Configuration via Environment Variables @@ -176,7 +174,7 @@ Configuration parameters set via environment variables take precedence over parameters set via the `Rocket.toml` configuration file. Note that _any_ parameter can be set via an environment variable, include _extras_. For more about configuration in Rocket, see the [configuration section of the -guide](/guide/overview/#configuration). +guide](../../guide/overview/#configuration). ### And Plenty More! @@ -392,5 +390,5 @@ contributing! Not already using Rocket? Rocket is extensively documented, making it easy for you to start writing your web applications in Rocket! See the -[overview](/overview) or start writing code immediately by reading through [the -guide](/guide). +[overview](../../overview) or start writing code immediately by reading through +[the guide](../../guide). diff --git a/site/news/2017-07-14-version-0.3.md b/site/news/2017-07-14-version-0.3.md @@ -21,8 +21,8 @@ sacrificing flexibility or type safety. All with minimal code. Not already using Rocket? Join the thousands of users and dozens of companies happily using Rocket today! Rocket's extensive documentation makes it easy. Get -started now by [reading through the guide](/guide) or learning more from [the -overview](/overview). +started now by [reading through the guide](../../guide) or learning more from +[the overview](../../overview). ## What's New? @@ -61,7 +61,7 @@ limitations and abilities, and includes implementation examples. I encourage you to experiment with fairings and report your experiences. As always, feedback is instrumental in solidifying a robust design. -[`Fairing`]: https://api.rocket.rs/rocket/fairing/trait.Fairing.html +[`Fairing`]: @api/rocket/fairing/trait.Fairing.html [fairings guide]: /guide/fairings ### Native TLS Support @@ -126,10 +126,10 @@ automatically generates a fresh key at launch. For more details on private cookies, see the [private cookies] section of the guide. -[`Cookies`]: https://api.rocket.rs/rocket/http/enum.Cookies.html -[`get_private`]: https://api.rocket.rs/rocket/http/enum.Cookies.html#method.get_private -[`add_private`]: https://api.rocket.rs/rocket/http/enum.Cookies.html#method.add_private -[`remove_private`]: https://api.rocket.rs/rocket/http/enum.Cookies.html#method.remove_private +[`Cookies`]: @api/rocket/http/enum.Cookies.html +[`get_private`]: @api/rocket/http/enum.Cookies.html#method.get_private +[`add_private`]: @api/rocket/http/enum.Cookies.html#method.add_private +[`remove_private`]: @api/rocket/http/enum.Cookies.html#method.remove_private [private cookies]: /guide/requests/#private-cookies ### Form Field Naming @@ -163,9 +163,9 @@ struct External { Rocket will automatically match the form field named "type" to the structure field named `api_type`. For more details on form field naming, see the [field -renaming](/guide/requests/#field-renaming) section of the guide. +renaming](../../guide/requests/#field-renaming) section of the guide. -[`FromForm`]: https://api.rocket.rs/rocket/request/trait.FromForm.html +[`FromForm`]: @api/rocket/request/trait.FromForm.html ### And Plenty More! @@ -189,7 +189,7 @@ following new features: * [`Response::content_type()`] was added to retrieve the Content-Type header of a response. * Data limits on incoming data are [now - configurable](/guide/configuration/#data-limits). + configurable](../../guide/configuration/#data-limits). * [`Request::limits()`] was added to retrieve incoming data limits. * Responders may dynamically adjust their response based on the incoming request. @@ -211,28 +211,28 @@ following new features: * The [`NotFound`] responder was added for simple **404** response construction. -[`MsgPack`]: https://api.rocket.rs/rocket_contrib/struct.MsgPack.html -[`Rocket::launch()`]: https://api.rocket.rs/rocket/struct.Rocket.html#method.launch -[`LaunchError`]: https://api.rocket.rs/rocket/error/struct.LaunchError.html -[Default rankings]: https://api.rocket.rs/rocket/struct.Route.html -[`&Route`]: https://api.rocket.rs/rocket/struct.Route.html -[`Route`]: https://api.rocket.rs/rocket/struct.Route.html -[`Accept`]: https://api.rocket.rs/rocket/http/struct.Accept.html -[`Request::accept()`]: https://api.rocket.rs/rocket/struct.Request.html#method.accept -[`contrib`]: https://api.rocket.rs/rocket_contrib/ -[`Rocket::routes()`]: https://api.rocket.rs/rocket/struct.Rocket.html#method.routes -[`Response::body_string()`]: https://api.rocket.rs/rocket/struct.Response.html#method.body_string -[`Response::body_bytes()`]: https://api.rocket.rs/rocket/struct.Response.html#method.body_bytes -[`Response::content_type()`]: https://api.rocket.rs/rocket/struct.Response.html#method.content_type -[`Request::guard()`]: https://api.rocket.rs/rocket/struct.Request.html#method.guard -[`Request::limits()`]: https://api.rocket.rs/rocket/struct.Request.html#method.limits -[`Request::route()`]: https://api.rocket.rs/rocket/struct.Request.html#method.route -[`Config`]: https://api.rocket.rs/rocket/struct.Config.html -[`Cookies`]: https://api.rocket.rs/rocket/http/enum.Cookies.html -[`Config::get_datetime()`]: https://api.rocket.rs/rocket/struct.Config.html#method.get_datetime -[`LenientForm`]: https://api.rocket.rs/rocket/request/struct.LenientForm.html -[configuration parameters]: https://api.rocket.rs/rocket/config/index.html#environment-variables -[`NotFound`]: https://api.rocket.rs/rocket/response/status/struct.NotFound.html +[`MsgPack`]: @api/rocket_contrib/struct.MsgPack.html +[`Rocket::launch()`]: @api/rocket/struct.Rocket.html#method.launch +[`LaunchError`]: @api/rocket/error/struct.LaunchError.html +[Default rankings]: @api/rocket/struct.Route.html +[`&Route`]: @api/rocket/struct.Route.html +[`Route`]: @api/rocket/struct.Route.html +[`Accept`]: @api/rocket/http/struct.Accept.html +[`Request::accept()`]: @api/rocket/struct.Request.html#method.accept +[`contrib`]: @api/rocket_contrib/ +[`Rocket::routes()`]: @api/rocket/struct.Rocket.html#method.routes +[`Response::body_string()`]: @api/rocket/struct.Response.html#method.body_string +[`Response::body_bytes()`]: @api/rocket/struct.Response.html#method.body_bytes +[`Response::content_type()`]: @api/rocket/struct.Response.html#method.content_type +[`Request::guard()`]: @api/rocket/struct.Request.html#method.guard +[`Request::limits()`]: @api/rocket/struct.Request.html#method.limits +[`Request::route()`]: @api/rocket/struct.Request.html#method.route +[`Config`]: @api/rocket/struct.Config.html +[`Cookies`]: @api/rocket/http/enum.Cookies.html +[`Config::get_datetime()`]: @api/rocket/struct.Config.html#method.get_datetime +[`LenientForm`]: @api/rocket/request/struct.LenientForm.html +[configuration parameters]: @api/rocket/config/index.html#environment-variables +[`NotFound`]: @api/rocket/response/status/struct.NotFound.html ## Breaking Changes @@ -267,9 +267,9 @@ In addition to new features, Rocket saw the following improvements: * The format of a request is always logged when available. [`yansi`]: https://crates.io/crates/yansi -[`Request`]: https://api.rocket.rs/rocket/struct.Request.html -[`State`]: https://api.rocket.rs/rocket/struct.State.html -[`Config`]: https://api.rocket.rs/rocket/struct.Config.html +[`Request`]: @api/rocket/struct.Request.html +[`State`]: @api/rocket/struct.State.html +[`Config`]: @api/rocket/struct.Config.html ## What's Next? diff --git a/site/news.toml b/site/news/index.toml diff --git a/site/overview.toml b/site/overview.toml @@ -47,7 +47,7 @@ Each dynamic parameter (`name` and `age`) must have a type, here `&str` and `u8`, respectively. Rocket will attempt to parse the string in the parameter's position in the path into that type. The route will only be called if parsing succeeds. To parse the string, Rocket uses the -[FromParam](https://api.rocket.rs/rocket/request/trait.FromParam.html) trait, +[FromParam](@api/rocket/request/trait.FromParam.html) trait, which you can implement for your own types! ''' @@ -55,7 +55,7 @@ which you can implement for your own types! name = "Handling Data" content = ''' Request body data is handled in a special way in Rocket: via the -[FromData](https://api.rocket.rs/rocket/data/trait.FromData.html) trait. Any +[FromData](@api/rocket/data/trait.FromData.html) trait. Any type that implements `FromData` can be derived from incoming body data. To tell Rocket that you're expecting request body data, the `data` route argument is used with the name of the parameter in the request handler: @@ -69,13 +69,13 @@ fn login(user_form: Form<UserLogin>) -> String { The `login` route above says that it expects `data` of type `Form<UserLogin>` in the `user_form` parameter. The -[Form](https://api.rocket.rs/rocket/request/struct.Form.html) type is a built-in +[Form](@api/rocket/request/struct.Form.html) type is a built-in Rocket type that knows how to parse web forms into structures. Rocket will automatically attempt to parse the request body into the `Form` and call the `login` handler if parsing succeeds. Other built-in `FromData` types include -[`Data`](https://api.rocket.rs/rocket/struct.Data.html), -[`Json`](https://api.rocket.rs/rocket_contrib/struct.Json.html), and -[`Flash`](https://api.rocket.rs/rocket/response/struct.Flash.html) +[`Data`](@api/rocket/struct.Data.html), +[`Json`](@api/rocket_contrib/struct.Json.html), and +[`Flash`](@api/rocket/response/struct.Flash.html) ''' [[panels]] @@ -99,7 +99,7 @@ fn sensitive(key: ApiKey) -> &'static str { ... } `ApiKey` protects the `sensitive` handler from running incorrectly. In order for Rocket to call the `sensitive` handler, the `ApiKey` type needs to be derived through a -[FromRequest](https://api.rocket.rs/rocket/request/trait.FromRequest.html) +[FromRequest](@api/rocket/request/trait.FromRequest.html) implementation, which in this case, validates the API key header. Request guards are a powerful and unique Rocket concept; they centralize application policy and invariants through types. @@ -109,7 +109,7 @@ invariants through types. name = "Responders" content = ''' The return type of a request handler can be any type that implements -[Responder](https://api.rocket.rs/rocket/response/trait.Responder.html): +[Responder](@api/rocket/response/trait.Responder.html): ```rust #[get("/")] @@ -119,12 +119,12 @@ fn route() -> T { ... } Above, T must implement `Responder`. Rocket implements `Responder` for many of the standard library types including `&str`, `String`, `File`, `Option`, and `Result`. Rocket also implements custom responders such as -[Redirect](https://api.rocket.rs/rocket/response/struct.Redirect.html), -[Flash](https://api.rocket.rs/rocket/response/struct.Flash.html), and -[Template](https://api.rocket.rs/rocket_contrib/struct.Template.html). +[Redirect](@api/rocket/response/struct.Redirect.html), +[Flash](@api/rocket/response/struct.Flash.html), and +[Template](@api/rocket_contrib/struct.Template.html). The task of a `Responder` is to generate a -[`Response`](https://api.rocket.rs/rocket/response/struct.Response.html), if +[`Response`](@api/rocket/response/struct.Response.html), if possible. `Responder`s can fail with a status code. When they do, Rocket calls the corresponding error catcher, a `catch` route, which can be declared as follows: