So. Many. Changes. This is an insane commit: simultaneously one of the best (because of all the wonderful improvements!) and one of the worst (because it is just massive) in the project's history. Routing: * All UTF-8 characters are accepted everywhere in route paths. (#998) * `path` is now `uri` in `route` attribute: `#[route(GET, path = "..")]` becomes `#[route(GET, uri = "..")]`. Forms Revamp * All form related types now reside in a new `form` module. * Multipart forms are supported. (resolves #106) * Collections are supported in forms and queries. (resolves #205) * Nested structures in forms and queries are supported. (resolves #313) * Form fields can be ad-hoc validated with `#[field(validate = expr)]`. * `FromFormValue` is now `FromFormField`, blanket implements `FromForm`. * Form field values are always percent-decoded apriori. Temporary Files * A new `TempFile` data and form guard allows streaming data directly to a file which can then be persisted. * A new `temp_dir` config parameter specifies where to store `TempFile`. * The limits `file` and `file/$ext`, where `$ext` is the file extension, determines the data limit for a `TempFile`. Capped * A new `Capped` type is used to indicate when data has been truncated due to incoming data limits. It allows checking whether data is complete or truncated. * `DataStream` methods return `Capped` types. * `DataStream` API has been revamped to account for `Capped` types. * Several `Capped<T>` types implement `FromData`, `FromForm`. * HTTP 413 (Payload Too Large) errors are now returned when data limits are exceeded. (resolves #972) Hierarchical Limits * Data limits are now hierarchical, delimited with `/`. A limit of `a/b/c` falls back to `a/b` then `a`. Core * `&RawStr` no longer implements `FromParam`. * `&str` implements `FromParam`, `FromData`, `FromForm`. * `FromTransformedData` was removed. * `FromData` gained a lifetime for use with request-local data. * The default error HTML is more compact. * `&Config` is a request guard. * The `DataStream` interface was entirely revamped. * `State` is only exported via `rocket::State`. * A `request::local_cache!()` macro was added for storing values in request-local cache without consideration for type uniqueness by using a locally generated anonymous type. * `Request::get_param()` is now `Request::param()`. * `Request::get_segments()` is now `Request::segments()`, takes a range. * `Request::get_query_value()` is now `Request::query_value()`, can parse any `FromForm` including sequences. * `std::io::Error` implements `Responder` like `Debug<std::io::Error>`. * `(Status, R)` where `R: Responder` implements `Responder` by overriding the `Status` of `R`. * The name of a route is printed first during route matching. * `FlashMessage` now only has one lifetime generic. HTTP * `RawStr` implements `serde::{Serialize, Deserialize}`. * `RawStr` implements _many_ more methods, in particular, those related to the `Pattern` API. * `RawStr::from_str()` is now `RawStr::new()`. * `RawStr::url_decode()` and `RawStr::url_decode_lossy()` only allocate as necessary, return `Cow`. * `Status` implements `Default` with `Status::Ok`. * `Status` implements `PartialEq`, `Eq`, `Hash`, `PartialOrd`, `Ord`. * Authority and origin part of `Absolute` can be modified with new `Absolute::{with,set}_authority()`, `Absolute::{with,set}_origin()` methods. * `Origin::segments()` was removed in favor of methods split into query and path parts and into raw and decoded versions. * The `Segments` iterator is smarter, returns decoded `&str` items. * `Segments::into_path_buf()` is now `Segments::to_path_buf()`. * A new `QuerySegments` is the analogous query segment iterator. * Once set, `expires` on private cookies is not overwritten. (resolves #1506) * `Origin::path()` and `Origin::query()` return `&RawStr`, not `&str`. Codegen * Preserve more spans in `uri!` macro. * Preserve spans `FromForm` field types. * All dynamic parameters in a query string must typecheck as `FromForm`. * `FromFormValue` derive removed; `FromFormField` added. * The `form` `FromForm` and `FromFormField` field attribute is now named `field`. `#[form(field = ..)]` is now `#[field(name = ..)]`. Contrib * `Json` implements `FromForm`. * `MsgPack` implements `FromForm`. * The `json!` macro is exported as `rocket_contrib::json::json!`. * Added clarifying docs to `StaticFiles`. Examples * `form_validation` and `form_kitchen_sink` removed in favor of `forms`. * The `hello_world` example uses unicode in paths. * The `json` example only allocates as necessary. Internal * Codegen uses new `exports` module with the following conventions: - Locals starts with `__` and are lowercased. - Rocket modules start with `_` and are lowercased. - `std` types start with `_` and are titlecased. - Rocket types are titlecased. * A `header` module was added to `http`, contains header types. * `SAFETY` is used as doc-string keyword for `unsafe` related comments. * The `Uri` parser no longer recognizes Rocket route URIs.
11 KiB
Overview
Rocket provides primitives to build web servers and applications with Rust: Rocket provides routing, pre-processing of requests, and post-processing of responses; the rest is up to you. Your application code instructs Rocket on what to pre-process and post-process and fills the gaps between pre-processing and post-processing.
Lifecycle
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:
-
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.
-
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.
-
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
. -
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:
# #[macro_use] extern crate rocket;
#[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. Instead of #[get]
, we could have used #[post]
or
#[put]
for other HTTP methods, or #[catch]
for serving custom error
pages. Additionally, other route parameters may be
necessary when building more interesting applications. The
Requests chapter, which follows this one, has further details on
routing and error handling.
! note: We prefer #[macro_use]
, but you may prefer explicit imports.
Throughout this guide and the majority of Rocket's documentation, we import
rocket
explicitly with #[macro_use]
even though the Rust 2018 edition
makes explicitly importing crates optional. However, explicitly importing with
#[macro_use]
imports macros globally, allowing you to use Rocket's macros
anywhere in your application without importing them explicitly.
You may instead prefer to import macros explicitly or refer to them with
absolute paths: use rocket::get;
or #[rocket::get]
. The hello_2018
example showcases this alternative.
Mounting
Before Rocket can dispatch requests to a route, the route needs to be mounted:
# #[macro_use] extern crate rocket;
# #[get("/world")]
# fn world() -> &'static str {
# "hello, world!"
# }
rocket::ignite().mount("/hello", routes![world]);
The mount
method takes as input:
- A base path to namespace a list of routes under, here,
/hello
. - A list of routes via the
routes!
macro: here,routes![world]
, with multiple routes:routes![a, b, c]
.
This creates a new Rocket
instance via the ignite
function and mounts the
world
route to the /hello
base path, making Rocket aware of the route.
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:
# #[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 "/"
.
Launching
Rocket begins serving requests after being launched, which starts a multi-threaded asynchronous server and dispatches requests to matching routes as they arrive.
There are two mechnisms by which a Rocket
can be launched. The first and
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:
#[macro_use] extern crate rocket;
#[get("/world")]
fn world() -> &'static str {
"Hello, world!"
}
#[launch]
fn rocket() -> rocket::Rocket {
rocket::ignite().mount("/hello", routes![world])
}
Running the application, the console shows:
> cargo run
🔧 Configured for debug.
=> address: 127.0.0.1
=> port: 8000
=> workers: 64
=> log level: normal
=> secret key: [zero]
=> limits: forms = 32KiB
=> cli colors: true
=> keep-alive: 5s
=> tls: disabled
🛰 Mounting /hello:
=> GET /hello/world (world)
🚀 Rocket has launched from http://127.0.0.1:8000
! tip: You can also return _
from a #[launch]
function!
If you find it more pleasing, #[launch]
can infer the return type of
Rocket
for you by using _
as the return type:
#[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. You can find dozens of other complete
examples, spanning all of Rocket's features, in the GitHub examples
directory.
The second approach uses the #[rocket::main]
route attribute.
#[rocket::main]
also generates a main
function that sets up an async
runtime but unlike #[launch]
, allows you to start the server:
# #[macro_use] extern crate rocket;
#
# #[get("/world")]
# fn world() -> &'static str {
# "Hello, world!"
# }
#[rocket::main]
async fn main() {
rocket::ignite()
.mount("/hello", routes![world])
.launch()
.await;
}
#[rocket::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.
Futures and Async
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
as filesystem and network access while still allowing other requests to be make
progress. For an overview of Rust Future
s, see Asynchronous Programming in
Rust.
In general, you should prefer to use async-ready libraries instead of synchronous equivalents inside Rocket applications.
async
appears in several places in Rocket:
- Routes and Error Catchers can be
async fn
s. Inside anasync fn
, you can.await
Future
s from Rocket or other libraries. - Several of Rocket's traits, such as
FromData
andFromRequest
, have methods that returnFuture
s. Data
andDataStream
, incoming request data, andResponse
andBody
, outgoing response data, are based ontokio::io::AsyncRead
instead ofstd::io::Read
.
You can find async-ready libraries on crates.io with the
async
tag.
! note
Rocket master uses the tokio runtime. The runtime is started for you if you
use #[launch]
or #[rocket::main]
, but you can still launch()
a Rocket
instance on a custom-built runtime by not using either attribute.
Async Routes
Rocket makes it easy to use async/await
in routes.
# #[macro_use] extern crate rocket;
use rocket::tokio::time::{sleep, Duration};
#[get("/delay/<seconds>")]
async fn delay(seconds: u64) -> String {
sleep(Duration::from_secs(seconds)).await;
format!("Waited for {} seconds", seconds)
}
First, notice that the route function is an async fn
. This enables the use of
await
inside the handler. sleep
is an asynchronous function, so we must
await
it.
Multitasking
Rust's Future
s are a form of cooperative multitasking. In general, Future
s
and async fn
s should only .await
on operations and never block. Some common
examples of blocking include locking non-async
mutexes, joining threads, or
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,
stalls, or sometimes even deadlocks can occur.
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
:
# #[macro_use] extern crate rocket;
use std::io;
use rocket::tokio::task::spawn_blocking;
use rocket::response::Debug;
#[get("/blocking_task")]
async fn blocking_task() -> Result<Vec<u8>, Debug<io::Error>> {
// In a real app, use rocket::response::NamedFile or tokio::fs::File.
let vec = spawn_blocking(|| std::fs::read("data.txt")).await
.map_err(|e| io::Error::new(io::ErrorKind::Interrupted, e))??;
Ok(vec)
}