Skip to main content

Module p2

Module p2 

Source
Available on crate feature p2 only.
Expand description

§Wasmtime’s WASI HTTPp2 Implementation

This module 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:

The crate also contains an implementation of the wasi:http/proxy world.

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 WasiHttp{View,Hooks} traits

All bindgen!-generated Host traits are implemented for the WasiHttpCtxView type. This type is created from a store’s data T through the WasiHttpView trait. The add_to_linker_async function, for example, uses WasiHttpView to acquire the context view.

The WasiHttpCtxView structure requires that a ResourceTable and WasiHttpCtx live within the store. This is store-specific state that is used to implement various APIs and store host state.

The final hooks field within WasiHttpCtxView is a trait object of WasiHttpHooks. This provides a few more hooks, dynamically, to configure how wasi:http behaves. For example WasiHttpHooks::send_request can customize how outgoing HTTP requests are handled. The hooks field can be initialized with the default_hooks function for the default behavior.

§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<T>.
  2. Add WASI HTTP interfaces to a wasmtime::component::Linker<T>. There are a few options of how to do this:
  3. Use ProxyPre to pre-instantiate a component before serving requests.
  4. When serving requests use ProxyPre::instantiate_async to create instances and handle HTTP requests.

A standalone example of doing all this looks like:

use wasmtime::bail;
use hyper::server::conn::http1;
use std::sync::Arc;
use tokio::net::TcpListener;
use wasmtime::component::{Component, Linker, ResourceTable};
use wasmtime::{Engine, Result, Store};
use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView};
use wasmtime_wasi_http::p2::bindings::ProxyPre;
use wasmtime_wasi_http::p2::bindings::http::types::Scheme;
use wasmtime_wasi_http::p2::body::HyperOutgoingBody;
use wasmtime_wasi_http::io::TokioIo;
use wasmtime_wasi_http::{WasiHttpCtx, p2::{WasiHttpView, WasiHttpCtxView}};

#[tokio::main]
async fn main() -> Result<()> {
    let component = std::env::args().nth(1).unwrap();

    // Prepare the `Engine` for Wasmtime
    let engine = Engine::default();

    // 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::p2::add_to_linker_async(&mut linker)?;
    wasmtime_wasi_http::p2::add_only_http_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<MyClientState>,
}

impl MyServer {
    async fn handle_request(
        &self,
        req: hyper::Request<hyper::body::Incoming>,
    ) -> Result<hyper::Response<HyperOutgoingBody>> {
        // 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: WasiCtx::builder().inherit_stdio().build(),
                http: WasiHttpCtx::new(),
            },
        );
        let (sender, receiver) = tokio::sync::oneshot::channel();
        let req = store.data_mut().http().new_incoming_request(Scheme::Http, req)?;
        let out = store.data_mut().http().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(Ok(())) => {
                        bail!("guest never invoked `response-outparam::set` method")
                    }
                    Ok(Err(e)) => e,
                    Err(e) => e.into(),
                };
                return Err(e.context("guest never invoked `response-outparam::set` method"));
            }
        }
    }
}

struct MyClientState {
    wasi: WasiCtx,
    http: WasiHttpCtx,
    table: ResourceTable,
}

impl WasiView for MyClientState {
    fn ctx(&mut self) -> WasiCtxView<'_> {
        WasiCtxView { ctx: &mut self.wasi, table: &mut self.table }
    }
}

impl WasiHttpView for MyClientState {
    fn http(&mut self) -> WasiHttpCtxView<'_> {
        WasiHttpCtxView {
            ctx: &mut self.http,
            table: &mut self.table,
            hooks: Default::default(),
        }
    }
}

Modules§

bindings
Raw bindings to the wasi:http package.
body
Implementation of the wasi:http/types interface’s various body types.
types
Implements the base structure that will provide the implementation of the wasi-http API.

Structs§

HeaderError
A wasi:http-specific error type used to represent either a trap or an types::HeaderError.
HttpError
A wasi:http-specific error type used to represent either a trap or an ErrorCode.
WasiHttp
The type for which this crate implements the wasi:http interfaces.
WasiHttpCtxView
Structure which wasi:http Host-style traits are implemented for.

Constants§

DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS
The default value configured for WasiHttpHooks::outgoing_body_buffer_chunks in WasiHttpView.
DEFAULT_OUTGOING_BODY_CHUNK_SIZE
The default value configured for WasiHttpHooks::outgoing_body_chunk_size in WasiHttpView.

Traits§

WasiHttpHooks
A trait which provides hooks into internal WASI HTTP operations.
WasiHttpView
A trait used to project state that this crate needs to implement wasi:http from the self type.

Functions§

add_only_http_to_linker_async
A slimmed down version of add_to_linker_async which only adds wasi:http interfaces to the linker.
add_only_http_to_linker_sync
A slimmed down version of add_to_linker_sync which only adds wasi:http interfaces to the linker.
add_to_linker_async
Add all of the wasi:http/proxy world’s interfaces to a wasmtime::component::Linker.
add_to_linker_sync
Add all of the wasi:http/proxy world’s interfaces to a wasmtime::component::Linker.
default_hooksdefault-send-request
Returns a value suitable for the WasiHttpCtxView::hooks field which has the default behavior for wasi:http.
default_send_requestdefault-send-request
The default implementation of how an outgoing request is sent.
default_send_request_handlerdefault-send-request
The underlying implementation of how an outgoing request is sent. This should likely be spawned in a task.
http_request_error
Translate a [http::Error] to a wasi-http ErrorCode in the context of a request.
hyper_request_error
Translate a [hyper::Error] to a wasi-http ErrorCode in the context of a request.
hyper_response_error
Translate a [hyper::Error] to a wasi-http ErrorCode in the context of a response.

Type Aliases§

HeaderResult
A Result type where the error type defaults to HeaderError.
HttpResult
A Result type where the error type defaults to HttpError.