Skip to content
This repository was archived by the owner on Sep 8, 2025. It is now read-only.

Commit dabcd77

Browse files
committed
fixup! feat(p3): implement wasi:http
1 parent 1283bc9 commit dabcd77

7 files changed

Lines changed: 176 additions & 104 deletions

File tree

crates/test-programs/artifacts/build.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ fn build_and_generate_tests() {
7878
s if s.starts_with("preview1_") => "preview1",
7979
s if s.starts_with("preview2_") => "preview2",
8080
s if s.starts_with("cli_") => "cli",
81+
s if s.starts_with("api_0_3") => "api_0_3",
8182
s if s.starts_with("api_") => "api",
8283
s if s.starts_with("nn_") => "nn",
8384
s if s.starts_with("piped_") => "piped",
@@ -114,6 +115,7 @@ fn build_and_generate_tests() {
114115
s if s.starts_with("sockets_0_3") => &reactor_adapter,
115116
s if s.starts_with("http_0_3") => &reactor_adapter,
116117
s if s.starts_with("async_") => &reactor_adapter,
118+
s if s.starts_with("api_0_3_proxy") => &proxy_adapter,
117119
s if s.starts_with("api_proxy") => &proxy_adapter,
118120
_ => &command_adapter,
119121
};
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use futures::SinkExt as _;
2+
use test_programs::p3::wasi::http::types::{ErrorCode, Headers, Request, Response};
3+
use test_programs::p3::{wit_future, wit_stream};
4+
use wit_bindgen_rt::async_support::spawn;
5+
6+
struct T;
7+
8+
test_programs::p3::proxy::export!(T);
9+
10+
impl test_programs::p3::proxy::exports::wasi::http::handler::Guest for T {
11+
async fn handle(request: Request) -> Result<Response, ErrorCode> {
12+
assert!(request.scheme().is_some());
13+
assert!(request.authority().is_some());
14+
assert!(request.path_with_query().is_some());
15+
16+
// TODO: adapt below
17+
//test_filesystem();
18+
19+
let header = String::from("custom-forbidden-header");
20+
let req_hdrs = request.headers();
21+
22+
assert!(
23+
!req_hdrs.has(&header),
24+
"forbidden `custom-forbidden-header` found in request"
25+
);
26+
27+
assert!(req_hdrs.delete(&header).is_err());
28+
assert!(req_hdrs.append(&header, b"no".as_ref()).is_err());
29+
30+
assert!(
31+
!req_hdrs.has(&header),
32+
"append of forbidden header succeeded"
33+
);
34+
35+
assert!(
36+
!req_hdrs.has("host"),
37+
"forbidden host header present in incoming request"
38+
);
39+
40+
let hdrs = Headers::new();
41+
let (mut contents_tx, contents_rx) = wit_stream::new();
42+
let (trailers_tx, trailers_rx) = wit_future::new();
43+
let (resp, transmit) = Response::new(hdrs, Some(contents_rx), trailers_rx);
44+
spawn(async {
45+
contents_tx
46+
.send(b"hello, world!".to_vec())
47+
.await
48+
.expect("writing response");
49+
drop(contents_tx);
50+
trailers_tx.write(Ok(None));
51+
transmit.await.expect("failed to transmit response");
52+
});
53+
Ok(resp)
54+
}
55+
}
56+
57+
// Technically this should not be here for a proxy, but given the current
58+
// framework for tests it's required since this file is built as a `bin`
59+
fn main() {}
60+
61+
// TODO: adapt below
62+
//fn test_filesystem() {
63+
// assert!(std::fs::File::open(".").is_err());
64+
//}

crates/test-programs/src/p3/mod.rs

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ wit_bindgen::generate!({
1212
export wasi:cli/run@0.3.0;
1313
}
1414
",
15-
path: [
16-
"../wasi-http/src/p3/wit",
17-
],
15+
path: "../wasi-http/src/p3/wit",
1816
world: "wasmtime:test/testp3",
1917
default_bindings_module: "test_programs::p3",
2018
pub_export_macro: true,
@@ -36,3 +34,37 @@ wit_bindgen::generate!({
3634
},
3735
generate_all
3836
});
37+
38+
pub mod proxy {
39+
wit_bindgen::generate!({
40+
inline: "
41+
package wasmtime:test;
42+
43+
world proxyp3 {
44+
include wasi:http/proxy@0.3.0-draft;
45+
}
46+
",
47+
path: "../wasi-http/src/p3/wit",
48+
world: "wasmtime:test/proxyp3",
49+
default_bindings_module: "test_programs::p3::proxy",
50+
pub_export_macro: true,
51+
async: {
52+
imports: [
53+
"wasi:http/handler@0.3.0-draft#handle",
54+
],
55+
exports: [
56+
"wasi:http/handler@0.3.0-draft#handle",
57+
],
58+
},
59+
with: {
60+
"wasi:http/handler@0.3.0-draft": generate,
61+
"wasi:http/types@0.3.0-draft": crate::p3::wasi::http::types,
62+
"wasi:random/random@0.3.0": crate::p3::wasi::random::random,
63+
"wasi:cli/stdout@0.3.0": crate::p3::wasi::cli::stdout,
64+
"wasi:cli/stderr@0.3.0": crate::p3::wasi::cli::stderr,
65+
"wasi:cli/stdin@0.3.0": crate::p3::wasi::cli::stdin,
66+
"wasi:clocks/monotonic-clock@0.3.0": crate::p3::wasi::clocks::monotonic_clock,
67+
"wasi:clocks/wall-clock@0.3.0": crate::p3::wasi::clocks::wall_clock,
68+
},
69+
});
70+
}

crates/wasi-http/src/p3/proxy.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ impl Proxy {
3030
/// Call `handle` on [Proxy].
3131
pub async fn handle<T>(
3232
&self,
33-
mut store: impl AsContextMut<Data = T> + Sync + Send + 'static,
33+
mut store: impl AsContextMut<Data = T> + Send + 'static,
3434
req: impl Into<Request>,
3535
) -> wasmtime::Result<
3636
Result<

crates/wasi-http/src/p3/response.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ impl Response {
6161
/// Convert [Response] into [http::Response].
6262
pub fn into_http<T: ResourceView + 'static>(
6363
self,
64-
mut store: impl AsContextMut<Data = T> + Sync + Send + 'static,
64+
mut store: impl AsContextMut<Data = T> + Send + 'static,
6565
) -> anyhow::Result<(
6666
http::Response<UnsyncBoxBody<Bytes, Option<ErrorCode>>>,
6767
Option<FutureWriter<Result<(), ErrorCode>>>,

crates/wasi-http/tests/all/p3/mod.rs

Lines changed: 73 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use core::future::Future;
22

33
use bytes::Bytes;
4+
use http_body::Body;
5+
use http_body_util::{BodyExt as _, Collected, Empty};
46
use wasmtime::component::{Component, Linker, ResourceTable};
57
use wasmtime::Store;
68
use wasmtime_wasi::p3::cli::{WasiCliCtx, WasiCliView};
@@ -11,13 +13,14 @@ use wasmtime_wasi::p3::sockets::{WasiSocketsCtx, WasiSocketsView};
1113
use wasmtime_wasi::p3::ResourceView;
1214
use wasmtime_wasi::{IoView, WasiCtx, WasiCtxBuilder, WasiView};
1315
use wasmtime_wasi_http::p3::bindings::http::types::ErrorCode;
16+
use wasmtime_wasi_http::p3::bindings::Proxy;
1417
use wasmtime_wasi_http::p3::{
1518
default_send_request, Client, RequestOptions, WasiHttpCtx, WasiHttpView,
1619
};
1720

1821
use crate::http_server::Server;
1922

20-
mod tests;
23+
mod outgoing;
2124

2225
struct Ctx<C: Client = TestClient> {
2326
cli: WasiCliCtx,
@@ -103,7 +106,7 @@ impl<C: Client> WasiHttpView for Ctx<C> {
103106
&self.http
104107
}
105108

106-
fn is_forbidden_header(&mut self, name: &hyper::header::HeaderName) -> bool {
109+
fn is_forbidden_header(&mut self, name: &http::header::HeaderName) -> bool {
107110
name.as_str() == "custom-forbidden-header"
108111
}
109112
}
@@ -148,104 +151,75 @@ impl Client for TestClient {
148151
}
149152
}
150153

151-
// TODO: Port below
154+
async fn run_wasi_http<E: Into<ErrorCode> + 'static>(
155+
component_filename: &str,
156+
req: http::Request<impl Body<Data = Bytes, Error = E> + Send + Sync + 'static>,
157+
client: TestClient,
158+
) -> anyhow::Result<Result<hyper::Response<Collected<Bytes>>, ErrorCode>> {
159+
let engine = test_programs_artifacts::engine(|config| {
160+
config.wasm_backtrace_details(wasmtime::WasmBacktraceDetails::Enable);
161+
config.async_support(true);
162+
config.wasm_component_model_async(true);
163+
});
164+
let component = Component::from_file(&engine, component_filename)?;
152165

153-
//async fn run_wasi_http(
154-
// component_filename: &str,
155-
// req: hyper::Request<BoxBody<Bytes, hyper::Error>>,
156-
// send_request: Option<RequestSender>,
157-
// rejected_authority: Option<String>,
158-
//) -> anyhow::Result<Result<hyper::Response<Collected<Bytes>>, ErrorCode>> {
159-
// let stdout = MemoryOutputPipe::new(4096);
160-
// let stderr = MemoryOutputPipe::new(4096);
161-
// let table = ResourceTable::new();
162-
//
163-
// let mut config = Config::new();
164-
// config.wasm_backtrace_details(wasmtime::WasmBacktraceDetails::Enable);
165-
// config.wasm_component_model(true);
166-
// config.async_support(true);
167-
// let engine = Engine::new(&config)?;
168-
// let component = Component::from_file(&engine, component_filename)?;
169-
//
170-
// // Create our wasi context.
171-
// let mut builder = WasiCtxBuilder::new();
172-
// builder.stdout(stdout.clone());
173-
// builder.stderr(stderr.clone());
174-
// let wasi = builder.build();
175-
// let http = WasiHttpCtx::new();
176-
// let ctx = Ctx {
177-
// table,
178-
// wasi,
179-
// http,
180-
// stderr,
181-
// stdout,
182-
// send_request,
183-
// rejected_authority,
184-
// };
185-
// let mut store = Store::new(&engine, ctx);
186-
//
187-
// let mut linker = Linker::new(&engine);
188-
// wasmtime_wasi_http::add_to_linker_async(&mut linker)?;
189-
// let proxy =
190-
// wasmtime_wasi_http::bindings::Proxy::instantiate_async(&mut store, &component, &linker)
191-
// .await?;
192-
//
193-
// let req = store.data_mut().new_incoming_request(Scheme::Http, req)?;
194-
//
195-
// let (sender, receiver) = tokio::sync::oneshot::channel();
196-
// let out = store.data_mut().new_response_outparam(sender)?;
197-
//
198-
// let handle = wasmtime_wasi::runtime::spawn(async move {
199-
// proxy
200-
// .wasi_http_incoming_handler()
201-
// .call_handle(&mut store, req, out)
202-
// .await?;
203-
//
204-
// Ok::<_, anyhow::Error>(())
205-
// });
206-
//
207-
// let resp = match receiver.await {
208-
// Ok(Ok(resp)) => {
209-
// let (parts, body) = resp.into_parts();
210-
// let collected = BodyExt::collect(body).await?;
211-
// Some(Ok(hyper::Response::from_parts(parts, collected)))
212-
// }
213-
// Ok(Err(e)) => Some(Err(e)),
214-
//
215-
// // Fall through below to the `resp.expect(...)` which will hopefully
216-
// // return a more specific error from `handle.await`.
217-
// Err(_) => None,
218-
// };
219-
//
220-
// // Now that the response has been processed, we can wait on the wasm to
221-
// // finish without deadlocking.
222-
// handle.await.context("Component execution")?;
223-
//
224-
// Ok(resp.expect("wasm never called set-response-outparam"))
225-
//}
226-
//
227-
//#[test_log::test(tokio::test)]
228-
//async fn wasi_http_proxy_tests() -> anyhow::Result<()> {
229-
// let req = hyper::Request::builder()
230-
// .header("custom-forbidden-header", "yes")
231-
// .uri("http://example.com:8080/test-path")
232-
// .method(http::Method::GET);
233-
//
234-
// let resp = run_wasi_http(
235-
// test_programs_artifacts::API_PROXY_COMPONENT,
236-
// req.body(body::empty())?,
237-
// None,
238-
// None,
239-
// )
240-
// .await?;
241-
//
242-
// match resp {
243-
// Ok(resp) => println!("response: {resp:?}"),
244-
// Err(e) => panic!("Error given in response: {e:?}"),
245-
// };
246-
//
247-
// Ok(())
248-
//}
166+
let mut store = Store::new(
167+
&engine,
168+
Ctx {
169+
cli: WasiCliCtx {
170+
..WasiCliCtx::default()
171+
},
172+
http: WasiHttpCtx { client },
173+
..Ctx::default()
174+
},
175+
);
176+
177+
let mut linker = Linker::new(&engine);
178+
wasmtime_wasi::add_to_linker_async(&mut linker)?;
179+
wasmtime_wasi_http::p3::add_to_linker(&mut linker)?;
180+
let proxy = Proxy::instantiate_async(&mut store, &component, &linker).await?;
181+
match proxy.handle(store, req).await? {
182+
Ok((resp, fut)) => {
183+
let (parts, body) = resp.into_parts();
184+
let body = body
185+
.collect()
186+
.await
187+
.map_err(|err| err.expect("trailer future dropped"))?;
188+
if let Some(fut) = fut {
189+
let _fut = fut.write(Ok(()));
190+
// TODO: Should we await the future, if so, how do we do that after having moved
191+
// the store?
192+
}
193+
Ok(Ok(http::Response::from_parts(parts, body)))
194+
}
195+
Err(err) => Ok(Err(err)),
196+
}
197+
}
198+
199+
#[test_log::test(tokio::test)]
200+
async fn wasi_http_proxy_tests() -> anyhow::Result<()> {
201+
let req = hyper::Request::builder()
202+
// TODO: remove forbidden headers?
203+
//.header("custom-forbidden-header", "yes")
204+
.uri("http://example.com:8080/test-path")
205+
.method(http::Method::GET);
206+
207+
let resp = run_wasi_http(
208+
test_programs_artifacts::API_0_3_PROXY_COMPONENT,
209+
req.body(Empty::new())?,
210+
TestClient::default(),
211+
)
212+
.await?;
213+
214+
match resp {
215+
Ok(resp) => println!("response: {resp:?}"),
216+
Err(e) => panic!("Error given in response: {e:?}"),
217+
};
218+
219+
Ok(())
220+
}
221+
222+
// TODO: Port below
249223
//
250224
//#[test_log::test(tokio::test)]
251225
//async fn wasi_http_hash_all() -> Result<()> {

0 commit comments

Comments
 (0)