mirror of https://github.com/rwf2/Rocket.git
623 lines
21 KiB
Markdown
623 lines
21 KiB
Markdown
# Rocket v0.5: Stable, Async, Sentinels, Streams, SSE, Forms, WebSockets, & So Much More
|
|
|
|
<p class="metadata"><strong>
|
|
Posted by <a href="https://sergio.bz">Sergio Benitez</a> on Nov 17, 2023
|
|
</strong></p>
|
|
|
|
Four years, four release candidates, a thousand commits, and over a thousand
|
|
issues, discussions, and PRs later, I am ~~relieved~~ thrilled to announce the
|
|
general availability of Rocket v0.5.
|
|
|
|
> **Rocket** is an async backend web framework for Rust with a focus on
|
|
> usability, security, extensibility, and speed. Rocket makes it simple to write
|
|
> secure web applications without sacrificing productivity or performance.
|
|
|
|
We encourage all users to upgrade. For a guided migration from Rocket v0.4 to
|
|
Rocket v0.5, please consult the newly available [upgrading guide]. Rocket v0.4
|
|
will continue to be supported and receive security updates until the time Rocket
|
|
v0.6 is released.
|
|
|
|
! note: This is a co-announcement [along with the prelaunch] of [RWF2].
|
|
|
|
We're addressing the community's concerns regarding the pace of Rocket's
|
|
development, leadership, and release cadence in a separate announcement.
|
|
Please see the accompanying [RWF2 prelaunch announcement](../2023-11-17-rwf2-prelaunch/)
|
|
to learn more and see how you can get involved.
|
|
|
|
[RWF2]: https://rwf2.org
|
|
[along with the prelaunch]: ../2023-11-17-rwf2-prelaunch/
|
|
[upgrading guide]: @guide-v0.5/upgrading
|
|
|
|
## What's New?
|
|
|
|
Almost every bit has been reevaluated with a focus on usability and developer
|
|
productivity, security, and consistency across the library and [broader
|
|
ecosystem]. The changes are numerous, so we focus on the most noteworthy changes
|
|
here and encourage everyone to read the [CHANGELOG] for a complete list. For
|
|
answers to frequently asked questions, see the new [FAQ].
|
|
|
|
[CHANGELOG]: https://github.com/rwf2/Rocket/blob/v0.5.0/CHANGELOG.md
|
|
[broader ecosystem]: @guide-v0.5/faq/#releases
|
|
[FAQ]: @guide-v0.5/faq
|
|
|
|
### ⚓ Support for Stable `rustc` <badge>since `rc.1`</badge>
|
|
|
|
Rocket v0.5 compiles and builds on Rust stable. You can now compile and build
|
|
Rocket applications with `rustc` from the stable release channel and remove all
|
|
`#![feature(..)]` crate attributes. The complete canonical example with a single
|
|
`hello` route becomes:
|
|
|
|
```rust
|
|
#[macro_use] extern crate rocket;
|
|
|
|
#[get("/<name>/<age>")]
|
|
fn hello(name: &str, age: u8) -> String {
|
|
format!("Hello, {} year old named {}!", age, name)
|
|
}
|
|
|
|
#[launch]
|
|
fn rocket() -> _ {
|
|
rocket::build().mount("/hello", routes![hello])
|
|
}
|
|
```
|
|
|
|
<details>
|
|
<summary>See a <code>diff</code> of the changes from v0.4.</summary>
|
|
|
|
```diff
|
|
- #![feature(proc_macro_hygiene, decl_macro)]
|
|
-
|
|
#[macro_use] extern crate rocket;
|
|
|
|
#[get("/<name>/<age>")]
|
|
- fn hello(name: String, age: u8) -> String {
|
|
+ fn hello(name: &str, age: u8) -> String {
|
|
format!("Hello, {} year old named {}!", age, name)
|
|
}
|
|
|
|
- fn main() {
|
|
- rocket::ignite().mount("/hello", routes![hello]).launch();
|
|
- }
|
|
+ #[launch]
|
|
+ fn rocket() -> _ {
|
|
+ rocket::build().mount("/hello", routes![hello])
|
|
+ }
|
|
```
|
|
|
|
</details>
|
|
|
|
Note the new [`launch`] attribute, which simplifies starting an `async` runtime
|
|
for Rocket applications. See the [migration guide] for more on transitioning to
|
|
a stable toolchain.
|
|
|
|
[`launch`]: @api-v0.5/rocket/attr.launch.html
|
|
|
|
### 📥 Async I/O <badge>since `rc.1`</badge>
|
|
|
|
Rocket's core request handling was rebuilt in v0.5 to take advantage of the
|
|
latest `async` networking facilities in Rust. Backed by `tokio`, Rocket
|
|
automatically multiplexes request handling across `async` tasks on all of the
|
|
available cores on the machine. As a result, route handlers can now be declared
|
|
`async` and make use of `await` syntax:
|
|
|
|
```rust
|
|
use rocket::tokio;
|
|
use rocket::data::{Data, ToByteUnit};
|
|
|
|
#[post("/debug", data = "<data>")]
|
|
async fn debug(data: Data<'_>) -> std::io::Result<()> {
|
|
// Stream at most 512KiB all of the body data to stdout.
|
|
data.open(512.kibibytes())
|
|
.stream_to(tokio::io::stdout())
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
See the [Blocking I/O](@guide-v0.5/upgrading#blocking-io) section of the upgrading
|
|
guide for complete details on the `async` I/O transition.
|
|
|
|
### 💂 Sentinels <badge>since `rc.1`</badge>
|
|
|
|
Rocket v0.5 introduces [sentinels]. Entirely unique to Rocket, sentinels offer
|
|
an automatic last line of defense against runtime errors by enabling any type
|
|
that appears in a route to abort application launch under invalid conditions.
|
|
For example, the [`&State<T>`] guard in v0.5 is a [`Sentinel`] that aborts
|
|
launch if the type `T` is not in managed state, thus preventing associated
|
|
runtime errors.
|
|
|
|
[`Sentinel`]s can be implemented outside of Rocket, too, and you should seek to
|
|
do so whenever possible. For instance, the [`Template`] type from
|
|
[`rocket_dyn_templates`] is a sentinel that ensures templates are properly
|
|
registered. As another example, consider a `MyResponder` that expects:
|
|
|
|
* A specific type `T` to be in managed state.
|
|
* An catcher to be registered for the `400` status code.
|
|
|
|
Making `MyResponder` a sentinel that guards against these conditions is as
|
|
simple as:
|
|
|
|
```rust
|
|
use rocket::{Rocket, Ignite, Sentinel};
|
|
# struct MyResponder;
|
|
# struct T;
|
|
|
|
impl Sentinel for MyResponder {
|
|
fn abort(r: &Rocket<Ignite>) -> bool {
|
|
r.state::<T>().is_none() || !r.catchers().any(|c| c.code == Some(400))
|
|
}
|
|
}
|
|
```
|
|
|
|
[sentinels]: @api-v0.5/rocket/trait.Sentinel.html
|
|
[`Sentinel`]: @api-v0.5/rocket/trait.Sentinel.html
|
|
[`&State<T>`]: @api-v0.5/rocket/struct.State.html
|
|
[`Template`]: @api-v0.5/rocket_dyn_templates/struct.Template.html
|
|
[`rocket_dyn_templates`]: @api-v0.5/rocket_dyn_templates/index.html
|
|
|
|
### ☄️ Streams and SSE <badge>since `rc.1`</badge>
|
|
|
|
Powered by the new asynchronous core, Rocket v0.5 introduces real-time, typed
|
|
`async` [streams]. The new [async streams] section of the guide contains further
|
|
details, and we encourage all interested parties to see the new real-time,
|
|
multi-room [chat example].
|
|
|
|
As a taste of what's possible, the following `stream` route emits a `"pong"`
|
|
Server-Sent Event every `n` seconds, defaulting to `1`:
|
|
|
|
```rust
|
|
# use rocket::*;
|
|
use rocket::tokio::time::{interval, Duration};
|
|
use rocket::response::stream::{Event, EventStream};;
|
|
|
|
#[get("/ping?<n>")]
|
|
fn stream(n: Option<u64>) -> EventStream![] {
|
|
EventStream! {
|
|
let mut timer = interval(Duration::from_secs(n.unwrap_or(1)));
|
|
loop {
|
|
yield Event::data("pong");
|
|
timer.tick().await;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
[streams]: @api-v0.5/rocket/response/stream/index.html
|
|
[async streams]: @guide-v0.5/responses/#async-streams
|
|
[chat example]: @example/chat
|
|
|
|
### 🔌 WebSockets <badge>since `rc.4`</badge>
|
|
|
|
Rocket v0.5 introduces support for HTTP connection upgrades via a new [upgrade
|
|
API]. The API allows responders to assume control of raw I/O with the client in
|
|
an existing HTTP connection, thus allowing HTTP connections to be _upgraded_ to
|
|
any protocol, including WebSockets!
|
|
|
|
The newly introduced [`rocket_ws`] library takes advantage of the new API to
|
|
implement first-class support for WebSockets entirely outside of Rocket's core.
|
|
Working with `rocket_ws` to implement an echo server looks like this:
|
|
|
|
```rust
|
|
# use rocket::get;
|
|
use rocket_ws::{WebSocket, Stream};
|
|
|
|
#[get("/echo")]
|
|
fn echo_compose(ws: WebSocket) -> Stream!['static] {
|
|
ws.stream(|io| io)
|
|
}
|
|
```
|
|
|
|
Just like the newly introduced `async` streams, `rocket_ws` also supports using
|
|
generator syntax for WebSocket messages:
|
|
|
|
```rust
|
|
# use rocket::get;
|
|
use rocket_ws::{WebSocket, Stream};
|
|
|
|
#[get("/echo")]
|
|
fn echo_stream(ws: WebSocket) -> Stream!['static] {
|
|
Stream! { ws =>
|
|
for await message in ws {
|
|
yield message?;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
For complete usage details, see the [`rocket_ws`] documentation.
|
|
|
|
[upgrade API]: @api-v0.5/rocket/response/struct.Response.html#upgrading
|
|
[`rocket_ws`]: @api-v0.5/rocket_ws
|
|
|
|
### 📝 Comprehensive Forms <badge>since `rc.1`</badge>
|
|
|
|
Rocket v0.5 entirely revamps [forms] with support for [multipart uploads],
|
|
[arbitrary collections] with [arbitrary nesting], [ad-hoc validation], and an
|
|
improved [`FromForm` derive], obviating the need for nearly all custom
|
|
implementations of `FromForm` or `FromFormField`. Rocket's new wire protocol for
|
|
forms allows applications to express _any structure_ with _any level of nesting
|
|
and collection_ without any custom code, eclipsing what's offered by other web
|
|
frameworks.
|
|
|
|
As an illustrative example, consider the following structures:
|
|
|
|
```rust
|
|
use rocket::form::FromForm;
|
|
|
|
#[derive(FromForm)]
|
|
struct MyForm<'r> {
|
|
owner: Person<'r>,
|
|
pet: Pet<'r>,
|
|
}
|
|
|
|
#[derive(FromForm)]
|
|
struct Person<'r> {
|
|
name: &'r str
|
|
}
|
|
|
|
#[derive(FromForm)]
|
|
struct Pet<'r> {
|
|
name: &'r str,
|
|
#[field(validate = eq(true))]
|
|
good_pet: bool,
|
|
}
|
|
```
|
|
|
|
To parse request data into a `MyForm`, a form with fields of `owner.name`,
|
|
`pet.name`, and `pet.good_pet` must be submitted. The ad-hoc validation on
|
|
`good_pet` validates that `good_pet` parses as `true`. Such a form, URL-encoded,
|
|
may look like:
|
|
|
|
```rust,ignore
|
|
"owner.name=Bob&pet.name=Sally&pet.good_pet=yes"
|
|
```
|
|
|
|
Rocket's derived `FromForm` implementation for `MyForm` will automatically parse
|
|
such a submission into the correct value:
|
|
|
|
```rust,ignore
|
|
MyForm {
|
|
owner: Person {
|
|
name: "Bob".into()
|
|
},
|
|
pet: Pet {
|
|
name: "Sally".into(),
|
|
good_pet: true,
|
|
}
|
|
}
|
|
# };
|
|
```
|
|
|
|
The rewritten [forms guide] provides complete details on revamped forms support.
|
|
|
|
[forms guide]: @guide-v0.5/requests/#forms
|
|
[ad-hoc validation]: @guide-v0.5/requests#ad-hoc-validation
|
|
[arbitrary nesting]: @guide-v0.5/requests#nesting
|
|
[multipart uploads]: @guide-v0.5/requests#multipart
|
|
[forms]: @guide-v0.5/requests#forms
|
|
[`FromFormField`]: @api-v0.5/rocket/form/trait.FromFormField.html
|
|
[arbitrary collections]: @guide-v0.5/requests#collections
|
|
[`FromForm` derive]: @api-v0.5/rocket/derive.FromForm.html
|
|
|
|
### 🚀 And so much more!
|
|
|
|
Rocket v0.5 introduces over **40** new features and major improvements! We
|
|
encourage everyone to review the [CHANGELOG] to learn about them all. Here are a
|
|
few more we don't want you to miss:
|
|
|
|
* An automatically enabled [`Shield`]: security and privacy headers for all responses.
|
|
* [Graceful shutdown] with configurable grace periods, [notification], and [shutdown fairings].
|
|
* An entirely new, flexible and robust [configuration system] based on [Figment].
|
|
* Type-system enforced [incoming data limits] to mitigate memory-based DoS attacks.
|
|
* Support for [mutual TLS] and client [`Certificate`]s.
|
|
* Asynchronous database pooling support via [`rocket_db_pools`].
|
|
* Compile-time URI literals via a fully revamped [`uri!`] macro.
|
|
|
|
[`Shield`]: @api-v0.5/rocket/shield/struct.Shield.html
|
|
[graceful shutdown]: @api-v0.5/rocket/config/struct.Shutdown.html#summary
|
|
[notification]: @api-v0.5/rocket/struct.Shutdown.html
|
|
[shutdown fairings]: @api-v0.5/rocket/fairing/trait.Fairing.html#shutdown
|
|
[configuration system]: @guide-v0.5/configuration/#configuration
|
|
[Figment]: https://docs.rs/figment/
|
|
[incoming data limits]: @guide-v0.5/requests/#streaming
|
|
[mutual TLS]: @guide-v0.5/configuration/#mutual-tls
|
|
[`uri!`]: @api-v0.5/rocket/macro.uri.html
|
|
[`rocket_db_pools`]: @api-v0.5/rocket_db_pools/index.html
|
|
[`Certificate`]: @api-v0.5/rocket/mtls/struct.Certificate.html
|
|
[migration guide]: @guide-v0.5/upgrading
|
|
|
|
## What's Next?
|
|
|
|
We think Rocket provides the most productive and confidence-inspiring web
|
|
development experience in Rust today, but as always, there's room for
|
|
improvement. To that end, here's what's on the docket for the next major
|
|
release:
|
|
|
|
0. **Migration to RWF2**
|
|
|
|
Discussed further in the [RWF2 prelaunch announcement], Rocket will
|
|
transition to being managed by the newly formed Rocket Web Framework
|
|
Foundation: _RWF2_. The net effect is increased development transparency,
|
|
including public roadmaps and periodic updates, financial support for
|
|
high-quality contributions, and codified pathways into the project's
|
|
governance.
|
|
|
|
0. **Pluggable Connection Listeners**
|
|
|
|
Rocket currently expects and enjoins connection origination via
|
|
TCP/IP. While sufficient for the common case, it excludes other desirable
|
|
interfaces such as Unix Domain Sockets (UDS).
|
|
|
|
In the next major release, Rocket will expose [an interface for implementing
|
|
and plugging-in custom connection listeners]. Rocket itself will make use
|
|
of this interface to expose more common mediums out-of-the-box, such as the
|
|
aforementioned UDS.
|
|
|
|
0. **Native `async` Traits**
|
|
|
|
Given the [stabilization of `async fn` in traits], the next major release
|
|
will seek to eliminate Rocket's dependence on `#[async_trait]` opting instead
|
|
for native `async` traits. This will greatly improve our documentation, which
|
|
currently calls out the attribute for each affected trait, as well as offer
|
|
modest performance improvements.
|
|
|
|
0. [**Typed Catchers**](https://github.com/rwf2/Rocket/issues/749)
|
|
|
|
Today's catchers cannot receive strictly typed error data. This results
|
|
in workarounds where error data is queried for well-typedness at runtime.
|
|
While it _has_ been possible to implement a form of typed error catching
|
|
prior, doing so necessitated limiting error data to `'static` values, as
|
|
other Rust web frameworks do, a concession we're unwilling to make.
|
|
|
|
After much experimentation, we have an approach that is ergonomic to use,
|
|
safe, and correct, all without the `'static` limitation. This will allow error
|
|
catchers to "pattern match" against error types at compile-time. At runtime,
|
|
Rocket will match emerging error types against the declared catchers and
|
|
call the appropriate catcher with the fully-typed value.
|
|
|
|
0. **Short-Circuitable Request Processing**
|
|
|
|
Whether with success or failure, fairings and guards cannot presently
|
|
terminate request processing early. The rationale for forbidding this
|
|
functionality was that it would allow third-party crates and plugins to
|
|
dictate responses without offering any recourse to the top-level application.
|
|
|
|
With the advent of typed catchers, however, we now have a mechanism by which
|
|
a top-level application can intercept early responses via their type,
|
|
resolving the prior concern. As such, in the next major release, fairings and
|
|
guards will be able to respond to requests early, and catchers will be able to
|
|
intercept those early responses at will.
|
|
|
|
0. **Associated Resources**
|
|
|
|
Often a set of routes will share a set requirements. For example, they
|
|
may share a URI prefix, subset of guards, and some managed state. In today's
|
|
Rocket, these common requirements must be repeatedly specified for each route.
|
|
While this is by design (we _want_ a route's requirements to be obvious), the
|
|
repetition is arduous and potentially error prone.
|
|
|
|
In an upcoming major release, Rocket will introduce new mechanisms by which
|
|
a set of routes can share an explicitly declared set of requirements. Their
|
|
_explicit_ and _declarative_ nature results in requirements that are
|
|
simultaneously obvious _and_ declared once.
|
|
|
|
We're really excited about this upcoming change and will be announcing more
|
|
in the near future.
|
|
|
|
0. **Performance Improvements**
|
|
|
|
Rocket appears to lag behind other Rust web frameworks in benchmarks. This is
|
|
partly due to [poor benchmarking], partly due to security-minded design
|
|
decisions, and partially due to unexploited opportunities. In the next
|
|
release, we'll be addressing the latter points. Specifically:
|
|
|
|
- _Explore making work stealing optional._
|
|
|
|
Rocket currently defaults to using tokio's multithreaded, work-stealing
|
|
scheduler. This avoids tail latency issues when faced with irregular and
|
|
heterogeneous tasks at the expense of throughput due to higher bookkeeping costs
|
|
associated with work stealing. Other Rust web frameworks instead opt to use
|
|
tokio's single-threaded scheduler, which while theoretically suboptimal,
|
|
may yield better performance results in practice, especially when
|
|
benchmarking homogeneous workloads.
|
|
|
|
While we believe work-stealing schedulers are the right choice for the
|
|
majority of applications desireing robust performance characteristics, we also
|
|
believe the choice should be the user's. We'll seek to make this choice
|
|
easier in the next release.
|
|
|
|
- _Reduce conversions from external to internal HTTP types._
|
|
|
|
Rocket revalidates and sometimes copies incoming HTTP request data.
|
|
In Rocket v0.5, we began transitioning to a model where we revalidate
|
|
security insensitive data in debug mode only, allowing for bugs to be
|
|
caught and reported while reducing performance impacts in production. In
|
|
the next release, we seek to extend this approach.
|
|
|
|
[an interface for implementing and plugging-in custom connection listeners]:
|
|
https://github.com/rwf2/Rocket/issues/1070#issuecomment-1491101952
|
|
[stabilization of `async fn` in traits]: https://github.com/rust-lang/rust/pull/115822
|
|
[poor benchmarking]: @guide-v0.5/faq/#performance
|
|
|
|
<!-- custom routers? -->
|
|
|
|
## ❤️ Thank You
|
|
|
|
A very special thank you to [Jeb Rosen], Rocket's maintainer from v0.4 to
|
|
v0.5-rc.1, without whom Rocket v0.5 wouldn't exist. Jeb is responsible for
|
|
leading the migration to `async` and Rust stable along with tireless efforts to
|
|
improve Rocket's documentation and address the community. Rocket is better for
|
|
having had Jeb along for the ride. Thank you, Jeb.
|
|
|
|
[Jeb Rosen]: https://github.com/rwf2/Rocket/commits?author=jebrosen
|
|
|
|
A special thank you to all of Rocket's users, especially those who diligently
|
|
waded through all four release candidates, raised issues, and participated on
|
|
[GitHub] and the [Matrix channel]. You all are an awesome, kind, and thoughtful
|
|
bunch. Thank you.
|
|
|
|
A heartfelt _thank you_ as well to _all_ **148** who contributed to Rocket v0.5:
|
|
|
|
<ul class="columns">
|
|
<li>Aaron Leopold</li>
|
|
<li>Abdullah Alyan</li>
|
|
<li>Aditya</li>
|
|
<li>Alex Macleod</li>
|
|
<li>Alex Sears</li>
|
|
<li>Alexander van Ratingen</li>
|
|
<li>ami-GS</li>
|
|
<li>Antoine Martin</li>
|
|
<li>arctic-alpaca</li>
|
|
<li>arlecchino</li>
|
|
<li>Arthur Woimbée</li>
|
|
<li>atouchet</li>
|
|
<li>Aurora</li>
|
|
<li>badoken</li>
|
|
<li>Beep LIN</li>
|
|
<li>Ben Sully</li>
|
|
<li>Benedikt Weber</li>
|
|
<li>Benjamin B</li>
|
|
<li>BlackDex</li>
|
|
<li>Bonex</li>
|
|
<li>Brenden Matthews</li>
|
|
<li>Brendon Federko</li>
|
|
<li>Brett Buford</li>
|
|
<li>Cedric Hutchings</li>
|
|
<li>Cezar Halmagean</li>
|
|
<li>Charles-Axel Dein</li>
|
|
<li>Compro Prasad</li>
|
|
<li>cui fliter</li>
|
|
<li>Daniel Wiesenberg</li>
|
|
<li>David Venhoek</li>
|
|
<li>Dimitri Sabadie</li>
|
|
<li>Dinu Blanovschi</li>
|
|
<li>Dominik Boehi</li>
|
|
<li>Doni Rubiagatra</li>
|
|
<li>Edgar Onghena</li>
|
|
<li>Edwin Svensson</li>
|
|
<li>est31</li>
|
|
<li>Felix Suominen</li>
|
|
<li>Fenhl</li>
|
|
<li>Filip Gospodinov</li>
|
|
<li>Flying-Toast</li>
|
|
<li>Follpvosten</li>
|
|
<li>Francois Stephany</li>
|
|
<li>Gabriel Fontes</li>
|
|
<li>gcarq</li>
|
|
<li>George Cheng</li>
|
|
<li>Giles Cope</li>
|
|
<li>Gonçalo Ribeiro</li>
|
|
<li>hiyoko3m</li>
|
|
<li>Howard Su</li>
|
|
<li>hpodhaisky</li>
|
|
<li>Ian Jackson</li>
|
|
<li>IFcoltransG</li>
|
|
<li>Indosaram</li>
|
|
<li>inyourface34456</li>
|
|
<li>J. Cohen</li>
|
|
<li>Jacob Pratt</li>
|
|
<li>Jacob Sharf</li>
|
|
<li>Jacob Simpson</li>
|
|
<li>Jakub Dąbek</li>
|
|
<li>Jakub Wieczorek</li>
|
|
<li>James Tai</li>
|
|
<li>Jason Hinch</li>
|
|
<li>Jeb Rosen</li>
|
|
<li>Jeremy Kaplan</li>
|
|
<li>Jieyou Xu</li>
|
|
<li>Joakim Soderlund</li>
|
|
<li>Johannes Liebermann</li>
|
|
<li>John-John Tedro</li>
|
|
<li>Jonah Brüchert</li>
|
|
<li>Jonas Møller</li>
|
|
<li>Jonathan Dickinson</li>
|
|
<li>Jonty</li>
|
|
<li>Joscha</li>
|
|
<li>Joshua Nitschke</li>
|
|
<li>JR Heard</li>
|
|
<li>Juhasz Sandor</li>
|
|
<li>Julian Büttner</li>
|
|
<li>Juraj Fiala</li>
|
|
<li>Kenneth Allen</li>
|
|
<li>Kevin Wang</li>
|
|
<li>Kian-Meng Ang</li>
|
|
<li>Konrad Borowski</li>
|
|
<li>Leonora Tindall</li>
|
|
<li>Lev Kokotov</li>
|
|
<li>lewis</li>
|
|
<li>Lionel G</li>
|
|
<li>Lucille Blumire</li>
|
|
<li>Mai-Lapyst</li>
|
|
<li>Manuel</li>
|
|
<li>Manuel Transfeld</li>
|
|
<li>Marc Schreiber</li>
|
|
<li>Marc-Stefan Cassola</li>
|
|
<li>Marshall Bowers</li>
|
|
<li>Martin1887</li>
|
|
<li>Martinez</li>
|
|
<li>Matthew Pomes</li>
|
|
<li>Maxime Guerreiro</li>
|
|
<li>meltinglava</li>
|
|
<li>Michael Howell</li>
|
|
<li>Mikail Bagishov</li>
|
|
<li>mixio</li>
|
|
<li>multisn8</li>
|
|
<li>Necmettin Karakaya</li>
|
|
<li>Ning Sun</li>
|
|
<li>Nya</li>
|
|
<li>Paolo Barbolini</li>
|
|
<li>Paul Smith</li>
|
|
<li>Paul van Tilburg</li>
|
|
<li>Paul Weaver</li>
|
|
<li>pennae</li>
|
|
<li>Petr Portnov</li>
|
|
<li>philipp</li>
|
|
<li>Pieter Frenssen</li>
|
|
<li>PROgrm_JARvis</li>
|
|
<li>Razican</li>
|
|
<li>Redrield</li>
|
|
<li>Riley Patterson</li>
|
|
<li>Rodolphe Bréard</li>
|
|
<li>Roger Mo</li>
|
|
<li>RotesWasser</li>
|
|
<li>rotoclone</li>
|
|
<li>Ruben Schmidmeister</li>
|
|
<li>Rudi Floren</li>
|
|
<li>Rémi Lauzier</li>
|
|
<li>Samuele Esposito</li>
|
|
<li>Scott McMurray</li>
|
|
<li>Sergio Benitez</li>
|
|
<li>Silas Sewell</li>
|
|
<li>Soham Roy</li>
|
|
<li>Steven Murdoch</li>
|
|
<li>Stuart Hinson</li>
|
|
<li>Thibaud Martinez</li>
|
|
<li>Thomas Eckert</li>
|
|
<li>ThouCheese</li>
|
|
<li>Tilen Pintarič</li>
|
|
<li>timando</li>
|
|
<li>timokoesters</li>
|
|
<li>toshokan</li>
|
|
<li>TotalKrill</li>
|
|
<li>Unpublished</li>
|
|
<li>Vasili</li>
|
|
<li>Vladimir Ignatev</li>
|
|
<li>Wesley Norris</li>
|
|
<li>xelivous</li>
|
|
<li>YetAnotherMinion</li>
|
|
<li>Yohannes Kifle</li>
|
|
<li>Yusuke Kominami</li>
|
|
</ul>
|
|
|
|
[GitHub discussions]: https://github.com/rwf2/Rocket/discussions
|
|
[Matrix channel]: https://chat.mozilla.org/#/room/#rocket:mozilla.org
|
|
|
|
## Get Involved
|
|
|
|
Looking to help with Rocket? To contribute code, head over to [GitHub]. To get
|
|
involved with the project, see the [RWF2 prelaunch announcement]. We'd love to have you.
|
|
|
|
[GitHub]: https://github.com/rwf2/Rocket
|
|
[RWF2 prelaunch announcement]: ../2023-11-17-rwf2-prelaunch/
|