News

The latest in the world of Rocket.

Rocket v0.5: Stable, Async, Sentinels, Streams, SSE, Forms, WebSockets, & So Much More

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.

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:

1
2
3
4
5
6
7
8
9
10
11
#[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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- #![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:

1
2
3
4
5
6
7
8
9
10
11
12
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.

Sentinels 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:

1
2
3
4
5
6
7
use rocket::{Rocket, Ignite, Sentinel};

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
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:

1
2
3
4
5
6
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:

1
2
3
4
5
6
7
8
9
10
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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:

1
"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:

1
2
3
4
5
6
7
8
9
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:

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:

  1. 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.

  2. 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.

  3. Native async Traits

    Given the stabilization of async fn in traits, the next major release will seek to eliminate Rocket's dependence on #[async_trait] opting instead for native async traits. This will greatly improve our documentation, which currently calls out the attribute for each affected trait, as well as offer modest performance improvements.

  4. Typed Catchers

    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.

  5. 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.

  6. 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.

  7. 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.