Rocket/site/news/2017-02-06-version-0.2.md
2017-07-10 05:05:25 -07:00

14 KiB

Rocket v0.2: Managed State & More

Posted by Sergio Benitez on February 06, 2017

Today marks the first major release since Rocket's debut a little over a month ago. Rocket v0.2 packs a ton of new features, fixes, and general improvements. Much of the development in v0.2 was led by the community, either through reports via the GitHub issue tracker or via direct contributions. In fact, there have been 20 unique contributors to Rocket's codebase since Rocket's initial introduction! Community feedback has been incredible. As a special thank you, we include the names of these contributors at the end of this article.

About Rocket

Rocket is a web framework for Rust with a focus on ease of use, expressibility, and speed. Rocket makes it simple to write fast web applications without sacrificing flexibility or type safety. All with minimal code.

Rocket's so simple, you feel like you're doing something wrong. It's like if you're making fire with rocks and suddently someone gives you a lighter. Even though you know the lighter makes fire, and does it even faster and better and with a simple flick, the rock's still in your brain.

-- Artem "impowski" Biryukov, January 17, 2017, on #rocket

New Features

Rocket v0.2 includes several new features that make developing Rocket applications simpler, faster, and safer than ever before.

Managed State

Undoubtedly, the star feature of this release is managed state. Managed state allows you to pass state to Rocket prior to launching your application and later retrieve that state from any request handler by simply including the state's type in the function signature. It works in two easy steps:

  1. Call manage on the Rocket instance corresponding to your application with the initial value of the state.
  2. Add a State<T> type to any request handler, where T is the type of the value passed into manage.

Rocket takes care of the rest! State works through Rocket's request guards. You can call manage any number of times, as long as each call corresponds to a value of a different type.

As a simple example, consider the following "hit counter" example application:

struct HitCount(AtomicUsize);

#[get("/")]
fn index(hit_count: State<HitCount>) -> &'static str {
    hit_count.0.fetch_add(1, Ordering::Relaxed);
    "Your visit has been recorded!"
}

#[get("/count")]
fn count(hit_count: State<HitCount>) -> String {
    hit_count.0.load(Ordering::Relaxed).to_string()
}

fn main() {
    rocket::ignite()
        .mount("/", routes![index, count])
        .manage(HitCount(AtomicUsize::new(0)))
        .launch()
}

Visiting / will record a visit by incrementing the hit count by 1. Visiting the /count path will display the current hit count.

One concern when using managed state is that you might forget to call manage with some state's value before launching your application. Not to worry: Rocket has your back! Let's imagine for a second that we forgot to add the call to manage on line 17 in the example above. Here's what the compiler would emit when we compile our buggy application:

warning: HitCount is not currently being managed by Rocket
 --> src/main.rs:4:21
  |
4 | fn index(hit_count: State<HitCount>) -> &'static str {
  |                     ^^^^^^^^^^^^^^^
  |
  = note: this State request guard will always fail
help: maybe add a call to 'manage' here?
 --> src/main.rs:15:5
  |
15|     rocket::ignite()
  |     ^^^^^^^^^^^^^^^^

warning: HitCount is not currently being managed by Rocket
  --> src/main.rs:10:21
   |
10 | fn count(hit_count: State<HitCount>) -> String {
   |                     ^^^^^^^^^^^^^^^
   |
   = note: this State request guard will always fail
help: maybe add a call to 'manage' here?
  --> src/main.rs:15:5
   |
15 |     rocket::ignite()
   |     ^^^^^^^^^^^^^^^^

You can read more about managed state in the guide, the API docs for manage, and the API docs for State.

Unmounted Routes Lint

A common mistake that new Rocketeers make is forgetting to mount declared routes. In Rocket v0.2, Rocket adds a lint that results in a compile-time warning for unmounted routes. As a simple illustration, consider the canonical "Hello, world!" Rocket application below, and note that we've forgotten to mount the hello route:

#[get("/")]
fn hello() -> &'static str {
    "Hello, world!"
}

