22 KiB
Rocket v0.5: Stable, Async, Sentinels, Streams, SSE, Forms, WebSockets, & So Much More
Posted by Sergio Benitez on Nov 17, 2023
Four years, four release candidates, a thousand commits, and over a thousand
issues, discussions, and PRs later, I am relieved thrilled to announce the
general availability of Rocket v0.5.
Rocket is an async backend web framework for Rust with a focus on usability, security, extensibility, and speed. Rocket makes it simple to write secure web applications without sacrificing productivity or performance.
We encourage all users to upgrade. For a guided migration from Rocket v0.4 to Rocket v0.5, please consult the newly available upgrading guide. Rocket v0.4 will continue to be supported and receive security updates until the time Rocket v0.6 is released.
! note: This is a co-announcement along with the prelaunch of RWF2.
We're addressing the community's concerns regarding the pace of Rocket's development, leadership, and release cadence in a separate announcement. Please see the accompanying RWF2 prelaunch announcement to learn more and see how you can get involved.
What's New?
Almost every bit has been reevaluated with a focus on usability and developer productivity, security, and consistency across the library and broader ecosystem. The changes are numerous, so we focus on the most noteworthy changes here and encourage everyone to read the CHANGELOG for a complete list. For answers to frequently asked questions, see the new FAQ.
⚓ Support for Stable rustc
since rc.1
Rocket v0.5 compiles and builds on Rust stable. You can now compile and build
Rocket applications with rustc
from the stable release channel and remove all
#![feature(..)]
crate attributes. The complete canonical example with a single
hello
route becomes:
#[macro_use] extern crate rocket;
#[get("/<name>/<age>")]
fn hello(name: &str, age: u8) -> String {
format!("Hello, {} year old named {}!", age, name)
}
#[launch]
fn rocket() -> _ {
rocket::build().mount("/hello", routes![hello])
}
See a diff
of the changes from v0.4.
- #![feature(proc_macro_hygiene, decl_macro)]
-
#[macro_use] extern crate rocket;
#[get("/<name>/<age>")]
- fn hello(name: String, age: u8) -> String {
+ fn hello(name: &str, age: u8) -> String {
format!("Hello, {} year old named {}!", age, name)
}
- fn main() {
- rocket::ignite().mount("/hello", routes![hello]).launch();
- }
+ #[launch]
+ fn rocket() -> _ {
+ rocket::build().mount("/hello", routes![hello])
+ }
Note the new launch
attribute, which simplifies starting an async
runtime
for Rocket applications. See the migration guide for more on transitioning to
a stable toolchain.
📥 Async I/O since rc.1
Rocket's core request handling was rebuilt in v0.5 to take advantage of the
latest async
networking facilities in Rust. Backed by tokio
, Rocket
automatically multiplexes request handling across async
tasks on all of the
available cores on the machine. As a result, route handlers can now be declared
async
and make use of await
syntax:
use rocket::tokio;
use rocket::data::{Data, ToByteUnit};
#[post("/debug", data = "<data>")]
async fn debug(data: Data<'_>) -> std::io::Result<()> {
// Stream at most 512KiB all of the body data to stdout.
data.open(512.kibibytes())
.stream_to(tokio::io::stdout())
.await?;
Ok(())
}
See the Blocking I/O section of the upgrading
guide for complete details on the async
I/O transition.
💂 Sentinels since rc.1
Rocket v0.5 introduces sentinels. Entirely unique to Rocket, sentinels offer
an automatic last line of defense against runtime errors by enabling any type
that appears in a route to abort application launch under invalid conditions.
For example, the &State<T>
guard in v0.5 is a Sentinel
that aborts
launch if the type T
is not in managed state, thus preventing associated
runtime errors.
Sentinel
s can be implemented outside of Rocket, too, and you should seek to
do so whenever possible. For instance, the Template
type from
rocket_dyn_templates
is a sentinel that ensures templates are properly
registered. As another example, consider a MyResponder
that expects:
- A specific type
T
to be in managed state. - An catcher to be registered for the
400
status code.
Making MyResponder
a sentinel that guards against these conditions is as
simple as:
use rocket::{Rocket, Ignite, Sentinel};
# struct MyResponder;
# struct T;
impl Sentinel for MyResponder {
fn abort(r: &Rocket<Ignite>) -> bool {
r.state::<T>().is_none() || !r.catchers().any(|c| c.code == Some(400))
}
}
☄️ Streams and SSE since rc.1
Powered by the new asynchronous core, Rocket v0.5 introduces real-time, typed
async
streams. The new async streams section of the guide contains further
details, and we encourage all interested parties to see the new real-time,
multi-room chat example.
As a taste of what's possible, the following stream
route emits a "pong"
Server-Sent Event every n
seconds, defaulting to 1
:
# use rocket::*;
use rocket::tokio::time::{interval, Duration};
use rocket::response::stream::{Event, EventStream};;
#[get("/ping?<n>")]
fn stream(n: Option<u64>) -> EventStream![] {
EventStream! {
let mut timer = interval(Duration::from_secs(n.unwrap_or(1)));
loop {
yield Event::data("pong");
timer.tick().await;
}
}
}
🔌 WebSockets since rc.4
Rocket v0.5 introduces support for HTTP connection upgrades via a new upgrade API. The API allows responders to assume control of raw I/O with the client in an existing HTTP connection, thus allowing HTTP connections to be upgraded to any protocol, including WebSockets!
The newly introduced rocket_ws
library takes advantage of the new API to
implement first-class support for WebSockets entirely outside of Rocket's core.
Working with rocket_ws
to implement an echo server looks like this:
# use rocket::get;
use rocket_ws::{WebSocket, Stream};
#[get("/echo")]
fn echo_compose(ws: WebSocket) -> Stream!['static] {
ws.stream(|io| io)
}
Just like the newly introduced async
streams, rocket_ws
also supports using
generator syntax for WebSocket messages:
# use rocket::get;
use rocket_ws::{WebSocket, Stream};
#[get("/echo")]
fn echo_stream(ws: WebSocket) -> Stream!['static] {
Stream! { ws =>
for await message in ws {
yield message?;
}
}
}
For complete usage details, see the rocket_ws
documentation.
📝 Comprehensive Forms since rc.1
Rocket v0.5 entirely revamps forms with support for multipart uploads,
arbitrary collections with arbitrary nesting, ad-hoc validation, and an
improved FromForm
derive, obviating the need for nearly all custom
implementations of FromForm
or FromFormField
. Rocket's new wire protocol for
forms allows applications to express any structure with any level of nesting
and collection without any custom code, eclipsing what's offered by other web
frameworks.
As an illustrative example, consider the following structures:
use rocket::form::FromForm;
#[derive(FromForm)]
struct MyForm<'r> {
owner: Person<'r>,
pet: Pet<'r>,
}
#[derive(FromForm)]
struct Person<'r> {
name: &'r str
}
#[derive(FromForm)]
struct Pet<'r> {
name: &'r str,
#[field(validate = eq(true))]
good_pet: bool,
}
To parse request data into a MyForm
, a form with fields of owner.name
,
pet.name
, and pet.good_pet
must be submitted. The ad-hoc validation on
good_pet
validates that good_pet
parses as true
. Such a form, URL-encoded,
may look like:
"owner.name=Bob&pet.name=Sally&pet.good_pet=yes"
Rocket's derived FromForm
implementation for MyForm
will automatically parse
such a submission into the correct value:
MyForm {
owner: Person {
name: "Bob".into()
},
pet: Pet {
name: "Sally".into(),
good_pet: true,
}
}
# };
The rewritten forms guide provides complete details on revamped forms support.
🚀 And so much more!
Rocket v0.5 introduces over 40 new features and major improvements! We encourage everyone to review the CHANGELOG to learn about them all. Here are a few more we don't want you to miss:
- An automatically enabled
Shield
: security and privacy headers for all responses. - Graceful shutdown with configurable grace periods, notification, and shutdown fairings.
- An entirely new, flexible and robust configuration system based on Figment.
- Type-system enforced incoming data limits to mitigate memory-based DoS attacks.
- Support for mutual TLS and client
Certificate
s. - Asynchronous database pooling support via
rocket_db_pools
. - Compile-time URI literals via a fully revamped
uri!
macro.
What's Next?
We think Rocket provides the most productive and confidence-inspiring web development experience in Rust today, but as always, there's room for improvement. To that end, here's what's on the docket for the next major release:
-
Migration to RWF2
Discussed further in the RWF2 prelaunch announcement, Rocket will transition to being managed by the newly formed Rocket Web Framework Foundation: RWF2. The net effect is increased development transparency, including public roadmaps and periodic updates, financial support for high-quality contributions, and codified pathways into the project's governance.
-
Pluggable Connection Listeners
Rocket currently expects and enjoins connection origination via TCP/IP. While sufficient for the common case, it excludes other desirable interfaces such as Unix Domain Sockets (UDS).
In the next major release, Rocket will expose an interface for implementing and plugging-in custom connection listeners. Rocket itself will make use of this interface to expose more common mediums out-of-the-box, such as the aforementioned UDS.
-
Native
async
TraitsGiven the stabilization of
async fn
in traits, the next major release will seek to eliminate Rocket's dependence on#[async_trait]
opting instead for nativeasync
traits. This will greatly improve our documentation, which currently calls out the attribute for each affected trait, as well as offer modest performance improvements. -
Today's catchers cannot receive strictly typed error data. This results in workarounds where error data is queried for well-typedness at runtime. While it has been possible to implement a form of typed error catching prior, doing so necessitated limiting error data to
'static
values, as other Rust web frameworks do, a concession we're unwilling to make.After much experimentation, we have an approach that is ergonomic to use, safe, and correct, all without the
'static
limitation. This will allow error catchers to "pattern match" against error types at compile-time. At runtime, Rocket will match emerging error types against the declared catchers and call the appropriate catcher with the fully-typed value. -
Short-Circuitable Request Processing
Whether with success or failure, fairings and guards cannot presently terminate request processing early. The rationale for forbidding this functionality was that it would allow third-party crates and plugins to dictate responses without offering any recourse to the top-level application.
With the advent of typed catchers, however, we now have a mechanism by which a top-level application can intercept early responses via their type, resolving the prior concern. As such, in the next major release, fairings and guards will be able to respond to requests early, and catchers will be able to intercept those early responses at will.
-
Associated Resources
Often a set of routes will share a set requirements. For example, they may share a URI prefix, subset of guards, and some managed state. In today's Rocket, these common requirements must be repeatedly specified for each route. While this is by design (we want a route's requirements to be obvious), the repetition is arduous and potentially error prone.
In an upcoming major release, Rocket will introduce new mechanisms by which a set of routes can share an explicitly declared set of requirements. Their explicit and declarative nature results in requirements that are simultaneously obvious and declared once.
We're really excited about this upcoming change and will be announcing more in the near future.
-
Performance Improvements
Rocket appears to lag behind other Rust web frameworks in benchmarks. This is partly due to poor benchmarking, partly due to security-minded design decisions, and partially due to unexploited opportunities. In the next release, we'll be addressing the latter points. Specifically:
-
Explore making work stealing optional.
Rocket currently defaults to using tokio's multithreaded, work-stealing scheduler. This avoids tail latency issues when faced with irregular and heterogeneous tasks at the expense of throughput due to higher bookkeeping costs associated with work stealing. Other Rust web frameworks instead opt to use tokio's single-threaded scheduler, which while theoretically suboptimal, may yield better performance results in practice, especially when benchmarking homogeneous workloads.
While we believe work-stealing schedulers are the right choice for the majority of applications desireing robust performance characteristics, we also believe the choice should be the user's. We'll seek to make this choice easier in the next release.
-
Reduce conversions from external to internal HTTP types.
Rocket revalidates and sometimes copies incoming HTTP request data. In Rocket v0.5, we began transitioning to a model where we revalidate security insensitive data in debug mode only, allowing for bugs to be caught and reported while reducing performance impacts in production. In the next release, we seek to extend this approach.
-
❤️ Thank You
A very special thank you to Jeb Rosen, Rocket's maintainer from v0.4 to
v0.5-rc.1, without whom Rocket v0.5 wouldn't exist. Jeb is responsible for
leading the migration to async
and Rust stable along with tireless efforts to
improve Rocket's documentation and address the community. Rocket is better for
having had Jeb along for the ride. Thank you, Jeb.
A special thank you to all of Rocket's users, especially those who diligently waded through all four release candidates, raised issues, and participated on GitHub and the Matrix channel. You all are an awesome, kind, and thoughtful bunch. Thank you.
A heartfelt thank you as well to all 148 who contributed to Rocket v0.5:
- Aaron Leopold
- Abdullah Alyan
- Aditya
- Alex Macleod
- Alex Sears
- Alexander van Ratingen
- ami-GS
- Antoine Martin
- arctic-alpaca
- arlecchino
- Arthur Woimbée
- atouchet
- Aurora
- badoken
- Beep LIN
- Ben Sully
- Benedikt Weber
- Benjamin B
- BlackDex
- Bonex
- Brenden Matthews
- Brendon Federko
- Brett Buford
- Cedric Hutchings
- Cezar Halmagean
- Charles-Axel Dein
- Compro Prasad
- cui fliter
- Daniel Wiesenberg
- David Venhoek
- Dimitri Sabadie
- Dinu Blanovschi
- Dominik Boehi
- Doni Rubiagatra
- Edgar Onghena
- Edwin Svensson
- est31
- Felix Suominen
- Fenhl
- Filip Gospodinov
- Flying-Toast
- Follpvosten
- Francois Stephany
- Gabriel Fontes
- gcarq
- George Cheng
- Giles Cope
- Gonçalo Ribeiro
- hiyoko3m
- Howard Su
- hpodhaisky
- Ian Jackson
- IFcoltransG
- Indosaram
- inyourface34456
- J. Cohen
- Jacob Pratt
- Jacob Sharf
- Jacob Simpson
- Jakub Dąbek
- Jakub Wieczorek
- James Tai
- Jason Hinch
- Jeb Rosen
- Jeremy Kaplan
- Jieyou Xu
- Joakim Soderlund
- Johannes Liebermann
- John-John Tedro
- Jonah Brüchert
- Jonas Møller
- Jonathan Dickinson
- Jonty
- Joscha
- Joshua Nitschke
- JR Heard
- Juhasz Sandor
- Julian Büttner
- Juraj Fiala
- Kenneth Allen
- Kevin Wang
- Kian-Meng Ang
- Konrad Borowski
- Leonora Tindall
- Lev Kokotov
- lewis
- Lionel G
- Lucille Blumire
- Mai-Lapyst
- Manuel
- Manuel Transfeld
- Marc Schreiber
- Marc-Stefan Cassola
- Marshall Bowers
- Martin1887
- Martinez
- Matthew Pomes
- Maxime Guerreiro
- meltinglava
- Michael Howell
- Mikail Bagishov
- mixio
- multisn8
- Necmettin Karakaya
- Ning Sun
- Nya
- Paolo Barbolini
- Paul Smith
- Paul van Tilburg
- Paul Weaver
- pennae
- Petr Portnov
- philipp
- Pieter Frenssen
- PROgrm_JARvis
- Razican
- Redrield
- Riley Patterson
- Rodolphe Bréard
- Roger Mo
- RotesWasser
- rotoclone
- Ruben Schmidmeister
- Rudi Floren
- Rémi Lauzier
- Samuele Esposito
- Scott McMurray
- Sergio Benitez
- Silas Sewell
- Soham Roy
- Steven Murdoch
- Stuart Hinson
- Thibaud Martinez
- Thomas Eckert
- ThouCheese
- Tilen Pintarič
- timando
- timokoesters
- toshokan
- TotalKrill
- Unpublished
- Vasili
- Vladimir Ignatev
- Wesley Norris
- xelivous
- YetAnotherMinion
- Yohannes Kifle
- Yusuke Kominami
Get Involved
Looking to help with Rocket? To contribute code, head over to GitHub. To get involved with the project, see the RWF2 prelaunch announcement. We'd love to have you.