xref: /wasmtime-44.0.1/crates/wasi-http/src/lib.rs (revision 7fb4e49b)
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::p2::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_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::p2::{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(Ok(())) => {
183 //!                         bail!("guest never invoked `response-outparam::set` method")
184 //!                     }
185 //!                     Ok(Err(e)) => e,
186 //!                     Err(e) => e.into(),
187 //!                 };
188 //!                 return Err(e.context("guest never invoked `response-outparam::set` method"));
189 //!             }
190 //!         }
191 //!     }
192 //! }
193 //!
194 //! struct MyClientState {
195 //!     wasi: WasiCtx,
196 //!     http: WasiHttpCtx,
197 //!     table: ResourceTable,
198 //! }
199 //! impl IoView for MyClientState {
200 //!     fn table(&mut self) -> &mut ResourceTable {
201 //!         &mut self.table
202 //!     }
203 //! }
204 //! impl WasiView for MyClientState {
205 //!     fn ctx(&mut self) -> &mut WasiCtx {
206 //!         &mut self.wasi
207 //!     }
208 //! }
209 //!
210 //! impl WasiHttpView for MyClientState {
211 //!     fn ctx(&mut self) -> &mut WasiHttpCtx {
212 //!         &mut self.http
213 //!     }
214 //! }
215 //! ```
216 
217 #![deny(missing_docs)]
218 #![doc(test(attr(deny(warnings))))]
219 #![doc(test(attr(allow(dead_code, unused_variables, unused_mut))))]
220 
221 mod error;
222 mod http_impl;
223 mod types_impl;
224 
225 pub mod body;
226 pub mod io;
227 pub mod types;
228 
229 pub mod bindings;
230 
231 pub use crate::error::{
232     HttpError, HttpResult, http_request_error, hyper_request_error, hyper_response_error,
233 };
234 #[doc(inline)]
235 pub use crate::types::{
236     DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS, DEFAULT_OUTGOING_BODY_CHUNK_SIZE, WasiHttpCtx,
237     WasiHttpImpl, WasiHttpView,
238 };
239 use wasmtime::component::{HasData, Linker};
240 use wasmtime_wasi::p2::IoImpl;
241 
242 /// Add all of the `wasi:http/proxy` world's interfaces to a [`wasmtime::component::Linker`].
243 ///
244 /// This function will add the `async` variant of all interfaces into the
245 /// `Linker` provided. By `async` this means that this function is only
246 /// compatible with [`Config::async_support(true)`][async]. For embeddings with
247 /// async support disabled see [`add_to_linker_sync`] instead.
248 ///
249 /// [async]: wasmtime::Config::async_support
250 ///
251 /// # Example
252 ///
253 /// ```
254 /// use wasmtime::{Engine, Result, Config};
255 /// use wasmtime::component::{ResourceTable, Linker};
256 /// use wasmtime_wasi::p2::{IoView, WasiCtx, WasiView};
257 /// use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView};
258 ///
259 /// fn main() -> Result<()> {
260 ///     let mut config = Config::new();
261 ///     config.async_support(true);
262 ///     let engine = Engine::new(&config)?;
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 IoView for MyState {
278 ///     fn table(&mut self) -> &mut ResourceTable { &mut self.table }
279 /// }
280 /// impl WasiHttpView for MyState {
281 ///     fn ctx(&mut self) -> &mut WasiHttpCtx { &mut self.http_ctx }
282 /// }
283 /// impl WasiView for MyState {
284 ///     fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx }
285 /// }
286 /// ```
287 pub fn add_to_linker_async<T>(l: &mut wasmtime::component::Linker<T>) -> anyhow::Result<()>
288 where
289     T: WasiHttpView + wasmtime_wasi::p2::WasiView + 'static,
290 {
291     wasmtime_wasi::p2::add_to_linker_proxy_interfaces_async(l)?;
292     add_only_http_to_linker_async(l)
293 }
294 
295 /// A slimmed down version of [`add_to_linker_async`] which only adds
296 /// `wasi:http` interfaces to the linker.
297 ///
298 /// This is useful when using [`wasmtime_wasi::p2::add_to_linker_async`] for
299 /// example to avoid re-adding the same interfaces twice.
300 pub fn add_only_http_to_linker_async<T>(
301     l: &mut wasmtime::component::Linker<T>,
302 ) -> anyhow::Result<()>
303 where
304     T: WasiHttpView + 'static,
305 {
306     let options = crate::bindings::LinkOptions::default(); // FIXME: Thread through to the CLI options.
307     crate::bindings::http::outgoing_handler::add_to_linker::<_, WasiHttp<T>>(l, |x| {
308         WasiHttpImpl(IoImpl(x))
309     })?;
310     crate::bindings::http::types::add_to_linker::<_, WasiHttp<T>>(l, &options.into(), |x| {
311         WasiHttpImpl(IoImpl(x))
312     })?;
313 
314     Ok(())
315 }
316 
317 struct WasiHttp<T>(T);
318 
319 impl<T: 'static> HasData for WasiHttp<T> {
320     type Data<'a> = WasiHttpImpl<&'a mut T>;
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::p2::{IoView, WasiCtx, WasiView};
335 /// use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView};
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::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 IoView for MyState {
354 ///     fn table(&mut self) -> &mut ResourceTable { &mut self.table }
355 /// }
356 /// impl WasiHttpView for MyState {
357 ///     fn ctx(&mut self) -> &mut WasiHttpCtx { &mut self.http_ctx }
358 /// }
359 /// impl WasiView for MyState {
360 ///     fn ctx(&mut self) -> &mut WasiCtx { &mut self.ctx }
361 /// }
362 /// ```
363 pub fn add_to_linker_sync<T>(l: &mut Linker<T>) -> anyhow::Result<()>
364 where
365     T: WasiHttpView + wasmtime_wasi::p2::WasiView + 'static,
366 {
367     wasmtime_wasi::p2::add_to_linker_proxy_interfaces_sync(l)?;
368     add_only_http_to_linker_sync(l)
369 }
370 
371 /// A slimmed down version of [`add_to_linker_sync`] which only adds
372 /// `wasi:http` interfaces to the linker.
373 ///
374 /// This is useful when using [`wasmtime_wasi::p2::add_to_linker_sync`] for
375 /// example to avoid re-adding the same interfaces twice.
376 pub fn add_only_http_to_linker_sync<T>(l: &mut Linker<T>) -> anyhow::Result<()>
377 where
378     T: WasiHttpView + 'static,
379 {
380     let options = crate::bindings::LinkOptions::default(); // FIXME: Thread through to the CLI options.
381     crate::bindings::sync::http::outgoing_handler::add_to_linker::<_, WasiHttp<T>>(l, |x| {
382         WasiHttpImpl(IoImpl(x))
383     })?;
384     crate::bindings::sync::http::types::add_to_linker::<_, WasiHttp<T>>(l, &options.into(), |x| {
385         WasiHttpImpl(IoImpl(x))
386     })?;
387 
388     Ok(())
389 }
390