Skip to content

Commit 0910085

Browse files
committed
Add cli arguments to serve or run a component
Signed-off-by: Doru Blânzeanu <dblnz@pm.me>
1 parent af9867d commit 0910085

8 files changed

Lines changed: 281 additions & 57 deletions

File tree

Cargo.lock

Lines changed: 74 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,18 @@ name = "hyperlight-wasm-http-example"
33
edition = "2024"
44

55
[dependencies]
6+
anyhow = "1.0"
7+
bytes = "1"
8+
clap = { version = "4.5.60", features = ["derive"] }
9+
getrandom = "0.3.3"
10+
http-body-util = "0.1"
11+
hyper = "1.7"
12+
hyper-util = { version = "0.1", features = ["full"] }
613
hyperlight-component-macro = { version = "0.9.0" }
714
hyperlight-common = { version = "0.9.0" }
815
hyperlight-host = { version = "0.9.0", default-features = false, features = [ "kvm", "mshv2" ] }
916
hyperlight-wasm = { version = "0.9.0" }
10-
11-
hyper = "1.7"
17+
once_cell = "1.21.3"
1218
reqwest = { version = "0.12", features = ["stream", "gzip", "brotli", "deflate", "rustls-tls", "blocking"] }
13-
bytes = "1"
1419
tokio = { version = "1.47", features = ["full"] }
15-
anyhow = "1.0"
16-
http-body-util = "0.1"
17-
hyper-util = { version = "0.1", features = ["full"] }
18-
getrandom = "0.3.3"
20+

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,10 @@ You can then run the server:
9494

9595
Rust:
9696
```sh
97-
cargo run -- out/sample_wasi_http_rust.aot
97+
cargo run -- serve --addr 0.0.0.0:9999 {{ OUT_DIR }}/sample_wasi_http_rust.aot
9898
```
9999

100100
JS:
101101
```sh
102-
cargo run -- out/sample_wasi_http_js.aot
102+
cargo run -- serve --addr 0.0.0.0:8888 {{ OUT_DIR }}/sample_wasi_http_js.aot
103103
```

justfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ build: make-wit-world
3939
cargo build
4040

4141
run-rust: build build-rust-component
42-
cargo run -- {{ OUT_DIR }}/sample_wasi_http_rust.aot
42+
cargo run -- serve --addr 0.0.0.0:9999 {{ OUT_DIR }}/sample_wasi_http_rust.aot
4343

4444
run-js: build build-js-component
45-
cargo run -- {{ OUT_DIR }}/sample-wasi-http-js.aot
45+
cargo run -- serve --addr 0.0.0.0:8888 {{ OUT_DIR }}/sample-wasi-http-js.aot

src/main.rs

Lines changed: 106 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,119 @@
11
extern crate alloc;
22

3+
mod sandbox_pool;
34
mod wasi_impl;
45

5-
use wasi_impl::{
6-
Resource,
7-
bindings::{RootSandbox, register_host_functions, wasi, wasi::http::IncomingHandler},
8-
types,
9-
types::{WasiImpl, http_incoming_body::IncomingBody, io_stream::Stream},
10-
worker::RUNTIME,
11-
};
6+
use wasi_impl::Resource;
7+
use wasi_impl::bindings::wasi::cli::Run;
8+
use wasi_impl::bindings::wasi::http::IncomingHandler;
9+
use wasi_impl::types::{WasiImpl, http_incoming_body::IncomingBody, io_stream::Stream};
10+
use wasi_impl::worker::RUNTIME;
1211

1312
use std::{convert::Infallible, net::SocketAddr, str::FromStr, sync::Arc};
1413

1514
use bytes::Bytes;
15+
use clap::{Args, Parser, Subcommand};
1616
use http_body_util::{BodyExt, Full};
1717
use hyper::{server::conn::http1, service::service_fn};
1818
use hyper_util::rt::TokioIo;
19-
use hyperlight_host::sandbox::SandboxConfiguration;
20-
use hyperlight_wasm::LoadedWasmSandbox;
19+
use once_cell::sync::Lazy;
20+
use sandbox_pool::SandboxPool;
2121
use tokio::{net::TcpListener, sync::Mutex};
2222

