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, WasiHttpView};
230use wasmtime::component::Linker;
231
232mod error;
233mod http_impl;
234mod types_impl;
235
236pub mod bindings;
237pub mod body;
238pub mod types;
239
240pub use self::error::*;
241
242/// The default value configured for [`WasiHttpHooks::p2_outgoing_body_buffer_chunks`] in [`WasiHttpView`].
243///
244/// [`WasiHttpHooks::p2_outgoing_body_buffer_chunks`]: crate::WasiHttpHooks::p2_outgoing_body_buffer_chunks
245pub const DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS: usize = 1;
246/// The default value configured for [`WasiHttpHooks::p2_outgoing_body_chunk_size`] in [`WasiHttpView`].
247///
248/// [`WasiHttpHooks::p2_outgoing_body_chunk_size`]: crate::WasiHttpHooks::p2_outgoing_body_chunk_size
249pub const DEFAULT_OUTGOING_BODY_CHUNK_SIZE: usize = 1024 * 1024;
250
251/// Add all of the `wasi:http/proxy` world's interfaces to a [`wasmtime::component::Linker`].
252///
253/// This function will add the `async` variant of all interfaces into the
254/// `Linker` provided. For embeddings with async support disabled see
255/// [`add_to_linker_sync`] instead.
256///
257/// # Example
258///
259/// ```
260/// use wasmtime::{Engine, Result};
261/// use wasmtime::component::{ResourceTable, Linker};
262/// use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView};
263/// use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView, WasiHttpCtxView};
264///
265/// fn main() -> Result<()> {
266/// let engine = Engine::default();
267///
268/// let mut linker = Linker::<MyState>::new(&engine);
269/// wasmtime_wasi_http::p2::add_to_linker_async(&mut linker)?;
270/// // ... add any further functionality to `linker` if desired ...
271///
272/// Ok(())
273/// }
274///
275/// struct MyState {
276/// ctx: WasiCtx,
277/// http_ctx: WasiHttpCtx,
278/// table: ResourceTable,
279/// }
280///
281/// impl WasiHttpView for MyState {
282/// fn http(&mut self) -> WasiHttpCtxView<'_> {
283/// WasiHttpCtxView {
284/// ctx: &mut self.http_ctx,
285/// table: &mut self.table,
286/// hooks: Default::default(),
287/// }
288/// }
289/// }
290///
291/// impl WasiView for MyState {
292/// fn ctx(&mut self) -> WasiCtxView<'_> {
293/// WasiCtxView { ctx: &mut self.ctx, table: &mut self.table }
294/// }
295/// }
296/// ```
297pub fn add_to_linker_async<T>(l: &mut wasmtime::component::Linker<T>) -> wasmtime::Result<()>
298where
299 T: WasiHttpView + wasmtime_wasi::WasiView + 'static,
300{
301 wasmtime_wasi::p2::add_to_linker_proxy_interfaces_async(l)?;
302 add_only_http_to_linker_async(l)
303}
304
305/// A slimmed down version of [`add_to_linker_async`] which only adds
306/// `wasi:http` interfaces to the linker.
307///
308/// This is useful when using [`wasmtime_wasi::p2::add_to_linker_async`] for
309/// example to avoid re-adding the same interfaces twice.
310pub fn add_only_http_to_linker_async<T>(
311 l: &mut wasmtime::component::Linker<T>,
312) -> wasmtime::Result<()>
313where
314 T: WasiHttpView + 'static,
315{
316 let options = bindings::LinkOptions::default(); // FIXME: Thread through to the CLI options.
317 bindings::http::outgoing_handler::add_to_linker::<_, WasiHttp>(l, T::http)?;
318 bindings::http::types::add_to_linker::<_, WasiHttp>(l, &options.into(), T::http)?;
319
320 Ok(())
321}
322
323/// Add all of the `wasi:http/proxy` world's interfaces to a [`wasmtime::component::Linker`].
324///
325/// This function will add the `sync` variant of all interfaces into the
326/// `Linker` provided. For embeddings with async support see
327/// [`add_to_linker_async`] instead.
328///
329/// # Example
330///
331/// ```
332/// use wasmtime::{Engine, Result, Config};
333/// use wasmtime::component::{ResourceTable, Linker};
334/// use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView};
335/// use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView, WasiHttpCtxView};
336///
337/// fn main() -> Result<()> {
338/// let config = Config::default();
339/// let engine = Engine::new(&config)?;
340///
341/// let mut linker = Linker::<MyState>::new(&engine);
342/// wasmtime_wasi_http::p2::add_to_linker_sync(&mut linker)?;
343/// // ... add any further functionality to `linker` if desired ...
344///
345/// Ok(())
346/// }
347///
348/// struct MyState {
349/// ctx: WasiCtx,
350/// http_ctx: WasiHttpCtx,
351/// table: ResourceTable,
352/// }
353/// impl WasiHttpView for MyState {
354/// fn http(&mut self) -> WasiHttpCtxView<'_> {
355/// WasiHttpCtxView {
356/// ctx: &mut self.http_ctx,
357/// table: &mut self.table,
358/// hooks: Default::default(),
359/// }
360/// }
361/// }
362/// impl WasiView for MyState {
363/// fn ctx(&mut self) -> WasiCtxView<'_> {
364/// WasiCtxView { ctx: &mut self.ctx, table: &mut self.table }
365/// }
366/// }
367/// ```
368pub fn add_to_linker_sync<T>(l: &mut Linker<T>) -> wasmtime::Result<()>
369where
370 T: WasiHttpView + wasmtime_wasi::WasiView + 'static,
371{
372 wasmtime_wasi::p2::add_to_linker_proxy_interfaces_sync(l)?;
373 add_only_http_to_linker_sync(l)
374}
375
376/// A slimmed down version of [`add_to_linker_sync`] which only adds
377/// `wasi:http` interfaces to the linker.
378///
379/// This is useful when using [`wasmtime_wasi::p2::add_to_linker_sync`] for
380/// example to avoid re-adding the same interfaces twice.
381pub fn add_only_http_to_linker_sync<T>(l: &mut Linker<T>) -> wasmtime::Result<()>
382where
383 T: WasiHttpView + 'static,
384{
385 let options = bindings::LinkOptions::default(); // FIXME: Thread through to the CLI options.
386 bindings::sync::http::outgoing_handler::add_to_linker::<_, WasiHttp>(l, T::http)?;
387 bindings::sync::http::types::add_to_linker::<_, WasiHttp>(l, &options.into(), T::http)?;
388
389 Ok(())
390}