Let me share what I've learned about implementing async WASIp2 components in Rust. My goal is to get the entire Tokio ecosystem working together seamlessly. This isn't a complete test of the ecosystem - some things might be simpler than we expect. Check out dicej's wasi-socket-tests repository for examples.
The first obstacle: you'll need a nightly version of Rust. Without it, you'll need major ecosystem changes to avoid the wasip2 module in the Rust standard library and use wasi crates for the necessary functionality.
Let's walk through the steps to get Reqwest working with Tokio.
Socket2 provides a safe interface to the socket API. Tokio depends on it to create sockets. A pull request exists to add wasip2 support, but remains unmerged.
We need to modify Mio to drive futures-based async I/O. Dicej wrote a pull request that adds this support. While rejected, discussion continues about the best path forward. The PR works in many cases, but we'll need a leaner solution that leverages the standard library and mio's existing polling functionality. Review this PR before proceeding.
This area needs help from someone with deep socket programming experience. I'm still exploring the best approach.
I've created an updated branch of Mio, rebased from current main.
Compile Tokio with the unstable
features enabled. Dicej maintains an active branch with needed changes. I've made additional updates here.
I've updated the hyper-util crate to work with reqwest and wasip2.
Find my wasip2 branch here. Note: this won't compile without patching socket2 in your component. The code uses Tokio and Mio directly. @brooksmtownsend offers an alternative approach in this pull request. Their PR avoids async sockets but demonstrates wasip2 support without mio.
Ring and aws-lc typically provide crypto for rustls. Neither links to our wasip2 component because they use assembly implementations. Without trusted cryptographic primitives in wasip2, we can't use these components in production.
To work around this, try the rustls-rustcrypto crate, which implements crypto primitives in pure Rust. Warning: don't use this crate in production yet.
Important patches in Cargo.toml:
[dependencies.reqwest]
git = "https://github.com/pimeys/reqwest"
branch = "wasip2"
default-features = false
features = ["rustls-tls-webpki-roots-no-provider", "json"]
[patch.crates-io]
tokio = { git = "https://github.com/pimeys/tokio", branch = "wasip2" }
socket2 = { git = "https://github.com/pimeys/socket2", branch = "wasip2" }
hyper-util = { git = "https://github.com/pimeys/hyper-util", branch = "wasip2" }
TLS dependencies:
rustls = { version = "0.23.23", default-features = false }
rustls-rustcrypto = "0.0.2-alpha"
webpki-roots = "0.26.8"
Reqwest initialization:
// Initialize the crypto provider. Reqwest will panic if we do not do this first.
// DO NOT USE THIS IN PRODUCTION.
rustls_rustcrypto::provider()
.install_default()
.expect("could not install crypto provider");
// Utilizes compiled-in root certificates from Mozilla. Might not be what you want, but they
// work well for this demo.
let root_store = rustls::RootCertStore {
roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(),
};
let verifier = WebPkiServerVerifier::builder(Arc::new(root_store)).build()?;
let config = rustls::ClientConfig::builder()
.with_webpki_verifier(verifier)
.with_no_client_auth();
// Use this client as you'd normally use reqwest.
let http_client = reqwest::Client::builder().use_preconfigured_tls(config).build()?;
Use the latest nightly Rust and compile either a wasi binary or component library by running:
cargo build --target wasm32-wasip2
Since wasi runs single-threaded, use the current thread runtime.
The wstd runtime offers an async runtime for wasip2, demonstrating how simply we can implement wasi async runtimes. Unfortunately, the Rust async ecosystem depends heavily on Tokio. We should have standardized async readers, writers, timers, and spawning earlier. While you can adapt the crates to work without Tokio, upstreaming changes takes significant effort.
We'd benefit from having multiple well-supported runtimes, with at least one supporting wasi. But that's not our current reality.
Who's working on what? What are the project goals? The WASIp3 project board suggests the next preview arrives soon, focusing on futures and streams. Many of us might wait for this before adding wasi support.
The wasmtime runtime stands out as particularly exciting. You can compile wasi extensions and load them into your server. While it supports async configuration, questions remain about whether guest functions running their own runtime block the host thread. The docs describe calling async host functions as sync functions in the guest, but blocking futures might still block host threads.
@pimeys kufos putting this together! i'm wondering if you've managed to get a simple reqwest based URL get to be executed with wasmtime? something like this: