Repository: bytecodealliance/wasmtime
Pull request #8340 (2024-04-11 19:55:42)

Fix a few minor issues with `fd_{write,read}`
* Explicitly document why `blocking_mode` is being ignored.
* Don't write-in-a-loop to doing a partial write and then returning an error.
* Spawn a background read/write when the embedding cannot block the current thread.
* Don't panic on overflows for addition.
* Fix panic on reads with shared memory

Follow-ups to #8303

<!--
Please make sure you include the following information:

- If this work has been discussed elsewhere, please include a link to that
  conversation. If it was discussed in an issue, just mention "issue #...".

- Explain why this change is needed. If the details are in an issue already,
  this can be brief.

Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.html

Please ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->



Patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 171f99d65..dd14702ce 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -103,18 +103,29 @@ jobs:
         GH_TOKEN: ${{ github.token }}
 
   # Ensure dependencies are vetted. See https://mozilla.github.io/cargo-vet/
+  #
+  # Note that this step, on PRs only, is allowed to fail. This is then followed
+  # up with the `cargo_vet_failure_for_prs` step below. The intention is to
+  # avoid causing this check to fail PRs while still enabling it to fail the
+  # merge queue checks. That way PRs can enter the merge queue when this step is
+  # failing if `main` has picked up `cargo vet` entries in the meantime for the
+  # failures.
   cargo_vet:
     name: Cargo vet
     needs: determine
     if: needs.determine.outputs.audit
     runs-on: ubuntu-latest
+    outputs:
+      outcome: ${{ steps.vet.outcome }}
     steps:
     - uses: actions/checkout@v4
       with:
         submodules: true
     - uses: ./.github/actions/install-rust
     - uses: ./.github/actions/install-cargo-vet
-    - run: cargo vet --locked
+    - id: vet
+      run: cargo vet --locked
+      continue-on-error: ${{ github.event_name == 'pull_request' }}
 
     # common logic to cancel the entire run if this job fails
     - run: gh run cancel ${{ github.run_id }}
@@ -122,6 +133,28 @@ jobs:
       env:
         GH_TOKEN: ${{ github.token }}
 
+  cargo_vet_failure_for_prs:
+    name: Cargo vet failed on a Pull Request
+    needs:
+    - determine
+    - cargo_vet
+    if: |
+      needs.determine.outputs.audit
+      && github.event_name == 'pull_request'
+      && needs.cargo_vet.outputs.outcome == 'failed'
+    runs-on: ubuntu-latest
+    steps:
+    # NB: this message ideally would link back to the previous step, but I'm not
+    # sure how to easily do that.
+    - run: |
+        echo 'failed to run "cargo vet", see previous `Cargo vet` step'
+        echo 'exiting with a nonzero status now to alert PR authors'
+        echo 'note, though, that this PR can still enter the merge queue'
+        echo ''
+        echo 'See https://docs.wasmtime.dev/contributing-coding-guidelines.html#cargo-vet-for-contributors'
+        echo 'for more information about the vetting process for Wasmtime'
+        exit 1
+
   # This job is a dependency of many of the jobs below. This calculates what's
   # actually being run for this workflow. For example:
   #
@@ -853,12 +886,6 @@ jobs:
       - build-wasmtime-target-wasm32
     if: always()
     steps:
-    - name: Dump needs context
-      env:
-        CONTEXT: ${{ toJson(needs) }}
-      run: |
-        echo -e "\033[33;1;4mDump context\033[0m"
-        echo -e "$CONTEXT\n"
     - name: Successful test and build
       if: ${{ !(contains(needs.*.result, 'failure')) }}
       run: exit 0
diff --git a/crates/wasi-http/src/http_impl.rs b/crates/wasi-http/src/http_impl.rs
index f25df6a20..a01dcc3af 100644
--- a/crates/wasi-http/src/http_impl.rs
+++ b/crates/wasi-http/src/http_impl.rs
@@ -4,7 +4,7 @@ use crate::{
         types::{self, Scheme},
     },
     http_request_error, internal_error,
