Rocket ​
Rocket provides primitives to build web servers and applications with Rust: Rocket provides routing, pre-processing of requests, and post-processing of responses
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.
- Routing
- Validation
- Processing
- Response
Routing ​
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.
The parameters to match against include static paths, dynamic paths, path segments, forms, query strings, request format specifiers, and body data.
#[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.
Mounting ​
Before Rocket can dispatch requests to a route, the route needs to be mounted: 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].
rocket::build().mount("/hello", routes![world]);This creates a new Rocket instance via the build 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:
rocket::build()
.mount("/hello", routes![world])
.mount("/hi", routes![world]);Launching ​
Rocket begins serving requests after being launched, which starts a multi-threaded asynchronous server and dispatches requests to matching routes as they arrive.
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.
#[macro_use] extern crate rocket;
#[get("/world")]
fn world() -> &'static str {
"Hello, world!"
}
#[launch]
fn rocket() -> _ {
rocket::build().mount("/hello", routes![world])
}Special to Rocket's
#[launch]attribute, the return type of a function decorated with#[launch]is automatically inferred when the return type is set to_. If you prefer, you can also set the return type explicitly toRocket<Build>.
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:
#[rocket::main]
async fn main() -> Result<(), rocket::Error> {
let _rocket = rocket::build()
.mount("/hello", routes![world])
.launch()
.await?;
Ok(())
}Futures and Async ​
Asynchronous programming with Futures and async/await allows route handlers to perform wait-heavy I/O such as filesystem and network access while still allowing other requests to make progress.
In general, you should prefer to use async-ready libraries instead of synchronous equivalents inside Rocket applications.
Rust's Futures are a form of cooperative multitasking. In general, Futures and async fns should only .await on operations and never block.
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
use std::io;
use rocket::tokio::task::spawn_blocking;
#[get("/blocking_task")]
async fn blocking_task() -> io::Result<Vec<u8>> {
// In a real app, use rocket::fs::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)
}