Rocket/site/news/2018-12-08-version-0.4.md

23 KiB

Rocket v0.4: Typed URIs, Database Support, Revamped Queries, & More!

Posted by Sergio Benitez on December 08, 2018

I am elated to announce that the next major release of Rocket is now available! Rocket 0.4 is a step forward in every direction: it is packed with features and improvements that increase developer productivity, improve application security and robustness, provide new opportunities for extensibility, and deliver a renewed degree of toolchain stability.

Rocket 0.4 is the culmination of more than a year of development. During this time, more than 600 changes were committed, almost 400 issues were closed, and over 165 pull requests were submitted. The Rocket community has proved steadfast in its support: a sincere thank you to everyone involved!

About Rocket

Rocket is a web framework for Rust with a focus on usability, security, and performance. Rocket makes it simple to write fast, secure web applications without sacrificing flexibility or type safety.

Not already using Rocket? Join the tens of thousands of users and hundreds of companies happily using Rocket today! Rocket's extensive documentation makes it easy. Get started now by reading through the guide or learning more from the overview.

What's New?

Rocket 0.4 is the largest release to date by a wide margin. It is packed with hundreds of changes. We highlight the largest of them here. For a complete description of everything new and different in 0.4, please see the CHANGELOG.

Maintainers += 1

An open source project is as much about the people as it is about the code. This is why I am delighted to welcome @jebrosen as Rocket's first co-maintainer! Jeb is directly responsible for several of the new features in 0.4, has painstakingly code reviewed many other changes, and actively answers questions and resolves issues on GitHub, IRC, and offline.

Needless to say, Rocket is a better project thanks to you, Jeb. Welcome!

Codegen Rewrite

In 0.4, the rocket_codegen crate has been entirely rewritten to use to-be-stable procedural macro APIs where it previously used private, unstable rustc APIs. While this is largely an internal change, it has big, positive implications for all Rocket users.

First and foremost, the path to Rocket on stable is now clearly in sight. While there are still hurdles to overcome, we are actively working with the Rust team to make Rocket on stable a reality as soon as possible. We expect the next major Rocket release to support the stable channel.

Second, but equally important, we expect breakages due to nightly changes to drop dramatically, likely to zero. This means that Rocket is largely already de-facto toolchain stable.

The new prelude import for Rocket applications is:

- #![feature(plugin)]
- #![plugin(rocket_codegen)]
+ #![feature(proc_macro_hygiene, decl_macro)]

- extern crate rocket;
+ #[macro_use] extern crate rocket;

rocket_codegen should not be a direct dependency. Remove it from your Cargo.toml:

[dependencies]
- rocket = "0.3"
+ rocket = "0.4"
- rocket_codegen = "0.3"

Typed URIs

Rocket 0.4 introduces the uri! macro, allowing you to construct URIs to routes in a robust, type-safe, and URI-safe manner. Type or route parameter mismatches are caught at compile-time, and changes to route URIs are automatically reflected in the generated URIs.

To illustrate, consider the following route:

#[get("/person/<name>?<age>")]
fn person(name: String, age: Option<u8>)

URIs to this person route can be created as follows:

// with unnamed parameters, in route URI declaration order
let uri = uri!(person: "Mike Smith", 28);
assert_eq!(uri.to_string(), "/person/Mike%20Smith?age=28");

// with named parameters, order irrelevant
let uri = uri!(person: name = "Mike", age = 28);
let uri = uri!(person: age = 28, name = "Mike");
assert_eq!(uri.to_string(), "/person/Mike?age=28");

// with a specific mount-point
let uri = uri!("/api", person: name = "Mike", age = 28);
assert_eq!(uri.to_string(), "/api/person/Mike?age=28");

// with optional query parameters ignored
let uri = uri!(person: "Mike", _);
let uri = uri!(person: name = "Mike", age = _);
assert_eq!(uri.to_string(), "/person/Mike");

Should your route's URI change in an incompatible manner, or should you mistype parameters, Rocket informs you of the error at compile-time with a helpful message:

error: person route uri expects 2 parameters but 1 was supplied
 --> examples/uri/src/main.rs:9:29
  |
9 |     uri!(person: "Mike Smith");
  |                  ^^^^^^^^^^^^
  |
  = note: expected parameters: name: String, age: Option<u8>

The same applies to type errors: Rocket informs you of any type errors at compile-time as well:

error: the trait bound u8: FromUriParam<Query, &str> is not satisfied
 --> examples/uri/src/main.rs:9:35
  |
9 |     uri!(person: age = "10", name = "Mike");
  |                        ^^^^ FromUriParam<Query, &str> is not implemented for u8
  |

We recommend that uri! is exclusively used when constructing route URIs. For more information on typed URIs, see the new Typed URIs guide section and the uri! macro documentation.

Database Support

Rocket now includes built-in, ORM-agnostic support for database connection pooling. More specifically, Rocket allows you to easily configure and connect your Rocket application to databases through connection pools in three simple, largely automated steps:

  1. Configure databases in Rocket.toml.
  2. Associate a request guard type and fairing with each database.
  3. Use the request guard to retrieve a connection in a handler.

