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::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::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::{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(r) => r.unwrap_err(),
183//!                     Err(e) => e.into(),
184//!                 };
185//!                 bail!("guest never invoked `response-outparam::set` method: {e:?}")
186//!             }
187//!         }
188//!     }
189//! }
190//!
191//! struct MyClientState {
192//!     wasi: WasiCtx,
193//!     http: WasiHttpCtx,
194//!     table: ResourceTable,
195//! }
196//! impl IoView for MyClientState {
197//!     fn table(&mut self) -> &mut ResourceTable {
198//!         &mut self.table
199//!     }
200//! }
201//! impl WasiView for MyClientState {
202//!     fn ctx(&mut self) -> &mut WasiCtx {
203//!         &mut self.wasi
204//!     }
205//! }
206//!
207//! impl WasiHttpView for MyClientState {
208//!     fn ctx(&mut self) -> &mut WasiHttpCtx {
209//!         &mut self.http
210//!     }
211//! }
212//! ```
213
214#![deny(missing_docs)]
215#![doc(test(attr(deny(warnings))))]
216#![doc(test(attr(allow(dead_code, unused_variables, unused_mut))))]
217#![expect(clippy::allow_attributes_without_reason, reason = "crate not migrated")]
218
219mod error;
220mod http_impl;
221mod types_impl;
222
223pub mod body;
224pub mod io;
225pub mod types;
226
227pub mod bindings;
228
229pub use crate::error::{
230    http_request_error, hyper_request_error, hyper_response_error, HttpError, HttpResult,
231};
232#[doc(inline)]
233pub use crate::types::{
234    WasiHttpCtx, WasiHttpImpl, WasiHttpView, DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS,
235    DEFAULT_OUTGOING_BODY_CHUNK_SIZE,
236};
237use wasmtime_wasi::IoImpl;
238/// Add all of the `wasi:http/proxy` world's interfaces to a [`wasmtime::component::Linker`].
239///
240/// This function will add the `async` variant of all interfaces into the
241/// `Linker` provided. By `async` this means that this function is only
242/// compatible with [`Config::async_support(true)`][async]. For embeddings with
243/// async support disabled see [`add_to_linker_sync`] instead.
244///
245/// [async]: wasmtime::Config::async_support
246///
247/// # Example
248///
249/// ```
250/// use wasmtime::{Engine, Result, Config};
251/// use wasmtime::component::{ResourceTable, Linker};
252/// use wasmtime_wasi::{IoView, WasiCtx, WasiView};
253/// use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView};
254///
255/// fn main() -> Result<()> {
256///     let mut config = Config::new();
257///     config.async_support(true);
258///     let engine = Engine::new(&config)?;
259///
260///     let mut linker = Linker::<MyState>::new(&engine);
261///     wasmtime_wasi_http::add_to_linker_async(&mut linker)?;
262///     // ... add any further functionality to `linker` if desired ...
263///
264///     Ok(())
265/// }
266///
267/// struct MyState {
268///     ctx: WasiCtx,
269///     http_ctx: WasiHttpCtx,
270///     table: ResourceTable,
271/// }
272///
273/// impl IoView for MyState {
274///     fn table(&mut self) -> &mut ResourceTable { &mut self.table }
275/// }
276/// impl WasiHttpView for MyState {
277///     fn ctx(&mut self) -> &mut WasiHttpCtx { &mut self.http_ctx }
278/// }
279/// impl WasiView for MyState {
280///     fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx }
281/// }
282/// ```
283pub fn add_to_linker_async<T>(l: &mut wasmtime::component::Linker<T>) -> anyhow::Result<()>
284where
285    T: WasiHttpView + wasmtime_wasi::WasiView,
286{
287    let io_closure = type_annotate_io::<T, _>(|t| wasmtime_wasi::IoImpl(t));
288    wasmtime_wasi::bindings::io::poll::add_to_linker_get_host(l, io_closure)?;
289    wasmtime_wasi::bindings::io::error::add_to_linker_get_host(l, io_closure)?;
290    wasmtime_wasi::bindings::io::streams::add_to_linker_get_host(l, io_closure)?;
291
292    let closure = type_annotate_wasi::<T, _>(|t| wasmtime_wasi::WasiImpl(wasmtime_wasi::IoImpl(t)));
293    wasmtime_wasi::bindings::clocks::wall_clock::add_to_linker_get_host(l, closure)?;
294    wasmtime_wasi::bindings::clocks::monotonic_clock::add_to_linker_get_host(l, closure)?;
295    wasmtime_wasi::bindings::cli::stdin::add_to_linker_get_host(l, closure)?;
296    wasmtime_wasi::bindings::cli::stdout::add_to_linker_get_host(l, closure)?;
297    wasmtime_wasi::bindings::cli::stderr::add_to_linker_get_host(l, closure)?;
298    wasmtime_wasi::bindings::random::random::add_to_linker_get_host(l, closure)?;
299
300    add_only_http_to_linker_async(l)
301}
302
303// NB: workaround some rustc inference - a future refactoring may make this
304// obsolete.
305fn type_annotate_http<T, F>(val: F) -> F
306where
307    F: Fn(&mut T) -> WasiHttpImpl<&mut T>,
308{
309    val
310}
311fn type_annotate_wasi<T, F>(val: F) -> F
312where
313    F: Fn(&mut T) -> wasmtime_wasi::WasiImpl<&mut T>,
314{
315    val
316}
317fn type_annotate_io<T, F>(val: F) -> F
318where
319    F: Fn(&mut T) -> wasmtime_wasi::IoImpl<&mut T>,
320{
321    val
322}
323
324/// A slimmed down version of [`add_to_linker_async`] which only adds
325/// `wasi:http` interfaces to the linker.
326///
327/// This is useful when using [`wasmtime_wasi::add_to_linker_async`] for
328/// example to avoid re-adding the same interfaces twice.
329pub fn add_only_http_to_linker_async<T>(
330    l: &mut wasmtime::component::Linker<T>,
331) -> anyhow::Result<()>
332where
333    T: WasiHttpView,
334{
335    let closure = type_annotate_http::<T, _>(|t| WasiHttpImpl(IoImpl(t)));
336    crate::bindings::http::outgoing_handler::add_to_linker_get_host(l, closure)?;
337    crate::bindings::http::types::add_to_linker_get_host(l, closure)?;
338
339    Ok(())
340}
341
342/// Add all of the `wasi:http/proxy` world's interfaces to a [`wasmtime::component::Linker`].
343///
344/// This function will add the `sync` variant of all interfaces into the
345/// `Linker` provided. For embeddings with async support see
346/// [`add_to_linker_async`] instead.
347///
348/// # Example
349///
350/// ```
351/// use wasmtime::{Engine, Result, Config};
352/// use wasmtime::component::{ResourceTable, Linker};
353/// use wasmtime_wasi::{IoView, WasiCtx, WasiView};
354/// use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView};
355///
356/// fn main() -> Result<()> {
357///     let config = Config::default();
358///     let engine = Engine::new(&config)?;
359///
360///     let mut linker = Linker::<MyState>::new(&engine);
361///     wasmtime_wasi_http::add_to_linker_sync(&mut linker)?;
362///     // ... add any further functionality to `linker` if desired ...
363///
364///     Ok(())
365/// }
366///
367/// struct MyState {
368///     ctx: WasiCtx,
369///     http_ctx: WasiHttpCtx,
370///     table: ResourceTable,
371/// }
372/// impl IoView for MyState {
373///     fn table(&mut self) -> &mut ResourceTable { &mut self.table }
374/// }
375/// impl WasiHttpView for MyState {
376///     fn ctx(&mut self) -> &mut WasiHttpCtx { &mut self.http_ctx }
377/// }
378/// impl WasiView for MyState {
379///     fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx }
380/// }
381/// ```
382pub fn add_to_linker_sync<T>(l: &mut wasmtime::component::Linker<T>) -> anyhow::Result<()>
383where
384    T: WasiHttpView + wasmtime_wasi::WasiView,
385{
386    let io_closure = type_annotate_io::<T, _>(|t| wasmtime_wasi::IoImpl(t));
387    // For the sync linker, use the definitions of poll and streams from the
388    // wasmtime_wasi::bindings::sync space because those are defined using in_tokio.
389    wasmtime_wasi::bindings::sync::io::poll::add_to_linker_get_host(l, io_closure)?;
390    wasmtime_wasi::bindings::sync::io::streams::add_to_linker_get_host(l, io_closure)?;
391    // The error interface in the wasmtime_wasi is synchronous
392    wasmtime_wasi::bindings::io::error::add_to_linker_get_host(l, io_closure)?;
393
394    let closure = type_annotate_wasi::<T, _>(|t| wasmtime_wasi::WasiImpl(wasmtime_wasi::IoImpl(t)));
395
396    wasmtime_wasi::bindings::clocks::wall_clock::add_to_linker_get_host(l, closure)?;
397    wasmtime_wasi::bindings::clocks::monotonic_clock::add_to_linker_get_host(l, closure)?;
398    wasmtime_wasi::bindings::cli::stdin::add_to_linker_get_host(l, closure)?;
399    wasmtime_wasi::bindings::cli::stdout::add_to_linker_get_host(l, closure)?;
400    wasmtime_wasi::bindings::cli::stderr::add_to_linker_get_host(l, closure)?;
401    wasmtime_wasi::bindings::random::random::add_to_linker_get_host(l, closure)?;
402
403    add_only_http_to_linker_sync(l)?;
404
405    Ok(())
406}
407
408/// A slimmed down version of [`add_to_linker_sync`] which only adds
409/// `wasi:http` interfaces to the linker.
410///
411/// This is useful when using [`wasmtime_wasi::add_to_linker_sync`] for
412/// example to avoid re-adding the same interfaces twice.
413pub fn add_only_http_to_linker_sync<T>(l: &mut wasmtime::component::Linker<T>) -> anyhow::Result<()>
414where
415    T: WasiHttpView,
416{
417    let closure = type_annotate_http::<T, _>(|t| WasiHttpImpl(IoImpl(t)));
418
419    crate::bindings::http::outgoing_handler::add_to_linker_get_host(l, closure)?;
420    crate::bindings::http::types::add_to_linker_get_host(l, closure)?;
421
422    Ok(())
423}