Improve async, mount sections of overview guide.

Resolves #1458.
Closes #1464.
This commit is contained in:
Sergio Benitez 2020-11-03 12:50:31 -08:00
parent 86ff66a69c
commit 86a6b4b12f
1 changed files with 126 additions and 65 deletions

View File

@ -102,32 +102,50 @@ Before Rocket can dispatch requests to a route, the route needs to be _mounted_:
# "hello, world!" # "hello, world!"
# } # }
fn main() { rocket::ignite().mount("/hello", routes![world]);
rocket::ignite().mount("/hello", routes![world]);
}
``` ```
The `mount` method takes as input: The `mount` method takes as input:
1. A _base_ path to namespace a list of routes under, here, `"/hello"`. 1. A _base_ path to namespace a list of routes under, here, `/hello`.
2. A list of routes via the `routes!` macro: here, `routes![world]`, with 2. A list of routes via the `routes!` macro: here, `routes![world]`, with
multiple routes: `routes![a, b, c]`. multiple routes: `routes![a, b, c]`.
This creates a new `Rocket` instance via the `ignite` function and mounts the This creates a new `Rocket` instance via the `ignite` function and mounts the
`world` route to the `"/hello"` path, making Rocket aware of the route. `GET` `world` route to the `/hello` base path, making Rocket aware of the route.
requests to `"/hello/world"` will be directed to the `world` function. `GET` requests to `/hello/world` will be directed to the `world` function.
The `mount` method, like all other builder methods on `Rocket`, can be chained
any number of times, and routes can be reused by mount points:
```rust
# #[macro_use] extern crate rocket;
# #[get("/world")]
# fn world() -> &'static str {
# "hello, world!"
# }
rocket::ignite()
.mount("/hello", routes![world])
.mount("/hi", routes![world]);
```
By mounting `world` to both `/hello` and `/hi`, requests to `"/hello/world"`
_and_ `"/hi/world"` will be directed to the `world` function.
! note: In many cases, the base path will simply be `"/"`. ! note: In many cases, the base path will simply be `"/"`.
## Launching ## Launching
Now that Rocket knows about the route, you can tell Rocket to start accepting Rocket begins serving requests after being _launched_, which starts a
requests via the `launch` method. The method starts up the server and waits for multi-threaded asynchronous server and dispatches requests to matching routes as
incoming requests. When a request arrives, Rocket finds the matching route and they arrive.
dispatches the request to the route's handler.
We typically use `#[launch]`, which generates a `main` function. There are two mechnisms by which a `Rocket` can be launched. The first and
Our complete _Hello, world!_ application thus looks like: preferred approach is via the `#[launch]` route attribute, which generates a
`main` function that sets up an async runtime and starts the server. With
`#[launch]`, our complete _Hello, world!_ application looks like:
```rust ```rust
#[macro_use] extern crate rocket; #[macro_use] extern crate rocket;
@ -143,41 +161,78 @@ fn rocket() -> rocket::Rocket {
} }
``` ```
We've 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: Running the application, the console shows:
```sh ```sh
🔧 Configured for development. > cargo run
=> address: localhost 🔧 Configured for debug.
=> address: 127.0.0.1
=> port: 8000 => port: 8000
=> log: normal => workers: 64
=> workers: [logical cores * 2] => log level: normal
=> secret key: generated => secret key: [zero]
=> limits: forms = 32KiB => limits: forms = 32KiB
=> cli colors: true
=> keep-alive: 5s => keep-alive: 5s
=> tls: disabled => tls: disabled
🛰 Mounting '/hello': 🛰 Mounting /:
=> GET /hello/world (world) => GET / (hello)
🚀 Rocket has launched from http://localhost:8000 🚀 Rocket has launched from http://127.0.0.1:8000
``` ```
If we visit `localhost:8000/hello/world`, we see `Hello, world!`, exactly as we ! tip: You can also return `_` from a `#[launch]` function!
expected.
A version of this example's complete crate, ready to `cargo run`, can be found If you find it more pleasing, `#[launch]` can infer the return type of
on [GitHub](@example/hello_world). You can find dozens of other complete `Rocket` for you by using `_` as the return type:
examples, spanning all of Rocket's features, in the [GitHub examples
directory](@example/). `
#[launch] fn rocket() -> _ { /* ... */ }
`
If we visit `http://127.0.0.1:8000/hello/world`, we see `Hello, world!`, exactly
as we expected.
! note: This and other examples are on GitHub.
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/).
The second approach uses the `#[main]` route attribute. `#[main]` _also_
generates a `main` function that sets up an async runtime but unlike
`#[launch]`, allows _you_ to start the server:
```rust
# #[macro_use] extern crate rocket;
#
# #[get("/world")]
# fn world() -> &'static str {
# "Hello, world!"
# }
#[main]
async fn main() {
rocket::ignite()
.mount("/hello", routes![world])
.launch()
.await;
}
```
`#[main]` is useful when a handle to the `Future` returned by `launch()` is
desired, or when the return value of [`launch()`] is to be inspected. The
[errors example] for instance, inspects the return value.
[`launch()`]: @api/rocket/struct.Rocket.html#method.launch
[errors example]: @example/errors
## Futures and Async ## Futures and Async
Rocket uses Rust `Future`s for concurrency. Asynchronous programming with Rocket uses Rust [`Future`]s for concurrency. Asynchronous programming with
`Future`s and `async/await` allows route handlers to perform wait-heavy I/O such `Future`s and `async/await` allows route handlers to perform wait-heavy I/O such
as filesystem and network access while still allowing other requests to be as filesystem and network access while still allowing other requests to be make
processed. For an overview of Rust `Future`s, see [Asynchronous Programming in progress. For an overview of Rust `Future`s, see [Asynchronous Programming in
Rust](https://rust-lang.github.io/async-book/). Rust](https://rust-lang.github.io/async-book/).
In general, you should prefer to use async-ready libraries instead of In general, you should prefer to use async-ready libraries instead of
@ -185,53 +240,59 @@ synchronous equivalents inside Rocket applications.
`async` appears in several places in Rocket: `async` appears in several places in Rocket:
* [Routes](../requests) and [Error Catchers](../requests#error-catchers) can be * [Routes] and [Error Catchers] can be `async fn`s. Inside an `async fn`, you
`async fn`s. Inside an `async fn`, you can `.await` `Future`s from Rocket or can `.await` `Future`s from Rocket or other libraries.
other libraries * Several of Rocket's traits, such as [`FromData`] and [`FromRequest`], have
* Several of Rocket's traits, such as [`FromData`](../requests#body-data) and methods that return `Future`s.
[`FromRequest`](../requests#request-guards), have methods that return * [`Data`] and [`DataStream`], incoming request data, and `Response` and `Body`,
`Future`s. outgoing response data, are based on `tokio::io::AsyncRead` instead of
* `Data` and `DataStream` (incoming request data) and `Response` and `Body`
(outgoing response data) are based on `tokio::io::AsyncRead` instead of
`std::io::Read`. `std::io::Read`.
You can find async-ready libraries on [crates.io](https://crates.io) with the You can find async-ready libraries on [crates.io](https://crates.io) with the
`async` tag. `async` tag.
[`Future`]: @std/future/trait.Future.html
[`Data`]: @api/rocket/struct.Data.html
[`DataStream`]: @api/rocket/data/struct.DataStream.html
[Routes]: ../requests
[Error Catchers]: ../requests#error-catchers
[`FromData`]: ../requests#body-data
[`FromRequest`]: ../requests#request-guards
! note ! note
Rocket master uses the tokio (0.2) runtime. The runtime is started for you if Rocket master uses the tokio runtime. The runtime is started for you if you
you use `#[launch]` or `#[rocket::main]`, but you can still `launch()` a use `#[launch]` or `#[main]`, but you can still `launch()` a Rocket instance
rocket instance on a custom-built `Runtime`. on a custom-built runtime by not using _either_ attribute.
### Cooperative Multitasking ### Multitasking
Rust's `Future`s are a form of *cooperative multitasking*. In general, `Future`s Rust's `Future`s are a form of *cooperative multitasking*. In general, `Future`s
and `async fn`s should only `.await` on other operations and never block. Some and `async fn`s should only `.await` on operations and never block. Some common
common examples of blocking include locking mutexes, joining threads, or using examples of blocking include locking non-`async` mutexes, joining threads, or
non-`async` library functions (including those in `std`) that perform I/O. using non-`async` library functions (including those in `std`) that perform I/O.
If a `Future` or `async fn` blocks the thread, inefficient resource usage, If a `Future` or `async fn` blocks the thread, inefficient resource usage,
stalls, or sometimes even deadlocks can occur. stalls, or sometimes even deadlocks can occur.
! note Sometimes there is no good `async` alternative for a library or operation. If
necessary, you can convert a synchronous operation to an async one with
[`tokio::task::spawn_blocking`]:
Sometimes there is no good async alternative for a library or operation. If ```rust
necessary, you can convert a synchronous operation to an async one with # #[macro_use] extern crate rocket;
`tokio::task::spawn_blocking`: use std::io;
use rocket::tokio::task::spawn_blocking;
use rocket::response::Debug;
```rust #[get("/blocking_task")]
# #[macro_use] extern crate rocket; async fn blocking_task() -> Result<Vec<u8>, Debug<io::Error>> {
use std::io; // In a real app, use rocket::response::NamedFile or tokio::fs::File.
use rocket::tokio::task::spawn_blocking; let vec = spawn_blocking(|| std::fs::read("data.txt")).await
use rocket::response::Debug; .map_err(|e| io::Error::new(io::ErrorKind::Interrupted, e))??;
#[get("/blocking_task")] Ok(vec)
async fn blocking_task() -> Result<Vec<u8>, Debug<io::Error>> { }
// In a real app, we'd use rocket::response::NamedFile or tokio::fs::File. ```
let io_result = spawn_blocking(|| std::fs::read("data.txt")).await
.map_err(|join_err| io::Error::new(io::ErrorKind::Interrupted, join_err))?;
Ok(io_result?) [`tokio::task::spawn_blocking`]: @tokio/task/fn.spawn_blocking.html
}
```