xref: /wasmtime-44.0.1/crates/wasi-http/src/lib.rs (revision bda02c19)
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 
221 mod error;
222 mod http_impl;
223 mod types_impl;
224 
225 pub mod body;
226 #[cfg(feature = "component-model-async")]
227 pub mod handler;
228 pub mod io;
229 pub mod types;
230 
231 pub mod bindings;
232 
233 #[cfg(feature = "p3")]
234 pub mod p3;
235 
236 pub use crate::error::{
237     HttpError, HttpResult, http_request_error, hyper_request_error, hyper_response_error,
238 };
239 #[doc(inline)]
240 pub use crate::types::{
241     DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS, DEFAULT_OUTGOING_BODY_CHUNK_SIZE, WasiHttpCtx,
242     WasiHttpImpl, WasiHttpView,
243 };
244 use http::header::CONTENT_LENGTH;
245 use 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 /// ```
288 pub fn add_to_linker_async<T>(l: &mut wasmtime::component::Linker<T>) -> wasmtime::Result<()>
289 where
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.
301 pub fn add_only_http_to_linker_async<T>(
302     l: &mut wasmtime::component::Linker<T>,
303 ) -> wasmtime::Result<()>
304 where
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 
318 struct WasiHttp<T>(T);
319 
320 impl<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 /// ```
364 pub fn add_to_linker_sync<T>(l: &mut Linker<T>) -> wasmtime::Result<()>
365 where
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.
377 pub fn add_only_http_to_linker_sync<T>(l: &mut Linker<T>) -> wasmtime::Result<()>
378 where
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.
395 fn 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 }
403