As an example, for a Diesel-based SQLite database named sqlite_logs, your Rocket.toml would record the URL to the database in the databases table:

[global.databases]
sqlite_logs = { url = "/path/to/database.sqlite" }

In the application, a unit-like struct with one internal type (the database connection) is decorated with the #[database] attribute and the name of the configured database. This generates a fairing which must then be attached:

#[database("sqlite_logs")]
struct LogsDbConn(diesel::SqliteConnection);

rocket::ignite().attach(LogsDbConn::fairing())

That's it! Whenever a connection to the database is needed, the type can be used as a request guard:

#[get("/logs/<id>")]
fn get_logs(conn: LogsDbConn, id: usize) -> Result<Logs> {
    logs::filter(id.eq(log_id)).load(&conn)
}

For more information on Rocket's database support, see the new Database guide section and the rocket_contrib::databases module documentation.

Revamped Queries

In Rocket 0.4, query string handling has been completely overhauled, resolving some of the most called for requests in Rocket's history (#608). The new query handling route syntax and semantics were designed with the following goals in mind:

  • Enable matching of static query components.
  • No special-casing of any kind, preferring type-driven flows.
  • Ad-hoc matching of specific query key/value pairs.
  • Lenient parsing by default, allowing missing parameters.
  • Order-independent matching of query parameters.

To illustrate the new system in action, consider the following route:

#[derive(FromForm)]
struct DogDetails {
    color: Color,
    height: Inches,
    weight: Pounds
}

#[get("/animal?dog&<name>&<nickname>&<rest..>")]
fn dog(name: String, nickname: Option<String>, rest: Form<DogDetails>)

This route matches any GET request with a path of /animal, a static query component of dog, and key/value parameters of color, height, and weight that validate as Color, Inches, and Pounds, respectively. Furthermore, it optionally accepts a key/value parameter of nickname. If the value is present, nickname will be Some; if it is not, nickname will be None.

Single parameters (<param>) like name and nickname are validated using the existing FromFormValue trait while trailing parameters (<param..>) are validated using the new FromQuery trait. Both traits are user implementable, and FromFormValue can be derived.

For more details on handling query strings, see the new Query Strings guide section and the updated route attribute documentation.

Stateful Handlers

The type of a handler has been generalized in 0.4 to any type that implements the new Handler trait. Among other things, this allows handlers to refer to internal state during request handling.

The new StaticFiles contrib type uses this functionality to provide easier-than-ever static file serving. For example, to make local files from a /static directory accessible at /public, you need simply write:

fn main() {
    rocket::ignite()
        .mount("/public", StaticFiles::from("/static"))
        .launch();
}

We encourage users to explore the new Handler API and contribute libraries with pluggable handlers! For more details, see the Handler documentation.

Responder Derive

In Rocket 0.4, the Responder trait can be derived for enums and structs with named fields. This greatly simplifies returning multiple types of responses from a single handler.

To illustrate, consider a route that returns either a Json<Info> structure for 401 (unauthorized) errors or a NamedFile with a dynamic Content-Type for 404 (not found) errors. To accomplish this previously, Result values could be arbitrarily nested, an unappealing and semantically incorrect approach. Alternatively, an enum could be declared with the appropriate variants, and Responder could be manually implemented for the enum. As of 0.4, that implementation can be automatically derived:

#[derive(Responder, Debug)]
enum Error {
    #[response(status = 401)]
    Unauthorized(Json<Info>),
    #[response(status = 404)]
    NotFound(NamedFile, ContentType),
}

A value of this type can then be returned from a hander or used as part of wrapping responders:

#[get("/<item>")]
fn handler(user: Option<User>, item: Option<Item>) -> Result<T, Error> {
    if user.is_none() {
        Err(Error::Unauthorized(..))
    } else if item.is_none() {
        Err(Error::NotFound(..))
    } else {
        Ok(..)
    }
}

The status for each variant will be automatically set to the value of the status variant attribute, and fields beyond the first will be added as headers to the response (here, ContentType).

For more on using the Responder derive, see the new Custom Responders guide section and the Responder derive documentation.

Live Template Reloading

Rocket 0.4 automatically reloads changed templates at runtime without requiring recompilation. This works on all major platforms. For security and performance reasons, live template reloading is only enabled when the application is compiled in debug mode.

There is no configuration necessary: this just works out of the box!

And Plenty More!

In addition to the features highlighted above, Rocket 0.4 also contains the following new features:

Breaking Changes

This release includes many breaking changes. Please see the CHANGELOG for a complete list of breaking changes along with details on handling the breaking change in existing applications.

Rocket 0.3 will continue as a security maintenance release only. All users are encouraged to migrate their applications to 0.4.

General Improvements

