From c0d89813463c89f55bc7c5a3e7bc51a4e49447e6 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Mon, 8 Jan 2024 00:55:56 +0000 Subject: [PATCH] Better web implementation --- .github/workflows/publish-sidevm-quickjs.yml | 2 +- sidevm-quickjs/Cargo.toml | 3 + sidevm-quickjs/Makefile | 9 ++- sidevm-quickjs/src/phatjs.rs | 40 +++++++++- sidevm-quickjs/web/cors-proxy.py | 78 ++++++++++++++++++++ sidevm-quickjs/web/index.html | 39 ++++++---- sidevm-quickjs/web/main.js | 36 ++++++--- 7 files changed, 174 insertions(+), 33 deletions(-) create mode 100755 sidevm-quickjs/web/cors-proxy.py diff --git a/.github/workflows/publish-sidevm-quickjs.yml b/.github/workflows/publish-sidevm-quickjs.yml index cd670d8..3b32910 100644 --- a/.github/workflows/publish-sidevm-quickjs.yml +++ b/.github/workflows/publish-sidevm-quickjs.yml @@ -46,4 +46,4 @@ jobs: sidevm-quickjs/*.wasm sidevm-quickjs/phatjs-x86_64-unknown-linux-musl sidevm-quickjs/hash.txt - sidevm-quickjs/web.tar.gz + sidevm-quickjs/phatjs-web.tar.gz diff --git a/sidevm-quickjs/Cargo.toml b/sidevm-quickjs/Cargo.toml index fd2b930..d68ea59 100755 --- a/sidevm-quickjs/Cargo.toml +++ b/sidevm-quickjs/Cargo.toml @@ -4,6 +4,9 @@ version = "0.9.5" authors = ["[your_name] <[your_email]>"] edition = "2021" +[profile.release] +lto = true + [[bin]] name = "sidejs" path = "src/sidejs.rs" diff --git a/sidevm-quickjs/Makefile b/sidevm-quickjs/Makefile index bf8b3e4..eaee2b5 100644 --- a/sidevm-quickjs/Makefile +++ b/sidevm-quickjs/Makefile @@ -5,14 +5,19 @@ BUILD_OUTPUT_DIR=target/wasm32-wasi/release BUILD_OUTPUT=$(addsuffix .wasm, $(TARGETS)) OPTIMIZED_OUTPUT=$(addsuffix -stripped.wasm, $(TARGETS)) WEB_BUILD_OUTPUT_DIR=target/wasm32-unknown-unknown/release +OPT?=0 .PHONY: all clean opt deep-clean install run test web phatjs-web.wasm wasi all: wasi web native wasi: $(BUILD_OUTPUT) web: phatjs-web.wasm - -wasm-bindgen phatjs-web.wasm --out-dir web/dist --typescript --target web --out-name phatjs - tar czvf web.tar.gz web/ + wasm-bindgen phatjs-web.wasm --out-dir web/dist --typescript --target web --out-name phatjs +ifeq ($(OPT),1) + wasm-opt web/dist/phatjs_bg.wasm -Os -o web/dist/phatjs_bg.wasm + wasm-tools strip web/dist/phatjs_bg.wasm -o web/dist/phatjs_bg.wasm +endif + tar czvf phatjs-web.tar.gz web/ %.wasm: cargo build --release --target wasm32-wasi --no-default-features --features js-hash,sidevm diff --git a/sidevm-quickjs/src/phatjs.rs b/sidevm-quickjs/src/phatjs.rs index 9b9d115..5735025 100644 --- a/sidevm-quickjs/src/phatjs.rs +++ b/sidevm-quickjs/src/phatjs.rs @@ -26,16 +26,32 @@ async fn main() { mod web { use super::*; use pink_types::js::JsValue as QjsValue; - use wasm_bindgen::JsValue as WebJsValue; + use wasm_bindgen::{prelude::*, JsValue as WebJsValue}; - #[wasm_bindgen::prelude::wasm_bindgen] + #[wasm_bindgen(start)] + pub fn start() { + runtime::init_logger(); + } + + /// Get the version of the runtime. + #[wasm_bindgen] pub async fn version() -> String { env!("CARGO_PKG_VERSION").to_string() } - #[wasm_bindgen::prelude::wasm_bindgen] + /// Run a script. + /// + /// # Arguments + /// - `args` - a list of arguments to pass to the runtime, including the script name and arguments. + /// + /// # Example + /// + /// ```js + /// const result = await run(["phatjs", "-c", "console.log(scriptArgs)", "--", "Hello, world!"]); + /// console.log(result); + /// ``` + #[wasm_bindgen] pub async fn run(args: Vec) -> Result { - runtime::init_logger(); let result = js_eval::run(args.into_iter()).await; match result { Ok(value) => Ok({ @@ -51,6 +67,22 @@ mod web { Err(err) => Err(err.to_string().into()), } } + + /// Set a hook for the runtime. + /// + /// # Available hooks + /// - `fetch` - a function that takes a `Request` object and returns a `Response` object. + #[wasm_bindgen(js_name = "setHook")] + pub fn set_hook(hook_name: String, hook_value: WebJsValue) -> Result<(), String> { + match hook_name.as_str() { + "fetch" => { + js_sys::Reflect::set(&js_sys::global(), &"phatjsFetch".into(), &hook_value) + .expect("Failed to set phatjsFetch"); + } + _ => return Err(format!("Unknown hook name: {}", hook_name)), + } + Ok(()) + } } #[cfg(not(feature = "native"))] diff --git a/sidevm-quickjs/web/cors-proxy.py b/sidevm-quickjs/web/cors-proxy.py new file mode 100755 index 0000000..31e2354 --- /dev/null +++ b/sidevm-quickjs/web/cors-proxy.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 + +from http.server import BaseHTTPRequestHandler, HTTPServer +from socketserver import ThreadingMixIn +from urllib import request + + +class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): + """Handle requests in a separate thread.""" + daemon_threads = True # Optional: Set True to make the threads exit when the main thread does. + + +class CORSProxyHTTPRequestHandler(BaseHTTPRequestHandler): + def do_REQUEST(self, method): + target_url = self.path[1:] # Remove the leading '/' + + if not is_url_allowed(target_url): + self.send_response(403) + self.end_headers() + self.wfile.write(b"403 Forbidden") + return + + content_length = int(self.headers.get('Content-Length', 0)) + body = self.rfile.read(content_length) if content_length else None + headers = {key: val for (key, val) in self.headers.items() if key.lower() not in ('host', 'connection', 'content-length', 'content-type')} + + try: + req = request.Request(target_url, data=body, headers=headers, method=method) + with request.urlopen(req) as response: + self.send_response(response.status) + + # Set up response headers, excluding the Access-Control-Allow-Origin header + for header, value in response.headers.items(): + if header.lower() != 'access-control-allow-origin': + self.send_header(header, value) + + # Replace the Access-Control-Allow-Origin header with our own + self.send_header('Access-Control-Allow-Origin', '*') + + self.end_headers() + self.wfile.write(response.read()) + except Exception as e: + self.send_response(500) + self.end_headers() + self.wfile.write(str(e).encode()) + + def do_HEAD(self): + self.do_REQUEST('HEAD') + + def do_GET(self): + self.do_REQUEST('GET') + + def do_POST(self): + self.do_REQUEST('POST') + + def do_PUT(self): + self.do_REQUEST('PUT') + + def do_DELETE(self): + self.do_REQUEST('DELETE') + + def do_OPTIONS(self): + self.do_REQUEST('OPTIONS') + + +def is_url_allowed(url): + return True + + +def run(server_class=ThreadedHTTPServer, handler_class=CORSProxyHTTPRequestHandler, port=8080): + server_address = ('', port) + httpd = server_class(server_address, handler_class) + print(f"CORS proxy server listening on port {port}") + httpd.serve_forever() + + +if __name__ == '__main__': + run(port=3000) \ No newline at end of file diff --git a/sidevm-quickjs/web/index.html b/sidevm-quickjs/web/index.html index aa6e9ed..8157dda 100644 --- a/sidevm-quickjs/web/index.html +++ b/sidevm-quickjs/web/index.html @@ -4,28 +4,37 @@ Play with QuickJS +
-
-

Output:

+

Output:

+
diff --git a/sidevm-quickjs/web/main.js b/sidevm-quickjs/web/main.js index 9bd5a8d..d65b19c 100644 --- a/sidevm-quickjs/web/main.js +++ b/sidevm-quickjs/web/main.js @@ -1,27 +1,41 @@ -import init, { run } from "./dist/phatjs.js"; +import init, { run, setHook } from "./dist/phatjs.js"; -// Provide custom fetch implementation for phatjs -window.phatjsFetch = (resource, options) => { - console.log("Fetch: ", resource, options); - return fetch(resource, options); +function setRunable(enabled, runner) { + document.getElementById("btn-run").disabled = !enabled; + if (runner) { + document.getElementById("btn-run").onclick = runner; + } +} + +function setOutput(text) { + document.getElementById("output").value = text; } async function runScript() { const script = document.getElementById("input-code").value; - document.getElementById("btn-run").disabled = true; const args = ["42"]; try { + setRunable(false); + setOutput("Running..."); const output = await run(["phatjs", "-c", script, "--", ...args]); - document.getElementById("output").innerText = output; - } finally { - document.getElementById("btn-run").disabled = false; + setOutput(output); + } catch (error) { + setOutput(error); + } + finally { + setRunable(true); } } async function main() { await init(); - document.getElementById("btn-run").onclick = runScript; - document.getElementById("btn-run").disabled = false; + + // Provide custom fetch implementation for phatjs + setHook("fetch", (req) => { + // req = new Request("http://localhost:3000/" + req.url, req); + return fetch(req); + }); + setRunable(true, runScript); } main().catch(console.error)