23 KiB
Rocket v0.4: Typed URIs, Database Support, Revamed Queries, Stateful Handlers, & 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:
- Configure databases in
Rocket.toml
. - Associate a request guard type and fairing with each database.
- 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.
Revamed 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
is deriveable.
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 enum
s and struct
s
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)]
enum Error {
#[response(status = 400)]
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 with the following new features:
- Introduced Request-Local State.
- Introduced transforming data guards via
FromData::transform()
. - Introduced the
SpaceHelmet
security and privacy headers fairing. - Private cookies are gated behind a
private-cookies
default feature. - Added derive for
FromFormValue
. - Added
Template::custom()
for customizing templating engines. - Cookies are automatically tracked and propagated by
Client
. - Private cookies can be added to local requests with
LocalRequest::private_cookie()
. - Release builds default to the
production
environment. - Keep-alive can be configured via the
keep_alive
configuration parameter. - Allow CLI colors and emoji to be disabled with
ROCKET_CLI_COLORS=off
. - Route
format
accepts shorthands such asjson
andhtml
. - Implemented
Responder
forStatus
. - Added
Response::cookies()
for retrieving response cookies. - All logging is disabled when
log
is set tooff
. - Added
Metadata
guard for retrieving templating information. - The
Uri
type parses according to RFC 7230 into one ofOrigin
,Absolute
, orAuthority
. - Added
Outcome::and_then()
,Outcome::failure_then()
, andOutcome::forward_then()
. - Implemented
Responder
for&[u8]
. - Any
T: Into<Vec<Route>>
can bemount()
ed. - Added
Request::get_query_value()
for retrieving a query value by key. - Applications can launch without a working directory.
- Added
State::from()
for constructingState
values.
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 maintance 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 of500
. - 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 forConfig::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 onAdHoc::on_attach()
,AdHoc::on_launch()
was removed.AdHoc::on_attach()
,AdHoc::on_launch()
accept anFnOnce
.- 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
andLenientForm
can be publicly constructed.- Console coloring uses default terminal colors instead of white.
- Console coloring is consistent across all messages.
i128
andu128
now implementFromParam
,FromFormValue
.- The
base64
dependency was updated to0.10
. - The
log
dependency was updated to0.4
. - The
handlebars
dependency was updated to1.0
. - The
tera
dependency was updated to0.11
. - The
uuid
dependency was updated to0.7
. - The
rustls
dependency was updated to0.14
. - The
cookie
dependency was updated to0.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:
- Support for Rust Stable (#19)
Finally! Rocket 0.5 will compile and run on stable versions of the Rust compiler.
- Aynchronous 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.
- 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.
- 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!