Rocket's master branch is currently fully asynchronous and supports async/await!
The async APIs are based on rust's async/await feature and std::future::Future, running on the tokio (0.2) runtime.
#[get|post|route(...)] async fn(params...) -> R where R is any Responder type. Routes that do not await anything can remain unchanged. #[catch]ers can also be defined as an async fn.Data now return Futures that must be .awaited. As a consequence, any route that uses Data directly must be async. Routes that use data guards such as Json are not affected by this change.async, as it will allow other requests to continue while waiting for the operation.FromTransformedData and FromData, Fairing, Responder)async-trait, re-exported as #[rocket::async_trait].DataStream implements tokio::io::AsyncRead instead of Read. Thus, FromTransformedData::transform and From(Transformed)Data::from_data are async fn or return Futures.set_body and related methods expect tokio::io::AsyncRead instead of Read.Fairing::on_response and Responder::respond_to are now async fn. This allows Responders and Fairings to access and/or replace the body data.rocket::local::asynchronous and rocket::local::blocking. The blocking test API is simpler to use, and recommended when possible.#[rocket::main], #[rocket::launch], and #[rocket::async_test] have been added to make async fn easily accessible for main() and test()..awaiting. Currently nothing "falls over", because tokio and/or hyper catch panics already. But we can do better, such as deliberately responding with a 500.AsyncRead vs Stream<Item=Result<Chunk, Error>>async "unlocks" the number of in-flight requests from num_threads to infinity, which makes it even more important than ever to have convenient APIs for per-request limits.FIXME: Document on a few macrosclippy or rustc would be much nicer, if it's feasible, but probably wouldn't be ready in time for a 0.5 release.TODO.async) throughout code.The async_await feature is now required in addition to
proc_macro_hygineanddecl_macro
Wasn't decl_macro removed on the master branch a while back?
Currently, routes must either be
fn -> R where R: Responder(non-async) orasync fn -> R where R: Responder(async). Should we try to support (non-async)fn -> impl Future<Output=R> where R: Responder?
I don't see why not. Though it would be relatively trivial to create it async and await the returned value(s).
I'll take a look at uuid tomorrow to see if any changes are necessary. I suspect the answer is no.
@jebrosen Roughly how much of the documentation can be switched over to async? Are we going to primarily document in async, or will we essentially double everything up?
Wasn't
decl_macroremoved on the master branch a while back?
Right! I'll fix that.
Currently, routes must either be
fn -> R where R: Responder(non-async) orasync fn -> R where R: Responder(async). Should we try to support (non-async)fn -> impl Future<Output=R> where R: Responder?I don't see why not. Though it would be relatively trivial to create it
asyncandawaitthe returned value(s).
The issue is the return type of routes. Currently the codegen assumes that a non-async function returns some type R that implements Responder and an async function returns a Future whose output type is some R implementing Responder. Allowing functions to return either a Responder or a Future is hairy because both are traits and pretty soon we would end up wrestling with specialization. But I might have missed something in my analysis, and I would really like to support it if we can!
@jebrosen Roughly how much of the documentation can be switched over to
async? Are we going to primarily document inasync, or will we essentially double everything up?
The only place where there is a choice is routes; currently everything else is an async-only API and documentation should reflect that. I was going to wait a bit before pushing hard for docs, as there are likely a few design issues to be resolved with which APIs return which futures with what lifetime bounds.
My admittedly hand-wavy assumption was that we would explain traits like FromData and Responder in terms of async and futures, but use mostly non-async routes. We should definitely have a few examples of async routes, perhaps for HTTP requests or File I/O. For a lot of routes I do expect it to be as easy as changing fn to async fn and using .await in the right places.
Another really important point here is that manual FromData and Responder implementations should be relatively rare and are much more in the domain of API documentation than in the guide. I am personally more worried about the accessibility and understandability of the guide.
The issue is the return type of routes. Currently the codegen assumes that a non-async function returns some type
Rthat implementsResponderand an async function returns aFuturewhose output type is someRimplementingResponder. Allowing functions to return either aResponderor aFutureis hairy because both are traits and pretty soon we would end up wrestling with specialization. But I might have missed something in my analysis, and I would really like to support it if we can!
That certainly makes sense. Adding support for -> impl Future would be backwards compatible, as it doesn't currently work. So that's a point that can certainly be postponed to see if there's even demand after async lands.
The only place where there is a choice is routes; currently everything else is an async-only API and documentation should reflect that. I was going to wait a bit before pushing hard for docs, as there are likely a few design issues to be resolved with which APIs return which futures with what lifetime bounds.
Probably best to hold off on docs for the time being, then.
compression - Not for the faint of heart; there might be some important APIs that are not yet available. HELP WELCOME
Quick advertisement for https://github.com/rustasync/async-compression, this should allow just drop-in wrappers to transparently compress/decompress AsyncRead/AsyncWrite instances. Not all encodings are supported for the read/write interfaces yet as the first framework it was used in used the impl Stream<Item = io::Result<Bytes>> adaptors instead, but it should be straightforward to expand them (at least once I merge the initial AsyncWrite support).
@jebrosen this is awesome!
Just as a side note, both tokio and hyper master are working with std::future::Future now. It's still a WIP but in my testing the essentials are all working, so this could already be based on the master branches instead of dealing with the compat layer.
contrib/lib/src/uuid.rs looks good to me as-is. It's just a thin wrapper around the uuid crate, so there's not really any room for making things async, unless we were to go all in and parse it asynchronously.
databases - We are not necessarily pushing for asynchronous database engines, but we should at least ensure they will be usable. postgres is a good candidate for exploring this.
Hi - what would you be looking for in a pull request to explore this? I've been developing some projects using Rocket and have been using both tokio-postgres and the synchronous rust-postgres, as well as their connection pools, and I would like to help if I think I know how.
Hi - what would you be looking for in a pull request to explore this?
I have not tried too hard to test what "ensure they will be usable" entails. There are a few problems I can see ahead of time:
tokio-postgres. This is a non-obvious compatibility problem that isn't expressed in any type signature.tokio-postgres, for example. So we should evaluate if making FromRequest return a future would be worth it for making databases (among other things) easier to work with.At a minimum, I think we should be able to:
tokio-postgres and/or bb8 and put it in managed state. This should tell us if on_attach and on_launch need to be async-capable.rocket_contrib::databases.async fn.tokio-postgres) with some kind of message passing between them. I leave this intentionally vague because it's an annoying solution to an annoying problem and someone other than I will probably think of a better idea.I am mostly waiting on #1071 (after a new tokio alpha and hyperium/hyper#1897) so that we don't need to significantly rewrite too much code that directly deals with the low-level bits -- which are pretty different between tokio 0.1 and 0.2. For example, I suspect #1070 will take almost as much work again as it would to implement it now.
At present all public APIs use async/await and futures 0.3, but everything runs on a tokio 0.1 runtime. This means that I/O types from tokio 0.2 cannot be used directly in routes yet, for example.
For anyone interested in kicking the tires pretty early on, I have written a short and possibly incomplete guide to trying projects against the async branch. At this stage the most helpful feedback would be examples of "things I can't do anymore" or that now require expensive allocations, especially in implementations of FromData or Fairing.
rocket = { git = "https://github.com/SergioBenitez/Rocket", branch = "async" } and rocket_contrib = { git = "https://github.com/SergioBenitez/Rocket", branch = "async" }#![feature(async_await)] to your crate.#[feature(decl_macro)] thanks to #964.FromData, Fairing, AdHoc::on_response, and Responder must be converted to use futures. For some cases, wrapping the function body in Box::pin(async move { }) will be enough. For some traits, just like with FromData for rocket_contrib::json::Json, you might need to copy some data out of the request before creating the future.ring and handlebars have been updated, which may consequently break your application. You should get obvious build failures if this affects you.async, and await futures inside of them.@jebrosen have you taken a look at async-trait? I know I've mentioned it before, and it reduces some of the overhead associated with async trait fns, like lifetimes and boxing. As a bonus, all we'd have to do after GATs are (some day) implemented is remove the attribute.
I did try async-trait today and ran into some issues that might be able to be worked around. I would prefer to use async-trait on either all affected traits or none of them, and I'm hopeful that it will be usable.
As a bonus, all we'd have to do after GATs are (some day) implemented is remove the attribute.
Unfortunately that would be a breaking change to any public traits, as far as I know there's no way to be forward compatible with GATs in traits. You can be forward compatible with using 'static bound async blocks in traits (once type_alias_impl_trait is stabilized) by using an associated type and setting that to be the pinned-box in implementations, e.g. skimming the changes it looks like FromDataSimple could be defined like this (and maybe FromData since it uses an external lifetime), but Fairing can't as it borrows from its arguments.
Yeah, it would definitely be a breaking change, as APIs would be different. I was referring to the changes in the code itself, which would amount to deleting a few lines.
Update on the attempt to use async-trait everywhere: it's currently not possible. There is a bug in the compiler that prevents async default fn or default async fn, which is necessary where specialization is currently used.
Test, figure out, and document cross-runtime compatibility (or lack thereof). That is, a definitive statement in the form of "Rocket uses tokio 0.2, so your database futures have to as well" if that's the case. Alternatively, maybe we could find a proof of concept of running two runtimes - tokio 0.2 (for rocket) alongside tokio 0.1 (for tokio-postgres) with some kind of message passing between them. I leave this intentionally vague because it's an annoying solution to an annoying problem and someone other than I will probably think of a better idea.
tokio-postgres has a std-futures branch with an in-progress port to tokio 0.2 (and std::future). We could probably just specify that that version is needed for compatibility.
Update on the attempt to use
async-traiteverywhere: it's currently not possible. There is a bug in the compiler that preventsasync default fnordefault async fn, which is necessary where specialization is currently used.
I fixed this bug on nightly. Please make sure however that your uses of default async? fn are #[cfg(...)] gated in a separate module once you decide to make rocket work on stable.
Thanks @Centril! I believe @jebrosen has some way to avoid needing specialization โ I recall seeing it being eliminated in a PR or something like that.
Hopefully others can now use async-trait in more situations.
After attempting to convert r-spacex/Titan to the current HEAD of the async branch (dcea956), I ran into a glaring issue that needs to be addressed before even a prerelease. Currently, diesel (at least its PostgreSQL type) doesn't implement Sync, so it can't be used for a large swath of cases. @jebrosen has pointed out that it does implement Send, so it can be used in some cases.
While not directly related to Rocket, this is something we'll likely want to work with @sgrif et al. to ensure full compatibility.
I tried the async hello world it was working fine.
When I tried to convert it into a tls version so it resembles the tls example from the non-async rocket master, it was still serving up a http url. I confirmed this to be a true bug because if I just switch to the master branch within the Cargo.toml, it does enable tls and does serve up a https url.
[dependencies]
rocket = { git = "https://github.com/SergioBenitez/Rocket", features = ["tls"], branch = "async" }
# rocket = { git = "https://github.com/SergioBenitez/Rocket", features = ["tls"] }
THIS IS THE ASYNC BRANCH OUTPUT:
$ ROCKET_ENV=dev ./target/release/helloasyncrocket
๐ง Configured for development.
=> address: localhost
=> port: 8000
=> log: normal
=> workers: 24
=> secret key: generated
=> limits: forms = 32KiB
=> keep-alive: 5s
=> tls: enabled
๐ฐ Mounting /:
=> GET / (hello)
๐ Rocket has launched from http://localhost:8000
I was expecting to see an output similar to the one from THE NON-ASYNC BRANCH OUTPUT behaving correctly:
$ ROCKET_ENV=dev ./target/release/helloasyncrocket
๐ง Configured for development.
=> address: localhost
=> port: 8000
=> log: normal
=> workers: 24
=> secret key: generated
=> limits: forms = 32KiB
=> keep-alive: 5s
=> tls: enabled
๐ฐ Mounting /:
=> GET / (hello)
๐ Rocket has launched from https://localhost:8000
Yes, TLS is not implemented yet. I'll make that clearer in the main comment.
Not sure if this is the right place for such a question, but here we go:
I'm currently writing a backend for a web app with Rocket, using tokio-postgres directly at the moment (but this should apply to pools as well).
To remove boilerplate at the start of each handler which needs database access, I've implemented FromRequestAsync for my typed wrapper around the connection, so I can just fn handler(client: db::Client) -> ...
Since connecting to a database is an inherently expensive operation, this is an obvious use-case for request-local state; however, both establishing a connection with an async driver and retrieving one from an async pool are async operations, and local_cache currently doesn't have an async equivalent.
Would it be possible to implement such an async variant? I have no idea how much work would have to go into that, as far as I can tell, it probably wouldn't be as elegant as the current local_cache implementation, but it should be doable (taking a future instead of a closure and awaiting it once). If it's not too hard, maybe I could even contribute it.
EDIT, 10 minutes later:
pub async fn local_cache_async<T, F>(&self, fut: F) -> &T
where F: std::future::Future<Output = T>,
T: Send + Sync + 'static
{
if let Some(s) = self.state.cache.try_get() {
s
} else {
self.state.cache.set(fut.await);
self.state.cache.get()
}
}
I just want to say that I love async.
Why use local_cache for this at all instead of a connection pool? Do you need to use the same connection in many request guards and fairings while processing one route?
You're right, it's not a good example; I'm currently waiting for Rocket to implement async pooling support, that's why I use the connection directly.
However, as I said, this also applies to async pools, and if I want to implement something like FromRequestAsync for User and Admin with an async connection pool (to go with the example in the docs), I'll also want to query the database async. That should be the reason for providing an async local cache variant, not my specific example.
You can still implement FromRequestAsync for User with a connection pool, without the local cache (e.g. https://git.jebrosen.com/jeb/sirus/src/branch/async-db-pool-icky/sirus-server/src/guards.rs#L39). The local cache would save the (hopefully small!) expense of pulling from the pool multiple times, though, so it's still a good idea - I just don't see why it's necessary.
(side note: let's move this conversation to #1117)
Sorry if this was unclear, but as far as I can see, the expensive blocking operation in this example would be pulling the User from the database, and caching it would avoid doing everything twice.
This example for request-local state is what I'm talking about - avoiding multiple connections and importantly, multiple queries as well (like db.get_user(id)), which I think should also be async when using an async database driver. (Your website seems to pull the User in a blocking way and each time the request guard gets called)
So the async version of the second example would look something like this (imo, haven't checked if it would even compile):
impl<'a, 'r> FromRequestAsync<'a, 'r> for &'a User {
type Error = !;
fn from_request<'fut>(request: &'a Request<'r>) -> FromRequestFuture<'fut, &'a User, !>
where 'a: 'fut
{
Box::pin(async move {
// This future will run at most once per request, regardless of
// the number of times the `User` guard is executed.
let user_result = request.local_cache_async(async {
let db = try_outcome!(Database::from_request(request).await.succeeded());
if let Some(id) = request.cookies()
.get_private("user_id")
.and_then(|cookie| cookie.value().parse().ok()) {
// The important part
db.get_user(id).await.ok()
} else {
None
}
}).await;
user_result.as_ref().or_forward(())
})
}
}
impl<'a, 'r> FromRequestAsync<'a, 'r> for Admin<'a> {
type Error = !;
fn from_request<'fut>(request: &'a Request<'r>) -> FromRequestFuture<'fut, Admin<'a>, !>
where 'a: 'fut
{
Box::pin(async move {
let user: &User = try_outcome!(User::from_request(request).await);
if user.is_admin {
Outcome::Success(Admin { user })
} else {
Outcome::Forward(())
}
})
}
}
Or am I getting something wrong, did something change since that example was written, so it's not a valid concern anymore?
I'm okay with moving this to #1117 if it's not relevant to async Rocket in general.
as far as I can see, the expensive blocking operation in this example would be pulling the User from the database
Ah, I wasn't even thinking of the case where the request guard is actually used multiple times. I usually try to structure routes in such a way that those things don't happen too often.
I've added local_cache to the list of APIs that need to be async or have async equivalents.
I usually try to structure routes in such a way that those things don't happen too often.
That was my first thought as well - I noticed this because I had a "/category/<id>" route and a "/category/<name>", rank = 2 route, and the second one took twice as long to execute. My first thought was to change the routes to something like "/category/by-name/
I've added
local_cacheto the list of APIs that need to be async or have async equivalents.
Since I already have a working implementation, should I open a PR adding it? (I currently call it local_cache_async)
Since I already have a working implementation, should I open a PR adding it?
Sure! We can also work out whether it's valuable to keep both or perhaps only the async version in that PR.
Now that rust-lang-nursery/futures-rs#1788 has been merged, we should replace our Take adapter with that one once it's in a released version of futures-preview.
I think this can be checked off, since deed151ebd0f8b5353f8ba84bcd411927b33291b did that.
Hi!
I see that Rocket is using tokio and that it removed the async-std dependency not long ago. The talk about async-std in RustFest showed great benchmarks (better than those from tokio, and the stability (version 1.0) seems a good selling point too.
Is there plans to use it in place of (or along with) tokio? can it even replace tokio?
As long as the async-std maintainers are banning their own contributors for PRs in unrelated projects they don't like, I would be against making any spending too much effort to support it.
I have no interest in entering this debate at all, but async-std has not handed out any blocks and we're annoyed about GHs way of applying blocks. I have commented multiple times about it and I'm sure you have read those posts. Please don't spread misinformation and please don't make technical issues on projects a fighting ground.
Thanks.
@skade AFAIK there is one person blocked in async-std. You said it yourself:
async-std still has only one block handed out.
And my understanding is that specific person is indeed one involved in this whole fiasco.
For context, the past dependency on async-std was a bandaid to fix CI while one particular functionality was temporarily broken in tokio. I have favored tokio so far mostly because it is hyper's default and partly to only have one set of I/O traits during development.
In principle rocket itself does not require any particular runtime, but in practice there are a few useful or necessary facilities of "the current runtime, whatever it is, as long as _____ is available," that there aren't yet any good abstractions over. I would like rocket to either support an approach similar to hyper (where a handle to such functionality can be passed in), or to remove or work around those cases. Right now I am waiting until the next tokio alpha before making a set of updates to rocket's API, and I will start marking code that is in any way runtime-specific so it's easier to track.
Rather than thinking of being able to switch between tokio or async-std I think the better place to focus on is the interaction between the application written with Rocket and the web server that the application is running on. Being able to run on different servers provides more than just being able to use a different underlying runtime (though it will provide that too), it allows running on servers that take a completely different approach to handling the http connection.
A good example of this is the work in http-service-lambda to allow running Tide applications on AWS lambda by just swapping out the server the HttpService implementation is hosted on (I realise that http-rs is involved in the same issues as async-rs, but this is the example I'm most familiar with, please just think of this as an example of the abstraction layer possible).
@jebrosen It would be interesting to see a list of what other facilities Rocket relies on from the runtime than just the web server, and whether they're required for the core functionality or just specific extensions.
@lnicola This is not the appropriate place for this discussion and that person is not involved.
@Nemo157 Generally speaking, as far as other servers go a few options such as tower-service have been suggested and I know it's desired functionality. There is a less flexible API that would support any socketlike type (along the lines of Listener::poll_accept(&mut self) -> impl AsyncRead+AsyncWrite+UndecidedStuff), but it's still private and not fleshed out. Publicly exposing a more general request -> response API might slip, however: I'm hesistant to block a 0.5 release on having it.
As for the "other facilities", off the top of my head: 1) spawn is currently used. Rocket's usage can probably be worked around with some self-referential data "tricks" but hyper also needs something similar passed to it. 2) Something like spawn_blocking or block_in_place is necessary for rocket_contrib's existing integration with synchronous databases assuming we keep those. 3) the currently implemented/planned listeners (TCP and Unix socket, with/without TLS) do/would use tokio::timer, {Tcp,Unix}Listener, and any runtime facilities tokio-rustls uses. 4) Optional Ctrl-C handling (which needs to be reworked) from tokio::signal.
In all, that's: spawn and blocking_of_some_kind are probably required, timer and net listeners are used for the default listeners (which should be swappable and behind a feature flag), and signal is behind a feature flag and can be implemented "around" a rocket server anyway.
A few other things that I remembered later: timer might actually be used for all listeners, tokio::fs is used for File (and NamedFile) responders, and of course all I/O traits are currently the ones defined in tokio.
hyper (in master) is now using tokio 0.2 and I expect another alpha or a release soon, so now is a good time to review the current state of affairs.
In general, the async branch is quite usable by itself, but it can be tricky to make use of other crates while everyone is still transitioning to the latest releases of tokio, futures, and so on. I am mostly happy with the API, with the main exception of some lifetime bounds in traits. async_trait would be really nice, but until rust-lang/rust#60658 is resolved I don't think there's much hope. Instead, I'm going to review the lifetime bounds in trait methods and try to make sure they make sense, provide the necessary capabilities, and are not too difficult to write (at least for the common cases). This is probably (hopefully) the most significant/widespread breaking API change left to make.
Some things that need decisions soon are:
There are a few features that would be nice to have, but can be worked around or don't necessarily need to happen right away (many need upstream cooperation as well):
on_attach fairings. This would require some pretty big API changes.Write-like RespondersListener API for full control over the network connection layerFinally, I definitely want to wait on the following in the interest of having a "fixed" release (even just an alpha) that people can test:
Service traitAs of right now I am waiting for a new alpha or release of hyper in order to push the tokio 0.2 update to the async branch, at which point I will be focusing more efforts on those cleanups and documentation.
Thanks for the update @jebrosen. I'm finishing up a rewrite of the time crate, but once that is done let me know if there's anything I can do to help!
Just curious, though, what's the use case for an async attach_on?
Just curious, though, what's the use case for an async
attach_on?
One big use case is setting up a database connection pool, which might want to (asynchronously) set up a certain number of connections at initialization.
1117 - asynchronous database pools, in particular 1) whether to keep or remove the synchronous database pools (they don't mix well with async code) and 2) whether to block a 0.5 release on having built-in async database support.
If the release schedule going forwards is planned to be similar to what it has been so far (i.e. several months between releases), then I'd suggest that we do block a 0.5 on async database support. Pretty much every web backend uses a database, and async pool support is necessary to fully take advantage of the performance benefits that come with moving to async.
From me, this is what takes Rocket into "does everything I absolutely need out of the box" territory.
On the basis on this post, I would like to reverse my above opinion. It seems that using a database pool with Rocket 0.5 is already easy simply by using managed state. As such, I would like to suggest that database support does not block a 0.5 release. It can of course be added again later.
Hi, I'm getting a bunch of Error: FATAL: sorry, too many clients already errors when running tests in the async branch. It seems that if I run a bunch of tests in parallel, I get the error.
@Razican Do you know what code that is ultimately coming from? As far as I can tell it's not from rocket or hyper.
@jebrosen solved. It turns out it was coming from the PostgreSQL database backend. It seems that doing all the tests in parallel, it was creating too many connections at the same time :)
@jebrosen whats the status on getting the async branch merged? Are there any issues we can help with to push it over the finish line?
Since my last update comment above, these were also done:
async_trait - it works for enough traits we went ahead and used itAsyncSeek for bodiesI am currently working on the following, in #1242 (and forgot to link it back here):
- Asynchronous on_attach fairings. This would require some pretty big API changes.
My next PR after that will likely be a wrapper making it safe(r) to use the currently supported blocking databases.
I will look into any async-specific issues that could use assistance. One that comes to mind now is typed headers - e.g. https://github.com/hyperium/headers/issues/48#issuecomment-530057524, or an investigation into which typed header crate(s) would be suitable for built-in support in Rocket would be helpful.
I have posted a shortup writeup on what kind of investigation needs to be done on typed headers, if anyone is interested in helping with that particular effort - https://github.com/SergioBenitez/Rocket/issues/1067#issuecomment-599267834
Has there been any work regarding logging? I think tracing would fit the bill quite nicely as outlined in #21
Btw very excited for 0.5, awesome work!
Hi all,
I've switched over fully to the latest Rocket with full async for a couple of my latest projects, but was running into the fact that compression support doesn't exist yet. This was inflating responses to my users and bringing up the cost of my bill for network egress, so I hacked up a placeholder solution that adds back gzip support: https://github.com/Ameobea/Rocket/commit/eab53cf979cd12aed3d8cc805bf49f0e1e31d2dc
The API is exactly the same as before; you just add features = ["compression"] for rocket-contrib and add the Compression::fairing(). The implementation is not optimal in any way; the compression itself isn't async but it really shouldn't be a big deal for almost all workloads and the huge benefit of having gzip compression outweighs any increase in response time by a huge margin.
I figured this may be useful to other people who wanted to gain the benefits of compression and the latest async Rocket without having to re-write any of their code. Once someone gets around to doing the job properly, it should just be a change to Cargo.toml to fix it.
Most helpful comment
Since my last update comment above, these were also done:
async_trait- it works for enough traits we went ahead and used itAsyncSeekfor bodiesI am currently working on the following, in #1242 (and forgot to link it back here):
My next PR after that will likely be a wrapper making it safe(r) to use the currently supported blocking databases.
I will look into any async-specific issues that could use assistance. One that comes to mind now is typed headers - e.g. https://github.com/hyperium/headers/issues/48#issuecomment-530057524, or an investigation into which typed header crate(s) would be suitable for built-in support in Rocket would be helpful.