Skip to content

Commit 8a88ff6

Browse files
Pat Hickeyelliotttacfoltzer
authored
Wasi-http: support inbound requests (proxy world) (#7091)
* Move the incoming_handler impl into http_impl * Remove the incoming handler -- we need to use it as a guest export * Start adding a test-programs test for the server side of wasi-http * Progress towards running a server test * Implement incoming-request-method * Validate outparam value * Initial incoming handler test * Implement more of the incoming api * Finish the incoming api implementations * Initial cut at `wasmtime serve` * fix warning * wasmtime-cli: invoke ServeCommand, and add enough stuff to the linker to run trivial test * fix warnings * fix warnings * argument parsing: allow --addr to specify sockaddr * rustfmt * sync wit definitions between wasmtime-wasi and wasmtime-wasi-http * cargo vet: add an import config and wildcard audit for wasmtime-wmemcheck * cargo vet: audit signal-hook-registry * Remove duplicate add_to_linker calls for preview2 interfaces prtest:full * Add a method to finish outgoing responses Co-authored-by: Adam Foltzer <[email protected]> Co-authored-by: Pat Hickey <[email protected]> * Mark the result of the incoming_{request,response}_consume methods as own * Explicit versions for http-body and http-body-util * Explicit `serve` feature for the `wasmtime serve` command * Move the spawn outside of the future returned by `ProxyHandler::call` * Review feedback --------- Co-authored-by: Trevor Elliott <[email protected]> Co-authored-by: Adam Foltzer <[email protected]>
1 parent 40c1f9b commit 8a88ff6

File tree

26 files changed

+905
-158
lines changed

26 files changed

+905
-158
lines changed

Cargo.lock

Lines changed: 21 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ serde_json = { workspace = true }
4848
wasmparser = { workspace = true }
4949
wasm-encoder = { workspace = true }
5050

51+
async-trait = { workspace = true }
52+
tokio = { workspace = true, optional = true, features = [ "signal", "macros" ] }
53+
hyper = { workspace = true, optional = true }
54+
http-body = { workspace = true }
55+
http-body-util = { workspace = true }
56+
5157
[target.'cfg(unix)'.dependencies]
5258
rustix = { workspace = true, features = ["mm", "param"] }
5359

@@ -60,7 +66,7 @@ filecheck = { workspace = true }
6066
tempfile = { workspace = true }
6167
test-programs = { path = "crates/test-programs" }
6268
wasmtime-runtime = { workspace = true }
63-
tokio = { version = "1.8.0", features = ["rt", "time", "macros", "rt-multi-thread"] }
69+
tokio = { workspace = true, features = ["rt", "time", "macros", "rt-multi-thread"] }
6470
wast = { workspace = true }
6571
criterion = "0.5.0"
6672
num_cpus = "1.13.0"
@@ -101,6 +107,7 @@ members = [
101107
"crates/jit-icache-coherence",
102108
"crates/test-programs/wasi-tests",
103109
"crates/test-programs/wasi-http-tests",
110+
"crates/test-programs/wasi-http-proxy-tests",
104111
"crates/test-programs/wasi-sockets-tests",
105112
"crates/test-programs/command-tests",
106113
"crates/test-programs/reactor-tests",
@@ -251,7 +258,11 @@ tempfile = "3.1.0"
251258
filecheck = "0.5.0"
252259
libc = "0.2.60"
253260
file-per-thread-logger = "0.2.0"
254-
tokio = { version = "1.26.0" }
261+
tokio = { version = "1.26.0", features = [ "rt", "time" ] }
262+
hyper = "=1.0.0-rc.3"
263+
http = "0.2.9"
264+
http-body = "=1.0.0-rc.2"
265+
http-body-util = "=0.1.0-rc.2"
255266
bytes = "1.4"
256267
futures = { version = "0.3.27", default-features = false }
257268
indexmap = "2.0.0"
@@ -275,7 +286,7 @@ jitdump = ["wasmtime/jitdump"]
275286
vtune = ["wasmtime/vtune"]
276287
wasi-nn = ["dep:wasmtime-wasi-nn"]
277288
wasi-threads = ["dep:wasmtime-wasi-threads"]
278-
wasi-http = ["dep:wasmtime-wasi-http", "wasmtime-wasi-http?/sync"]
289+
wasi-http = ["component-model", "dep:wasmtime-wasi-http", "dep:tokio", "dep:hyper", "wasmtime-wasi-http?/sync"]
279290
pooling-allocator = ["wasmtime/pooling-allocator", "wasmtime-cli-flags/pooling-allocator"]
280291
all-arch = ["wasmtime/all-arch"]
281292
component-model = [
@@ -286,6 +297,9 @@ component-model = [
286297
winch = ["wasmtime/winch"]
287298
wmemcheck = ["wasmtime/wmemcheck"]
288299

300+
# Enable the `wasmtime serve` command
301+
serve = ["wasi-http", "component-model"]
302+
289303
[[test]]
290304
name = "host_segfault"
291305
harness = false

crates/test-programs/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ heck = { workspace = true }
1515

1616
[dependencies]
1717
anyhow = { workspace = true }
18+
bytes = { workspace = true }
1819
http = { version = "0.2.9" }
1920
http-body = "1.0.0-rc.2"
2021
http-body-util = "0.1.0-rc.2"

crates/test-programs/build.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ fn build_and_generate_tests() {
3333
println!("cargo:rerun-if-changed=./wasi-sockets-tests");
3434
if BUILD_WASI_HTTP_TESTS {
3535
println!("cargo:rerun-if-changed=./wasi-http-tests");
36+
println!("cargo:rerun-if-changed=./wasi-http-proxy-tests");
3637
} else {
3738
println!("cargo:rustc-cfg=skip_wasi_http_tests");
3839
}
@@ -50,6 +51,7 @@ fn build_and_generate_tests() {
5051
.env_remove("CARGO_ENCODED_RUSTFLAGS");
5152
if BUILD_WASI_HTTP_TESTS {
5253
cmd.arg("--package=wasi-http-tests");
54+
cmd.arg("--package=wasi-http-proxy-tests");
5355
}
5456
let status = cmd.status().unwrap();
5557
assert!(status.success());
@@ -60,8 +62,14 @@ fn build_and_generate_tests() {
6062
components_rs(&meta, "wasi-tests", "bin", &command_adapter, &out_dir);
6163

6264
if BUILD_WASI_HTTP_TESTS {
63-
modules_rs(&meta, "wasi-http-tests", "bin", &out_dir);
6465
components_rs(&meta, "wasi-http-tests", "bin", &command_adapter, &out_dir);
66+
components_rs(
67+
&meta,
68+
"wasi-http-proxy-tests",
69+
"cdylib",
70+
&reactor_adapter,
71+
&out_dir,
72+
);
6573
}
6674

6775
components_rs(&meta, "command-tests", "bin", &command_adapter, &out_dir);

crates/test-programs/tests/wasi-http-components-sync.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ use wasmtime::{
44
Config, Engine, Store,
55
};
66
use wasmtime_wasi::preview2::{
7-
command::sync::{add_to_linker, Command},
8-
pipe::MemoryOutputPipe,
9-
IsATTY, Table, WasiCtx, WasiCtxBuilder, WasiView,
7+
command::sync::Command, pipe::MemoryOutputPipe, IsATTY, Table, WasiCtx, WasiCtxBuilder,
8+
WasiView,
109
};
1110
use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView};
1211

@@ -60,8 +59,7 @@ fn instantiate_component(
6059
ctx: Ctx,
6160
) -> Result<(Store<Ctx>, Command), anyhow::Error> {
6261
let mut linker = Linker::new(&ENGINE);
63-
add_to_linker(&mut linker)?;
64-
wasmtime_wasi_http::proxy::add_to_linker(&mut linker)?;
62+
wasmtime_wasi_http::proxy::sync::add_to_linker(&mut linker)?;
6563

6664
let mut store = Store::new(&ENGINE, ctx);
6765

crates/test-programs/tests/wasi-http-components.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ use wasmtime::{
44
Config, Engine, Store,
55
};
66
use wasmtime_wasi::preview2::{
7-
command::{add_to_linker, Command},
8-
pipe::MemoryOutputPipe,
9-
IsATTY, Table, WasiCtx, WasiCtxBuilder, WasiView,
7+
command::Command, pipe::MemoryOutputPipe, IsATTY, Table, WasiCtx, WasiCtxBuilder, WasiView,
108
};
119
use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView};
1210

@@ -60,7 +58,6 @@ async fn instantiate_component(
6058
ctx: Ctx,
6159
) -> Result<(Store<Ctx>, Command), anyhow::Error> {
6260
let mut linker = Linker::new(&ENGINE);
63-
add_to_linker(&mut linker)?;
6461
wasmtime_wasi_http::proxy::add_to_linker(&mut linker)?;
6562

6663
let mut store = Store::new(&ENGINE, ctx);
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
#![cfg(all(feature = "test_programs", not(skip_wasi_http_tests)))]
2+
use anyhow::Context;
3+
use wasmtime::{
4+
component::{Component, Linker},
5+
Config, Engine, Store,
6+
};
7+
use wasmtime_wasi::preview2::{
8+
self, pipe::MemoryOutputPipe, IsATTY, Table, WasiCtx, WasiCtxBuilder, WasiView,
9+
};
10+
use wasmtime_wasi_http::{proxy::Proxy, WasiHttpCtx, WasiHttpView};
11+
12+
lazy_static::lazy_static! {
13+
static ref ENGINE: Engine = {
14+
let mut config = Config::new();
15+
config.wasm_backtrace_details(wasmtime::WasmBacktraceDetails::Enable);
16+
config.wasm_component_model(true);
17+
config.async_support(true);
18+
let engine = Engine::new(&config).unwrap();
19+
engine
20+
};
21+
}
22+
// uses ENGINE, creates a fn get_module(&str) -> Module
23+
include!(concat!(
24+
env!("OUT_DIR"),
25+
"/wasi_http_proxy_tests_components.rs"
26+
));
27+
28+
struct Ctx {
29+
table: Table,
30+
wasi: WasiCtx,
31+
http: WasiHttpCtx,
32+
}
33+
34+
impl WasiView for Ctx {
35+
fn table(&self) -> &Table {
36+
&self.table
37+
}
38+
fn table_mut(&mut self) -> &mut Table {
39+
&mut self.table
40+
}
41+
fn ctx(&self) -> &WasiCtx {
42+
&self.wasi
43+
}
44+
fn ctx_mut(&mut self) -> &mut WasiCtx {
45+
&mut self.wasi
46+
}
47+
}
48+
49+
impl WasiHttpView for Ctx {
50+
fn table(&mut self) -> &mut Table {
51+
&mut self.table
52+
}
53+
fn ctx(&mut self) -> &mut WasiHttpCtx {
54+
&mut self.http
55+
}
56+
}
57+
58+
async fn instantiate(component: Component, ctx: Ctx) -> Result<(Store<Ctx>, Proxy), anyhow::Error> {
59+
let mut linker = Linker::new(&ENGINE);
60+
wasmtime_wasi_http::proxy::add_to_linker(&mut linker)?;
61+
62+
let mut store = Store::new(&ENGINE, ctx);
63+
64+
let (proxy, _instance) = Proxy::instantiate_async(&mut store, &component, &linker).await?;
65+
Ok((store, proxy))
66+
}
67+
68+
#[test_log::test(tokio::test)]
69+
async fn wasi_http_proxy_tests() -> anyhow::Result<()> {
70+
let stdout = MemoryOutputPipe::new(4096);
71+
let stderr = MemoryOutputPipe::new(4096);
72+
73+
let mut table = Table::new();
74+
let component = get_component("wasi_http_proxy_tests");
75+
76+
// Create our wasi context.
77+
let mut builder = WasiCtxBuilder::new();
78+
builder.stdout(stdout.clone(), IsATTY::No);
79+
builder.stderr(stderr.clone(), IsATTY::No);
80+
for (var, val) in test_programs::wasi_tests_environment() {
81+
builder.env(var, val);
82+
}
83+
let wasi = builder.build(&mut table)?;
84+
let http = WasiHttpCtx;
85+
86+
let ctx = Ctx { table, wasi, http };
87+
88+
let (mut store, proxy) = instantiate(component, ctx).await?;
89+
90+
let req = {
91+
use http_body_util::{BodyExt, Empty};
92+
93+
let req = hyper::Request::builder().method(http::Method::GET).body(
94+
Empty::<bytes::Bytes>::new()
95+
.map_err(|e| anyhow::anyhow!(e))
96+
.boxed(),
97+
)?;
98+
store.data_mut().new_incoming_request(req)?
99+
};
100+
101+
let (sender, receiver) = tokio::sync::oneshot::channel();
102+
let out = store.data_mut().new_response_outparam(sender)?;
103+
104+
let handle = preview2::spawn(async move {
105+
proxy
106+
.wasi_http_incoming_handler()
107+
.call_handle(&mut store, req, out)
108+
.await?;
109+
110+
Ok::<_, anyhow::Error>(())
111+
});
112+
113+
let resp = match receiver.await {
114+
Ok(Ok(resp)) => {
115+
use http_body_util::BodyExt;
116+
let (parts, body) = resp.into_parts();
117+
let collected = BodyExt::collect(body).await?;
118+
Ok(hyper::Response::from_parts(parts, collected))
119+
}
120+
121+
Ok(Err(e)) => Err(e),
122+
123+
// This happens if the wasm never calls `set-response-outparam`
124+
Err(e) => panic!("Failed to receive a response: {e:?}"),
125+
};
126+
127+
// Now that the response has been processed, we can wait on the wasm to finish without
128+
// deadlocking.
129+
handle.await.context("Component execution")?;
130+
131+
let stdout = stdout.contents();
132+
if !stdout.is_empty() {
133+
println!("[guest] stdout:\n{}\n===", String::from_utf8_lossy(&stdout));
134+
}
135+
let stderr = stderr.contents();
136+
if !stderr.is_empty() {
137+
println!("[guest] stderr:\n{}\n===", String::from_utf8_lossy(&stderr));
138+
}
139+
140+
match resp {
141+
Ok(resp) => println!("response: {resp:?}"),
142+
Err(e) => panic!("Error given in response: {e:?}"),
143+
};
144+
145+
Ok(())
146+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "wasi-http-proxy-tests"
3+
version = "0.0.0"
4+
readme = "README.md"
5+
edition = "2021"
6+
publish = false
7+
8+
[lib]
9+
crate-type=["cdylib"]
10+
11+
[dependencies]
12+
wit-bindgen = { workspace = true, features = ["macros", "realloc"] }

0 commit comments

Comments
 (0)