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