11extern crate alloc;
22
3+ mod sandbox_pool;
34mod 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
1312use std:: { convert:: Infallible , net:: SocketAddr , str:: FromStr , sync:: Arc } ;
1413
1514use bytes:: Bytes ;
15+ use clap:: { Args , Parser , Subcommand } ;
1616use http_body_util:: { BodyExt , Full } ;
1717use hyper:: { server:: conn:: http1, service:: service_fn} ;
1818use 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 ;
2121use 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
86143async 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