46个稳定版本 (15个主要版本)

24.0.0 2024年8月20日
23.0.2 2024年8月12日
23.0.1 2024年7月22日
22.0.0 2024年6月20日
9.0.4 2023年6月13日

#1258WebAssembly

Download history 4856/week @ 2024-05-02 5169/week @ 2024-05-09 4483/week @ 2024-05-16 4796/week @ 2024-05-23 4597/week @ 2024-05-30 4541/week @ 2024-06-06 4777/week @ 2024-06-13 5215/week @ 2024-06-20 4144/week @ 2024-06-27 2870/week @ 2024-07-04 4372/week @ 2024-07-11 5452/week @ 2024-07-18 5983/week @ 2024-07-25 4464/week @ 2024-08-01 3666/week @ 2024-08-08 4512/week @ 2024-08-15

19,823 每月下载
用于 18 个Crates(直接使用11个)

Apache-2.0 WITH LLVM-exception

3MB
52K SLoC

Wasmtime的WASI HTTP实现

这个包是Wasmtime对WASIp2中 wasi:http 包的宿主实现。这个包的实现主要基于 hypertokio

WASI HTTP接口

这个包包含以下接口的实现

这个包还包含 wasi:http/proxy 世界的实现。

这个包与 wasmtime-wasi 非常相似,因为它使用Wasmtime中的 bindgen! 宏来生成接口的绑定。绑定位于 bindings 模块。

WasiHttpView 特性

所有由bindgen!生成的Host特性都是基于一个WasiHttpView特性实现的,该特性提供了对WasiHttpCtx的基本访问、WASI HTTP配置以及所有宿主定义组件模型资源的wasmtime_wasi::ResourceTable状态的支持。

WasiHttpView特性还额外提供了一些其他配置方法,例如WasiHttpView::send_request,用于自定义如何处理发出的HTTP请求。

异步和同步

该包中既有异步绑定也有同步绑定。例如,add_to_linker_async是针对异步嵌入者的,而add_to_linker_sync是针对同步嵌入者的。注意,底层两个版本都是使用在tokio之上的async实现的。

示例

使用此包需要几个步骤来连接一切

  1. 首先为你的类型实现WasiHttpView,该类型是wasmtime::Store<T>中的T
  2. 将WASI HTTP接口添加到wasmtime::component::Linker<T>。有几种方法可以实现这一点
    • 使用add_to_linker_async将所有接口打包在一起到wasi:http/proxy
    • 使用add_only_http_to_linker_async只添加HTTP接口但不添加其他接口。这在与wasmtime_wasi::add_to_linker_async等一起使用时很有用。
    • 使用诸如bindings::http::outgoing_handler::add_to_linker_get_host函数之类的单个接口。
  3. 使用ProxyPre在处理请求之前预实例化一个组件。
  4. 在处理请求时,使用ProxyPre::instantiate_async来创建实例并处理HTTP请求。

完成所有这些的独立示例如下

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<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: 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
    }
}

依赖关系

~27–41MB
~799K SLoC