wasmtime_wasi_http/lib.rs
1//! # Wasmtime's WASI HTTP Implementation
2//!
3//! This crate is Wasmtime's host implementation of the `wasi:http` package as
4//! part of WASIp2. This crate's implementation is primarily built on top of
5//! [`hyper`] and [`tokio`].
6//!
7//! # WASI HTTP Interfaces
8//!
9//! This crate contains implementations of the following interfaces:
10//!
11//! * [`wasi:http/incoming-handler`]
12//! * [`wasi:http/outgoing-handler`]
13//! * [`wasi:http/types`]
14//!
15//! The crate also contains an implementation of the [`wasi:http/proxy`] world.
16//!
17//! [`wasi:http/proxy`]: crate::bindings::Proxy
18//! [`wasi:http/outgoing-handler`]: crate::bindings::http::outgoing_handler::Host
19//! [`wasi:http/types`]: crate::bindings::http::types::Host
20//! [`wasi:http/incoming-handler`]: crate::bindings::exports::wasi::http::incoming_handler::Guest
21//!
22//! This crate is very similar to [`wasmtime-wasi`] in the it uses the
23//! `bindgen!` macro in Wasmtime to generate bindings to interfaces. Bindings
24//! are located in the [`bindings`] module.
25//!
26//! # The `WasiHttpView` trait
27//!
28//! All `bindgen!`-generated `Host` traits are implemented in terms of a
29//! [`WasiHttpView`] trait which provides basic access to [`WasiHttpCtx`],
30//! configuration for WASI HTTP, and a [`wasmtime_wasi::p2::ResourceTable`], the
31//! state for all host-defined component model resources.
32//!
33//! The [`WasiHttpView`] trait additionally offers a few other configuration
34//! methods such as [`WasiHttpView::send_request`] to customize how outgoing
35//! HTTP requests are handled.
36//!
37//! # Async and Sync
38//!
39//! There are both asynchronous and synchronous bindings in this crate. For
40//! example [`add_to_linker_async`] is for asynchronous embedders and
41//! [`add_to_linker_sync`] is for synchronous embedders. Note that under the
42//! hood both versions are implemented with `async` on top of [`tokio`].
43//!
44//! # Examples
45//!
46//! Usage of this crate is done through a few steps to get everything hooked up:
47//!
48//! 1. First implement [`WasiHttpView`] for your type which is the `T` in
49//! [`wasmtime::Store<T>`].
50//! 2. Add WASI HTTP interfaces to a [`wasmtime::component::Linker<T>`]. There
51//! are a few options of how to do this:
52//! * Use [`add_to_linker_async`] to bundle all interfaces in
53//! `wasi:http/proxy` together
54//! * Use [`add_only_http_to_linker_async`] to add only HTTP interfaces but
55//! no others. This is useful when working with
56//! [`wasmtime_wasi::p2::add_to_linker_async`] for example.
57//! * Add individual interfaces such as with the
58//! [`bindings::http::outgoing_handler::add_to_linker_get_host`] function.
59//! 3. Use [`ProxyPre`](bindings::ProxyPre) to pre-instantiate a component
60//! before serving requests.
61//! 4. When serving requests use
62//! [`ProxyPre::instantiate_async`](bindings::ProxyPre::instantiate_async)
63//! to create instances and handle HTTP requests.
64//!
65//! A standalone example of doing all this looks like:
66//!
67//! ```no_run
68//! use anyhow::bail;
69//! use hyper::server::conn::http1;
70//! use std::sync::Arc;
71//! use tokio::net::TcpListener;
72//! use wasmtime::component::{Component, Linker, ResourceTable};
73//! use wasmtime::{Config, Engine, Result, Store};
74//! use wasmtime_wasi::p2::{IoView, WasiCtx, WasiCtxBuilder, WasiView};
75//! use wasmtime_wasi_http::bindings::ProxyPre;
76//! use wasmtime_wasi_http::bindings::http::types::Scheme;
77//! use wasmtime_wasi_http::body::HyperOutgoingBody;
78//! use wasmtime_wasi_http::io::TokioIo;
79//! use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView};
80//!
81//! #[tokio::main]
82//! async fn main() -> Result<()> {
83//! let component = std::env::args().nth(1).unwrap();
84//!
85//! // Prepare the `Engine` for Wasmtime
86//! let mut config = Config::new();
87//! config.async_support(true);
88//! let engine = Engine::new(&config)?;
89//!
90//! // Compile the component on the command line to machine code
91//! let component = Component::from_file(&engine, &component)?;
92//!
93//! // Prepare the `ProxyPre` which is a pre-instantiated version of the
94//! // component that we have. This will make per-request instantiation
95//! // much quicker.
96//! let mut linker = Linker::new(&engine);
97//! wasmtime_wasi_http::add_to_linker_async(&mut linker)?;
98//! let pre = ProxyPre::new(linker.instantiate_pre(&component)?)?;
99//!
100//! // Prepare our server state and start listening for connections.
101//! let server = Arc::new(MyServer { pre });
102//! let listener = TcpListener::bind("127.0.0.1:8000").await?;
103//! println!("Listening on {}", listener.local_addr()?);
104//!
105//! loop {
106//! // Accept a TCP connection and serve all of its requests in a separate
107//! // tokio task. Note that for now this only works with HTTP/1.1.
108//! let (client, addr) = listener.accept().await?;
109//! println!("serving new client from {addr}");
110//!
111//! let server = server.clone();
112//! tokio::task::spawn(async move {
113//! if let Err(e) = http1::Builder::new()
114//! .keep_alive(true)
115//! .serve_connection(
116//! TokioIo::new(client),
117//! hyper::service::service_fn(move |req| {
118//! let server = server.clone();
119//! async move { server.handle_request(req).await }
120//! }),
121//! )
122//! .await
123//! {
124//! eprintln!("error serving client[{addr}]: {e:?}");
125//! }
126//! });
127//! }
128//! }
129//!
130//! struct MyServer {
131//! pre: ProxyPre<MyClientState>,
132//! }
133//!
134//! impl MyServer {
135//! async fn handle_request(
136//! &self,
137//! req: hyper::Request<hyper::body::Incoming>,
138//! ) -> Result<hyper::Response<HyperOutgoingBody>> {
139//! // Create per-http-request state within a `Store` and prepare the
140//! // initial resources passed to the `handle` function.
141//! let mut store = Store::new(
142//! self.pre.engine(),
143//! MyClientState {
144//! table: ResourceTable::new(),
145//! wasi: WasiCtxBuilder::new().inherit_stdio().build(),
146//! http: WasiHttpCtx::new(),
147//! },
148//! );
149//! let (sender, receiver) = tokio::sync::oneshot::channel();
150//! let req = store.data_mut().new_incoming_request(Scheme::Http, req)?;
151//! let out = store.data_mut().new_response_outparam(sender)?;
152//! let pre = self.pre.clone();
153//!
154//! // Run the http request itself in a separate task so the task can
155//! // optionally continue to execute beyond after the initial
156//! // headers/response code are sent.
157//! let task = tokio::task::spawn(async move {
158//! let proxy = pre.instantiate_async(&mut store).await?;
159//!
160//! if let Err(e) = proxy
161//! .wasi_http_incoming_handler()
162//! .call_handle(store, req, out)
163//! .await
164//! {
165//! return Err(e);
166//! }
167//!
168//! Ok(())
169//! });
170//!
171//! match receiver.await {
172//! // If the client calls `response-outparam::set` then one of these
173//! // methods will be called.
174//! Ok(Ok(resp)) => Ok(resp),
175//! Ok(Err(e)) => Err(e.into()),
176//!
177//! // Otherwise the `sender` will get dropped along with the `Store`
178//! // meaning that the oneshot will get disconnected and here we can
179//! // inspect the `task` result to see what happened
180//! Err(_) => {
181//! let e = match task.await {
182//! Ok(Ok(())) => {
183//! bail!("guest never invoked `response-outparam::set` method")
184//! }
185//! Ok(Err(e)) => e,
186//! Err(e) => e.into(),
187//! };
188//! return Err(e.context("guest never invoked `response-outparam::set` method"));
189//! }
190//! }
191//! }
192//! }
193//!
194//! struct MyClientState {
195//! wasi: WasiCtx,
196//! http: WasiHttpCtx,
197//! table: ResourceTable,
198//! }
199//! impl IoView for MyClientState {
200//! fn table(&mut self) -> &mut ResourceTable {
201//! &mut self.table
202//! }
203//! }
204//! impl WasiView for MyClientState {
205//! fn ctx(&mut self) -> &mut WasiCtx {
206//! &mut self.wasi
207//! }
208//! }
209//!
210//! impl WasiHttpView for MyClientState {
211//! fn ctx(&mut self) -> &mut WasiHttpCtx {
212//! &mut self.http
213//! }
214//! }
215//! ```
216
217#![deny(missing_docs)]
218#![doc(test(attr(deny(warnings))))]
219#![doc(test(attr(allow(dead_code, unused_variables, unused_mut))))]
220
221mod error;
222mod http_impl;
223mod types_impl;
224
225pub mod body;
226pub mod io;
227pub mod types;
228
229pub mod bindings;
230
231pub use crate::error::{
232 http_request_error, hyper_request_error, hyper_response_error, HttpError, HttpResult,
233};
234#[doc(inline)]
235pub use crate::types::{
236 WasiHttpCtx, WasiHttpImpl, WasiHttpView, DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS,
237 DEFAULT_OUTGOING_BODY_CHUNK_SIZE,
238};
239use wasmtime_wasi::p2::IoImpl;
240/// Add all of the `wasi:http/proxy` world's interfaces to a [`wasmtime::component::Linker`].
241///
242/// This function will add the `async` variant of all interfaces into the
243/// `Linker` provided. By `async` this means that this function is only
244/// compatible with [`Config::async_support(true)`][async]. For embeddings with
245/// async support disabled see [`add_to_linker_sync`] instead.
246///
247/// [async]: wasmtime::Config::async_support
248///
249/// # Example
250///
251/// ```
252/// use wasmtime::{Engine, Result, Config};
253/// use wasmtime::component::{ResourceTable, Linker};
254/// use wasmtime_wasi::p2::{IoView, WasiCtx, WasiView};
255/// use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView};
256///
257/// fn main() -> Result<()> {
258/// let mut config = Config::new();
259/// config.async_support(true);
260/// let engine = Engine::new(&config)?;
261///
262/// let mut linker = Linker::<MyState>::new(&engine);
263/// wasmtime_wasi_http::add_to_linker_async(&mut linker)?;
264/// // ... add any further functionality to `linker` if desired ...
265///
266/// Ok(())
267/// }
268///
269/// struct MyState {
270/// ctx: WasiCtx,
271/// http_ctx: WasiHttpCtx,
272/// table: ResourceTable,
273/// }
274///
275/// impl IoView for MyState {
276/// fn table(&mut self) -> &mut ResourceTable { &mut self.table }
277/// }
278/// impl WasiHttpView for MyState {
279/// fn ctx(&mut self) -> &mut WasiHttpCtx { &mut self.http_ctx }
280/// }
281/// impl WasiView for MyState {
282/// fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx }
283/// }
284/// ```
285pub fn add_to_linker_async<T>(l: &mut wasmtime::component::Linker<T>) -> anyhow::Result<()>
286where
287 T: WasiHttpView + wasmtime_wasi::p2::WasiView,
288{
289 let io_closure = type_annotate_io::<T, _>(|t| wasmtime_wasi::p2::IoImpl(t));
290 wasmtime_wasi::p2::bindings::io::poll::add_to_linker_get_host(l, io_closure)?;
291 wasmtime_wasi::p2::bindings::io::error::add_to_linker_get_host(l, io_closure)?;
292 wasmtime_wasi::p2::bindings::io::streams::add_to_linker_get_host(l, io_closure)?;
293
294 let closure =
295 type_annotate_wasi::<T, _>(|t| wasmtime_wasi::p2::WasiImpl(wasmtime_wasi::p2::IoImpl(t)));
296 wasmtime_wasi::p2::bindings::clocks::wall_clock::add_to_linker_get_host(l, closure)?;
297 wasmtime_wasi::p2::bindings::clocks::monotonic_clock::add_to_linker_get_host(l, closure)?;
298 wasmtime_wasi::p2::bindings::cli::stdin::add_to_linker_get_host(l, closure)?;
299 wasmtime_wasi::p2::bindings::cli::stdout::add_to_linker_get_host(l, closure)?;
300 wasmtime_wasi::p2::bindings::cli::stderr::add_to_linker_get_host(l, closure)?;
301 wasmtime_wasi::p2::bindings::random::random::add_to_linker_get_host(l, closure)?;
302
303 add_only_http_to_linker_async(l)
304}
305
306// NB: workaround some rustc inference - a future refactoring may make this
307// obsolete.
308fn type_annotate_http<T, F>(val: F) -> F
309where
310 F: Fn(&mut T) -> WasiHttpImpl<&mut T>,
311{
312 val
313}
314fn type_annotate_wasi<T, F>(val: F) -> F
315where
316 F: Fn(&mut T) -> wasmtime_wasi::p2::WasiImpl<&mut T>,
317{
318 val
319}
320fn type_annotate_io<T, F>(val: F) -> F
321where
322 F: Fn(&mut T) -> wasmtime_wasi::p2::IoImpl<&mut T>,
323{
324 val
325}
326
327/// A slimmed down version of [`add_to_linker_async`] which only adds
328/// `wasi:http` interfaces to the linker.
329///
330/// This is useful when using [`wasmtime_wasi::p2::add_to_linker_async`] for
331/// example to avoid re-adding the same interfaces twice.
332pub fn add_only_http_to_linker_async<T>(
333 l: &mut wasmtime::component::Linker<T>,
334) -> anyhow::Result<()>
335where
336 T: WasiHttpView,
337{
338 let closure = type_annotate_http::<T, _>(|t| WasiHttpImpl(IoImpl(t)));
339 crate::bindings::http::outgoing_handler::add_to_linker_get_host(l, closure)?;
340 crate::bindings::http::types::add_to_linker_get_host(l, closure)?;
341
342 Ok(())
343}
344
345/// Add all of the `wasi:http/proxy` world's interfaces to a [`wasmtime::component::Linker`].
346///
347/// This function will add the `sync` variant of all interfaces into the
348/// `Linker` provided. For embeddings with async support see
349/// [`add_to_linker_async`] instead.
350///
351/// # Example
352///
353/// ```
354/// use wasmtime::{Engine, Result, Config};
355/// use wasmtime::component::{ResourceTable, Linker};
356/// use wasmtime_wasi::p2::{IoView, WasiCtx, WasiView};
357/// use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView};
358///
359/// fn main() -> Result<()> {
360/// let config = Config::default();
361/// let engine = Engine::new(&config)?;
362///
363/// let mut linker = Linker::<MyState>::new(&engine);
364/// wasmtime_wasi_http::add_to_linker_sync(&mut linker)?;
365/// // ... add any further functionality to `linker` if desired ...
366///
367/// Ok(())
368/// }
369///
370/// struct MyState {
371/// ctx: WasiCtx,
372/// http_ctx: WasiHttpCtx,
373/// table: ResourceTable,
374/// }
375/// impl IoView for MyState {
376/// fn table(&mut self) -> &mut ResourceTable { &mut self.table }
377/// }
378/// impl WasiHttpView for MyState {
379/// fn ctx(&mut self) -> &mut WasiHttpCtx { &mut self.http_ctx }
380/// }
381/// impl WasiView for MyState {
382/// fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx }
383/// }
384/// ```
385pub fn add_to_linker_sync<T>(l: &mut wasmtime::component::Linker<T>) -> anyhow::Result<()>
386where
387 T: WasiHttpView + wasmtime_wasi::p2::WasiView,
388{
389 let io_closure = type_annotate_io::<T, _>(|t| wasmtime_wasi::p2::IoImpl(t));
390 // For the sync linker, use the definitions of poll and streams from the
391 // wasmtime_wasi::p2::bindings::sync space because those are defined using in_tokio.
392 wasmtime_wasi::p2::bindings::sync::io::poll::add_to_linker_get_host(l, io_closure)?;
393 wasmtime_wasi::p2::bindings::sync::io::streams::add_to_linker_get_host(l, io_closure)?;
394 // The error interface in the wasmtime_wasi is synchronous
395 wasmtime_wasi::p2::bindings::io::error::add_to_linker_get_host(l, io_closure)?;
396
397 let closure =
398 type_annotate_wasi::<T, _>(|t| wasmtime_wasi::p2::WasiImpl(wasmtime_wasi::p2::IoImpl(t)));
399
400 wasmtime_wasi::p2::bindings::clocks::wall_clock::add_to_linker_get_host(l, closure)?;
401 wasmtime_wasi::p2::bindings::clocks::monotonic_clock::add_to_linker_get_host(l, closure)?;
402 wasmtime_wasi::p2::bindings::cli::stdin::add_to_linker_get_host(l, closure)?;
403 wasmtime_wasi::p2::bindings::cli::stdout::add_to_linker_get_host(l, closure)?;
404 wasmtime_wasi::p2::bindings::cli::stderr::add_to_linker_get_host(l, closure)?;
405 wasmtime_wasi::p2::bindings::random::random::add_to_linker_get_host(l, closure)?;
406
407 add_only_http_to_linker_sync(l)?;
408
409 Ok(())
410}
411
412/// A slimmed down version of [`add_to_linker_sync`] which only adds
413/// `wasi:http` interfaces to the linker.
414///
415/// This is useful when using [`wasmtime_wasi::p2::add_to_linker_sync`] for
416/// example to avoid re-adding the same interfaces twice.
417pub fn add_only_http_to_linker_sync<T>(l: &mut wasmtime::component::Linker<T>) -> anyhow::Result<()>
418where
419 T: WasiHttpView,
420{
421 let closure = type_annotate_http::<T, _>(|t| WasiHttpImpl(IoImpl(t)));
422
423 crate::bindings::http::outgoing_handler::add_to_linker_get_host(l, closure)?;
424 crate::bindings::http::types::add_to_linker_get_host(l, closure)?;
425
426 Ok(())
427}