23-
fn main() {
24-
let args = std::env::args().collect::<Vec<_>>();
25-
if args.len() != 2 {
26-
eprintln!("Usage: {} <AOT_WASM_FILE>", args[0]);
27-
std::process::exit(1);
28-
}
23+
// Pool of sandboxes that are used at runtime to handle incoming HTTP requests.
24+
static SANDBOX_POOL: Lazy<Arc<Mutex<SandboxPool>>> =
25+
Lazy::new(|| Arc::new(Mutex::new(SandboxPool::default())));
2926

30-
let wasm_path = &args[1];
27+
/// Common options shared between subcommands (modeled after wasmtime).
28+
#[derive(Args, Debug, Clone)]
29+
struct CliOptions {
30+
/// Not used, it needs `wasi:filesystem` implementation which is not
31+
/// possible at the moment because `wasi:filesystem/types` conflicts with
32+
/// `wasi:http/types`.
33+
/// This is here for compatibility with `wasmtime` cli
34+
#[arg(long = "dir", value_name = "HOST_DIR::GUEST_DIR")]
35+
dirs: Vec<String>,
3136

32-
let builder = hyperlight_wasm::SandboxBuilder::new()
33-
.with_guest_heap_size(30 * 1024 * 1024)
34-
.with_guest_stack_size(1 * 1024 * 1024);
37+
/// Pass an environment variable to the guest.
38+
/// Format: `KEY=VALUE`
39+
#[arg(long = "env", value_name = "KEY=VALUE")]
40+
envs: Vec<String>,
3541

36-
// hyperlight wasm currently doesn't expose a way to set the host
37-
// function definition size, so we do it manually here with a
38-
// horrible hack to get a mutable reference to the config
39-
let config = builder.get_config() as *const _ as *mut SandboxConfiguration;
40-
let config = unsafe { config.as_mut().unwrap() };
41-
config.set_host_function_definition_size(20 * 1024);
42+
/// Path to the AOT-compiled WASM component file
43+
wasm_file: String,
44+
}
45+
46+
#[derive(Subcommand, Debug)]
47+
enum Command {
48+
/// Run a WASM component (guest exports wasi:cli/run)
49+
Run {
50+
#[command(flatten)]
51+
options: CliOptions,
52+
},
53+
/// Serve an HTTP component (guest exports wasi:http/incoming-handler)
54+
Serve {
55+
#[command(flatten)]
56+
options: CliOptions,
4257

43-
let mut sb = builder.build().unwrap();
58+
/// Socket address to listen on
59+
#[arg(long, default_value = "0.0.0.0:8080", value_name = "ADDR")]
60+
addr: String,
61+
},
62+
}
4463

45-
let state = WasiImpl::new();
46-
let rt = register_host_functions(&mut sb, state);
64+
#[derive(Parser, Debug)]
65+
#[command(name = "hyperlight-wasm-debug")]
66+
#[command(about = "Run a WASM component with Hyperlight")]
67+
struct Cli {
68+
#[command(subcommand)]
69+
command: Command,
70+
}
4771

48-
let sb = sb.load_runtime().unwrap();
49-
let sb = sb.load_module(wasm_path).unwrap();
72+
fn main() {
73+
let cli = Cli::parse();
5074

51-
let sb = RootSandbox { sb, rt };
52-
let sb = Arc::new(Mutex::new(sb));
75+
match cli.command {
76+
Command::Run { options } => {
77+
run_cli(options);
78+
}
79+
Command::Serve { options, addr } => {
80+
run_http(options, &addr);
81+
}
82+
}
83+
}
84+
85+
/// Run the guest component via `wasi:cli/run`.
86+
///
87+
/// The guest manages its own I/O (TCP, UDP, stdio, etc.) through the WASI
88+
/// imports it was compiled against.
89+
fn run_cli(options: CliOptions) {
90+
println!("Running component...");
91+
RUNTIME.block_on(async {
92+
SANDBOX_POOL.lock().await.from_options(options);
93+
let mut sb = SANDBOX_POOL.lock().await.get_sandbox();
94+
tokio::task::block_in_place(|| {
95+
let inst = wasi_impl::bindings::root::component::RootExports::run(&mut sb);
96+
match inst.run() {
97+
Ok(()) => println!("Component exited successfully."),
98+
Err(()) => eprintln!("Component exited with an error."),
99+
}
100+
});
101+
SANDBOX_POOL.lock().await.return_sandbox(sb);
102+
});
103+
}
104+
105+
/// Run an HTTP proxy server that forwards requests to the guest component's
106+
/// `wasi:http/incoming-handler` export.
107+
fn run_http(options: CliOptions, addr: &str) {
108+
let addr: SocketAddr = addr.parse().unwrap_or_else(|e| {
109+
eprintln!("Invalid address '{addr}': {e}");
110+
std::process::exit(1);
111+
});
53112

54113
RUNTIME.block_on(async move {
55-
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
114+
SANDBOX_POOL.lock().await.from_options(options);
115+
println!("Starting server on http://{addr}");
116+
56117
let listener = TcpListener::bind(addr).await.unwrap();
57118

58119
loop {
@@ -62,17 +123,13 @@ fn main() {
62123
// `hyper::rt` IO traits.
63124
let io = TokioIo::new(stream);
64125

65-
let sb = sb.clone();
66-
67126
RUNTIME.spawn(async move {
68127
// Finally, we bind the incoming connection to our `hello` service
69128
if let Err(err) = http1::Builder::new()
70129
// `service_fn` converts our function in a `Service`
71130
.serve_connection(
72131
io,
73-
service_fn(move |req: hyper::Request<hyper::body::Incoming>| {
74-
hello(sb.clone(), req)
75-
}),
132+
service_fn(move |req: hyper::Request<hyper::body::Incoming>| hello(req)),
76133
)
77134
.await
78135
{
@@ -84,11 +141,11 @@ fn main() {
84141
}
85142

86143
async fn hello(
87-
sb: Arc<Mutex<RootSandbox<WasiImpl, LoadedWasmSandbox>>>,
88144
mut req: hyper::Request<hyper::body::Incoming>,
89145
) -> Result<hyper::Response<Full<Bytes>>, Infallible> {
90-
let mut sb = sb.lock().await;
91-
let inst = wasi_impl::bindings::root::component::RootExports::incoming_handler(&mut *sb);
146+
let mut sb = SANDBOX_POOL.lock().await.get_sandbox();
147+
let snapshot = sb.sb.snapshot().unwrap();
148+
let inst = wasi_impl::bindings::root::component::RootExports::incoming_handler(&mut sb);
92149

93150
let body = req.body_mut();
94151
let mut full_body = Vec::new();
@@ -111,7 +168,7 @@ async fn hello(
111168
let _ = body_stream.write(&full_body);
112169
body_stream.close();
113170

114-
let req = types::http_incoming_request::IncomingRequest {
171+
let req = wasi_impl::types::http_incoming_request::IncomingRequest {
115172
method: req.method().into(),
116173
path_with_query: Some(
117174
req.uri()
@@ -147,6 +204,12 @@ async fn hello(
147204
inst.handle(req, outparam.clone());
148205
});
149206

207+
sb.sb.restore(&snapshot).unwrap();
208+
209+
// Return the sandbox to the pool immediately after the call, so it's available for the next
210+
// request while we wait for the guest to produce a response.
211+
SANDBOX_POOL.lock().await.return_sandbox(sb);
212+
150213
let Some(response) = outparam.write().await.response.take() else {
151214
let body = Full::new(Bytes::from("Error reading body"));
152215
let mut res = hyper::Response::new(body);
@@ -184,7 +247,7 @@ async fn hello(
184247
}
185248
}
186249

187-
impl From<&hyper::Method> for wasi::http::types::Method {
250+
impl From<&hyper::Method> for wasi_impl::bindings::wasi::http::types::Method {
188251
fn from(method: &hyper::Method) -> Self {
189252
match method.as_str() {
190253
"GET" => wasi_impl::bindings::wasi::http::types::Method::Get,

0 commit comments

Comments
 (0)