-    types::{HostFutureIncomingResponse, HostOutgoingRequest, OutgoingRequest},
+    types::{HostFutureIncomingResponse, HostOutgoingRequest, OutgoingRequestConfig},
     WasiHttpView,
 };
 use bytes::Bytes;
@@ -94,13 +94,16 @@ impl<T: WasiHttpView> outgoing_handler::Host for T {
             .body(body)
             .map_err(|err| internal_error(err.to_string()))?;
 
-        self.send_request(OutgoingRequest {
-            use_tls,
-            authority,
+        let future = self.send_request(
             request,
-            connect_timeout,
-            first_byte_timeout,
-            between_bytes_timeout,
-        })
+            OutgoingRequestConfig {
+                use_tls,
+                connect_timeout,
+                first_byte_timeout,
+                between_bytes_timeout,
+            },
+        )?;
+
+        Ok(self.table().push(future)?)
     }
 }
diff --git a/crates/wasi-http/src/types.rs b/crates/wasi-http/src/types.rs
index aa58ab605..b357b2066 100644
--- a/crates/wasi-http/src/types.rs
+++ b/crates/wasi-http/src/types.rs
@@ -5,7 +5,7 @@ use crate::io::TokioIo;
 use crate::{
     bindings::http::types::{self, Method, Scheme},
     body::{HostIncomingBody, HyperIncomingBody, HyperOutgoingBody},
-    dns_error, hyper_request_error, HttpResult,
+    dns_error, hyper_request_error,
 };
 use http_body_util::BodyExt;
 use hyper::header::HeaderName;
@@ -17,21 +17,26 @@ use wasmtime::component::{Resource, ResourceTable};
 use wasmtime_wasi::{runtime::AbortOnDropJoinHandle, Subscribe};
 
 /// Capture the state necessary for use in the wasi-http API implementation.
-pub struct WasiHttpCtx;
+pub struct WasiHttpCtx {
+    _priv: (),
+}
 
