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