In addition to new features, Rocket saw the following improvements:

  • Log messages now refer to routes by name.
  • Collision errors on launch name the colliding routes.
  • Launch fairing failures refer to the failing fairing by name.
  • The default 403 catcher now references authorization, not authentication.
  • Private cookies are set to HttpOnly and are given an expiration date of 1 week by default.
  • A Tera templates example was added.
  • All macros, derives, and attributes are individually documented in rocket_codegen.
  • Invalid client requests receive a response of 400 instead of 500.
  • Response bodies are reliably stripped on HEAD requests.
  • Added a default catcher for 504: Gateway Timeout.
  • Configuration information is logged in all environments.
  • Use of unsafe was reduced from 9 to 2 in core library.
  • FormItems now parses empty keys and values as well as keys without values.
  • Added Config::active() as a shorthand for Config::new(Environment::active()?).
  • Address/port binding errors at launch are detected and explicitly emitted.
  • Flash cookies are cleared only after they are inspected.
  • Sync bound on AdHoc::on_attach(), AdHoc::on_launch() was removed.
  • AdHoc::on_attach(), AdHoc::on_launch() accept an FnOnce.
  • Added Config::root_relative() for retrieving paths relative to the configuration file.
  • Added Config::tls_enabled() for determining whether TLS is actively enabled.
  • ASCII color codes are not emitted on versions of Windows that do not support them.
  • Added FLAC (audio/flac), Icon (image/x-icon), WEBA (audio/webm), TIFF (image/tiff), AAC (audio/aac), Calendar (text/calendar), MPEG (video/mpeg), TAR (application/x-tar), GZIP (application/gzip), MOV (video/quicktime), MP4 (video/mp4), ZIP (application/zip) as known media types.
  • Added .weba (WEBA), .ogv (OGG), .mp4 (MP4), .mpeg4 (MP4), .aac (AAC), .ics (Calendar), .bin (Binary), .mpg (MPEG), .mpeg (MPEG), .tar (TAR), .gz (GZIP), .tif (TIFF), .tiff (TIFF), .mov (MOV) as known extensions.
  • Interaction between route attributes and declarative macros has been improved.
  • Generated code now logs through logging infrastructures as opposed to using println!.
  • Routing has been optimized by caching routing metadata.
  • Form and LenientForm can be publicly constructed.
  • Console coloring uses default terminal colors instead of white.
  • Console coloring is consistent across all messages.
  • i128 and u128 now implement FromParam, FromFormValue.
  • The base64 dependency was updated to 0.10.
  • The log dependency was updated to 0.4.
  • The handlebars dependency was updated to 1.0.
  • The tera dependency was updated to 0.11.
  • The uuid dependency was updated to 0.7.
  • The rustls dependency was updated to 0.14.
  • The cookie dependency was updated to 0.11.

What's Next?

Rocket v0.5 is scheduled to be at least as exciting as 0.4! As always, the focus continues to be usability, stability, security, and performance. With this in mind, the roadmap for 0.5 includes:

  1. Support for Rust Stable (#19)

Finally! Rocket 0.5 will compile and run on stable versions of the Rust compiler.

  1. Asynchronous Request Handling (#17)

In 0.5, Rocket will migrate to the latest asynchronous version of hyper and futures with compatibility for async/await syntax. Of utmost importance is preserving Rocket's usability. As such, these changes will be largely internal, with asynchronous I/O peeking over the covers only when explicitly desired or required. As a side effect, we expect a substantial performance boost from the migration as well as resolution to long-standing issues.

  1. Multipart Form Support (#106)

The lack of built-in multipart form support makes handling file uploads and other submissions much more cumbersome than necessary. Rocket 0.5 will generalize its existing forms infrastructure to handle multipart forms.

  1. Stronger CSRF and XSS Protection (#14)

Since 0.3, Rocket uses SameSite: Strict private cookies to prevent CSRF attacks. This technique is only tenable in newer browsers. In 0.5, Rocket will protect against CSRF using more robust techniques. Rocket will also add support for automatic, browser-based XSS protection.

Rocket v0.4 Contributors

The following wonderful people helped make Rocket 0.4 happen:

  • Alexander Mielczarek
  • Alex Bowers
  • Alfie John
  • Alva Snædís
  • Ashley Williams
  • Beatriz Rizental
  • bohov
  • Christophe Courtaut
  • David Darrell
  • Desmond
  • Divyahans Gupta
  • Donald Robertson
  • EloD10
  • Eric Dattore
  • Henning Kowalk
  • Imron Alston
  • Jeb Rosen
  • kryptan
  • Kyle Clemens
  • lerina
  • Linus Unnebäck
  • Lukas Abfalterer
  • Marc Mettke
  • Max Furman
  • messense
  • Ning Sun
  • Philip Jenvey
  • Pyry Kontio
  • Richo Healey
  • Riley Trautman
  • Rolf Schmidt
  • Rukai
  • Sean Stangl
  • Sébastien Santoro
  • Sergio Benitez
  • Stanislav Ivanov
  • Tal Garfinkel
  • Tobias Stolzmann
  • Ville Hakulinen
  • Vishal Sodani
  • Zack Chandler
  • Zac Pullar-Strecker

Thank you all! Your contributions are greatly appreciated!

Looking to help with Rocket's development? Head over to Rocket's GitHub and start contributing!