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, expressiveness, 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 suddenly 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:
- Call
manage
on theRocket
instance corresponding to your application with the initial value of the state. - Add a
State<T>
type to any request handler, whereT
is the type of the value passed intomanage
.
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 viaConfigBuilder
, which follows the builder pattern.- Logging can be enabled or disabled on custom configuration via a second
parameter to the
Rocket::custom
method. name
andvalue
methods were added toHeader
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 theX-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 tocontrib
. rocket
androcket_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 aCookieBuilder
. - When a checkbox isn't present in a form,
bool
types in aFromForm
structure will parse asfalse
. - The
FormItems
iterator can be queried for a complete parse viacompleted
andexhausted
. - Routes for
OPTIONS
requests can be declared via theoptions
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 beingConfig
by value.A call in v0.1 of the form
Rocket::custom(&config)
is nowRocket::custom(config, false)
. -
Tera templates are named without their extension.
A templated named
name.html.tera
is now simplyname
. -
JSON
unwrap
method has been renamed tointo_inner
.A call to
.unwrap()
should be changed to.into_inner()
. -
The
map!
macro was removed in favor of thejson!
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 anInto<Header>
type directly. -
The
Content-Type
forString
is nowtext/plain
.Use
content::HTML<String>
for HTML-basedString
responses. -
Request.content_type()
returns anOption<ContentType>
.Use
.unwrap_or(ContentType::Any)
to get the old behavior. -
The
ContentType
request guard forwards when the request has noContent-Type
header.Use an
Option<ContentType>
and.unwrap_or(ContentType::Any)
for the old behavior. -
A
Rocket
instance must be declared before aMockRequest
.Change the order of the
rocket::ignite()
andMockRequest::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 asFormItems::from(string)
. -
from_from_string(&str)
inFromForm
removed in favor offrom_form_items(&mut FormItems)
.Most implementation should be using
FormItems
internally; simply use the passed inFormItems
. In other cases, the form string can be retrieved via theinner_str
method ofFormItems
. -
Config::{set, default_for}
are deprecated.Use the
set_{param}
methods instead ofset
, andnew
orbuild
in place ofdefault_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 theactive
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 properu16
. - Clippy issues injected by codegen are resolved.
- Handlebars was updated to
0.25
. - The
PartialEq
implementation ofConfig
doesn't consider the path or session key. - Hyper dependency updated to
0.10
. - The
Error
type forJSON as FromData
has been exposed asSerdeError
. - 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:
- 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.
- 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.
- 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.