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日 |
#1258 在 WebAssembly
19,823 每月下载
用于 18 个Crates(直接使用11个)
3MB
52K SLoC
Wasmtime的WASI HTTP实现
这个包是Wasmtime对WASIp2中 wasi:http
包的宿主实现。这个包的实现主要基于 hyper
和 tokio
。
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
实现的。
示例
使用此包需要几个步骤来连接一切
- 首先为你的类型实现
WasiHttpView
,该类型是wasmtime::Store<T>
中的T
。 - 将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
函数之类的单个接口。
- 使用
- 使用
ProxyPre
在处理请求之前预实例化一个组件。 - 在处理请求时,使用
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