Add v0.4 release announcement.

This commit is contained in:
Sergio Benitez 2018-12-08 08:45:06 -08:00
parent 5056705884
commit 2ec865dea2
2 changed files with 596 additions and 0 deletions

View File

@ -0,0 +1,574 @@
# Rocket v0.4: Typed URIs, Database Support, Revamed Queries, Stateful Handlers, & More!
<p class="metadata"><strong>
Posted by <a href="https://sergio.bz">Sergio Benitez</a> on December 08, 2018
</strong></p>
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](../../guide) or learning
more from [the overview](../../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].
[CHANGELOG]: https://github.com/SergioBenitez/Rocket/blob/v0.4.0/CHANGELOG.md#version-040-dec-06-2018
### 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!
[@jebrosen]: https://github.com/jebrosen
### 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:
```diff
- #![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`:
```diff
[dependencies]
- rocket = "0.3"
+ rocket = "0.4"
- rocket_codegen = "0.3"
```
[`rocket_codegen`]: https://api.rocket.rs/v0.4/rocket_codegen/index.html
### 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:
```rust
#[get("/person/<name>?<age>")]
fn person(name: String, age: Option<u8>)
```
URIs to this `person` route can be created as follows:
```rust
// 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:
```rust
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:
```rust
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.
[`uri!`]: @api/rocket_codegen/macro.uri.html
[Typed URIs]: ../../guide/responses/#typed-uris
### 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:
```toml
[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:
```rust
#[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:
```rust
#[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.
[Database]: ../../guide/state/#databases
[`rocket_contrib::databases`]: @api/rocket_contrib/databases/index.html
### 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:
```rust
#[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.
[`FromFormValue`]: @api/rocket/request/trait.FromFormValue.html
[`FromQuery`]: @api/rocket/request/trait.FromQuery.html
[`route` attribute]: @api/rocket_codegen/attr.get.html
[Query Strings]: ../../guide/requests/#query-strings
[#608]: https://github.com/SergioBenitez/Rocket/issues/608
### 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:
```rust
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.
[`Handler`]: @api/rocket/trait.Handler.html
[`StaticFiles`]: @api/rocket_contrib/serve/struct.StaticFiles.html
### 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:
```rust
#[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:
```rust
#[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.
[Custom Responders]: ../../guide/responses/#custom-responders
[`Responder` derive]: @api/rocket_codegen/derive.Responder.html
[`Responder`]: @api/rocket/response/trait.Responder.html
### 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 as `json` and `html`.
* Implemented [`Responder` for `Status`].
* Added [`Response::cookies()`] for retrieving response cookies.
* All logging is disabled when `log` is set to `off`.
* Added [`Metadata`] guard for retrieving templating information.
* The [`Uri`] type parses according to RFC 7230 into one of [`Origin`],
[`Absolute`], or [`Authority`].
* Added [`Outcome::and_then()`], [`Outcome::failure_then()`], and
[`Outcome::forward_then()`].
* Implemented `Responder` for `&[u8]`.
* Any `T: Into<Vec<Route>>` can be [`mount()`]ed.
* Added [`Request::get_query_value()`] for retrieving a query value by key.
* Applications can launch without a working directory.
* Added [`State::from()`] for constructing `State` values.
[`SpaceHelmet`]: https://api.rocket.rs/v0.4/rocket_contrib/helmet/index.html
[`State::from()`]: https://api.rocket.rs/v0.4/rocket/struct.State.html#method.from
[Typed URIs]: https://rocket.rs/v0.4/guide/responses/#typed-uris
[ORM agnostic database support]: https://rocket.rs/v0.4/guide/state/#databases
[`Template::custom()`]: https://api.rocket.rs/v0.4/rocket_contrib/templates/struct.Template.html#method.custom
[`LocalRequest::private_cookie()`]: https://api.rocket.rs/v0.4/rocket/local/struct.LocalRequest.html#method.private_cookie
[`LocalRequest`]: https://api.rocket.rs/v0.4/rocket/local/struct.LocalRequest.html
[shorthands]: https://api.rocket.rs/v0.4/rocket/http/struct.ContentType.html#method.parse_flexible
[derive for `FromFormValue`]: https://api.rocket.rs/v0.4/rocket_codegen/derive.FromFormValue.html
[derive for `Responder`]: https://api.rocket.rs/v0.4/rocket_codegen/derive.Responder.html
[`Response::cookies()`]: https://api.rocket.rs/v0.4/rocket/struct.Response.html#method.cookies
[`Client`]: https://api.rocket.rs/v0.4/rocket/local/struct.Client.html
[Request-Local State]: https://rocket.rs/v0.4/guide/state/#request-local-state
[`Metadata`]: https://api.rocket.rs/v0.4/rocket_contrib/templates/struct.Metadata.html
[`Uri`]: https://api.rocket.rs/v0.4/rocket/http/uri/enum.Uri.html
[`Origin`]: https://api.rocket.rs/v0.4/rocket/http/uri/struct.Origin.html
[`Absolute`]: https://api.rocket.rs/v0.4/rocket/http/uri/struct.Absolute.html
[`Authority`]: https://api.rocket.rs/v0.4/rocket/http/uri/struct.Authority.html
[`Outcome::and_then()`]: https://api.rocket.rs/v0.4/rocket/enum.Outcome.html#method.and_then
[`Outcome::forward_then()`]: https://api.rocket.rs/v0.4/rocket/enum.Outcome.html#method.forward_then
[`Outcome::failure_then()`]: https://api.rocket.rs/v0.4/rocket/enum.Outcome.html#method.failure_then
[`StaticFiles`]: https://api.rocket.rs/v0.4/rocket_contrib/serve/struct.StaticFiles.html
[live template reloading]: https://rocket.rs/v0.4/guide/responses/#live-reloading
[`Handler`]: https://api.rocket.rs/v0.4/rocket/trait.Handler.html
[`mount()`]: https://api.rocket.rs/v0.4/rocket/struct.Rocket.html#method.mount
[`FromData::transform()`]: https://api.rocket.rs/v0.4/rocket/data/trait.FromData.html#tymethod.transform
[transforming]: https://api.rocket.rs/v0.4/rocket/data/trait.FromData.html#transforming
[query string handling]: https://rocket.rs/v0.4/guide/requests/#query-strings
[Default rankings]: https://rocket.rs/v0.4/guide/requests/#default-ranking
[`Request::get_query_value()`]: https://api.rocket.rs/v0.4/rocket/struct.Request.html#method.get_query_value
[`Responder` for `Status`]: https://rocket.rs/v0.4/guide/responses/#status
## Breaking Changes
This release includes many breaking changes. Please see the
[CHANGELOG](https://github.com/SergioBenitez/Rocket/blob/v0.3.0/CHANGELOG.md#breaking-changes)
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 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`.
[Tera templates example]: @github/examples/tera_templates
[`FormItems`]: @api/rocket/request/enum.FormItems.html
[`Config::active()`]: @api/rocket/config/struct.Config.html#method.active
[`Flash`]: @api/rocket/response/struct.Flash.html
[`AdHoc::on_attach()`]: @api/rocket/fairing/struct.AdHoc.html#method.on_attach
[`AdHoc::on_launch()`]: @api/rocket/fairing/struct.AdHoc.html#method.on_launch
[`Config::root_relative()`]: @api/rocket/struct.Config.html#method.root_relative
[`Config::tls_enabled()`]: @api/rocket/struct.Config.html#method.tls_enabled
[`rocket_codegen`]: @api/rocket_codegen/index.html
[`FromParam`]: @api/rocket/request/trait.FromParam.html
[`FromFormValue`]: @api/rocket/request/trait.FromFormValue.html
[`Data`]: @api/rocket/struct.Data.html
[`Form`]: https://api.rocket.rs/v0.4/rocket/request/struct.Form.html
[`LenientForm`]: https://api.rocket.rs/v0.4/rocket/request/struct.LenientForm.html
## 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.
2. **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.
3. **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.
4. **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.
[#17]: https://github.com/SergioBenitez/Rocket/issues/17
[#19]: https://github.com/SergioBenitez/Rocket/issues/19
[#106]: https://github.com/SergioBenitez/Rocket/issues/106
[#14]: https://github.com/SergioBenitez/Rocket/issues/14
## Rocket v0.4 Contributors
The following wonderful people helped make Rocket 0.4 happen:
<ul class="columns">
<li>Alexander Mielczarek</li>
<li>Alex Bowers</li>
<li>Alfie John</li>
<li>Alva Snædís</li>
<li>Ashley Williams</li>
<li>Beatriz Rizental</li>
<li>bohov</li>
<li>Christophe Courtaut</li>
<li>David Darrell</li>
<li>Desmond</li>
<li>Divyahans Gupta</li>
<li>Donald Robertson</li>
<li>EloD10</li>
<li>Eric Dattore</li>
<li>Henning Kowalk</li>
<li>Imron Alston</li>
<li>Jeb Rosen</li>
<li>kryptan</li>
<li>Kyle Clemens</li>
<li>lerina</li>
<li>Linus Unnebäck</li>
<li>Lukas Abfalterer</li>
<li>Marc Mettke</li>
<li>Max Furman</li>
<li>messense</li>
<li>Ning Sun</li>
<li>Philip Jenvey</li>
<li>Pyry Kontio</li>
<li>Richo Healey</li>
<li>Riley Trautman</li>
<li>Rolf Schmidt</li>
<li>Rukai</li>
<li>Sean Stangl</li>
<li>Sébastien Santoro</li>
<li>Sergio Benitez</li>
<li>Stanislav Ivanov</li>
<li>Tal Garfinkel</li>
<li>Tobias Stolzmann</li>
<li>Ville Hakulinen</li>
<li>Vishal Sodani</li>
<li>Zack Chandler</li>
<li>Zac Pullar-Strecker</li>
</ul>
Thank you all! Your contributions are **greatly** appreciated!
Looking to help with Rocket's development? Head over to [Rocket's
GitHub](https://github.com/SergioBenitez/Rocket#contributing) and start
contributing!

View File

@ -1,3 +1,25 @@
[[articles]]
title = """
Rocket v0.4: Typed URIs, Database Support, Revamed Queries, Stateful Handlers,
& More!
"""
slug = "2018-12-08-version-0.4"
author = "Sergio Benitez"
author_url = "https://sergio.bz"
date = "December 08, 2018"
snippet = """
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!
"""
[[articles]]
title = "Rocket's 2nd v0.4 Release Candidate"
slug = "2018-11-30-version-0.4-rc-2"