Skip to main content

wasmtime_wasi_http/p2/
mod.rs

1//! # Wasmtime's WASI HTTPp2 Implementation
2//!
3//! This module 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::p2::bindings::Proxy
18//! [`wasi:http/outgoing-handler`]: crate::p2::bindings::http::outgoing_handler::Host
19//! [`wasi:http/types`]: crate::p2::bindings::http::types::Host
20//! [`wasi:http/incoming-handler`]: crate::p2::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 `WasiHttp{View,Hooks}` traits
27//!
28//! All `bindgen!`-generated `Host` traits are implemented for the
29//! [`WasiHttpCtxView`] type. This type is created from a store's data `T`
30//! through the [`WasiHttpView`] trait. The [`add_to_linker_async`] function,
31//! for example, uses [`WasiHttpView`] to acquire the context view.
32//!
33//! The [`WasiHttpCtxView`] structure requires that a [`ResourceTable`] and
34//! [`WasiHttpCtx`] live within the store. This is store-specific state that is
35//! used to implement various APIs and store host state.
36//!
37//! The final `hooks` field within [`WasiHttpCtxView`] is a trait object of
38//! [`WasiHttpHooks`]. This provides a few more hooks, dynamically, to configure
39//! how `wasi:http` behaves. For example [`WasiHttpHooks::send_request`] can
40//! customize how outgoing HTTP requests are handled. The `hooks` field can be
41//! initialized with the [`default_hooks`] function for the default behavior.
42//!
43//! # Async and Sync
44//!
45//! There are both asynchronous and synchronous bindings in this crate. For
46//! example [`add_to_linker_async`] is for asynchronous embedders and
47//! [`add_to_linker_sync`] is for synchronous embedders. Note that under the
48//! hood both versions are implemented with `async` on top of [`tokio`].
49//!
50//! # Examples
51//!
52//! Usage of this crate is done through a few steps to get everything hooked up:
53//!
54//! 1. First implement [`WasiHttpView`] for your type which is the `T` in
55//!    [`wasmtime::Store<T>`].
56//! 2. Add WASI HTTP interfaces to a [`wasmtime::component::Linker<T>`]. There
57//!    are a few options of how to do this:
58//!    * Use [`add_to_linker_async`] to bundle all interfaces in
59//!      `wasi:http/proxy` together
60//!    * Use [`add_only_http_to_linker_async`] to add only HTTP interfaces but
61//!      no others. This is useful when working with
62//!      [`wasmtime_wasi::p2::add_to_linker_async`] for example.
63//!    * Add individual interfaces such as with the
64//!      [`bindings::http::outgoing_handler::add_to_linker`] function.
65//! 3. Use [`ProxyPre`](bindings::ProxyPre) to pre-instantiate a component
66//!    before serving requests.
67//! 4. When serving requests use
68//!    [`ProxyPre::instantiate_async`](bindings::ProxyPre::instantiate_async)
69//!    to create instances and handle HTTP requests.
70//!
71//! A standalone example of doing all this looks like:
72//!
73//! ```no_run
74//! use wasmtime::bail;
75//! use hyper::server::conn::http1;
76//! use std::sync::Arc;
77//! use tokio::net::TcpListener;
78//! use wasmtime::component::{Component, Linker, ResourceTable};
79//! use wasmtime::{Engine, Result, Store};
80//! use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView};
81//! use wasmtime_wasi_http::p2::bindings::ProxyPre;
82//! use wasmtime_wasi_http::p2::bindings::http::types::Scheme;
83//! use wasmtime_wasi_http::p2::body::HyperOutgoingBody;
84//! use wasmtime_wasi_http::io::TokioIo;
85//! use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView, WasiHttpCtxView};
86//!
87//! #[tokio::main]
88//! async fn main() -> Result<()> {
89//!     let component = std::env::args().nth(1).unwrap();
90//!
91//!     // Prepare the `Engine` for Wasmtime
92//!     let engine = Engine::default();
93//!
94//!     // Compile the component on the command line to machine code
95//!     let component = Component::from_file(&engine, &component)?;
96//!
97//!     // Prepare the `ProxyPre` which is a pre-instantiated version of the
98//!     // component that we have. This will make per-request instantiation
99//!     // much quicker.
100//!     let mut linker = Linker::new(&engine);
101//!     wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;
102//!     wasmtime_wasi_http::p2::add_only_http_to_linker_async(&mut linker)?;
103//!     let pre = ProxyPre::new(linker.instantiate_pre(&component)?)?;
104//!
105//!     // Prepare our server state and start listening for connections.
106//!     let server = Arc::new(MyServer { pre });
107//!     let listener = TcpListener::bind("127.0.0.1:8000").await?;
108//!     println!("Listening on {}", listener.local_addr()?);
109//!
110//!     loop {
111//!         // Accept a TCP connection and serve all of its requests in a separate
112//!         // tokio task. Note that for now this only works with HTTP/1.1.
113//!         let (client, addr) = listener.accept().await?;
114//!         println!("serving new client from {addr}");
115//!
116//!         let server = server.clone();
117//!         tokio::task::spawn(async move {
118//!             if let Err(e) = http1::Builder::new()
119//!                 .keep_alive(true)
120//!                 .serve_connection(
121//!                     TokioIo::new(client),
122//!                     hyper::service::service_fn(move |req| {
123//!                         let server = server.clone();
124//!                         async move { server.handle_request(req).await }
125//!                     }),
126//!                 )
127//!                 .await
128//!             {
129//!                 eprintln!("error serving client[{addr}]: {e:?}");
130//!             }
131//!         });
132//!     }
133//! }
134//!
135//! struct MyServer {
136//!     pre: ProxyPre<MyClientState>,
137//! }
138//!
139//! impl MyServer {
140//!     async fn handle_request(
141//!         &self,
142//!         req: hyper::Request<hyper::body::Incoming>,
143//!     ) -> Result<hyper::Response<HyperOutgoingBody>> {
144//!         // Create per-http-request state within a `Store` and prepare the
145//!         // initial resources  passed to the `handle` function.
146//!         let mut store = Store::new(
147//!             self.pre.engine(),
148//!             MyClientState {
149//!                 table: ResourceTable::new(),
150//!                 wasi: WasiCtx::builder().inherit_stdio().build(),
151//!                 http: WasiHttpCtx::new(),
152//!             },
153//!         );
154//!         let (sender, receiver) = tokio::sync::oneshot::channel();
155//!         let req = store.data_mut().http().new_incoming_request(Scheme::Http, req)?;
156//!         let out = store.data_mut().http().new_response_outparam(sender)?;
157//!         let pre = self.pre.clone();
158//!
159//!         // Run the http request itself in a separate task so the task can
160//!         // optionally continue to execute beyond after the initial
161//!         // headers/response code are sent.
162//!         let task = tokio::task::spawn(async move {
163//!             let proxy = pre.instantiate_async(&mut store).await?;
164//!
165//!             if let Err(e) = proxy
166//!                 .wasi_http_incoming_handler()
167//!                 .call_handle(store, req, out)
168//!                 .await
169//!             {
170//!                 return Err(e);
171//!             }
172//!
173//!             Ok(())
174//!         });
175//!
176//!         match receiver.await {
177//!             // If the client calls `response-outparam::set` then one of these
178//!             // methods will be called.
179//!             Ok(Ok(resp)) => Ok(resp),
180//!             Ok(Err(e)) => Err(e.into()),
181//!
182//!             // Otherwise the `sender` will get dropped along with the `Store`
183//!             // meaning that the oneshot will get disconnected and here we can
184//!             // inspect the `task` result to see what happened
185//!             Err(_) => {
186//!                 let e = match task.await {
187//!                     Ok(Ok(())) => {
188//!                         bail!("guest never invoked `response-outparam::set` method")
189//!                     }
190//!                     Ok(Err(e)) => e,
191//!                     Err(e) => e.into(),
192//!                 };
193//!                 return Err(e.context("guest never invoked `response-outparam::set` method"));
194//!             }
195//!         }
196//!     }
197//! }
198//!
199//! struct MyClientState {
200//!     wasi: WasiCtx,
201//!     http: WasiHttpCtx,
202//!     table: ResourceTable,
203//! }
204//!
205//! impl WasiView for MyClientState {
206//!     fn ctx(&mut self) -> WasiCtxView<'_> {
207//!         WasiCtxView { ctx: &mut self.wasi, table: &mut self.table }
208//!     }
209//! }
210//!
211//! impl WasiHttpView for MyClientState {
212//!     fn http(&mut self) -> WasiHttpCtxView<'_> {
213//!         WasiHttpCtxView {
214//!             ctx: &mut self.http,
215//!             table: &mut self.table,
216//!             hooks: Default::default(),
217//!         }
218//!     }
219//! }
220//! ```
221//!
222//! [`WasiHttpCtx`]: crate::WasiHttpCtx
223//! [`WasiHttpCtxView`]: crate::WasiHttpCtxView
224//! [`WasiHttpHooks`]: crate::WasiHttpHooks
225//! [`WasiHttpHooks::send_request`]: crate::WasiHttpHooks::send_request
226//! [`ResourceTable`]: wasmtime::component::ResourceTable
227//! [`default_hooks`]: crate::default_hooks
228
229use crate::{WasiHttp, WasiHttpNamed, WasiHttpNamedView, WasiHttpView};
230use wasmtime::component::{Component, Linker};
231use wasmtime_wasi::{NamedId, WasiCtxNamedView};
232
233mod error;
234mod http_impl;
235mod types_impl;
236
237pub mod bindings;
238pub mod body;
239pub mod types;
240
241pub use self::error::*;
242
243/// The default value configured for [`WasiHttpHooks::p2_outgoing_body_buffer_chunks`] in [`WasiHttpView`].
244///
245/// [`WasiHttpHooks::p2_outgoing_body_buffer_chunks`]: crate::WasiHttpHooks::p2_outgoing_body_buffer_chunks
246pub const DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS: usize = 1;
247/// The default value configured for [`WasiHttpHooks::p2_outgoing_body_chunk_size`] in [`WasiHttpView`].
248///
249/// [`WasiHttpHooks::p2_outgoing_body_chunk_size`]: crate::WasiHttpHooks::p2_outgoing_body_chunk_size
250pub const DEFAULT_OUTGOING_BODY_CHUNK_SIZE: usize = 1024 * 1024;
251
252/// Add all of the `wasi:http/proxy` world's interfaces to a [`wasmtime::component::Linker`].
253///
254/// This function will add the `async` variant of all interfaces into the
255/// `Linker` provided. For embeddings with async support disabled see
256/// [`add_to_linker_sync`] instead.
257///
258/// # Example
259///
260/// ```
261/// use wasmtime::{Engine, Result};
262/// use wasmtime::component::{ResourceTable, Linker};
263/// use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView};
264/// use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView, WasiHttpCtxView};
265///
266/// fn main() -> Result<()> {
267///     let engine = Engine::default();
268///
269///     let mut linker = Linker::<MyState>::new(&engine);
270///     wasmtime_wasi_http::p2::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 http(&mut self) -> WasiHttpCtxView<'_> {
284///         WasiHttpCtxView {
285///             ctx: &mut self.http_ctx,
286///             table: &mut self.table,
287///             hooks: Default::default(),
288///         }
289///     }
290/// }
291///
292/// impl WasiView for MyState {
293///     fn ctx(&mut self) -> WasiCtxView<'_> {
294///         WasiCtxView { ctx: &mut self.ctx, table: &mut self.table }
295///     }
296/// }
297/// ```
298pub fn add_to_linker_async<T>(l: &mut wasmtime::component::Linker<T>) -> wasmtime::Result<()>
299where
300    T: WasiHttpView + wasmtime_wasi::WasiView + 'static,
301{
302    wasmtime_wasi::p2::add_to_linker_proxy_interfaces_async(l)?;
303    add_only_http_to_linker_async(l)
304}
305
306/// A slimmed down version of [`add_to_linker_async`] which only adds
307/// `wasi:http` interfaces to the linker.
308///
309/// This is useful when using [`wasmtime_wasi::p2::add_to_linker_async`] for
310/// example to avoid re-adding the same interfaces twice.
311pub fn add_only_http_to_linker_async<T>(
312    l: &mut wasmtime::component::Linker<T>,
313) -> wasmtime::Result<()>
314where
315    T: WasiHttpView + 'static,
316{
317    let options = bindings::LinkOptions::default(); // FIXME: Thread through to the CLI options.
318    bindings::http::outgoing_handler::add_to_linker::<_, WasiHttp>(l, T::http)?;
319    bindings::http::types::add_to_linker::<_, WasiHttp>(l, &options.into(), T::http)?;
320
321    Ok(())
322}
323
324/// Interfaces that are added via [`add_named_to_linker_async`].
325#[derive(Copy, Clone, PartialEq, Eq, Debug)]
326pub enum Interface {
327    /// `wasi:http/outgoing-handler`
328    HttpOutgoingHandler,
329    /// `wasi:http/types`
330    HttpTypes,
331}
332
333/// Add all `wasi:http` interfaces from this crate into the `linker` provided
334/// for any named imports that a component has.
335///
336/// This function is similar to [`add_only_http_to_linker_async`] except that
337/// it's specifically designed to work with named imports of `wasi:http`
338/// interfaces that components may have. This requires a [`Component`] parameter
339/// to be passed in when populating the [`Linker`] provided to see what the
340/// [`Component`] actually imports.
341///
342/// Like [`add_only_http_to_linker_async`] this only adds `wasi:http`
343/// interfaces, so [`wasmtime_wasi::p2::add_named_to_linker_async`] should
344/// additionally be used to bind named imports of the rest of WASI. If this
345/// isn't low level enough you can invoke the bindgen-generated `add_to_linker`
346/// functions within the [`named_imports`] module directly instead.
347///
348/// [`wasmtime_wasi::p2::add_named_to_linker_async`]: wasmtime_wasi::p2::add_named_to_linker_async
349/// [`named_imports`]: crate::p2::bindings::named_imports
350///
351/// The `lookup` function provided here is invoked for every named import found
352/// for a particular interface. The [`Interface`] given is what's being bound,
353/// and the `&str` argument is the name that the component imports it as. The
354/// embedder can then decide how it would like to allocate a [`NamedId`] for
355/// this import. If `Ok` is returned then the linker is populated with this
356/// name, and imported functions will pass the [`NamedId`] later to the
357/// implementation of [`WasiHttpNamedView`] on `T` when invoked. If `Err` is
358/// returned then the error will cause this entire function to fail and this
359/// function call will return the same error.
360///
361/// # Example
362///
363/// ```
364/// use std::collections::HashMap;
365/// use wasmtime::component::{Component, Linker, ResourceTable};
366/// use wasmtime::{Engine, Result, Store};
367/// use wasmtime_wasi::NamedId;
368/// use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpCtxView, WasiHttpNamedView};
369///
370/// fn main() -> Result<()> {
371///     let engine = Engine::default();
372///     let component = Component::new(&engine, "(component)")?;
373///
374///     let mut linker = Linker::<MyState>::new(&engine);
375///
376///     // ... add default functionality to `linker` as needed ...
377///
378///     // and then additionally fill in any specific named imports `component`
379///     // might have for `wasi:http` interfaces.
380///     let mut name_map = HashMap::new();
381///     wasmtime_wasi_http::p2::add_named_to_linker_async(&mut linker, &component, |_i, name| {
382///         let len = name_map.len();
383///         Ok(NamedId(*name_map.entry(name.to_string()).or_insert(len)))
384///     })?;
385///
386///     // Here a `WasiHttpCtx` is allocated per-named-import and will then be
387///     // referred to internally by the [`NamedId`] allocated above. You could
388///     // also use `name_map` to configure each context differently.
389///     let mut my_state = MyState::default();
390///     for _ in 0..name_map.len() {
391///         my_state.contexts.push(WasiHttpCtx::default());
392///     }
393///     let mut store = Store::new(&engine, my_state);
394///
395///     // ... use `linker` to instantiate within `store` ...
396///
397///     Ok(())
398/// }
399///
400/// #[derive(Default)]
401/// struct MyState {
402///     table: ResourceTable,
403///     contexts: Vec<WasiHttpCtx>,
404/// }
405///
406/// impl WasiHttpNamedView for MyState {
407///     fn http(&mut self, id: NamedId) -> WasiHttpCtxView<'_> {
408///         WasiHttpCtxView {
409///             ctx: &mut self.contexts[id.0],
410///             table: &mut self.table,
411///             hooks: Default::default(),
412///         }
413///     }
414/// }
415/// ```
416pub fn add_named_to_linker_async<T>(
417    linker: &mut Linker<T>,
418    component: &Component,
419    lookup: impl FnMut(Interface, &str) -> wasmtime::Result<NamedId>,
420) -> wasmtime::Result<()>
421where
422    T: WasiHttpNamedView,
423{
424    let options = bindings::LinkOptions::default(); // FIXME: Thread through to the CLI options.
425    add_named_to_linker_with_options_async(linker, &options, component, lookup)
426}
427
428/// Same as [`add_named_to_linker_async`] except [`bindings::LinkOptions`] can
429/// be specified to configure interfaces that are added.
430pub fn add_named_to_linker_with_options_async<T>(
431    linker: &mut Linker<T>,
432    options: &bindings::LinkOptions,
433    component: &Component,
434    mut lookup: impl FnMut(Interface, &str) -> wasmtime::Result<NamedId>,
435) -> wasmtime::Result<()>
436where
437    T: WasiHttpNamedView,
438{
439    use crate::p2::bindings::named_imports::wasi::http::{outgoing_handler, types};
440
441    let l = linker;
442    outgoing_handler::add_to_linker::<_, WasiHttpNamed<T>>(
443        l,
444        component,
445        |name| lookup(Interface::HttpOutgoingHandler, name),
446        |x| WasiCtxNamedView(x),
447    )?;
448    types::add_to_linker::<_, WasiHttpNamed<T>>(
449        l,
450        component,
451        |name| lookup(Interface::HttpTypes, name),
452        &options.into(),
453        |x| WasiCtxNamedView(x),
454    )?;
455    Ok(())
456}
457
458/// Add all of the `wasi:http/proxy` world's interfaces to a [`wasmtime::component::Linker`].
459///
460/// This function will add the `sync` variant of all interfaces into the
461/// `Linker` provided. For embeddings with async support see
462/// [`add_to_linker_async`] instead.
463///
464/// # Example
465///
466/// ```
467/// use wasmtime::{Engine, Result, Config};
468/// use wasmtime::component::{ResourceTable, Linker};
469/// use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView};
470/// use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView, WasiHttpCtxView};
471///
472/// fn main() -> Result<()> {
473///     let config = Config::default();
474///     let engine = Engine::new(&config)?;
475///
476///     let mut linker = Linker::<MyState>::new(&engine);
477///     wasmtime_wasi_http::p2::add_to_linker_sync(&mut linker)?;
478///     // ... add any further functionality to `linker` if desired ...
479///
480///     Ok(())
481/// }
482///
483/// struct MyState {
484///     ctx: WasiCtx,
485///     http_ctx: WasiHttpCtx,
486///     table: ResourceTable,
487/// }
488/// impl WasiHttpView for MyState {
489///     fn http(&mut self) -> WasiHttpCtxView<'_> {
490///         WasiHttpCtxView {
491///             ctx: &mut self.http_ctx,
492///             table: &mut self.table,
493///             hooks: Default::default(),
494///         }
495///     }
496/// }
497/// impl WasiView for MyState {
498///     fn ctx(&mut self) -> WasiCtxView<'_> {
499///         WasiCtxView { ctx: &mut self.ctx, table: &mut self.table }
500///     }
501/// }
502/// ```
503pub fn add_to_linker_sync<T>(l: &mut Linker<T>) -> wasmtime::Result<()>
504where
505    T: WasiHttpView + wasmtime_wasi::WasiView + 'static,
506{
507    wasmtime_wasi::p2::add_to_linker_proxy_interfaces_sync(l)?;
508    add_only_http_to_linker_sync(l)
509}
510
511/// A slimmed down version of [`add_to_linker_sync`] which only adds
512/// `wasi:http` interfaces to the linker.
513///
514/// This is useful when using [`wasmtime_wasi::p2::add_to_linker_sync`] for
515/// example to avoid re-adding the same interfaces twice.
516pub fn add_only_http_to_linker_sync<T>(l: &mut Linker<T>) -> wasmtime::Result<()>
517where
518    T: WasiHttpView + 'static,
519{
520    let options = bindings::LinkOptions::default(); // FIXME: Thread through to the CLI options.
521    bindings::sync::http::outgoing_handler::add_to_linker::<_, WasiHttp>(l, T::http)?;
522    bindings::sync::http::types::add_to_linker::<_, WasiHttp>(l, &options.into(), T::http)?;
523
524    Ok(())
525}