# Wasmtime's WASI HTTP Implementation
This crate is Wasmtime's host implementation of the `wasi:http` package as
part of WASIp2. This crate's implementation is primarily built on top of
[`hyper`] and [`tokio`].
# WASI HTTP Interfaces
This crate contains implementations of the following interfaces:
* [`wasi:http/incoming-handler`]
* [`wasi:http/outgoing-handler`]
* [`wasi:http/types`]
The crate also contains an implementation of the [`wasi:http/proxy`] world.
[`wasi:http/proxy`]: crate::bindings::Proxy
[`wasi:http/outgoing-handler`]: crate::bindings::http::outgoing_handler::Host
[`wasi:http/types`]: crate::bindings::http::types::Host
[`wasi:http/incoming-handler`]: crate::bindings::exports::wasi::http::incoming_handler::Guest
This crate is very similar to [`wasmtime-wasi`] in the it uses the
`bindgen!` macro in Wasmtime to generate bindings to interfaces. Bindings
are located in the [`bindings`] module.
# The `WasiHttpView` trait
All `bindgen!`-generated `Host` traits are implemented in terms of a
[`WasiHttpView`] trait which provides basic access to [`WasiHttpCtx`],
configuration for WASI HTTP, and a [`wasmtime_wasi::ResourceTable`], the
state for all host-defined component model resources.
The [`WasiHttpView`] trait additionally offers a few other configuration
methods such as [`WasiHttpView::send_request`] to customize how outgoing
HTTP requests are handled.
# Async and Sync
There are both asynchronous and synchronous bindings in this crate. For
example [`add_to_linker_async`] is for asynchronous embedders and
[`add_to_linker_sync`] is for synchronous embedders. Note that under the
hood both versions are implemented with `async` on top of [`tokio`].
# Examples
Usage of this crate is done through a few steps to get everything hooked up:
1. First implement [`WasiHttpView`] for your type which is the `T` in
[`wasmtime::Store`].
2. Add WASI HTTP interfaces to a [`wasmtime::component::Linker`]. There
are a few options of how to do this:
* Use [`add_to_linker_async`] to bundle all interfaces in
`wasi:http/proxy` together
* Use [`add_only_http_to_linker_async`] to add only HTTP interfaces but
no others. This is useful when working with
[`wasmtime_wasi::add_to_linker_async`] for example.
* Add individual interfaces such as with the
[`bindings::http::outgoing_handler::add_to_linker_get_host`] function.
3. Use [`ProxyPre`](bindings::ProxyPre) to pre-instantiate a component
before serving requests.
4. When serving requests use
[`ProxyPre::instantiate_async`](bindings::ProxyPre::instantiate_async)
to create instances and handle HTTP requests.
A standalone example of doing all this looks like:
```no_run
use anyhow::bail;
use hyper::server::conn::http1;
use std::sync::Arc;
use tokio::net::TcpListener;
use wasmtime::component::{Component, Linker, ResourceTable};
use wasmtime::{Config, Engine, Result, Store};
use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiView};
use wasmtime_wasi_http::bindings::ProxyPre;
use wasmtime_wasi_http::bindings::http::types::Scheme;
use wasmtime_wasi_http::body::HyperOutgoingBody;
use wasmtime_wasi_http::io::TokioIo;
use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView};
#[tokio::main]
async fn main() -> Result<()> {
let component = std::env::args().nth(1).unwrap();
// Prepare the `Engine` for Wasmtime
let mut config = Config::new();
config.async_support(true);
let engine = Engine::new(&config)?;
// Compile the component on the command line to machine code
let component = Component::from_file(&engine, &component)?;
// Prepare the `ProxyPre` which is a pre-instantiated version of the
// component that we have. This will make per-request instantiation
// much quicker.
let mut linker = Linker::new(&engine);
wasmtime_wasi_http::add_to_linker_async(&mut linker)?;
let pre = ProxyPre::new(linker.instantiate_pre(&component)?)?;
// Prepare our server state and start listening for connections.
let server = Arc::new(MyServer { pre });
let listener = TcpListener::bind("127.0.0.1:8000").await?;
println!("Listening on {}", listener.local_addr()?);
loop {
// Accept a TCP connection and serve all of its requests in a separate
// tokio task. Note that for now this only works with HTTP/1.1.
let (client, addr) = listener.accept().await?;
println!("serving new client from {addr}");
let server = server.clone();
tokio::task::spawn(async move {
if let Err(e) = http1::Builder::new()
.keep_alive(true)
.serve_connection(
TokioIo::new(client),
hyper::service::service_fn(move |req| {
let server = server.clone();
async move { server.handle_request(req).await }
}),
)
.await
{
eprintln!("error serving client[{addr}]: {e:?}");
}
});
}
}
struct MyServer {
pre: ProxyPre,
}
impl MyServer {
async fn handle_request(
&self,
req: hyper::Request,
) -> Result> {
// Create per-http-request state within a `Store` and prepare the
// initial resources passed to the `handle` function.
let mut store = Store::new(
self.pre.engine(),
MyClientState {
table: ResourceTable::new(),
wasi: WasiCtxBuilder::new().inherit_stdio().build(),
http: WasiHttpCtx::new(),
},
);
let (sender, receiver) = tokio::sync::oneshot::channel();
let req = store.data_mut().new_incoming_request(Scheme::Http, req)?;
let out = store.data_mut().new_response_outparam(sender)?;
let pre = self.pre.clone();
// Run the http request itself in a separate task so the task can
// optionally continue to execute beyond after the initial
// headers/response code are sent.
let task = tokio::task::spawn(async move {
let proxy = pre.instantiate_async(&mut store).await?;
if let Err(e) = proxy
.wasi_http_incoming_handler()
.call_handle(store, req, out)
.await
{
return Err(e);
}
Ok(())
});
match receiver.await {
// If the client calls `response-outparam::set` then one of these
// methods will be called.
Ok(Ok(resp)) => Ok(resp),
Ok(Err(e)) => Err(e.into()),
// Otherwise the `sender` will get dropped along with the `Store`
// meaning that the oneshot will get disconnected and here we can
// inspect the `task` result to see what happened
Err(_) => {
let e = match task.await {
Ok(r) => r.unwrap_err(),
Err(e) => e.into(),
};
bail!("guest never invoked `response-outparam::set` method: {e:?}")
}
}
}
}
struct MyClientState {
wasi: WasiCtx,
http: WasiHttpCtx,
table: ResourceTable,
}
impl WasiView for MyClientState {
fn ctx(&mut self) -> &mut WasiCtx {
&mut self.wasi
}
fn table(&mut self) -> &mut ResourceTable {
&mut self.table
}
}
impl WasiHttpView for MyClientState {
fn ctx(&mut self) -> &mut WasiHttpCtx {
&mut self.http
}
fn table(&mut self) -> &mut ResourceTable {
&mut self.table
}
}
```