Skip to main content

wasmtime_wasi_http/p2/
mod.rs

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")]
223use self::bindings::http::types::ErrorCode;
224use crate::{DEFAULT_FORBIDDEN_HEADERS, WasiHttpCtx};
225use http::HeaderName;
226use wasmtime::component::{HasData, Linker, ResourceTable};
227
228mod error;
229mod http_impl;
230mod types_impl;
231
232pub mod bindings;
233pub mod body;
234pub mod types;
235
236pub 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/// ```
285pub trait WasiHttpHooks: Send {
286    /// Send an outgoing request.
287    #[cfg(feature = "default-send-request")]
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"))]
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.
305    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.
312    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.
318    fn outgoing_body_chunk_size(&mut self) -> usize {
319        DEFAULT_OUTGOING_BODY_CHUNK_SIZE
320    }
321}
322
323#[cfg(feature = "default-send-request")]
324impl<'a> Default for &'a mut dyn WasiHttpHooks {
325    fn default() -> Self {
326        let x: &mut [(); 0] = &mut [];
327        x
328    }
329}
330
331#[doc(hidden)]
332#[cfg(feature = "default-send-request")]
333impl 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")]
338pub 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`].
343pub const DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS: usize = 1;
344/// The default value configured for [`WasiHttpHooks::outgoing_body_chunk_size`] in [`WasiHttpView`].
345pub 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.
353pub 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.
365pub struct WasiHttp;
366
367impl 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/// ```
403pub trait WasiHttpView {
404    /// Returns an instance of [`WasiHttpCtxView`] projected out of `self`.
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/// ```
454pub fn add_to_linker_async<T>(l: &mut wasmtime::component::Linker<T>) -> wasmtime::Result<()>
455where
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.
467pub fn add_only_http_to_linker_async<T>(
468    l: &mut wasmtime::component::Linker<T>,
469) -> wasmtime::Result<()>
470where
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/// ```
526pub fn add_to_linker_sync<T>(l: &mut Linker<T>) -> wasmtime::Result<()>
527where
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.
539pub fn add_only_http_to_linker_sync<T>(l: &mut Linker<T>) -> wasmtime::Result<()>
540where
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")]
555pub 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")]
570pub 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}