Skip to content

Commit

Permalink
Better web implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
kvinwang committed Jan 8, 2024
1 parent a4279a0 commit c0d8981
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 33 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/publish-sidevm-quickjs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions sidevm-quickjs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
9 changes: 7 additions & 2 deletions sidevm-quickjs/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
40 changes: 36 additions & 4 deletions sidevm-quickjs/src/phatjs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>) -> Result<WebJsValue, WebJsValue> {
runtime::init_logger();
let result = js_eval::run(args.into_iter()).await;
match result {
Ok(value) => Ok({
Expand All @@ -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"))]
Expand Down
78 changes: 78 additions & 0 deletions sidevm-quickjs/web/cors-proxy.py
Original file line number Diff line number Diff line change
@@ -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)
39 changes: 24 additions & 15 deletions sidevm-quickjs/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,37 @@
<head>
<meta charset="UTF-8">
<title>Play with QuickJS</title>
<style>
textarea {
font-family: monospace;
font-size: 14px;
width: 600px;
height: 300px;
}
</style>
</head>

<body>
<div class="result" id="result">
<textarea id="input-code" style="width: 500px; height: 200px;">
async function main() {
const url = "https://httpbin.org/get";
console.log("Getting: ", url);
const response = await fetch(url);
console.log("Response: ", response.status);
const body = await response.text();
Sidevm.inspect("Body: ", body);
return body;
}
main()
.then(result => scriptOutput = result)
.catch(err => scriptOutput = err)
.finally(() => Sidevm.exit());
<textarea id="input-code">
async function main() {
const url = "https://httpbin.org/get";
console.log("Getting: ", url);
const response = await fetch(url);
console.log("Response: ", response.status);
const body = await response.text();
console.log("Body: ", body);
return body;
}
main()
.then(result => scriptOutput = result)
.catch(err => scriptOutput = err)
.finally(() => process.exit());
</textarea>
<br>
<button id="btn-run" disabled="true">Run</button>
<p>Output: <span id="output"></span></p>
<p>Output:</p>
<textarea id="output"></textarea>
</div>
</body>

Expand Down
36 changes: 25 additions & 11 deletions sidevm-quickjs/web/main.js
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit c0d8981

Please sign in to comment.