fn main() {
    rocket::ignite().launch();
}

When this program is compiled, the compiler emits the following warning:

warning: the 'hello' route is not mounted
 --> src/main.rs:2:1
  |
2 |   fn hello() -> &'static str {
  |  _^ starting here...
3 | |     "Hello, world!"
4 | | }
  | |_^ ...ending here
  |
  = note: Rocket will not dispatch requests to unmounted routes.
help: maybe add a call to 'mount' here?
 --> src/main.rs:7:5
  |
7 |     rocket::ignite().launch();
  |     ^^^^^^^^^^^^^^^^

The lint can be disabled selectively per route by adding an #[allow(unmounted_route)] annotation to a given route declaration. It can also be disabled globally by adding #![allow(unmounted_route)]. You can read more about this lint in the codegen documentation.

Configuration via Environment Variables

A new feature that makes deploying Rocket apps to the cloud a little easier is configuration via environment variables. Simply put, any configuration parameter can be set via an environment variable of the form ROCKET_{PARAM}, where {PARAM} is the name of the configuration parameter. For example, to set the port Rocket listens on, simply set the ROCKET_PORT environment variable:

ROCKET_PORT=3000 cargo run --release

Configuration parameters set via environment variables take precedence over parameters set via the Rocket.toml configuration file. Note that any parameter can be set via an environment variable, include extras. For more about configuration in Rocket, see the configuration section of the guide.

And Plenty More!

Rocket v0.2 is full of many new features! In addition to the three features described above, v0.2 also includes the following:

  • Config structures can be built via ConfigBuilder, which follows the builder pattern.
  • Logging can be enabled or disabled on custom configuration via a second parameter to the Rocket::custom method.
  • name and value methods were added to Header to retrieve the name and value of a header.
  • A new configuration parameter, workers, can be used to set the number of threads Rocket uses.
  • The address of the remote connection is available via Request.remote(). Request preprocessing overrides remote IP with value from the X-Real-IP header, if present.
  • During testing, the remote address can be set via MockRequest.remote().
  • The SocketAddr request guard retrieves the remote address.
  • A UUID type has been added to contrib.
  • rocket and rocket_codegen will refuse to build with an incompatible nightly version and emit nice error messages.
  • Major performance and usability improvements were upstreamed to the cookie crate, including the addition of a CookieBuilder.
  • When a checkbox isn't present in a form, bool types in a FromForm structure will parse as false.
  • The FormItems iterator can be queried for a complete parse via completed and exhausted.
  • Routes for OPTIONS requests can be declared via the options decorator.
  • Strings can be percent-encoded via URI::percent_encode().

Breaking Changes

This release includes several breaking changes. These changes are listed below along with a short note about how to handle the breaking change in existing applications.

  • Rocket::custom takes two parameters, the first being Config by value.

    A call in v0.1 of the form Rocket::custom(&config) is now Rocket::custom(config, false).

  • Tera templates are named without their extension.

    A templated named name.html.tera is now simply name.

  • JSON unwrap method has been renamed to into_inner.

    A call to .unwrap() should be changed to .into_inner().

  • The map! macro was removed in favor of the json! macro.

    A call of the form map!{ "a" => b } can be written as: json!({ "a": b }).

  • The hyper::SetCookie header is no longer exported.

    Use the Cookie type as an Into<Header> type directly.

  • The Content-Type for String is now text/plain.

    Use content::HTML<String> for HTML-based String responses.

  • Request.content_type() returns an Option<ContentType>.

    Use .unwrap_or(ContentType::Any) to get the old behavior.

  • The ContentType request guard forwards when the request has no Content-Type header.

    Use an Option<ContentType> and .unwrap_or(ContentType::Any) for the old behavior.

  • A Rocket instance must be declared before a MockRequest.

    Change the order of the rocket::ignite() and MockRequest::new() calls.

  • A route with format specified only matches requests with the same format.

    Previously, a route with a format would match requests without a format specified. There is no workaround to this change; simply specify formats when required.

  • FormItems can no longer be constructed directly.

    Instead of constructing as FormItems(string), construct as FormItems::from(string).

  • from_from_string(&str) in FromForm removed in favor of from_form_items(&mut FormItems).

    Most implementation should be using FormItems internally; simply use the passed in FormItems. In other cases, the form string can be retrieved via the inner_str method of FormItems.

  • Config::{set, default_for} are deprecated.

    Use the set_{param} methods instead of set, and new or build in place of default_for.

  • Route paths must be absolute.

    Prepend a / to convert a relative path into an absolute one.

  • Route paths cannot contain empty segments.

    Remove any empty segments, including trailing ones, from a route path.

