From 8845edf2238d0a0b36eb592c13513539217df1d0 Mon Sep 17 00:00:00 2001 From: Theodore Brockman Date: Tue, 7 Oct 2025 15:53:18 -0400 Subject: [PATCH 01/17] Don't remember why I derived debug here... --- crates/wasi-http/src/p3/body.rs | 16 +++++++++++----- crates/wasi-http/src/p3/request.rs | 3 ++- crates/wasi-http/src/p3/response.rs | 1 + 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/crates/wasi-http/src/p3/body.rs b/crates/wasi-http/src/p3/body.rs index 1d74ffb3f989..3c1485dcee36 100644 --- a/crates/wasi-http/src/p3/body.rs +++ b/crates/wasi-http/src/p3/body.rs @@ -21,7 +21,7 @@ use wasmtime::component::{ use wasmtime::{AsContextMut, StoreContextMut}; /// The concrete type behind a `wasi:http/types/body` resource. -pub(crate) enum Body { +pub enum Body { /// Body constructed by the guest Guest { /// The body stream @@ -40,6 +40,12 @@ pub(crate) enum Body { }, } +impl std::fmt::Debug for Body { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + "Body { .. }".fmt(f) + } +} + /// [FutureConsumer] implementation for future passed to `consume-body`. struct BodyResultConsumer( Option> + Send>>>, @@ -69,7 +75,7 @@ where impl Body { /// Implementation of `consume-body` shared between requests and responses - pub(crate) fn consume( + pub fn consume( self, mut store: Access<'_, T, WasiHttp>, fut: FutureReader>, @@ -114,7 +120,7 @@ impl Body { } /// Implementation of `drop` shared between requests and responses - pub(crate) fn drop(self, mut store: impl AsContextMut) { + pub fn drop(self, mut store: impl AsContextMut) { if let Body::Guest { contents_rx, mut trailers_rx, @@ -256,7 +262,7 @@ impl StreamConsumer for UnlimitedGuestBodyConsumer { } /// [http_body::Body] implementation for bodies originating in the guest. -pub(crate) struct GuestBody { +pub struct GuestBody { contents_rx: Option>>, trailers_rx: Option>, ErrorCode>>>, content_length: Option, @@ -264,7 +270,7 @@ pub(crate) struct GuestBody { impl GuestBody { /// Construct a new [GuestBody] - pub(crate) fn new( + pub fn new( mut store: impl AsContextMut, contents_rx: Option>, trailers_rx: FutureReader>, ErrorCode>>, diff --git a/crates/wasi-http/src/p3/request.rs b/crates/wasi-http/src/p3/request.rs index 75164922dbfa..0fecd848d527 100644 --- a/crates/wasi-http/src/p3/request.rs +++ b/crates/wasi-http/src/p3/request.rs @@ -21,6 +21,7 @@ pub struct RequestOptions { } /// The concrete type behind a `wasi:http/types/request` resource. +#[derive(Debug)] pub struct Request { /// The method of the request. pub method: Method, @@ -35,7 +36,7 @@ pub struct Request { /// Request options. pub options: Option>, /// Request body. - pub(crate) body: Body, + pub body: Body, } impl Request { diff --git a/crates/wasi-http/src/p3/response.rs b/crates/wasi-http/src/p3/response.rs index 4f3ec7953d25..cba988f4713f 100644 --- a/crates/wasi-http/src/p3/response.rs +++ b/crates/wasi-http/src/p3/response.rs @@ -11,6 +11,7 @@ use std::sync::Arc; use wasmtime::AsContextMut; /// The concrete type behind a `wasi:http/types/response` resource. +#[derive(Debug)] pub struct Response { /// The status of the response. pub status: StatusCode, From 23cf8d5e77a4b39c26bc148cc57f4a3c01586b41 Mon Sep 17 00:00:00 2001 From: Theodore Brockman Date: Sun, 12 Oct 2025 22:00:09 -0400 Subject: [PATCH 02/17] Initial impl and test. --- crates/wasi-http/src/p3/request.rs | 131 ++++++++++++++++++++++++++++- 1 file changed, 130 insertions(+), 1 deletion(-) diff --git a/crates/wasi-http/src/p3/request.rs b/crates/wasi-http/src/p3/request.rs index 0fecd848d527..aab261a28e6c 100644 --- a/crates/wasi-http/src/p3/request.rs +++ b/crates/wasi-http/src/p3/request.rs @@ -1,6 +1,9 @@ +use crate::get_content_length; use crate::p3::bindings::http::types::ErrorCode; -use crate::p3::body::Body; +use crate::p3::body::{Body, GuestBody}; +use crate::p3::{WasiHttpView, WasiHttpCtxView}; use bytes::Bytes; +use wasmtime::AsContextMut; use core::time::Duration; use http::uri::{Authority, PathAndQuery, Scheme}; use http::{HeaderMap, Method}; @@ -8,6 +11,7 @@ use http_body_util::BodyExt as _; use http_body_util::combinators::BoxBody; use std::sync::Arc; use tokio::sync::oneshot; +use anyhow::Context; /// The concrete type behind a `wasi:http/types/request-options` resource. #[derive(Copy, Clone, Debug, Default)] @@ -120,6 +124,93 @@ impl Request { body.map_err(Into::into).boxed(), ) } + + /// Convert this [`Request`] into an [`http::Request>`]. + /// + /// The specified future `fut` can be used to communicate a request processing + /// error, if any, back to the caller (e.g., if this request was constructed + /// through `wasi:http/types.request#new`). + pub fn into_http( + self, + store: impl AsContextMut, + fut: impl Future> + Send + 'static, + ) -> wasmtime::Result>> { + self.into_http_with_getter(store, fut, T::http) + } + + /// Like [`Self::into_http`], but uses a custom getter for obtaining the [`WasiHttpCtxView`]. + pub fn into_http_with_getter( + self, + store: impl AsContextMut, + fut: impl Future> + Send + 'static, + getter: fn(&mut T) -> WasiHttpCtxView<'_>, + ) -> wasmtime::Result>> { + let Self { + method, + scheme, + authority, + path_with_query, + headers, + options: _, + body, + } = self; + + // Reconstruct URI from its components + let mut uri_builder = http::Uri::builder(); + if let Some(s) = scheme { + uri_builder = uri_builder.scheme(s); + } + if let Some(a) = authority { + uri_builder = uri_builder.authority(a.as_str()); + } + if let Some(pq) = path_with_query { + uri_builder = uri_builder.path_and_query(pq.as_str()); + } + let uri = uri_builder + .build() + .context("failed to build request URI")?; + + let mut builder = http::Request::builder().method(method).uri(uri); + + if let Some(headers_mut) = builder.headers_mut() { + *headers_mut = Arc::unwrap_or_clone(headers); + } else { + tracing::warn!("failed to get mutable headers from http request builder"); + } + + let body = match body { + Body::Guest { + contents_rx, + trailers_rx, + result_tx, + } => { + // Validate Content-Length if present + let headers = match builder.headers_mut() { + Some(headers) => Ok(headers), + None => Err(anyhow::anyhow!("missing headers")), + }?; + let content_length = get_content_length(headers) + .context("failed to parse `content-length`")?; + GuestBody::new( + store, + contents_rx, + trailers_rx, + result_tx, + fut, + content_length, + ErrorCode::HttpRequestBodySize, + getter, + ) + .boxed() + } + Body::Host { body, result_tx } => { + _ = result_tx.send(Box::new(fut)); + body + } + }; + + Ok(builder.body(body)?) + } } /// The default implementation of how an outgoing request is sent. @@ -349,3 +440,41 @@ pub async fn default_send_request( conn.await.map_err(ErrorCode::from_hyper_response_error) })) } + +#[cfg(test)] +mod tests { + use super::*; + use http::Request as HttpRequest; + use http_body_util::Full; + use std::convert::Infallible; + + struct TestView; + impl WasiHttpView for TestView { + fn http(&mut self) -> WasiHttpCtxView<'_> { + unimplemented!() + } + } + + #[tokio::test] + async fn test_request_into_http() { + let (req, fut) = Request::new( + Method::GET, + Some(Scheme::HTTPS), + Some(Authority::from_static("example.com")), + Some(PathAndQuery::from_static("/path?query=1")), + HeaderMap::new(), + None, + Full::new(Bytes::from_static(b"body")).map_err(|_| unreachable!()).boxed(), + ); + let (http_req) = req.into_http(TestView, fut).unwrap(); + assert_eq!(http_req.method(), Method::GET); + assert_eq!( + http_req.uri(), + &http::Uri::from_static("https://example.com/path?query=1") + ); + let body_bytes = hyper::body::to_bytes(http_req.into_body()) + .await + .unwrap(); + assert_eq!(&body_bytes[..], b"body"); + } +} \ No newline at end of file From b1078b2bdcff2f90c4e400144c6148b2c6cfb74c Mon Sep 17 00:00:00 2001 From: Theodore Brockman Date: Tue, 7 Oct 2025 15:53:18 -0400 Subject: [PATCH 03/17] Don't remember why I derived debug here... --- crates/wasi-http/src/p3/body.rs | 16 +++++++++++----- crates/wasi-http/src/p3/request.rs | 3 ++- crates/wasi-http/src/p3/response.rs | 1 + 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/crates/wasi-http/src/p3/body.rs b/crates/wasi-http/src/p3/body.rs index 1d74ffb3f989..3c1485dcee36 100644 --- a/crates/wasi-http/src/p3/body.rs +++ b/crates/wasi-http/src/p3/body.rs @@ -21,7 +21,7 @@ use wasmtime::component::{ use wasmtime::{AsContextMut, StoreContextMut}; /// The concrete type behind a `wasi:http/types/body` resource. -pub(crate) enum Body { +pub enum Body { /// Body constructed by the guest Guest { /// The body stream @@ -40,6 +40,12 @@ pub(crate) enum Body { }, } +impl std::fmt::Debug for Body { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + "Body { .. }".fmt(f) + } +} + /// [FutureConsumer] implementation for future passed to `consume-body`. struct BodyResultConsumer( Option> + Send>>>, @@ -69,7 +75,7 @@ where impl Body { /// Implementation of `consume-body` shared between requests and responses - pub(crate) fn consume( + pub fn consume( self, mut store: Access<'_, T, WasiHttp>, fut: FutureReader>, @@ -114,7 +120,7 @@ impl Body { } /// Implementation of `drop` shared between requests and responses - pub(crate) fn drop(self, mut store: impl AsContextMut) { + pub fn drop(self, mut store: impl AsContextMut) { if let Body::Guest { contents_rx, mut trailers_rx, @@ -256,7 +262,7 @@ impl StreamConsumer for UnlimitedGuestBodyConsumer { } /// [http_body::Body] implementation for bodies originating in the guest. -pub(crate) struct GuestBody { +pub struct GuestBody { contents_rx: Option>>, trailers_rx: Option>, ErrorCode>>>, content_length: Option, @@ -264,7 +270,7 @@ pub(crate) struct GuestBody { impl GuestBody { /// Construct a new [GuestBody] - pub(crate) fn new( + pub fn new( mut store: impl AsContextMut, contents_rx: Option>, trailers_rx: FutureReader>, ErrorCode>>, diff --git a/crates/wasi-http/src/p3/request.rs b/crates/wasi-http/src/p3/request.rs index 75164922dbfa..0fecd848d527 100644 --- a/crates/wasi-http/src/p3/request.rs +++ b/crates/wasi-http/src/p3/request.rs @@ -21,6 +21,7 @@ pub struct RequestOptions { } /// The concrete type behind a `wasi:http/types/request` resource. +#[derive(Debug)] pub struct Request { /// The method of the request. pub method: Method, @@ -35,7 +36,7 @@ pub struct Request { /// Request options. pub options: Option>, /// Request body. - pub(crate) body: Body, + pub body: Body, } impl Request { diff --git a/crates/wasi-http/src/p3/response.rs b/crates/wasi-http/src/p3/response.rs index 4f3ec7953d25..cba988f4713f 100644 --- a/crates/wasi-http/src/p3/response.rs +++ b/crates/wasi-http/src/p3/response.rs @@ -11,6 +11,7 @@ use std::sync::Arc; use wasmtime::AsContextMut; /// The concrete type behind a `wasi:http/types/response` resource. +#[derive(Debug)] pub struct Response { /// The status of the response. pub status: StatusCode, From ec39f67d991aeb9260d7c17729c58918e06363fb Mon Sep 17 00:00:00 2001 From: Theodore Brockman Date: Sun, 12 Oct 2025 22:32:13 -0400 Subject: [PATCH 04/17] Fix test. --- crates/wasi-http/src/p3/request.rs | 62 ++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/crates/wasi-http/src/p3/request.rs b/crates/wasi-http/src/p3/request.rs index aab261a28e6c..daa35cd6ed67 100644 --- a/crates/wasi-http/src/p3/request.rs +++ b/crates/wasi-http/src/p3/request.rs @@ -444,19 +444,50 @@ pub async fn default_send_request( #[cfg(test)] mod tests { use super::*; - use http::Request as HttpRequest; - use http_body_util::Full; - use std::convert::Infallible; + use http_body_util::{BodyExt, Full}; + use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView}; + use crate::p3::WasiHttpCtx; + use wasmtime::{Engine, Store}; - struct TestView; - impl WasiHttpView for TestView { + struct TestHttpCtx; + struct TestCtx { + table: ResourceTable, + wasi: WasiCtx, + http: TestHttpCtx, + } + + impl TestCtx { + fn new() -> Self { + Self { + table: ResourceTable::default(), + wasi: WasiCtxBuilder::new().build(), + http: TestHttpCtx, + } + } + } + + impl WasiView for TestCtx { + fn ctx(&mut self) -> WasiCtxView<'_> { + WasiCtxView { + ctx: &mut self.wasi, + table: &mut self.table, + } + } + } + + impl WasiHttpCtx for TestHttpCtx {} + + impl WasiHttpView for TestCtx { fn http(&mut self) -> WasiHttpCtxView<'_> { - unimplemented!() + WasiHttpCtxView { + ctx: &mut self.http, + table: &mut self.table, + } } } #[tokio::test] - async fn test_request_into_http() { + async fn test_request_into_http() -> Result<(), anyhow::Error> { let (req, fut) = Request::new( Method::GET, Some(Scheme::HTTPS), @@ -466,15 +497,22 @@ mod tests { None, Full::new(Bytes::from_static(b"body")).map_err(|_| unreachable!()).boxed(), ); - let (http_req) = req.into_http(TestView, fut).unwrap(); + + let engine = Engine::default(); + let mut store = Store::new( + &engine, + TestCtx::new(), + ); + let http_req = req.into_http(&mut store, fut).unwrap(); assert_eq!(http_req.method(), Method::GET); assert_eq!( http_req.uri(), &http::Uri::from_static("https://example.com/path?query=1") ); - let body_bytes = hyper::body::to_bytes(http_req.into_body()) - .await - .unwrap(); - assert_eq!(&body_bytes[..], b"body"); + let body_bytes = http_req.into_body().collect() + .await?; + + assert_eq!(*body_bytes.to_bytes(), *b"body"); + Ok(()) } } \ No newline at end of file From 9575bdc09c45107279ff1e8a209bb0bdbcb15829 Mon Sep 17 00:00:00 2001 From: Theodore Brockman Date: Sun, 12 Oct 2025 22:47:11 -0400 Subject: [PATCH 05/17] Remove earlier changes (before discussion) and unnecessary derives. --- crates/wasi-http/src/p3/body.rs | 16 +++++----------- crates/wasi-http/src/p3/request.rs | 3 +-- crates/wasi-http/src/p3/response.rs | 1 - 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/crates/wasi-http/src/p3/body.rs b/crates/wasi-http/src/p3/body.rs index 3c1485dcee36..1d74ffb3f989 100644 --- a/crates/wasi-http/src/p3/body.rs +++ b/crates/wasi-http/src/p3/body.rs @@ -21,7 +21,7 @@ use wasmtime::component::{ use wasmtime::{AsContextMut, StoreContextMut}; /// The concrete type behind a `wasi:http/types/body` resource. -pub enum Body { +pub(crate) enum Body { /// Body constructed by the guest Guest { /// The body stream @@ -40,12 +40,6 @@ pub enum Body { }, } -impl std::fmt::Debug for Body { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - "Body { .. }".fmt(f) - } -} - /// [FutureConsumer] implementation for future passed to `consume-body`. struct BodyResultConsumer( Option> + Send>>>, @@ -75,7 +69,7 @@ where impl Body { /// Implementation of `consume-body` shared between requests and responses - pub fn consume( + pub(crate) fn consume( self, mut store: Access<'_, T, WasiHttp>, fut: FutureReader>, @@ -120,7 +114,7 @@ impl Body { } /// Implementation of `drop` shared between requests and responses - pub fn drop(self, mut store: impl AsContextMut) { + pub(crate) fn drop(self, mut store: impl AsContextMut) { if let Body::Guest { contents_rx, mut trailers_rx, @@ -262,7 +256,7 @@ impl StreamConsumer for UnlimitedGuestBodyConsumer { } /// [http_body::Body] implementation for bodies originating in the guest. -pub struct GuestBody { +pub(crate) struct GuestBody { contents_rx: Option>>, trailers_rx: Option>, ErrorCode>>>, content_length: Option, @@ -270,7 +264,7 @@ pub struct GuestBody { impl GuestBody { /// Construct a new [GuestBody] - pub fn new( + pub(crate) fn new( mut store: impl AsContextMut, contents_rx: Option>, trailers_rx: FutureReader>, ErrorCode>>, diff --git a/crates/wasi-http/src/p3/request.rs b/crates/wasi-http/src/p3/request.rs index daa35cd6ed67..8d839cf8ed5a 100644 --- a/crates/wasi-http/src/p3/request.rs +++ b/crates/wasi-http/src/p3/request.rs @@ -25,7 +25,6 @@ pub struct RequestOptions { } /// The concrete type behind a `wasi:http/types/request` resource. -#[derive(Debug)] pub struct Request { /// The method of the request. pub method: Method, @@ -515,4 +514,4 @@ mod tests { assert_eq!(*body_bytes.to_bytes(), *b"body"); Ok(()) } -} \ No newline at end of file +} diff --git a/crates/wasi-http/src/p3/response.rs b/crates/wasi-http/src/p3/response.rs index cba988f4713f..4f3ec7953d25 100644 --- a/crates/wasi-http/src/p3/response.rs +++ b/crates/wasi-http/src/p3/response.rs @@ -11,7 +11,6 @@ use std::sync::Arc; use wasmtime::AsContextMut; /// The concrete type behind a `wasi:http/types/response` resource. -#[derive(Debug)] pub struct Response { /// The status of the response. pub status: StatusCode, From f732627ec17f8ee7c4977046cc3a7a8a9308df06 Mon Sep 17 00:00:00 2001 From: Theodore Brockman Date: Sun, 12 Oct 2025 22:52:04 -0400 Subject: [PATCH 06/17] Undo visibility change. --- crates/wasi-http/src/p3/request.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wasi-http/src/p3/request.rs b/crates/wasi-http/src/p3/request.rs index 8d839cf8ed5a..bd81026c30bb 100644 --- a/crates/wasi-http/src/p3/request.rs +++ b/crates/wasi-http/src/p3/request.rs @@ -39,7 +39,7 @@ pub struct Request { /// Request options. pub options: Option>, /// Request body. - pub body: Body, + pub(crate) body: Body, } impl Request { From 1a6fca672461e753b3434f294738d1a9bf63d02a Mon Sep 17 00:00:00 2001 From: Theodore Brockman Date: Sun, 12 Oct 2025 23:33:23 -0400 Subject: [PATCH 07/17] Clean up and implement TryFrom. --- crates/wasi-http/src/p3/request.rs | 80 ++++++++++++++++-------------- 1 file changed, 42 insertions(+), 38 deletions(-) diff --git a/crates/wasi-http/src/p3/request.rs b/crates/wasi-http/src/p3/request.rs index bd81026c30bb..779ff4ed6b9c 100644 --- a/crates/wasi-http/src/p3/request.rs +++ b/crates/wasi-http/src/p3/request.rs @@ -42,6 +42,44 @@ pub struct Request { pub(crate) body: Body, } +impl TryFrom for http::Request { + type Error = http::Error; + + fn try_from( + Request { + method, + scheme, + authority, + path_with_query, + headers, + options: _, + body, + }: Request, + ) -> Result { + // Reconstruct URI from its components + let mut uri_builder = http::Uri::builder(); + if let Some(s) = scheme { + uri_builder = uri_builder.scheme(s); + } + if let Some(a) = authority { + uri_builder = uri_builder.authority(a.as_str()); + } + if let Some(pq) = path_with_query { + uri_builder = uri_builder.path_and_query(pq.as_str()); + } + let uri: http::Uri = uri_builder.build()?; + + let mut req = http::Request::builder().method(method).uri(uri); + + if let Some(headers_mut) = req.headers_mut() { + *headers_mut = Arc::unwrap_or_clone(headers); + } else { + tracing::warn!("failed to get mutable headers from http request builder"); + } + req.body(body) + } +} + impl Request { /// Construct a new [Request] /// @@ -144,38 +182,8 @@ impl Request { fut: impl Future> + Send + 'static, getter: fn(&mut T) -> WasiHttpCtxView<'_>, ) -> wasmtime::Result>> { - let Self { - method, - scheme, - authority, - path_with_query, - headers, - options: _, - body, - } = self; - - // Reconstruct URI from its components - let mut uri_builder = http::Uri::builder(); - if let Some(s) = scheme { - uri_builder = uri_builder.scheme(s); - } - if let Some(a) = authority { - uri_builder = uri_builder.authority(a.as_str()); - } - if let Some(pq) = path_with_query { - uri_builder = uri_builder.path_and_query(pq.as_str()); - } - let uri = uri_builder - .build() - .context("failed to build request URI")?; - - let mut builder = http::Request::builder().method(method).uri(uri); - - if let Some(headers_mut) = builder.headers_mut() { - *headers_mut = Arc::unwrap_or_clone(headers); - } else { - tracing::warn!("failed to get mutable headers from http request builder"); - } + let req = http::Request::try_from(self)?; + let (req, body) = req.into_parts(); let body = match body { Body::Guest { @@ -184,11 +192,7 @@ impl Request { result_tx, } => { // Validate Content-Length if present - let headers = match builder.headers_mut() { - Some(headers) => Ok(headers), - None => Err(anyhow::anyhow!("missing headers")), - }?; - let content_length = get_content_length(headers) + let content_length = get_content_length(&req.headers) .context("failed to parse `content-length`")?; GuestBody::new( store, @@ -208,7 +212,7 @@ impl Request { } }; - Ok(builder.body(body)?) + Ok(http::Request::from_parts(req, body)) } } From 5efd52988d3721b25674616b930ff064d681a96b Mon Sep 17 00:00:00 2001 From: Theodore Brockman Date: Mon, 13 Oct 2025 11:48:17 -0400 Subject: [PATCH 08/17] cargo fmt. --- crates/wasi-http/src/p3/request.rs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/crates/wasi-http/src/p3/request.rs b/crates/wasi-http/src/p3/request.rs index 779ff4ed6b9c..eb4e1a0d45af 100644 --- a/crates/wasi-http/src/p3/request.rs +++ b/crates/wasi-http/src/p3/request.rs @@ -1,9 +1,9 @@ use crate::get_content_length; use crate::p3::bindings::http::types::ErrorCode; use crate::p3::body::{Body, GuestBody}; -use crate::p3::{WasiHttpView, WasiHttpCtxView}; +use crate::p3::{WasiHttpCtxView, WasiHttpView}; +use anyhow::Context; use bytes::Bytes; -use wasmtime::AsContextMut; use core::time::Duration; use http::uri::{Authority, PathAndQuery, Scheme}; use http::{HeaderMap, Method}; @@ -11,7 +11,7 @@ use http_body_util::BodyExt as _; use http_body_util::combinators::BoxBody; use std::sync::Arc; use tokio::sync::oneshot; -use anyhow::Context; +use wasmtime::AsContextMut; /// The concrete type behind a `wasi:http/types/request-options` resource. #[derive(Copy, Clone, Debug, Default)] @@ -192,8 +192,8 @@ impl Request { result_tx, } => { // Validate Content-Length if present - let content_length = get_content_length(&req.headers) - .context("failed to parse `content-length`")?; + let content_length = + get_content_length(&req.headers).context("failed to parse `content-length`")?; GuestBody::new( store, contents_rx, @@ -447,10 +447,10 @@ pub async fn default_send_request( #[cfg(test)] mod tests { use super::*; - use http_body_util::{BodyExt, Full}; - use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView}; use crate::p3::WasiHttpCtx; + use http_body_util::{BodyExt, Full}; use wasmtime::{Engine, Store}; + use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView}; struct TestHttpCtx; struct TestCtx { @@ -498,22 +498,20 @@ mod tests { Some(PathAndQuery::from_static("/path?query=1")), HeaderMap::new(), None, - Full::new(Bytes::from_static(b"body")).map_err(|_| unreachable!()).boxed(), + Full::new(Bytes::from_static(b"body")) + .map_err(|_| unreachable!()) + .boxed(), ); let engine = Engine::default(); - let mut store = Store::new( - &engine, - TestCtx::new(), - ); + let mut store = Store::new(&engine, TestCtx::new()); let http_req = req.into_http(&mut store, fut).unwrap(); assert_eq!(http_req.method(), Method::GET); assert_eq!( http_req.uri(), &http::Uri::from_static("https://example.com/path?query=1") ); - let body_bytes = http_req.into_body().collect() - .await?; + let body_bytes = http_req.into_body().collect().await?; assert_eq!(*body_bytes.to_bytes(), *b"body"); Ok(()) From 166d7c7703f616e52b46bd69a9951d28e3c1ba04 Mon Sep 17 00:00:00 2001 From: Theodore Brockman Date: Wed, 15 Oct 2025 23:52:21 -0400 Subject: [PATCH 09/17] Requested changes. --- crates/wasi-http/src/p3/request.rs | 150 +++++++++++++++++++++++----- crates/wasi-http/src/p3/response.rs | 31 ++++++ 2 files changed, 155 insertions(+), 26 deletions(-) diff --git a/crates/wasi-http/src/p3/request.rs b/crates/wasi-http/src/p3/request.rs index eb4e1a0d45af..a4e308480ac5 100644 --- a/crates/wasi-http/src/p3/request.rs +++ b/crates/wasi-http/src/p3/request.rs @@ -2,11 +2,12 @@ use crate::get_content_length; use crate::p3::bindings::http::types::ErrorCode; use crate::p3::body::{Body, GuestBody}; use crate::p3::{WasiHttpCtxView, WasiHttpView}; -use anyhow::Context; use bytes::Bytes; +use http::header::HOST; +use tracing::debug; use core::time::Duration; use http::uri::{Authority, PathAndQuery, Scheme}; -use http::{HeaderMap, Method}; +use http::{HeaderMap, HeaderValue, Method, Uri}; use http_body_util::BodyExt as _; use http_body_util::combinators::BoxBody; use std::sync::Arc; @@ -178,22 +179,66 @@ impl Request { /// Like [`Self::into_http`], but uses a custom getter for obtaining the [`WasiHttpCtxView`]. pub fn into_http_with_getter( self, - store: impl AsContextMut, + mut store: impl AsContextMut, fut: impl Future> + Send + 'static, getter: fn(&mut T) -> WasiHttpCtxView<'_>, ) -> wasmtime::Result>> { - let req = http::Request::try_from(self)?; - let (req, body) = req.into_parts(); - + let Request { + method, + scheme, + authority, + path_with_query, + headers, + options: _, + body, + } = self; + let content_length = match get_content_length(&headers) { + Ok(content_length) => content_length, + Err(err) => { + body.drop(&mut store); + return Err(ErrorCode::InternalError(Some(format!("{err:#}"))).into()); + } + }; + let mut headers = Arc::unwrap_or_clone(headers); + let mut store_ctx = store.as_context_mut(); + let WasiHttpCtxView { ctx, table: _ } = getter(store_ctx.data_mut()); + if ctx.set_host_header() { + let host = if let Some(authority) = authority.as_ref() { + HeaderValue::try_from(authority.as_str()) + .map_err(|err| ErrorCode::InternalError(Some(err.to_string())))? + } else { + HeaderValue::from_static("") + }; + headers.insert(HOST, host); + } + let scheme = match scheme { + None => ctx.default_scheme().ok_or(ErrorCode::HttpProtocolError)?, + Some(scheme) if ctx.is_supported_scheme(&scheme) => scheme, + Some(..) => return Err(ErrorCode::HttpProtocolError.into()), + }; + let mut uri = Uri::builder().scheme(scheme); + if let Some(authority) = authority { + uri = uri.authority(authority) + }; + if let Some(path_with_query) = path_with_query { + uri = uri.path_and_query(path_with_query) + }; + let uri = uri.build().map_err(|err| { + debug!(?err, "failed to build request URI"); + ErrorCode::HttpRequestUriInvalid + })?; + let mut req = http::Request::builder(); + if let Some(headers_mut) = req.headers_mut() { + *headers_mut = headers; + } else { + return Err(ErrorCode::InternalError(Some("failed to get mutable headers from request builder".to_string())).into()); + } let body = match body { Body::Guest { contents_rx, trailers_rx, result_tx, } => { - // Validate Content-Length if present - let content_length = - get_content_length(&req.headers).context("failed to parse `content-length`")?; GuestBody::new( store, contents_rx, @@ -211,7 +256,11 @@ impl Request { body } }; - + let req = req.method(method) + .uri(uri) + .body(body) + .map_err(|err| ErrorCode::InternalError(Some(err.to_string())))?; + let (req, body) = req.into_parts(); Ok(http::Request::from_parts(req, body)) } } @@ -448,6 +497,10 @@ pub async fn default_send_request( mod tests { use super::*; use crate::p3::WasiHttpCtx; + use anyhow::Result; + use std::task::{Context, Waker}; + use std::future::Future; + use std::str::FromStr; use http_body_util::{BodyExt, Full}; use wasmtime::{Engine, Store}; use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView}; @@ -490,30 +543,75 @@ mod tests { } #[tokio::test] - async fn test_request_into_http() -> Result<(), anyhow::Error> { + async fn test_request_into_http_schemes() -> Result<()> { + let schemes = vec![ + Some(Scheme::HTTP), + Some(Scheme::HTTPS), + None, + ]; + let engine = Engine::default(); + + for scheme in schemes { + let (req, fut) = Request::new( + Method::GET, + scheme.clone(), + Some(Authority::from_static("example.com")), + Some(PathAndQuery::from_static("/path?query=1")), + HeaderMap::new(), + None, + Full::new(Bytes::from_static(b"body")) + .map_err(|x| match x {}) + .boxed(), + ); + let mut store = Store::new(&engine, TestCtx::new()); + let http_req = req.into_http(&mut store, async { Ok(()) }).unwrap(); + assert_eq!(http_req.method(), Method::GET); + let expected_scheme = scheme.unwrap_or(Scheme::HTTPS); // default scheme + assert_eq!( + http_req.uri(), + &http::Uri::from_str(&format!( + "{}://example.com/path?query=1", + expected_scheme.as_str() + )).unwrap() + ); + let body_bytes = http_req.into_body().collect().await?; + assert_eq!(*body_bytes.to_bytes(), *b"body"); + let mut cx = Context::from_waker(Waker::noop()); + let mut fut = Box::pin(fut); + let result = fut.as_mut().poll(&mut cx); + assert!(matches!(result, futures::task::Poll::Ready(Ok(())))); + } + + Ok(()) + } + + #[tokio::test] + async fn test_request_into_http_invalid_content_length() -> Result<()> { + let engine = Engine::default(); + let mut headers = HeaderMap::new(); + headers.insert(http::header::CONTENT_LENGTH, HeaderValue::from_static("invalid")); + let (req, fut) = Request::new( Method::GET, - Some(Scheme::HTTPS), + Some(Scheme::HTTP), Some(Authority::from_static("example.com")), - Some(PathAndQuery::from_static("/path?query=1")), - HeaderMap::new(), None, - Full::new(Bytes::from_static(b"body")) - .map_err(|_| unreachable!()) + headers, + None, + Full::new(Bytes::new()) + .map_err(|x| match x {}) .boxed(), ); - - let engine = Engine::default(); let mut store = Store::new(&engine, TestCtx::new()); - let http_req = req.into_http(&mut store, fut).unwrap(); - assert_eq!(http_req.method(), Method::GET); - assert_eq!( - http_req.uri(), - &http::Uri::from_static("https://example.com/path?query=1") - ); - let body_bytes = http_req.into_body().collect().await?; + let result = req.into_http(&mut store, async { Err(ErrorCode::InternalError(Some("uh oh".to_string()))) }); + assert!(matches!(result, Err(_))); + + let mut cx = Context::from_waker(Waker::noop()); + let result = Box::pin(fut).as_mut().poll(&mut cx); + // `fut` returns Ok(()), as the error in `into_http` currently drops the body before anything is sent + // is this desired behavior? + assert!(matches!(result, futures::task::Poll::Ready(Ok(())))); - assert_eq!(*body_bytes.to_bytes(), *b"body"); Ok(()) } } diff --git a/crates/wasi-http/src/p3/response.rs b/crates/wasi-http/src/p3/response.rs index 4f3ec7953d25..6a4fb9082f6d 100644 --- a/crates/wasi-http/src/p3/response.rs +++ b/crates/wasi-http/src/p3/response.rs @@ -7,6 +7,8 @@ use bytes::Bytes; use http::{HeaderMap, StatusCode}; use http_body_util::BodyExt as _; use http_body_util::combinators::BoxBody; +use std::future::Future; +use std::pin::Pin; use std::sync::Arc; use wasmtime::AsContextMut; @@ -89,4 +91,33 @@ impl Response { }; Ok(http::Response::from_parts(res, body)) } + + /// Convert [http::Response] into [Response]. + /// This is the reverse of [Self::into_http]. + pub fn from_http(res: http::Response) -> (Response, impl Future>) + where + B: http_body::Body + Send + Sync + 'static, + E: Into, + { + let (parts, body) = res.into_parts(); + let (result_tx, result_rx) = tokio::sync::oneshot::channel(); + + let wasi_response = Response { + status: parts.status, + headers: Arc::new(parts.headers), + body: Body::Host { + body: body.map_err(|e| e.into()).boxed(), + result_tx, + }, + }; + + let io_future = async move { + match result_rx.await { + Ok(fut) => Pin::from(fut).await, + Err(_) => Ok(()), + } + }; + + (wasi_response, io_future) + } } From f012036d01345ee67737c341b018e390ea1a6b46 Mon Sep 17 00:00:00 2001 From: Theodore Brockman Date: Thu, 16 Oct 2025 10:53:26 -0400 Subject: [PATCH 10/17] Remove unrelated change. --- crates/wasi-http/src/p3/response.rs | 31 ----------------------------- 1 file changed, 31 deletions(-) diff --git a/crates/wasi-http/src/p3/response.rs b/crates/wasi-http/src/p3/response.rs index 6a4fb9082f6d..4f3ec7953d25 100644 --- a/crates/wasi-http/src/p3/response.rs +++ b/crates/wasi-http/src/p3/response.rs @@ -7,8 +7,6 @@ use bytes::Bytes; use http::{HeaderMap, StatusCode}; use http_body_util::BodyExt as _; use http_body_util::combinators::BoxBody; -use std::future::Future; -use std::pin::Pin; use std::sync::Arc; use wasmtime::AsContextMut; @@ -91,33 +89,4 @@ impl Response { }; Ok(http::Response::from_parts(res, body)) } - - /// Convert [http::Response] into [Response]. - /// This is the reverse of [Self::into_http]. - pub fn from_http(res: http::Response) -> (Response, impl Future>) - where - B: http_body::Body + Send + Sync + 'static, - E: Into, - { - let (parts, body) = res.into_parts(); - let (result_tx, result_rx) = tokio::sync::oneshot::channel(); - - let wasi_response = Response { - status: parts.status, - headers: Arc::new(parts.headers), - body: Body::Host { - body: body.map_err(|e| e.into()).boxed(), - result_tx, - }, - }; - - let io_future = async move { - match result_rx.await { - Ok(fut) => Pin::from(fut).await, - Err(_) => Ok(()), - } - }; - - (wasi_response, io_future) - } } From ad91d84c73ea4d3b418a350bf0185eb03dec534c Mon Sep 17 00:00:00 2001 From: Theodore Brockman Date: Thu, 16 Oct 2025 10:55:48 -0400 Subject: [PATCH 11/17] cargo fmt --- crates/wasi-http/src/p3/request.rs | 62 +++++++++++++++--------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/crates/wasi-http/src/p3/request.rs b/crates/wasi-http/src/p3/request.rs index a4e308480ac5..395f30c95aba 100644 --- a/crates/wasi-http/src/p3/request.rs +++ b/crates/wasi-http/src/p3/request.rs @@ -3,15 +3,15 @@ use crate::p3::bindings::http::types::ErrorCode; use crate::p3::body::{Body, GuestBody}; use crate::p3::{WasiHttpCtxView, WasiHttpView}; use bytes::Bytes; -use http::header::HOST; -use tracing::debug; use core::time::Duration; +use http::header::HOST; use http::uri::{Authority, PathAndQuery, Scheme}; use http::{HeaderMap, HeaderValue, Method, Uri}; use http_body_util::BodyExt as _; use http_body_util::combinators::BoxBody; use std::sync::Arc; use tokio::sync::oneshot; +use tracing::debug; use wasmtime::AsContextMut; /// The concrete type behind a `wasi:http/types/request-options` resource. @@ -231,32 +231,34 @@ impl Request { if let Some(headers_mut) = req.headers_mut() { *headers_mut = headers; } else { - return Err(ErrorCode::InternalError(Some("failed to get mutable headers from request builder".to_string())).into()); + return Err(ErrorCode::InternalError(Some( + "failed to get mutable headers from request builder".to_string(), + )) + .into()); } let body = match body { Body::Guest { contents_rx, trailers_rx, result_tx, - } => { - GuestBody::new( - store, - contents_rx, - trailers_rx, - result_tx, - fut, - content_length, - ErrorCode::HttpRequestBodySize, - getter, - ) - .boxed() - } + } => GuestBody::new( + store, + contents_rx, + trailers_rx, + result_tx, + fut, + content_length, + ErrorCode::HttpRequestBodySize, + getter, + ) + .boxed(), Body::Host { body, result_tx } => { _ = result_tx.send(Box::new(fut)); body } }; - let req = req.method(method) + let req = req + .method(method) .uri(uri) .body(body) .map_err(|err| ErrorCode::InternalError(Some(err.to_string())))?; @@ -498,10 +500,10 @@ mod tests { use super::*; use crate::p3::WasiHttpCtx; use anyhow::Result; - use std::task::{Context, Waker}; + use http_body_util::{BodyExt, Full}; use std::future::Future; use std::str::FromStr; - use http_body_util::{BodyExt, Full}; + use std::task::{Context, Waker}; use wasmtime::{Engine, Store}; use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView}; @@ -544,11 +546,7 @@ mod tests { #[tokio::test] async fn test_request_into_http_schemes() -> Result<()> { - let schemes = vec![ - Some(Scheme::HTTP), - Some(Scheme::HTTPS), - None, - ]; + let schemes = vec![Some(Scheme::HTTP), Some(Scheme::HTTPS), None]; let engine = Engine::default(); for scheme in schemes { @@ -572,7 +570,8 @@ mod tests { &http::Uri::from_str(&format!( "{}://example.com/path?query=1", expected_scheme.as_str() - )).unwrap() + )) + .unwrap() ); let body_bytes = http_req.into_body().collect().await?; assert_eq!(*body_bytes.to_bytes(), *b"body"); @@ -589,7 +588,10 @@ mod tests { async fn test_request_into_http_invalid_content_length() -> Result<()> { let engine = Engine::default(); let mut headers = HeaderMap::new(); - headers.insert(http::header::CONTENT_LENGTH, HeaderValue::from_static("invalid")); + headers.insert( + http::header::CONTENT_LENGTH, + HeaderValue::from_static("invalid"), + ); let (req, fut) = Request::new( Method::GET, @@ -598,12 +600,12 @@ mod tests { None, headers, None, - Full::new(Bytes::new()) - .map_err(|x| match x {}) - .boxed(), + Full::new(Bytes::new()).map_err(|x| match x {}).boxed(), ); let mut store = Store::new(&engine, TestCtx::new()); - let result = req.into_http(&mut store, async { Err(ErrorCode::InternalError(Some("uh oh".to_string()))) }); + let result = req.into_http(&mut store, async { + Err(ErrorCode::InternalError(Some("uh oh".to_string()))) + }); assert!(matches!(result, Err(_))); let mut cx = Context::from_waker(Waker::noop()); From c6690436c69f6e1228a17c7d346897107809f68b Mon Sep 17 00:00:00 2001 From: Theodore Brockman Date: Mon, 20 Oct 2025 17:41:41 -0400 Subject: [PATCH 12/17] Remove TryFrom. Re-order to prevent resource leak. Use Empty::new. --- crates/wasi-http/src/p3/request.rs | 89 ++++++++++-------------------- 1 file changed, 28 insertions(+), 61 deletions(-) diff --git a/crates/wasi-http/src/p3/request.rs b/crates/wasi-http/src/p3/request.rs index 395f30c95aba..b6be66b8df53 100644 --- a/crates/wasi-http/src/p3/request.rs +++ b/crates/wasi-http/src/p3/request.rs @@ -43,44 +43,6 @@ pub struct Request { pub(crate) body: Body, } -impl TryFrom for http::Request { - type Error = http::Error; - - fn try_from( - Request { - method, - scheme, - authority, - path_with_query, - headers, - options: _, - body, - }: Request, - ) -> Result { - // Reconstruct URI from its components - let mut uri_builder = http::Uri::builder(); - if let Some(s) = scheme { - uri_builder = uri_builder.scheme(s); - } - if let Some(a) = authority { - uri_builder = uri_builder.authority(a.as_str()); - } - if let Some(pq) = path_with_query { - uri_builder = uri_builder.path_and_query(pq.as_str()); - } - let uri: http::Uri = uri_builder.build()?; - - let mut req = http::Request::builder().method(method).uri(uri); - - if let Some(headers_mut) = req.headers_mut() { - *headers_mut = Arc::unwrap_or_clone(headers); - } else { - tracing::warn!("failed to get mutable headers from http request builder"); - } - req.body(body) - } -} - impl Request { /// Construct a new [Request] /// @@ -199,6 +161,32 @@ impl Request { return Err(ErrorCode::InternalError(Some(format!("{err:#}"))).into()); } }; + // This match must appear before any potential errors handled with '?' + // (or errors have to explicitly be addressed and drop the body, as above), + // as otherwise the Body::Guest resources will not be cleaned up when dropped. + // see: https://github.com/bytecodealliance/wasmtime/pull/11440#discussion_r2326139381 + // for additional context. + let body = match body { + Body::Guest { + contents_rx, + trailers_rx, + result_tx, + } => GuestBody::new( + &mut store, + contents_rx, + trailers_rx, + result_tx, + fut, + content_length, + ErrorCode::HttpRequestBodySize, + getter, + ) + .boxed(), + Body::Host { body, result_tx } => { + _ = result_tx.send(Box::new(fut)); + body + } + }; let mut headers = Arc::unwrap_or_clone(headers); let mut store_ctx = store.as_context_mut(); let WasiHttpCtxView { ctx, table: _ } = getter(store_ctx.data_mut()); @@ -236,27 +224,6 @@ impl Request { )) .into()); } - let body = match body { - Body::Guest { - contents_rx, - trailers_rx, - result_tx, - } => GuestBody::new( - store, - contents_rx, - trailers_rx, - result_tx, - fut, - content_length, - ErrorCode::HttpRequestBodySize, - getter, - ) - .boxed(), - Body::Host { body, result_tx } => { - _ = result_tx.send(Box::new(fut)); - body - } - }; let req = req .method(method) .uri(uri) @@ -500,7 +467,7 @@ mod tests { use super::*; use crate::p3::WasiHttpCtx; use anyhow::Result; - use http_body_util::{BodyExt, Full}; + use http_body_util::{BodyExt, Empty, Full}; use std::future::Future; use std::str::FromStr; use std::task::{Context, Waker}; @@ -600,7 +567,7 @@ mod tests { None, headers, None, - Full::new(Bytes::new()).map_err(|x| match x {}).boxed(), + Empty::new().map_err(|x| match x {}).boxed(), ); let mut store = Store::new(&engine, TestCtx::new()); let result = req.into_http(&mut store, async { From 78efafe8a657949ab397b030cea5566c4f982da2 Mon Sep 17 00:00:00 2001 From: Theodore Brockman Date: Mon, 20 Oct 2025 23:41:15 -0400 Subject: [PATCH 13/17] Cause URI error instead. --- crates/wasi-http/src/p3/request.rs | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/crates/wasi-http/src/p3/request.rs b/crates/wasi-http/src/p3/request.rs index b6be66b8df53..c4d17dd35728 100644 --- a/crates/wasi-http/src/p3/request.rs +++ b/crates/wasi-http/src/p3/request.rs @@ -552,34 +552,31 @@ mod tests { } #[tokio::test] - async fn test_request_into_http_invalid_content_length() -> Result<()> { - let engine = Engine::default(); - let mut headers = HeaderMap::new(); - headers.insert( - http::header::CONTENT_LENGTH, - HeaderValue::from_static("invalid"), - ); - + async fn test_request_into_http_uri_error() -> Result<()> { let (req, fut) = Request::new( Method::GET, Some(Scheme::HTTP), Some(Authority::from_static("example.com")), - None, - headers, + None, // <-- should fail, must be Some(_) when authority is set + HeaderMap::new(), None, Empty::new().map_err(|x| match x {}).boxed(), ); - let mut store = Store::new(&engine, TestCtx::new()); + let mut store = Store::new(&Engine::default(), TestCtx::new()); let result = req.into_http(&mut store, async { Err(ErrorCode::InternalError(Some("uh oh".to_string()))) }); - assert!(matches!(result, Err(_))); - + assert!(result.is_err()); + assert!(matches!( + result.unwrap_err().downcast_ref::(), + Some(ErrorCode::HttpRequestUriInvalid) + )); let mut cx = Context::from_waker(Waker::noop()); let result = Box::pin(fut).as_mut().poll(&mut cx); - // `fut` returns Ok(()), as the error in `into_http` currently drops the body before anything is sent - // is this desired behavior? - assert!(matches!(result, futures::task::Poll::Ready(Ok(())))); + assert!(matches!( + result, + futures::task::Poll::Ready(Err(ErrorCode::InternalError(Some(_)))) + )); Ok(()) } From 4a608bee78cd59cf130b1243cd69a66868778574 Mon Sep 17 00:00:00 2001 From: Theodore Brockman Date: Fri, 31 Oct 2025 11:42:08 -0400 Subject: [PATCH 14/17] Use POST. --- crates/wasi-http/src/p3/request.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/wasi-http/src/p3/request.rs b/crates/wasi-http/src/p3/request.rs index c4d17dd35728..ec8fb7fc437d 100644 --- a/crates/wasi-http/src/p3/request.rs +++ b/crates/wasi-http/src/p3/request.rs @@ -518,7 +518,7 @@ mod tests { for scheme in schemes { let (req, fut) = Request::new( - Method::GET, + Method::POST, scheme.clone(), Some(Authority::from_static("example.com")), Some(PathAndQuery::from_static("/path?query=1")), @@ -530,7 +530,7 @@ mod tests { ); let mut store = Store::new(&engine, TestCtx::new()); let http_req = req.into_http(&mut store, async { Ok(()) }).unwrap(); - assert_eq!(http_req.method(), Method::GET); + assert_eq!(http_req.method(), Method::POST); let expected_scheme = scheme.unwrap_or(Scheme::HTTPS); // default scheme assert_eq!( http_req.uri(), From 9973e9177bee1718c9533406591e00cf73137173 Mon Sep 17 00:00:00 2001 From: Theodore Brockman Date: Tue, 7 Oct 2025 15:53:18 -0400 Subject: [PATCH 15/17] Don't remember why I derived debug here... --- crates/wasi-http/src/p3/body.rs | 14 ++++++++++---- crates/wasi-http/src/p3/request.rs | 7 ++++++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/crates/wasi-http/src/p3/body.rs b/crates/wasi-http/src/p3/body.rs index 54e93d61f667..047ee7f2f749 100644 --- a/crates/wasi-http/src/p3/body.rs +++ b/crates/wasi-http/src/p3/body.rs @@ -40,6 +40,12 @@ pub(crate) enum Body { }, } +impl std::fmt::Debug for Body { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + "Body { .. }".fmt(f) + } +} + /// [FutureConsumer] implementation for future passed to `consume-body`. struct BodyResultConsumer( Option> + Send>>>, @@ -69,7 +75,7 @@ where impl Body { /// Implementation of `consume-body` shared between requests and responses - pub(crate) fn consume( + pub fn consume( self, mut store: Access<'_, T, WasiHttp>, fut: FutureReader>, @@ -114,7 +120,7 @@ impl Body { } /// Implementation of `drop` shared between requests and responses - pub(crate) fn drop(self, mut store: impl AsContextMut) { + pub fn drop(self, mut store: impl AsContextMut) { if let Body::Guest { contents_rx, mut trailers_rx, @@ -256,7 +262,7 @@ impl StreamConsumer for UnlimitedGuestBodyConsumer { } /// [http_body::Body] implementation for bodies originating in the guest. -pub(crate) struct GuestBody { +pub struct GuestBody { contents_rx: Option>>, trailers_rx: Option>, ErrorCode>>>, content_length: Option, @@ -264,7 +270,7 @@ pub(crate) struct GuestBody { impl GuestBody { /// Construct a new [GuestBody] - pub(crate) fn new( + pub fn new( mut store: impl AsContextMut, contents_rx: Option>, trailers_rx: FutureReader>, ErrorCode>>, diff --git a/crates/wasi-http/src/p3/request.rs b/crates/wasi-http/src/p3/request.rs index 4f921258f1af..4cd789423a08 100644 --- a/crates/wasi-http/src/p3/request.rs +++ b/crates/wasi-http/src/p3/request.rs @@ -20,7 +20,12 @@ pub struct RequestOptions { pub between_bytes_timeout: Option, } +<<<<<<< HEAD /// The concrete type behind a `wasi:http/types.request` resource. +======= +/// The concrete type behind a `wasi:http/types/request` resource. +#[derive(Debug)] +>>>>>>> b1078b2bd (Don't remember why I derived debug here...) pub struct Request { /// The method of the request. pub method: Method, @@ -35,7 +40,7 @@ pub struct Request { /// Request options. pub options: Option>, /// Request body. - pub(crate) body: Body, + pub body: Body, } impl Request { From 47bfdb474917905e5be623c6a0f7f4a487d8049a Mon Sep 17 00:00:00 2001 From: Theodore Brockman Date: Fri, 31 Oct 2025 11:54:29 -0400 Subject: [PATCH 16/17] Fix rebase. Update to use of 'boxed_unsync'. --- crates/wasi-http/src/p3/request.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/crates/wasi-http/src/p3/request.rs b/crates/wasi-http/src/p3/request.rs index 7cb4d2bc614a..6d3792f2e77a 100644 --- a/crates/wasi-http/src/p3/request.rs +++ b/crates/wasi-http/src/p3/request.rs @@ -25,12 +25,7 @@ pub struct RequestOptions { pub between_bytes_timeout: Option, } -<<<<<<< HEAD /// The concrete type behind a `wasi:http/types.request` resource. -======= -/// The concrete type behind a `wasi:http/types/request` resource. -#[derive(Debug)] ->>>>>>> b1078b2bd (Don't remember why I derived debug here...) pub struct Request { /// The method of the request. pub method: Method, @@ -45,7 +40,7 @@ pub struct Request { /// Request options. pub options: Option>, /// Request body. - pub body: Body, + pub(crate) body: Body, } impl Request { @@ -139,7 +134,7 @@ impl Request { self, store: impl AsContextMut, fut: impl Future> + Send + 'static, - ) -> wasmtime::Result>> { + ) -> wasmtime::Result>> { self.into_http_with_getter(store, fut, T::http) } @@ -149,7 +144,7 @@ impl Request { mut store: impl AsContextMut, fut: impl Future> + Send + 'static, getter: fn(&mut T) -> WasiHttpCtxView<'_>, - ) -> wasmtime::Result>> { + ) -> wasmtime::Result>> { let Request { method, scheme, @@ -186,7 +181,7 @@ impl Request { ErrorCode::HttpRequestBodySize, getter, ) - .boxed(), + .boxed_unsync(), Body::Host { body, result_tx } => { _ = result_tx.send(Box::new(fut)); body @@ -531,7 +526,7 @@ mod tests { None, Full::new(Bytes::from_static(b"body")) .map_err(|x| match x {}) - .boxed(), + .boxed_unsync(), ); let mut store = Store::new(&engine, TestCtx::new()); let http_req = req.into_http(&mut store, async { Ok(()) }).unwrap(); @@ -565,7 +560,7 @@ mod tests { None, // <-- should fail, must be Some(_) when authority is set HeaderMap::new(), None, - Empty::new().map_err(|x| match x {}).boxed(), + Empty::new().map_err(|x| match x {}).boxed_unsync(), ); let mut store = Store::new(&Engine::default(), TestCtx::new()); let result = req.into_http(&mut store, async { From 7af066bf0baa820c61ea2a54b790b6faa0f7f651 Mon Sep 17 00:00:00 2001 From: Theodore Brockman Date: Fri, 31 Oct 2025 12:02:18 -0400 Subject: [PATCH 17/17] Remove unintended. --- crates/wasi-http/src/p3/body.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/crates/wasi-http/src/p3/body.rs b/crates/wasi-http/src/p3/body.rs index 047ee7f2f749..54e93d61f667 100644 --- a/crates/wasi-http/src/p3/body.rs +++ b/crates/wasi-http/src/p3/body.rs @@ -40,12 +40,6 @@ pub(crate) enum Body { }, } -impl std::fmt::Debug for Body { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - "Body { .. }".fmt(f) - } -} - /// [FutureConsumer] implementation for future passed to `consume-body`. struct BodyResultConsumer( Option> + Send>>>, @@ -75,7 +69,7 @@ where impl Body { /// Implementation of `consume-body` shared between requests and responses - pub fn consume( + pub(crate) fn consume( self, mut store: Access<'_, T, WasiHttp>, fut: FutureReader>, @@ -120,7 +114,7 @@ impl Body { } /// Implementation of `drop` shared between requests and responses - pub fn drop(self, mut store: impl AsContextMut) { + pub(crate) fn drop(self, mut store: impl AsContextMut) { if let Body::Guest { contents_rx, mut trailers_rx, @@ -262,7 +256,7 @@ impl StreamConsumer for UnlimitedGuestBodyConsumer { } /// [http_body::Body] implementation for bodies originating in the guest. -pub struct GuestBody { +pub(crate) struct GuestBody { contents_rx: Option>>, trailers_rx: Option>, ErrorCode>>>, content_length: Option, @@ -270,7 +264,7 @@ pub struct GuestBody { impl GuestBody { /// Construct a new [GuestBody] - pub fn new( + pub(crate) fn new( mut store: impl AsContextMut, contents_rx: Option>, trailers_rx: FutureReader>, ErrorCode>>,