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::p2::add_to_linker_async`] for example.
57//!    * Add individual interfaces such as with the
58//!      [`bindings::http::outgoing_handler::add_to_linker`] 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::{WasiCtx, WasiCtxView, 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::p2::add_to_linker_async(&mut linker)?;
98//!     wasmtime_wasi_http::add_only_http_to_linker_async(&mut linker)?;
99//!     let pre = ProxyPre::new(linker.instantiate_pre(&component)?)?;
100//!
101//!     // Prepare our server state and start listening for connections.
102//!     let server = Arc::new(MyServer { pre });
103//!     let listener = TcpListener::bind("127.0.0.1:8000").await?;
104//!     println!("Listening on {}", listener.local_addr()?);
105//!
106//!     loop {
107//!         // Accept a TCP connection and serve all of its requests in a separate
108//!         // tokio task. Note that for now this only works with HTTP/1.1.
109//!         let (client, addr) = listener.accept().await?;
110//!         println!("serving new client from {addr}");
111//!
112//!         let server = server.clone();
113//!         tokio::task::spawn(async move {
114//!             if let Err(e) = http1::Builder::new()
115//!                 .keep_alive(true)
116//!                 .serve_connection(
117//!                     TokioIo::new(client),
118//!                     hyper::service::service_fn(move |req| {
119//!                         let server = server.clone();
120//!                         async move { server.handle_request(req).await }
121//!                     }),
122//!                 )
123//!                 .await
124//!             {
125//!                 eprintln!("error serving client[{addr}]: {e:?}");
126//!             }
127//!         });
128//!     }
129//! }
130//!
131//! struct MyServer {
132//!     pre: ProxyPre<MyClientState>,
133//! }
134//!
135//! impl MyServer {
136//!     async fn handle_request(
137//!         &self,
138//!         req: hyper::Request<hyper::body::Incoming>,
139//!     ) -> Result<hyper::Response<HyperOutgoingBody>> {
140//!         // Create per-http-request state within a `Store` and prepare the
141//!         // initial resources  passed to the `handle` function.
142//!         let mut store = Store::new(
143//!             self.pre.engine(),
144//!             MyClientState {
145//!                 table: ResourceTable::new(),
146//!                 wasi: WasiCtx::builder().inherit_stdio().build(),
147//!                 http: WasiHttpCtx::new(),
148//!             },
149//!         );
150//!         let (sender, receiver) = tokio::sync::oneshot::channel();
151//!         let req = store.data_mut().new_incoming_request(Scheme::Http, req)?;
152//!         let out = store.data_mut().new_response_outparam(sender)?;
153//!         let pre = self.pre.clone();
154//!
155//!         // Run the http request itself in a separate task so the task can
156//!         // optionally continue to execute beyond after the initial
157//!         // headers/response code are sent.
158//!         let task = tokio::task::spawn(async move {
159//!             let proxy = pre.instantiate_async(&mut store).await?;
160//!
161//!             if let Err(e) = proxy
162//!                 .wasi_http_incoming_handler()
163//!                 .call_handle(store, req, out)
164//!                 .await
165//!             {
166//!                 return Err(e);
167//!             }
168//!
169//!             Ok(())
170//!         });
171//!
172//!         match receiver.await {
173//!             // If the client calls `response-outparam::set` then one of these
174//!             // methods will be called.
175//!             Ok(Ok(resp)) => Ok(resp),
176//!             Ok(Err(e)) => Err(e.into()),
177//!
178//!             // Otherwise the `sender` will get dropped along with the `Store`
179//!             // meaning that the oneshot will get disconnected and here we can
180//!             // inspect the `task` result to see what happened
181//!             Err(_) => {
182//!                 let e = match task.await {
183//!                     Ok(Ok(())) => {
184//!                         bail!("guest never invoked `response-outparam::set` method")
185//!                     }
186//!                     Ok(Err(e)) => e,
187//!                     Err(e) => e.into(),
188//!                 };
189//!                 return Err(e.context("guest never invoked `response-outparam::set` method"));
190//!             }
191//!         }
192//!     }
193//! }
194//!
195//! struct MyClientState {
196//!     wasi: WasiCtx,
197//!     http: WasiHttpCtx,
198//!     table: ResourceTable,
199//! }
200//!
201//! impl WasiView for MyClientState {
202//!     fn ctx(&mut self) -> WasiCtxView<'_> {
203//!         WasiCtxView { ctx: &mut self.wasi, table: &mut self.table }
204//!     }
205//! }
206//!
207//! impl WasiHttpView for MyClientState {
208//!     fn ctx(&mut self) -> &mut WasiHttpCtx {
209//!         &mut self.http
210//!     }
211//!
212//!     fn table(&mut self) -> &mut ResourceTable {
213//!         &mut self.table
214//!     }
215//! }
216//! ```
217
218#![deny(missing_docs)]
219#![doc(test(attr(deny(warnings))))]
220#![doc(test(attr(allow(dead_code, unused_variables, unused_mut))))]
221#![cfg_attr(docsrs, feature(doc_cfg))]
222
223mod error;
224mod http_impl;
225mod types_impl;
226
227pub mod body;
228pub mod io;
229pub mod types;
230
231pub mod bindings;
232
233#[cfg(feature = "p3")]
234pub mod p3;
235
236pub use crate::error::{
237    HttpError, HttpResult, http_request_error, hyper_request_error, hyper_response_error,
238};
239#[doc(inline)]
240pub use crate::types::{
241    DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS, DEFAULT_OUTGOING_BODY_CHUNK_SIZE, WasiHttpCtx,
242    WasiHttpImpl, WasiHttpView,
243};
244use http::header::CONTENT_LENGTH;
245use wasmtime::component::{HasData, Linker};
246
247/// Add all of the `wasi:http/proxy` world's interfaces to a [`wasmtime::component::Linker`].
248///
249/// This function will add the `async` variant of all interfaces into the
250/// `Linker` provided. By `async` this means that this function is only
251/// compatible with [`Config::async_support(true)`][async]. For embeddings with
252/// async support disabled see [`add_to_linker_sync`] instead.
253///
254/// [async]: wasmtime::Config::async_support
255///
256/// # Example
257///
258/// ```
259/// use wasmtime::{Engine, Result, Config};
260/// use wasmtime::component::{ResourceTable, Linker};
261/// use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView};
262/// use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView};
263///
264/// fn main() -> Result<()> {
265///     let mut config = Config::new();
266///     config.async_support(true);
267///     let engine = Engine::new(&config)?;
268///
269///     let mut linker = Linker::<MyState>::new(&engine);
270///     wasmtime_wasi_http::add_to_linker_async(&mut linker)?;
271///     // ... add any further functionality to `linker` if desired ...
272///
273///     Ok(())
274/// }
275///
276/// struct MyState {
277///     ctx: WasiCtx,
278///     http_ctx: WasiHttpCtx,
279///     table: ResourceTable,
280/// }
281///
282/// impl WasiHttpView for MyState {
283///     fn ctx(&mut self) -> &mut WasiHttpCtx { &mut self.http_ctx }
284///     fn table(&mut self) -> &mut ResourceTable { &mut self.table }
285/// }
286///
287/// impl WasiView for MyState {
288///     fn ctx(&mut self) -> WasiCtxView<'_> {
289///         WasiCtxView { ctx: &mut self.ctx, table: &mut self.table }
290///     }
291/// }
292/// ```
293pub fn add_to_linker_async<T>(l: &mut wasmtime::component::Linker<T>) -> anyhow::Result<()>
294where
295    T: WasiHttpView + wasmtime_wasi::WasiView + 'static,
296{
297    wasmtime_wasi::p2::add_to_linker_proxy_interfaces_async(l)?;
298    add_only_http_to_linker_async(l)
299}
300
301/// A slimmed down version of [`add_to_linker_async`] which only adds
302/// `wasi:http` interfaces to the linker.
303///
304/// This is useful when using [`wasmtime_wasi::p2::add_to_linker_async`] for
305/// example to avoid re-adding the same interfaces twice.
306pub fn add_only_http_to_linker_async<T>(
307    l: &mut wasmtime::component::Linker<T>,
308) -> anyhow::Result<()>
309where
310    T: WasiHttpView + 'static,
311{
312    let options = crate::bindings::LinkOptions::default(); // FIXME: Thread through to the CLI options.
313    crate::bindings::http::outgoing_handler::add_to_linker::<_, WasiHttp<T>>(l, |x| {
314        WasiHttpImpl(x)
315    })?;
316    crate::bindings::http::types::add_to_linker::<_, WasiHttp<T>>(l, &options.into(), |x| {
317        WasiHttpImpl(x)
318    })?;
319
320    Ok(())
321}
322
323struct WasiHttp<T>(T);
324
325impl<T: 'static> HasData for WasiHttp<T> {
326    type Data<'a> = WasiHttpImpl<&'a mut T>;
327}
328
329/// Add all of the `wasi:http/proxy` world's interfaces to a [`wasmtime::component::Linker`].
330///
331/// This function will add the `sync` variant of all interfaces into the
332/// `Linker` provided. For embeddings with async support see
333/// [`add_to_linker_async`] instead.
334///
335/// # Example
336///
337/// ```
338/// use wasmtime::{Engine, Result, Config};
339/// use wasmtime::component::{ResourceTable, Linker};
340/// use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView};
341/// use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView};
342///
343/// fn main() -> Result<()> {
344///     let config = Config::default();
345///     let engine = Engine::new(&config)?;
346///
347///     let mut linker = Linker::<MyState>::new(&engine);
348///     wasmtime_wasi_http::add_to_linker_sync(&mut linker)?;
349///     // ... add any further functionality to `linker` if desired ...
350///
351///     Ok(())
352/// }
353///
354/// struct MyState {
355///     ctx: WasiCtx,
356///     http_ctx: WasiHttpCtx,
357///     table: ResourceTable,
358/// }
359/// impl WasiHttpView for MyState {
360///     fn ctx(&mut self) -> &mut WasiHttpCtx { &mut self.http_ctx }
361///     fn table(&mut self) -> &mut ResourceTable { &mut self.table }
362/// }
363/// impl WasiView for MyState {
364///     fn ctx(&mut self) -> WasiCtxView<'_> {
365///         WasiCtxView { ctx: &mut self.ctx, table: &mut self.table }
366///     }
367/// }
368/// ```
369pub fn add_to_linker_sync<T>(l: &mut Linker<T>) -> anyhow::Result<()>
370where
371    T: WasiHttpView + wasmtime_wasi::WasiView + 'static,
372{
373    wasmtime_wasi::p2::add_to_linker_proxy_interfaces_sync(l)?;
374    add_only_http_to_linker_sync(l)
375}
376
377/// A slimmed down version of [`add_to_linker_sync`] which only adds
378/// `wasi:http` interfaces to the linker.
379///
380/// This is useful when using [`wasmtime_wasi::p2::add_to_linker_sync`] for
381/// example to avoid re-adding the same interfaces twice.
382pub fn add_only_http_to_linker_sync<T>(l: &mut Linker<T>) -> anyhow::Result<()>
383where
384    T: WasiHttpView + 'static,
385{
386    let options = crate::bindings::LinkOptions::default(); // FIXME: Thread through to the CLI options.
387    crate::bindings::sync::http::outgoing_handler::add_to_linker::<_, WasiHttp<T>>(l, |x| {
388        WasiHttpImpl(x)
389    })?;
390    crate::bindings::sync::http::types::add_to_linker::<_, WasiHttp<T>>(l, &options.into(), |x| {
391        WasiHttpImpl(x)
392    })?;
393
394    Ok(())
395}
396
397/// Extract the `Content-Length` header value from a [`http::HeaderMap`], returning `None` if it's not
398/// present. This function will return `Err` if it's not possible to parse the `Content-Length`
399/// header.
400fn get_content_length(headers: &http::HeaderMap) -> wasmtime::Result<Option<u64>> {
401    let Some(v) = headers.get(CONTENT_LENGTH) else {
402        return Ok(None);
403    };
404    let v = v.to_str()?;
405    let v = v.parse()?;
406    Ok(Some(v))
407}