diff --git a/Cargo.lock b/Cargo.lock index 6d31a922..b54da194 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,7 +27,7 @@ dependencies = [ "getrandom", "once_cell", "version_check", - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -46,6 +46,9 @@ dependencies = [ "clap", "cliclack", "console", + "http-body-util", + "hyper", + "hyper-util", "nova_vm", "oxc_ast", "oxc_parser", @@ -138,6 +141,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7330592adf847ee2e3513587b4db2db410a0d751378654e7e993d9adcbe5c795" +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.3.0" @@ -189,6 +198,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bytes" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" + [[package]] name = "castaway" version = "0.2.3" @@ -356,6 +371,51 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + [[package]] name = "getrandom" version = "0.2.15" @@ -373,6 +433,25 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +[[package]] +name = "h2" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -385,6 +464,99 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + [[package]] name = "indexmap" version = "2.3.0" @@ -518,6 +690,18 @@ dependencies = [ "adler", ] +[[package]] +name = "mio" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys 0.52.0", +] + [[package]] name = "nova_vm" version = "0.1.0" @@ -721,6 +905,16 @@ dependencies = [ "unicode-id-start", ] +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + [[package]] name = "parking_lot_core" version = "0.9.10" @@ -786,12 +980,38 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "portable-atomic" version = "1.7.0" @@ -800,11 +1020,11 @@ checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" [[package]] name = "ppv-lite86" -version = "0.2.18" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee4364d9f3b902ef14fab8a1ddffb783a1cb6b4bba3bfc1fa3922732c7de97f" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy 0.6.6", + "zerocopy", ] [[package]] @@ -919,12 +1139,30 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + [[package]] name = "siphasher" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "small_string" version = "0.1.0" @@ -942,6 +1180,16 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -1034,9 +1282,93 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" dependencies = [ "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-util" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", "pin-project-lite", + "tokio", ] +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "unicode-id-start" version = "1.2.0" @@ -1073,6 +1405,15 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1224,34 +1565,14 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c01ae8492c38f52376efd3a17d0994b6bcf3df1e39c0226d458b7d81670b2a06" -[[package]] -name = "zerocopy" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854e949ac82d619ee9a14c66a1b674ac730422372ccb759ce0c39cabcf2bf8e6" -dependencies = [ - "byteorder", - "zerocopy-derive 0.6.6", -] - [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ - "zerocopy-derive 0.7.35", -] - -[[package]] -name = "zerocopy-derive" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "125139de3f6b9d625c39e2efdd73d41bdac468ccd556556440e322be0e1bbd91" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "byteorder", + "zerocopy-derive", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 6300728a..40e8feb6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,4 +17,7 @@ oxc_semantic = "0.20.0" andromeda-core = { path = "core" } andromeda-runtime = { path = "runtime" } anymap = "0.12.1" -tokio = { version = "1.39.0", features = ["rt", "sync", "time"]} +tokio = { version = "1.39.0", features = ["full"]} +hyper = { version = "1", features = ["full"] } +http-body-util = "0.1" +hyper-util = { version = "0.1", features = ["full"] } diff --git a/cli/Cargo.toml b/cli/Cargo.toml index e09e170e..63273cd5 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -18,4 +18,7 @@ oxc_parser = { workspace = true } oxc_span = { workspace = true } oxc_semantic = { workspace = true } anymap = { workspace = true } -tokio = { workspace = true } \ No newline at end of file +tokio = { workspace = true } +hyper = { workspace = true } +http-body-util = { workspace = true } +hyper-util = { workspace = true } diff --git a/cli/src/main.rs b/cli/src/main.rs index a5464c89..5402f245 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -6,6 +6,8 @@ use andromeda_runtime::{ recommended_builtins, recommended_eventloop_handler, recommended_extensions, }; use clap::{Parser as ClapParser, Subcommand}; + +mod serve; /// A JavaScript runtime #[derive(Debug, ClapParser)] #[command(name = "andromeda")] @@ -32,6 +34,11 @@ enum Command { #[arg(required = true)] paths: Vec, }, + /// Serves a file as a web server + Serve { + /// The file to serve + path: String, + }, } fn main() -> Result<(), Box> { @@ -74,6 +81,7 @@ fn main() -> Result<(), Box> { }), } } + Command::Serve { path } => serve::serve(path), }); rt.block_on(nova_thread) diff --git a/cli/src/serve.rs b/cli/src/serve.rs new file mode 100644 index 00000000..10c3e2dd --- /dev/null +++ b/cli/src/serve.rs @@ -0,0 +1,143 @@ +use std::cell::RefCell; +use std::convert::Infallible; +use std::net::SocketAddr; +use std::rc::Rc; + +use andromeda_core::{Runtime, RuntimeConfig}; +use andromeda_runtime::{ + recommended_builtins, recommended_eventloop_handler, recommended_extensions, RuntimeMacroTask, +}; +use http_body_util::Full; +use hyper::body::Bytes; +use hyper::server::conn::http1; +use hyper::service::service_fn; +use hyper::{Request, Response, StatusCode}; +use hyper_util::rt::TokioIo; +use nova_vm::ecmascript::execution::agent::RealmRoot; +use nova_vm::ecmascript::types::InternalMethods; +use nova_vm::ecmascript::{ + scripts_and_modules::script::{parse_script, script_evaluation}, + types, +}; +use nova_vm::SmallString; +use tokio::net::TcpListener; + +async fn run_script_for_request( + req: Request, + runtime: Rc>>, +) -> Result>, Infallible> { + let (realm_root, strict_mode) = { + let runtime = runtime.borrow(); + // TODO(Beast): RealmRoot in Nova maybe can derive Clone to make this not as cursed + let realm_root = unsafe { + std::mem::transmute_copy::<&RealmRoot, &'static RealmRoot>(&&runtime.realm_root) + }; + (realm_root, runtime.config.no_strict) + }; + + runtime + .borrow_mut() + .agent + .run_in_realm(&realm_root, |agent| { + let source_text = + types::String::from_string(agent, format!("serve(\"{}\")", req.uri().to_string())); + let realm = agent.current_realm_id(); + + let script = match parse_script(agent, source_text, realm, strict_mode, None) { + Ok(script) => script, + Err(_) => { + panic!("error"); + } + }; + + match script_evaluation(agent, script) { + Ok(value) => match value { + types::Value::String(_) => match value.to_string(agent) { + Ok(str) => Ok(Response::new(Full::new(Bytes::from( + str.as_str(agent).to_owned(), + )))), + _ => panic!("error"), + }, + types::Value::Object(obj) => { + let body = { + let body_property_descriptor = obj + .internal_get_own_property( + agent, + types::PropertyKey::SmallString( + SmallString::from_str_unchecked("body"), + ), + ) + .unwrap() + .unwrap(); + let body_value = body_property_descriptor.value.unwrap(); + body_value + .to_string(agent) + .unwrap() + .clone() + .as_str(agent) + .to_owned() + }; + let status = { + let status_code = obj + .internal_get_own_property( + agent, + types::PropertyKey::SmallString( + SmallString::from_str_unchecked("status"), + ), + ) + .unwrap() + .unwrap(); + let status_value = status_code.value.unwrap(); + status_value.to_int32(agent).unwrap() + }; + let status_code = StatusCode::from_u16(status as u16).unwrap(); + + let res = Response::builder() + .status(&status_code) + .body(Full::new(Bytes::from(body))) + .unwrap(); + Ok(res) + } + _ => panic!("serve function must return a string or an object"), + }, + _ => panic!("error"), + } + }) +} + +pub fn serve(path: String) { + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap() + .block_on(async move { + // setup code + let runtime = Rc::new(RefCell::new(Runtime::new(RuntimeConfig { + no_strict: false, + paths: vec![path], + verbose: false, + extensions: recommended_extensions(), + builtins: recommended_builtins(), + eventloop_handler: recommended_eventloop_handler, + }))); + _ = runtime.borrow_mut().run(); + + let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + let listener = TcpListener::bind(addr).await.unwrap(); + loop { + let (stream, _) = listener.accept().await.unwrap(); + let io = TokioIo::new(stream); + + if let Err(err) = http1::Builder::new() + // `service_fn` converts our function in a `Service` + .serve_connection( + io, + service_fn(|request| run_script_for_request(request, runtime.clone())), + ) + .await + { + eprintln!("Error serving connection: {:?}", err); + } + } + }) +} diff --git a/examples/http.ts b/examples/http.ts new file mode 100644 index 00000000..129273d6 --- /dev/null +++ b/examples/http.ts @@ -0,0 +1,8 @@ +let counter = 0; +function serve(path: string) { + console.log(path) + return { + status: 200, + body: `request from "${path}". request count: ` + counter++ + }; +}