-pub struct OutgoingRequest {
-    pub use_tls: bool,
-    pub authority: String,
-    pub request: hyper::Request<HyperOutgoingBody>,
-    pub connect_timeout: Duration,
-    pub first_byte_timeout: Duration,
-    pub between_bytes_timeout: Duration,
+impl WasiHttpCtx {
+    /// Create a new context.
+    pub fn new() -> Self {
+        Self { _priv: () }
+    }
 }
 
+/// A trait which provides internal WASI HTTP state.
 pub trait WasiHttpView: Send {
+    /// Returns a mutable reference to the WASI HTTP context.
     fn ctx(&mut self) -> &mut WasiHttpCtx;
+
+    /// Returns a mutable reference to the WASI HTTP resource table.
     fn table(&mut self) -> &mut ResourceTable;
 
+    /// Create a new incoming request resource.
     fn new_incoming_request(
         &mut self,
         req: hyper::Request<HyperIncomingBody>,
@@ -49,6 +54,7 @@ pub trait WasiHttpView: Send {
         Ok(self.table().push(incoming_req)?)
     }
 
+    /// Create a new outgoing response resource.
     fn new_response_outparam(
         &mut self,
         result: tokio::sync::oneshot::Sender<
@@ -59,14 +65,16 @@ pub trait WasiHttpView: Send {
         Ok(id)
     }
 
+    /// Send an outgoing request.
     fn send_request(
         &mut self,
-        request: OutgoingRequest,
-    ) -> HttpResult<Resource<HostFutureIncomingResponse>>
+        request: hyper::Request<HyperOutgoingBody>,
+        config: OutgoingRequestConfig,
+    ) -> crate::HttpResult<HostFutureIncomingResponse>
     where
         Self: Sized,
     {
-        default_send_request(self, request)
+        Ok(default_send_request(request, config))
     }
 
     fn is_forbidden_header(&mut self, _name: &HeaderName) -> bool {
@@ -110,44 +118,43 @@ pub(crate) fn remove_forbidden_headers(
     }
 }
 
+/// Configuration for an outgoing request.
+pub struct OutgoingRequestConfig {
+    /// Whether to use TLS for the request.
+    pub use_tls: bool,
+    /// The timeout for connecting.
+    pub connect_timeout: Duration,
+    /// The timeout until the first byte.
+    pub first_byte_timeout: Duration,
+    /// The timeout between chunks of a streaming body
+    pub between_bytes_timeout: Duration,
+}
+
+/// The default implementation of how an outgoing request is sent.
+///
+/// This implementation is used by the `wasi:http/outgoing-handler` interface
+/// default implementation.
 pub fn default_send_request(
-    view: &mut dyn WasiHttpView,
-    OutgoingRequest {
+    request: hyper::Request<HyperOutgoingBody>,
+    config: OutgoingRequestConfig,
+) -> HostFutureIncomingResponse {
+    let handle = wasmtime_wasi::runtime::spawn(async move { Ok(handler(request, config).await) });
+    HostFutureIncomingResponse::pending(handle)
+}
+
+async fn handler(
+    mut request: hyper::Request<HyperOutgoingBody>,
+    OutgoingRequestConfig {
         use_tls,
-        authority,
-        request,
         connect_timeout,
         first_byte_timeout,
         between_bytes_timeout,
-    }: OutgoingRequest,
-) -> HttpResult<Resource<HostFutureIncomingResponse>> {
-    let handle = wasmtime_wasi::runtime::spawn(async move {
-        let resp = handler(
-            authority,
-            use_tls,
-            connect_timeout,
-            first_byte_timeout,
-            request,
-            between_bytes_timeout,
-        )
-        .await;
-        Ok(resp)
-    });
-
-    let fut = view.table().push(HostFutureIncomingResponse::new(handle))?;
-
-    Ok(fut)
-}
-
-async fn handler(
-    authority: String,
-    use_tls: bool,
-    connect_timeout: Duration,
-    first_byte_timeout: Duration,
-    mut request: http::Request<HyperOutgoingBody>,
-    between_bytes_timeout: Duration,
-) -> Result<IncomingResponseInternal, types::ErrorCode> {
-    let tcp_stream = timeout(connect_timeout, TcpStream::connect(authority.clone()))
+    }: OutgoingRequestConfig,
+) -> Result<IncomingResponse, types::ErrorCode> {
+    let Some(authority) = request.uri().authority().map(ToString::to_string) else {
+        return Err(types::ErrorCode::HttpRequestUriInvalid);
+    };
+    let tcp_stream = timeout(connect_timeout, TcpStream::connect(&authority))
         .await
         .map_err(|_| types::ErrorCode::ConnectionTimeout)?
         .map_err(|e| match e.kind() {
@@ -264,9 +271,9 @@ async fn handler(
         .map_err(hyper_request_error)?
         .map(|body| body.map_err(hyper_request_error).boxed());
 
-    Ok(IncomingResponseInternal {
+    Ok(IncomingResponse {
         resp,
-        worker,
+        worker: Some(worker),
         between_bytes_timeout,
     })
 }
@@ -316,12 +323,14 @@ impl TryInto<http::Method> for types::Method {
     }
 }
 
+/// The concrete type behind a `wasi:http/types/incoming-request` resource.
 pub struct HostIncomingRequest {
     pub(crate) parts: http::request::Parts,
     pub body: Option<HostIncomingBody>,
 }
 
 impl HostIncomingRequest {
+    /// Create a new `HostIncomingRequest`.
     pub fn new(
         view: &mut dyn WasiHttpView,
         mut parts: http::request::Parts,
@@ -332,33 +341,13 @@ impl HostIncomingRequest {
     }
 }
 
+/// The concrete type behind a `was:http/types/response-outparam` resource.
 pub struct HostResponseOutparam {
     pub result:
         tokio::sync::oneshot::Sender<Result<hyper::Response<HyperOutgoingBody>, types::ErrorCode>>,
 }
 
-pub struct HostOutgoingRequest {
-    pub method: Method,
-    pub scheme: Option<Scheme>,
-    pub path_with_query: Option<String>,
-    pub authority: Option<String>,
-    pub headers: FieldMap,
-    pub body: Option<HyperOutgoingBody>,
-}
-
-#[derive(Default)]
-pub struct HostRequestOptions {
-    pub connect_timeout: Option<std::time::Duration>,
-    pub first_byte_timeout: Option<std::time::Duration>,
-    pub between_bytes_timeout: Option<std::time::Duration>,
-}
-
-pub struct HostIncomingResponse {
-    pub status: u16,
-    pub headers: FieldMap,
-    pub body: Option<HostIncomingBody>,
-}
-
+/// The concrete type behind a `wasi:http/types/outgoing-response` resource.
 pub struct HostOutgoingResponse {
     pub status: http::StatusCode,
     pub headers: FieldMap,
@@ -388,8 +377,32 @@ impl TryFrom<HostOutgoingResponse> for hyper::Response<HyperOutgoingBody> {
     }
 }
 
-pub type FieldMap = hyper::HeaderMap;
+/// The concrete type behind a `wasi:http/types/outgoing-request` resource.
+pub struct HostOutgoingRequest {
+    pub method: Method,
+    pub scheme: Option<Scheme>,
+    pub authority: Option<String>,
+    pub path_with_query: Option<String>,
+    pub headers: FieldMap,
+    pub body: Option<HyperOutgoingBody>,
+}
 
+/// The concrete type behind a `wasi:http/types/request-options` resource.
+#[derive(Default)]
+pub struct HostRequestOptions {
+    pub connect_timeout: Option<std::time::Duration>,
+    pub first_byte_timeout: Option<std::time::Duration>,
+    pub between_bytes_timeout: Option<std::time::Duration>,
+}
+
+/// The concrete type behind a `wasi:http/types/incoming-response` resource.
+pub struct HostIncomingResponse {
+    pub status: u16,
+    pub headers: FieldMap,
+    pub body: Option<HostIncomingBody>,
+}
+
+/// The concrete type behind a `wasi:http/types/fields` resource.
 pub enum HostFields {
     Ref {
         parent: u32,
@@ -405,33 +418,53 @@ pub enum HostFields {
     },
 }
 
-pub struct IncomingResponseInternal {
+/// An owned version of `HostFields`
+pub type FieldMap = hyper::HeaderMap;
+
+/// A handle to a future incoming response.
+pub type FutureIncomingResponseHandle =
+    AbortOnDropJoinHandle<anyhow::Result<Result<IncomingResponse, types::ErrorCode>>>;
+
+/// A response that is in the process of being received.
+pub struct IncomingResponse {
+    /// The response itself.
     pub resp: hyper::Response<HyperIncomingBody>,
-    pub worker: AbortOnDropJoinHandle<()>,
+    /// Optional worker task that continues to process the response.
+    pub worker: Option<AbortOnDropJoinHandle<()>>,
+    /// The timeout between chunks of the response.
     pub between_bytes_timeout: std::time::Duration,
 }
 
-type FutureIncomingResponseHandle =
-    AbortOnDropJoinHandle<anyhow::Result<Result<IncomingResponseInternal, types::ErrorCode>>>;
-
+/// The concrete type behind a `wasi:http/types/future-incoming-response` resource.
 pub enum HostFutureIncomingResponse {
+    /// A pending response
     Pending(FutureIncomingResponseHandle),
-    Ready(anyhow::Result<Result<IncomingResponseInternal, types::ErrorCode>>),
+    /// The response is ready.
+    ///
+    /// An outer error will trap while the inner error gets returned to the guest.
+    Ready(anyhow::Result<Result<IncomingResponse, types::ErrorCode>>),
+    /// The response has been consumed.
     Consumed,
 }
 
 impl HostFutureIncomingResponse {
-    pub fn new(handle: FutureIncomingResponseHandle) -> Self {
+    /// Create a new `HostFutureIncomingResponse` that is pending on the provided task handle.
+    pub fn pending(handle: FutureIncomingResponseHandle) -> Self {
         Self::Pending(handle)
     }
 
+    /// Create a new `HostFutureIncomingResponse` that is ready.
+    pub fn ready(result: anyhow::Result<Result<IncomingResponse, types::ErrorCode>>) -> Self {
+        Self::Ready(result)
+    }
+
+    /// Returns `true` if the response is ready.
     pub fn is_ready(&self) -> bool {
         matches!(self, Self::Ready(_))
     }
 
-    pub fn unwrap_ready(
-        self,
-    ) -> anyhow::Result<Result<IncomingResponseInternal, types::ErrorCode>> {
+    /// Unwrap the response, panicking if it is not ready.
+    pub fn unwrap_ready(self) -> anyhow::Result<Result<IncomingResponse, types::ErrorCode>> {
         match self {
             Self::Ready(res) => res,
             Self::Pending(_) | Self::Consumed => {
diff --git a/crates/wasi-http/src/types_impl.rs b/crates/wasi-http/src/types_impl.rs
index 001edf48d..7c6816cc6 100644
--- a/crates/wasi-http/src/types_impl.rs
+++ b/crates/wasi-http/src/types_impl.rs
@@ -846,7 +846,9 @@ impl<T: WasiHttpView> crate::bindings::http::types::HostFutureIncomingResponse f
             headers: parts.headers,
             body: Some({
                 let mut body = HostIncomingBody::new(body, resp.between_bytes_timeout);
-                body.retain_worker(resp.worker);
+                if let Some(worker) = resp.worker {
+                    body.retain_worker(worker);
+                }
                 body
             }),
         })?;
diff --git a/crates/wasi/src/filesystem.rs b/crates/wasi/src/filesystem.rs
index 49c03985c..197b2fe88 100644
--- a/crates/wasi/src/filesystem.rs
+++ b/crates/wasi/src/filesystem.rs
@@ -125,16 +125,28 @@ impl File {
         }
     }
 
+    /// Returns `Some` when the current thread is allowed to block in filesystem
+    /// operations, and otherwise returns `None` to indicate that
+    /// `spawn_blocking` must be used.
+    pub(crate) fn as_blocking_file(&self) -> Option<&cap_std::fs::File> {
+        if self.allow_blocking_current_thread {
+            Some(&self.file)
+        } else {
+            None
+        }
+    }
+
     fn _spawn_blocking<F, R>(&self, body: F) -> SpawnBlocking<R>
     where
         F: FnOnce(&cap_std::fs::File) -> R + Send + 'static,
         R: Send + 'static,
     {
-        if self.allow_blocking_current_thread {
-            SpawnBlocking::Done(body(&self.file))
-        } else {
-            let f = self.file.clone();
-            SpawnBlocking::Spawned(spawn_blocking(move || body(&f)))
+        match self.as_blocking_file() {
+            Some(file) => SpawnBlocking::Done(body(file)),
+            None => {
+                let f = self.file.clone();
+                SpawnBlocking::Spawned(spawn_blocking(move || body(&f)))
+            }
         }
     }
 }
diff --git a/crates/wasi/src/preview1.rs b/crates/wasi/src/preview1.rs
index 90475a7c6..41390112f 100644
--- a/crates/wasi/src/preview1.rs
+++ b/crates/wasi/src/preview1.rs
@@ -277,32 +277,6 @@ impl BlockingMode {
             }
         }
     }
-
-    fn block_write(
-        &self,
-        file: &super::filesystem::File,
-        bytes: GuestPtr<'_, [u8]>,
-        append: bool,
-        mut pos: u64,
-    ) -> Result<usize> {
-        let buf = bytes.as_cow()?;
-        let mut buf = buf.deref();
-
-        let mut total: usize = 0;
-        while !buf.is_empty() {
-            let nwritten = if append {
-                file.file.append(buf)
-            } else {
-                file.file.write_at(buf, pos)
-            };
-            let nwritten = nwritten.or_else(|e| Err(StreamError::LastOperationFailed(e.into())))?;
-            pos = pos.checked_add(nwritten as u64).expect("add overflow");
-            (_, buf) = buf.split_at(nwritten);
-            total = total.checked_add(nwritten).expect("add overflow");
-        }
-
-        Ok(total)
-    }
 }
 
 #[derive(Debug)]
@@ -1580,8 +1554,10 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
         match desc {
             Descriptor::File(File {
                 fd,
-                blocking_mode: _,
                 position,
+                // NB: the nonblocking flag is intentionally ignored here and
+                // blocking reads/writes are always performed.
+                blocking_mode: _,
                 ..
             }) => {
                 let fd = fd.borrowed();
@@ -1592,23 +1568,33 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
                 let Some(iov) = first_non_empty_iovec(iovs)? else {
                     return Ok(0);
                 };
-                let is_shared_memory = iov.is_shared_memory();
-                let bytes_read = if is_shared_memory {
-                    let mut buf = vec![0; iov.len() as usize];
-                    let bytes_read = file
-                        .file
-                        .read_at(&mut buf[..], pos)
-                        .or_else(|e| Err(StreamError::LastOperationFailed(e.into())))?;
-                    iov.copy_from_slice(&buf[0..bytes_read])?;
-                    bytes_read
-                } else {
-                    let mut buf = iov.as_slice_mut()?.expect("get none");
-                    let buf = buf.deref_mut();
-                    let bytes_read = file
-                        .file
-                        .read_at(buf, pos)
-                        .or_else(|e| Err(StreamError::LastOperationFailed(e.into())))?;
-                    bytes_read
+                let bytes_read = match (file.as_blocking_file(), iov.as_slice_mut()?) {
+                    // Try to read directly into wasm memory where possible
+                    // when the current thread can block and additionally wasm
+                    // memory isn't shared.
+                    (Some(file), Some(mut buf)) => file
+                        .read_at(&mut buf, pos)
+                        .map_err(|e| StreamError::LastOperationFailed(e.into()))?,
+                    // ... otherwise fall back to performing the read on a
+                    // blocking thread and which copies the data back into wasm
+                    // memory.
+                    (_, buf) => {
+                        drop(buf);
+                        let mut buf = vec![0; iov.len() as usize];
+                        let buf = file
+                            .spawn_blocking(move |file| -> Result<_, types::Error> {
+                                let bytes_read = file
+                                    .read_at(&mut buf, pos)
+                                    .map_err(|e| StreamError::LastOperationFailed(e.into()))?;
+                                buf.truncate(bytes_read);
+                                Ok(buf)
+                            })
+                            .await?;
+                        iov.get_range(0..u32::try_from(buf.len())?)
+                            .unwrap()
+                            .copy_from_slice(&buf)?;
+                        buf.len()
+                    }
                 };
 
                 let pos = pos
@@ -1700,12 +1686,17 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
         match desc {
             Descriptor::File(File {
                 fd,
-                blocking_mode,
                 append,
                 position,
+                // NB: files always use blocking writes regardless of what
+                // they're configured to use since OSes don't have nonblocking
+                // reads/writes anyway. This behavior originated in the first
+                // implementation of WASIp1 where flags were propagated to the
+                // OS and the OS ignored the nonblocking flag for files
+                // generally.
+                blocking_mode: _,
             }) => {
                 let fd = fd.borrowed();
-                let blocking_mode = *blocking_mode;
                 let position = position.clone();
                 let pos = position.load(Ordering::Relaxed);
                 let append = *append;
@@ -1714,7 +1705,29 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
                 let Some(buf) = first_non_empty_ciovec(ciovs)? else {
                     return Ok(0);
                 };
-                let nwritten = blocking_mode.block_write(f, buf, append, pos)?;
+
+                let write = move |f: &cap_std::fs::File, buf: &[u8]| {
+                    if append {
+                        f.append(&buf)
+                    } else {
+                        f.write_at(&buf, pos)
+                    }
+                };
+
+                let nwritten = match f.as_blocking_file() {
+                    // If we can block then skip the copy out of wasm memory and
+                    // write directly to `f`.
+                    Some(f) => write(f, &buf.as_cow()?),
+                    // ... otherwise copy out of wasm memory and use
+                    // `spawn_blocking` to do this write in a thread that can
+                    // block.
+                    None => {
+                        let buf = buf.to_vec()?;
+                        f.spawn_blocking(move |f| write(f, &buf)).await
+                    }
+                };
+
+                let nwritten = nwritten.map_err(|e| StreamError::LastOperationFailed(e.into()))?;
                 if append {
                     let len = self.stat(fd).await?;
                     position.store(len.size, Ordering::Relaxed);
diff --git a/docs/contributing-coding-guidelines.md b/docs/contributing-coding-guidelines.md
index 755b6adb2..b29ba01b6 100644
--- a/docs/contributing-coding-guidelines.md
+++ b/docs/contributing-coding-guidelines.md
@@ -86,12 +86,9 @@ set of dependencies in Wasmtime:
 
 Dependency additions or updates require action on behalf of project maintainers
 so we ask that you don't run `cargo vet` yourself or update the `supply-chain`
-folder yourself. Instead a maintainer will review your PR and push a commit to
-your PR which performs the vets as necessary. Note that reviewers will be
-verifying that you haven't yourself made changes and that the changes they
-approve to be merged match what they've done. It's still ok to rebase your PR if
-needed, but we ask that if a maintainer has pushed a `cargo vet` entry update
-that you leave it as a separate commit.
+folder yourself. Instead a maintainer will review your PR and perform the `cargo
+vet` entries themselves. Reviewers will typically make a separate pull request
+to add `cargo vet` entries and once that lands yours will be added to the queue.
 
 ### `cargo vet` for Maintainers
 
@@ -110,20 +107,22 @@ though to add vet entries this is done through one of a few methods:
   more-or-less is the same as the previous categories.
 
 * For contributors who should not add vet entries themselves maintainers should
-  review the PR and then when the PR is ready to be approved the maintainer
-  should push a commit to the contributor's PR directly. Maintainers should
-  check out the PR locally, apply `cargo vet` entries as needed, and then push
-  to the contributor's PR directly.
-
-Note for the last case it's important to ensure that after you push to the PR
-any future updates pushed by the contributor either contain or don't overwrite
-your vet entries. Also verify that if the PR branch is rebased or force-pushed,
-the details of your previously pushed vetting remain the same: e.g., versions
-were not bumped and descriptive reasons remain the same. If pushing a vetting
-commit to a contributor's PR and also asking for more changes, request that the
-contributor make the requested fixes in an additional commit rather than
-force-pushing a rewritten history, so your existing vetting commit remains
-untouched. These guidelines make it easier to verify no tampering has occurred
+  review the PR and add vet entries either in a separate PR or as part of the
+  contributor's PR itself. As a separate PR you'll check out the branch, run
+  `cargo vet`, then rebase away the contributor's commits and push your `cargo
+  vet` commit alone to merge. For pushing directly to the contributor's own PR
+  be sure to read the notes below.
+
+Note for the last case it's important to ensure that if you push directly to a
+contributor's PR any future updates pushed by the contributor either contain or
+don't overwrite your vet entries. Also verify that if the PR branch is rebased
+or force-pushed, the details of your previously pushed vetting remain the same:
+e.g., versions were not bumped and descriptive reasons remain the same. If
+pushing a vetting commit to a contributor's PR and also asking for more changes,
+request that the contributor make the requested fixes in an additional commit
+rather than force-pushing a rewritten history, so your existing vetting commit
+remains untouched. These guidelines make it easier to verify no tampering has
+occurred.
 
 ### Policy for adding `cargo vet` entries
 
diff --git a/src/commands/run.rs b/src/commands/run.rs
index d9b248505..d5e1d6242 100644
--- a/src/commands/run.rs
+++ b/src/commands/run.rs
@@ -697,7 +697,7 @@ impl RunCommand {
                     }
                 }
 
-                store.data_mut().wasi_http = Some(Arc::new(WasiHttpCtx {}));
+                store.data_mut().wasi_http = Some(Arc::new(WasiHttpCtx::new()));
             }
         }
 
diff --git a/src/commands/serve.rs b/src/commands/serve.rs
index 5e7e7f0bb..90cb660d5 100644
--- a/src/commands/serve.rs
+++ b/src/commands/serve.rs
@@ -149,7 +149,7 @@ impl ServeCommand {
         let mut host = Host {
             table: wasmtime::component::ResourceTable::new(),
             ctx: builder.build(),
-            http: WasiHttpCtx,
+            http: WasiHttpCtx::new(),
 
             limits: StoreLimits::default(),
 


Test patch:
diff --git a/crates/wasi-http/tests/all/main.rs b/crates/wasi-http/tests/all/main.rs
index 00a17e825..bcbd9af67 100644
--- a/crates/wasi-http/tests/all/main.rs
+++ b/crates/wasi-http/tests/all/main.rs
@@ -8,22 +8,22 @@ use sha2::{Digest, Sha256};
 use std::{collections::HashMap, iter, net::Ipv4Addr, str, sync::Arc};
 use tokio::task;
 use wasmtime::{
-    component::{Component, Linker, Resource, ResourceTable},
+    component::{Component, Linker, ResourceTable},
     Config, Engine, Store,
 };
 use wasmtime_wasi::{self, pipe::MemoryOutputPipe, WasiCtx, WasiCtxBuilder, WasiView};
 use wasmtime_wasi_http::{
     bindings::http::types::ErrorCode,
-    body::HyperIncomingBody,
+    body::{HyperIncomingBody, HyperOutgoingBody},
     io::TokioIo,
-    types::{self, HostFutureIncomingResponse, IncomingResponseInternal, OutgoingRequest},
+    types::{self, HostFutureIncomingResponse, IncomingResponse, OutgoingRequestConfig},
     HttpResult, WasiHttpCtx, WasiHttpView,
 };
 
 mod http_server;
 
 type RequestSender = Arc<
-    dyn Fn(&mut Ctx, OutgoingRequest) -> HttpResult<Resource<HostFutureIncomingResponse>>
+    dyn Fn(hyper::Request<HyperOutgoingBody>, OutgoingRequestConfig) -> HostFutureIncomingResponse
         + Send
         + Sync,
 >;
@@ -58,18 +58,20 @@ impl WasiHttpView for Ctx {
 
     fn send_request(
         &mut self,
-        request: OutgoingRequest,
-    ) -> HttpResult<Resource<HostFutureIncomingResponse>> {
+        request: hyper::Request<HyperOutgoingBody>,
+        config: OutgoingRequestConfig,
+    ) -> HttpResult<HostFutureIncomingResponse> {
         if let Some(rejected_authority) = &self.rejected_authority {
-            let (auth, _port) = request.authority.split_once(':').unwrap();
+            let authority = request.uri().authority().map(ToString::to_string).unwrap();
+            let (auth, _port) = authority.split_once(':').unwrap();
             if auth == rejected_authority {
                 return Err(ErrorCode::HttpRequestDenied.into());
             }
         }
         if let Some(send_request) = self.send_request.clone() {
-            send_request(self, request)
+            Ok(send_request(request, config))
         } else {
-            types::default_send_request(self, request)
+            Ok(types::default_send_request(request, config))
         }
     }
 
@@ -90,7 +92,7 @@ fn store(engine: &Engine, server: &Server) -> Store<Ctx> {
     let ctx = Ctx {
         table: ResourceTable::new(),
         wasi: builder.build(),
-        http: WasiHttpCtx {},
+        http: WasiHttpCtx::new(),
         stderr,
         stdout,
         send_request: None,
@@ -147,7 +149,7 @@ async fn run_wasi_http(
     builder.stdout(stdout.clone());
     builder.stderr(stderr.clone());
     let wasi = builder.build();
-    let http = WasiHttpCtx;
+    let http = WasiHttpCtx::new();
     let ctx = Ctx {
         table,
         wasi,
@@ -269,20 +271,19 @@ async fn do_wasi_http_hash_all(override_send_request: bool) -> Result<()> {
 
     let send_request = if override_send_request {
         Some(Arc::new(
-            move |view: &mut Ctx,
-                  OutgoingRequest {
-                      request,
+            move |request: hyper::Request<HyperOutgoingBody>,
+                  OutgoingRequestConfig {
                       between_bytes_timeout,
                       ..
                   }| {
                 let response = handle(request.into_parts().0).map(|resp| {
-                    Ok(IncomingResponseInternal {
+                    Ok(IncomingResponse {
                         resp,
-                        worker: wasmtime_wasi::runtime::spawn(future::ready(())),
+                        worker: None,
                         between_bytes_timeout,
                     })
                 });
-                Ok(WasiHttpView::table(view).push(HostFutureIncomingResponse::Ready(response))?)
+                HostFutureIncomingResponse::ready(response)
             },
         ) as RequestSender)
     } else {