xref: /wasmtime-44.0.1/crates/wasi-http/src/p2/mod.rs (revision 356c6f8a)
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, p2::{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 #[cfg(feature = "default-send-request")]
223 use self::bindings::http::types::ErrorCode;
224 use crate::{DEFAULT_FORBIDDEN_HEADERS, WasiHttpCtx};
225 use http::HeaderName;
226 use wasmtime::component::{HasData, Linker, ResourceTable};
227 
228 mod error;
229 mod http_impl;
230 mod types_impl;
231 
232 pub mod bindings;
233 pub mod body;
234 pub mod types;
235 
236 pub use self::error::*;
237 
238 /// A trait which provides hooks into internal WASI HTTP operations.
239 ///
240 /// # Example
241 ///
242 /// ```
243 /// use wasmtime::component::ResourceTable;
244 /// use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView};
245 /// use wasmtime_wasi_http::WasiHttpCtx;
246 /// use wasmtime_wasi_http::p2::{WasiHttpView, WasiHttpCtxView};
247 ///
248 /// struct MyState {
249 ///     ctx: WasiCtx,
250 ///     http_ctx: WasiHttpCtx,
251 ///     table: ResourceTable,
252 /// }
253 ///
254 /// impl WasiHttpView for MyState {
255 ///     fn http(&mut self) -> WasiHttpCtxView<'_> {
256 ///         WasiHttpCtxView {
257 ///             ctx: &mut self.http_ctx,
258 ///             table: &mut self.table,
259 ///             hooks: Default::default(),
260 ///         }
261 ///     }
262 /// }
263 ///
264 /// impl WasiView for MyState {
265 ///     fn ctx(&mut self) -> WasiCtxView<'_> {
266 ///         WasiCtxView { ctx: &mut self.ctx, table: &mut self.table }
267 ///     }
268 /// }
269 ///
270 /// impl MyState {
271 ///     fn new() -> MyState {
272 ///         let mut wasi = WasiCtx::builder();
273 ///         wasi.arg("./foo.wasm");
274 ///         wasi.arg("--help");
275 ///         wasi.env("FOO", "bar");
276 ///
277 ///         MyState {
278 ///             ctx: wasi.build(),
279 ///             table: ResourceTable::new(),
280 ///             http_ctx: WasiHttpCtx::new(),
281 ///         }
282 ///     }
283 /// }
284 /// ```
285 pub trait WasiHttpHooks: Send {
286     /// Send an outgoing request.
287     #[cfg(feature = "default-send-request")]
send_request( &mut self, request: hyper::Request<body::HyperOutgoingBody>, config: types::OutgoingRequestConfig, ) -> HttpResult<types::HostFutureIncomingResponse>288     fn send_request(
289         &mut self,
290         request: hyper::Request<body::HyperOutgoingBody>,
291         config: types::OutgoingRequestConfig,
292     ) -> HttpResult<types::HostFutureIncomingResponse> {
293         Ok(default_send_request(request, config))
294     }
295 
296     /// Send an outgoing request.
297     #[cfg(not(feature = "default-send-request"))]
send_request( &mut self, request: hyper::Request<body::HyperOutgoingBody>, config: types::OutgoingRequestConfig, ) -> HttpResult<types::HostFutureIncomingResponse>298     fn send_request(
299         &mut self,
300         request: hyper::Request<body::HyperOutgoingBody>,
301         config: types::OutgoingRequestConfig,
302     ) -> HttpResult<types::HostFutureIncomingResponse>;
303 
304     /// Whether a given header should be considered forbidden and not allowed.
is_forbidden_header(&mut self, name: &HeaderName) -> bool305     fn is_forbidden_header(&mut self, name: &HeaderName) -> bool {
306         DEFAULT_FORBIDDEN_HEADERS.contains(name)
307     }
308 
309     /// Number of distinct write calls to the outgoing body's output-stream
310     /// that the implementation will buffer.
311     /// Default: 1.
outgoing_body_buffer_chunks(&mut self) -> usize312     fn outgoing_body_buffer_chunks(&mut self) -> usize {
313         DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS
314     }
315 
316     /// Maximum size allowed in a write call to the outgoing body's output-stream.
317     /// Default: 1024 * 1024.
outgoing_body_chunk_size(&mut self) -> usize318     fn outgoing_body_chunk_size(&mut self) -> usize {
319         DEFAULT_OUTGOING_BODY_CHUNK_SIZE
320     }
321 }
322 
323 #[cfg(feature = "default-send-request")]
324 impl<'a> Default for &'a mut dyn WasiHttpHooks {
default() -> Self325     fn default() -> Self {
326         let x: &mut [(); 0] = &mut [];
327         x
328     }
329 }
330 
331 #[doc(hidden)]
332 #[cfg(feature = "default-send-request")]
333 impl WasiHttpHooks for [(); 0] {}
334 
335 /// Returns a value suitable for the `WasiHttpCtxView::hooks` field which has
336 /// the default behavior for `wasi:http`.
337 #[cfg(feature = "default-send-request")]
default_hooks() -> &'static mut dyn WasiHttpHooks338 pub fn default_hooks() -> &'static mut dyn WasiHttpHooks {
339     Default::default()
340 }
341 
342 /// The default value configured for [`WasiHttpHooks::outgoing_body_buffer_chunks`] in [`WasiHttpView`].
343 pub const DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS: usize = 1;
344 /// The default value configured for [`WasiHttpHooks::outgoing_body_chunk_size`] in [`WasiHttpView`].
345 pub const DEFAULT_OUTGOING_BODY_CHUNK_SIZE: usize = 1024 * 1024;
346 
347 /// Structure which `wasi:http` `Host`-style traits are implemented for.
348 ///
349 /// This structure is used by embedders with the [`WasiHttpView`] trait's return
350 /// value and is used to provide access to this crate all internals necessary to
351 /// implement `wasi:http`. This is similar to [`wasmtime_wasi::WasiCtxView`]
352 /// for example.
353 pub struct WasiHttpCtxView<'a> {
354     /// A reference to a per-store [`WasiHttpCtx`].
355     pub ctx: &'a mut WasiHttpCtx,
356     /// A reference to a per-store table of resources to store host structures
357     /// within.
358     pub table: &'a mut ResourceTable,
359     /// A reference to a per-store set of hooks that can be used to customize
360     /// `wasi:http` behavior.
361     pub hooks: &'a mut dyn WasiHttpHooks,
362 }
363 
364 /// The type for which this crate implements the `wasi:http` interfaces.
365 pub struct WasiHttp;
366 
367 impl HasData for WasiHttp {
368     type Data<'a> = WasiHttpCtxView<'a>;
369 }
370 
371 /// A trait used to project state that this crate needs to implement `wasi:http`
372 /// from the `self` type.
373 ///
374 /// This trait is used in [`add_to_linker_sync`] and [`add_to_linker_async`] for
375 /// example as a bound on `T` in `Store<T>`. This is used to access data from
376 /// `T`, the data within a `Store`, an instance of [`WasiHttpCtxView`]. The
377 /// [`WasiHttpCtxView`] contains contextual information such as the
378 /// [`ResourceTable`] for the store, HTTP context info in [`WasiHttpCtx`], and
379 /// any hooks via [`WasiHttpHooks`] if the embedder desires.
380 ///
381 /// # Example
382 ///
383 /// ```
384 /// use wasmtime::component::ResourceTable;
385 /// use wasmtime_wasi_http::WasiHttpCtx;
386 /// use wasmtime_wasi_http::p2::{WasiHttpView, WasiHttpCtxView};
387 ///
388 /// struct MyState {
389 ///     http_ctx: WasiHttpCtx,
390 ///     table: ResourceTable,
391 /// }
392 ///
393 /// impl WasiHttpView for MyState {
394 ///     fn http(&mut self) -> WasiHttpCtxView<'_> {
395 ///         WasiHttpCtxView {
396 ///             ctx: &mut self.http_ctx,
397 ///             table: &mut self.table,
398 ///             hooks: Default::default(),
399 ///         }
400 ///     }
401 /// }
402 /// ```
403 pub trait WasiHttpView {
404     /// Returns an instance of [`WasiHttpCtxView`] projected out of `self`.
http(&mut self) -> WasiHttpCtxView<'_>405     fn http(&mut self) -> WasiHttpCtxView<'_>;
406 }
407 
408 /// Add all of the `wasi:http/proxy` world's interfaces to a [`wasmtime::component::Linker`].
409 ///
410 /// This function will add the `async` variant of all interfaces into the
411 /// `Linker` provided. For embeddings with async support disabled see
412 /// [`add_to_linker_sync`] instead.
413 ///
414 /// # Example
415 ///
416 /// ```
417 /// use wasmtime::{Engine, Result};
418 /// use wasmtime::component::{ResourceTable, Linker};
419 /// use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView};
420 /// use wasmtime_wasi_http::{WasiHttpCtx, p2::{WasiHttpView, WasiHttpCtxView}};
421 ///
422 /// fn main() -> Result<()> {
423 ///     let engine = Engine::default();
424 ///
425 ///     let mut linker = Linker::<MyState>::new(&engine);
426 ///     wasmtime_wasi_http::p2::add_to_linker_async(&mut linker)?;
427 ///     // ... add any further functionality to `linker` if desired ...
428 ///
429 ///     Ok(())
430 /// }
431 ///
432 /// struct MyState {
433 ///     ctx: WasiCtx,
434 ///     http_ctx: WasiHttpCtx,
435 ///     table: ResourceTable,
436 /// }
437 ///
438 /// impl WasiHttpView for MyState {
439 ///     fn http(&mut self) -> WasiHttpCtxView<'_> {
440 ///         WasiHttpCtxView {
441 ///             ctx: &mut self.http_ctx,
442 ///             table: &mut self.table,
443 ///             hooks: Default::default(),
444 ///         }
445 ///     }
446 /// }
447 ///
448 /// impl WasiView for MyState {
449 ///     fn ctx(&mut self) -> WasiCtxView<'_> {
450 ///         WasiCtxView { ctx: &mut self.ctx, table: &mut self.table }
451 ///     }
452 /// }
453 /// ```
add_to_linker_async<T>(l: &mut wasmtime::component::Linker<T>) -> wasmtime::Result<()> where T: WasiHttpView + wasmtime_wasi::WasiView + 'static,454 pub fn add_to_linker_async<T>(l: &mut wasmtime::component::Linker<T>) -> wasmtime::Result<()>
455 where
456     T: WasiHttpView + wasmtime_wasi::WasiView + 'static,
457 {
458     wasmtime_wasi::p2::add_to_linker_proxy_interfaces_async(l)?;
459     add_only_http_to_linker_async(l)
460 }
461 
462 /// A slimmed down version of [`add_to_linker_async`] which only adds
463 /// `wasi:http` interfaces to the linker.
464 ///
465 /// This is useful when using [`wasmtime_wasi::p2::add_to_linker_async`] for
466 /// example to avoid re-adding the same interfaces twice.
add_only_http_to_linker_async<T>( l: &mut wasmtime::component::Linker<T>, ) -> wasmtime::Result<()> where T: WasiHttpView + 'static,467 pub fn add_only_http_to_linker_async<T>(
468     l: &mut wasmtime::component::Linker<T>,
469 ) -> wasmtime::Result<()>
470 where
471     T: WasiHttpView + 'static,
472 {
473     let options = bindings::LinkOptions::default(); // FIXME: Thread through to the CLI options.
474     bindings::http::outgoing_handler::add_to_linker::<_, WasiHttp>(l, T::http)?;
475     bindings::http::types::add_to_linker::<_, WasiHttp>(l, &options.into(), T::http)?;
476 
477     Ok(())
478 }
479 
480 /// Add all of the `wasi:http/proxy` world's interfaces to a [`wasmtime::component::Linker`].
481 ///
482 /// This function will add the `sync` variant of all interfaces into the
483 /// `Linker` provided. For embeddings with async support see
484 /// [`add_to_linker_async`] instead.
485 ///
486 /// # Example
487 ///
488 /// ```
489 /// use wasmtime::{Engine, Result, Config};
490 /// use wasmtime::component::{ResourceTable, Linker};
491 /// use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView};
492 /// use wasmtime_wasi_http::WasiHttpCtx;
493 /// use wasmtime_wasi_http::p2::{WasiHttpView, WasiHttpCtxView};
494 ///
495 /// fn main() -> Result<()> {
496 ///     let config = Config::default();
497 ///     let engine = Engine::new(&config)?;
498 ///
499 ///     let mut linker = Linker::<MyState>::new(&engine);
500 ///     wasmtime_wasi_http::p2::add_to_linker_sync(&mut linker)?;
501 ///     // ... add any further functionality to `linker` if desired ...
502 ///
503 ///     Ok(())
504 /// }
505 ///
506 /// struct MyState {
507 ///     ctx: WasiCtx,
508 ///     http_ctx: WasiHttpCtx,
509 ///     table: ResourceTable,
510 /// }
511 /// impl WasiHttpView for MyState {
512 ///     fn http(&mut self) -> WasiHttpCtxView<'_> {
513 ///         WasiHttpCtxView {
514 ///             ctx: &mut self.http_ctx,
515 ///             table: &mut self.table,
516 ///             hooks: Default::default(),
517 ///         }
518 ///     }
519 /// }
520 /// impl WasiView for MyState {
521 ///     fn ctx(&mut self) -> WasiCtxView<'_> {
522 ///         WasiCtxView { ctx: &mut self.ctx, table: &mut self.table }
523 ///     }
524 /// }
525 /// ```
add_to_linker_sync<T>(l: &mut Linker<T>) -> wasmtime::Result<()> where T: WasiHttpView + wasmtime_wasi::WasiView + 'static,526 pub fn add_to_linker_sync<T>(l: &mut Linker<T>) -> wasmtime::Result<()>
527 where
528     T: WasiHttpView + wasmtime_wasi::WasiView + 'static,
529 {
530     wasmtime_wasi::p2::add_to_linker_proxy_interfaces_sync(l)?;
531     add_only_http_to_linker_sync(l)
532 }
533 
534 /// A slimmed down version of [`add_to_linker_sync`] which only adds
535 /// `wasi:http` interfaces to the linker.
536 ///
537 /// This is useful when using [`wasmtime_wasi::p2::add_to_linker_sync`] for
538 /// example to avoid re-adding the same interfaces twice.
add_only_http_to_linker_sync<T>(l: &mut Linker<T>) -> wasmtime::Result<()> where T: WasiHttpView + 'static,539 pub fn add_only_http_to_linker_sync<T>(l: &mut Linker<T>) -> wasmtime::Result<()>
540 where
541     T: WasiHttpView + 'static,
542 {
543     let options = bindings::LinkOptions::default(); // FIXME: Thread through to the CLI options.
544     bindings::sync::http::outgoing_handler::add_to_linker::<_, WasiHttp>(l, T::http)?;
545     bindings::sync::http::types::add_to_linker::<_, WasiHttp>(l, &options.into(), T::http)?;
546 
547     Ok(())
548 }
549 
550 /// The default implementation of how an outgoing request is sent.
551 ///
552 /// This implementation is used by the `wasi:http/outgoing-handler` interface
553 /// default implementation.
554 #[cfg(feature = "default-send-request")]
default_send_request( request: hyper::Request<body::HyperOutgoingBody>, config: types::OutgoingRequestConfig, ) -> types::HostFutureIncomingResponse555 pub fn default_send_request(
556     request: hyper::Request<body::HyperOutgoingBody>,
557     config: types::OutgoingRequestConfig,
558 ) -> types::HostFutureIncomingResponse {
559     let handle = wasmtime_wasi::runtime::spawn(async move {
560         Ok(default_send_request_handler(request, config).await)
561     });
562     types::HostFutureIncomingResponse::pending(handle)
563 }
564 
565 /// The underlying implementation of how an outgoing request is sent. This should likely be spawned
566 /// in a task.
567 ///
568 /// This is called from [default_send_request] to actually send the request.
569 #[cfg(feature = "default-send-request")]
570 pub async fn default_send_request_handler(
571     mut request: hyper::Request<body::HyperOutgoingBody>,
572     types::OutgoingRequestConfig {
573         use_tls,
574         connect_timeout,
575         first_byte_timeout,
576         between_bytes_timeout,
577     }: types::OutgoingRequestConfig,
578 ) -> Result<types::IncomingResponse, ErrorCode> {
579     use crate::io::TokioIo;
580     use crate::p2::{error::dns_error, hyper_request_error};
581     use http_body_util::BodyExt;
582     use tokio::net::TcpStream;
583     use tokio::time::timeout;
584 
585     let authority = if let Some(authority) = request.uri().authority() {
586         if authority.port().is_some() {
587             authority.to_string()
588         } else {
589             let port = if use_tls { 443 } else { 80 };
590             format!("{}:{port}", authority.to_string())
591         }
592     } else {
593         return Err(ErrorCode::HttpRequestUriInvalid);
594     };
595     let tcp_stream = timeout(connect_timeout, TcpStream::connect(&authority))
596         .await
597         .map_err(|_| ErrorCode::ConnectionTimeout)?
598         .map_err(|e| match e.kind() {
599             std::io::ErrorKind::AddrNotAvailable => {
600                 dns_error("address not available".to_string(), 0)
601             }
602 
603             _ => {
604                 if e.to_string()
605                     .starts_with("failed to lookup address information")
606                 {
607                     dns_error("address not available".to_string(), 0)
608                 } else {
609                     ErrorCode::ConnectionRefused
610                 }
611             }
612         })?;
613 
614     let (mut sender, worker) = if use_tls {
615         use rustls::pki_types::ServerName;
616 
617         // derived from https://github.com/rustls/rustls/blob/main/examples/src/bin/simpleclient.rs
618         let root_cert_store = rustls::RootCertStore {
619             roots: webpki_roots::TLS_SERVER_ROOTS.into(),
620         };
621         let config = rustls::ClientConfig::builder()
622             .with_root_certificates(root_cert_store)
623             .with_no_client_auth();
624         let connector = tokio_rustls::TlsConnector::from(std::sync::Arc::new(config));
625         let mut parts = authority.split(":");
626         let host = parts.next().unwrap_or(&authority);
627         let domain = ServerName::try_from(host)
628             .map_err(|e| {
629                 tracing::warn!("dns lookup error: {e:?}");
630                 dns_error("invalid dns name".to_string(), 0)
631             })?
632             .to_owned();
633         let stream = connector.connect(domain, tcp_stream).await.map_err(|e| {
634             tracing::warn!("tls protocol error: {e:?}");
635             ErrorCode::TlsProtocolError
636         })?;
637         let stream = TokioIo::new(stream);
638 
639         let (sender, conn) = timeout(
640             connect_timeout,
641             hyper::client::conn::http1::handshake(stream),
642         )
643         .await
644         .map_err(|_| ErrorCode::ConnectionTimeout)?
645         .map_err(hyper_request_error)?;
646 
647         let worker = wasmtime_wasi::runtime::spawn(async move {
648             match conn.await {
649                 Ok(()) => {}
650                 // TODO: shouldn't throw away this error and ideally should
651                 // surface somewhere.
652                 Err(e) => tracing::warn!("dropping error {e}"),
653             }
654         });
655 
656         (sender, worker)
657     } else {
658         let tcp_stream = TokioIo::new(tcp_stream);
659         let (sender, conn) = timeout(
660             connect_timeout,
661             // TODO: we should plumb the builder through the http context, and use it here
662             hyper::client::conn::http1::handshake(tcp_stream),
663         )
664         .await
665         .map_err(|_| ErrorCode::ConnectionTimeout)?
666         .map_err(hyper_request_error)?;
667 
668         let worker = wasmtime_wasi::runtime::spawn(async move {
669             match conn.await {
670                 Ok(()) => {}
671                 // TODO: same as above, shouldn't throw this error away.
672                 Err(e) => tracing::warn!("dropping error {e}"),
673             }
674         });
675 
676         (sender, worker)
677     };
678 
679     // at this point, the request contains the scheme and the authority, but
680     // the http packet should only include those if addressing a proxy, so
681     // remove them here, since SendRequest::send_request does not do it for us
682     *request.uri_mut() = http::Uri::builder()
683         .path_and_query(
684             request
685                 .uri()
686                 .path_and_query()
687                 .map(|p| p.as_str())
688                 .unwrap_or("/"),
689         )
690         .build()
691         .expect("comes from valid request");
692 
693     let resp = timeout(first_byte_timeout, sender.send_request(request))
694         .await
695         .map_err(|_| ErrorCode::ConnectionReadTimeout)?
696         .map_err(hyper_request_error)?
697         .map(|body| body.map_err(hyper_request_error).boxed_unsync());
698 
699     Ok(types::IncomingResponse {
700         resp,
701         worker: Some(worker),
702         between_bytes_timeout,
703     })
704 }
705