Bug Fixes

Three bugs were fixed in this release:

  • Handlebars partials were not properly registered (#122).
  • Rocket::custom did not set the custom configuration as the active configuration.
  • Route path segments with more than one dynamic parameter were erroneously allowed.

General Improvements

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

  • Rocket no longer overwrites a catcher's response status.
  • The port Config type is now a proper u16.
  • Clippy issues injected by codegen are resolved.
  • Handlebars was updated to 0.25.
  • The PartialEq implementation of Config doesn't consider the path or session key.
  • Hyper dependency updated to 0.10.
  • The Error type for JSON as FromData has been exposed as SerdeError.
  • SVG was added as a known Content-Type.
  • Serde was updated to 0.9.
  • Form parse failure now results in a 422 error code.
  • Tera has been updated to 0.7.
  • pub(crate) is used throughout to enforce visibility rules.
  • Query parameters in routes (/path?<param>) are now logged.
  • Routes with and without query parameters no longer collide.

Rocket v0.2 also includes all of the new features, bug fixes, and improvements from versions 0.1.1 through 0.1.6. You can read more about these changes in the v0.1 CHANGELOG.

What's next?

Work now begins on Rocket v0.3! The focus of the next major release will be on security. In particular, three major security features are planned:

  1. Automatic CSRF protection across all payload-based requests (#14).
Rocket will automatically check the origin of requests made for HTTP `PUT`,
`POST`, `DELETE`, and `PATCH` requests, allowing only authorized requests to
be dispatched. This includes checking `POST`s from form submissions and any
requests made via JavaScript.
  1. Encryption and signing of session-based cookies (#20).
Built-in session support will encrypt and sign cookies using a user supplied
`session_key`. Encryption and signing will occur automatically for
session-based cookies.
  1. Explicit typing of raw HTTP data strings (#43).
A present, the standard `&str` type is used to represent raw HTTP data
strings. In the next release, a new type, `&RawStr`, will be used for this
purpose. This will make it clear when raw data is being handled. The type
will expose convenient methods such as `.url_decode()` and `.html_escape()`.

Work on Rocket v0.3 will also involve exploring built-in support for user authentication and authorization as well as automatic parsing of multipart forms.

Contributors to v0.2

The following wonderful people helped make Rocket v0.2 happen:

  • Cliff H
  • Dru Sellers
  • Eijebong
  • Eric D. Reichert
  • Ernestas Poskus
  • FliegendeWurst
  • Garrett Squire
  • Giovanni Capuano
  • Greg Edwards
  • Joel Roller
  • Josh Holmer
  • Liigo Zhuang
  • Lori Holden
  • Marcus Ball
  • Matt McCoy
  • Reilly Tucker Siemens
  • Robert Balicki
  • Sean Griffin
  • Seth Lopez
  • tborsa

Thank you all! Your contributions are greatly appreciated!

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

Start using Rocket today!

Not already using Rocket? Rocket is extensively documented, making it easy for you to start writing your web applications in Rocket! See the overview or start writing code immediately by reading through the guide.