diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..4b9a2d9 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "printWidth": 120, + "singleQuote": true, + "trailingComma": "all" +} diff --git a/docs/hello-multiple/api.go b/docs/hello-multiple/api.go new file mode 100644 index 0000000..0a28937 --- /dev/null +++ b/docs/hello-multiple/api.go @@ -0,0 +1,27 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + + wasmhttp "github.com/nlepage/go-wasm-http-server/v2" +) + +var binaryName = "" + +func main() { + http.HandleFunc("/hello", func(res http.ResponseWriter, req *http.Request) { + res.Header().Add("Content-Type", "application/json") + if err := json.NewEncoder(res).Encode(map[string]string{ + "message": fmt.Sprintf("Hello from %s at path %s", binaryName, os.Getenv("WASM_HTTP_PATH")), + }); err != nil { + panic(err) + } + }) + + wasmhttp.Serve(nil) + + select {} +} diff --git a/docs/hello-multiple/api1.wasm b/docs/hello-multiple/api1.wasm new file mode 100755 index 0000000..73bb187 Binary files /dev/null and b/docs/hello-multiple/api1.wasm differ diff --git a/docs/hello-multiple/api2.wasm b/docs/hello-multiple/api2.wasm new file mode 100755 index 0000000..10cd874 Binary files /dev/null and b/docs/hello-multiple/api2.wasm differ diff --git a/docs/hello-multiple/build.sh b/docs/hello-multiple/build.sh new file mode 100755 index 0000000..495801a --- /dev/null +++ b/docs/hello-multiple/build.sh @@ -0,0 +1,3 @@ +#!/bin/sh +GOOS=js GOARCH=wasm go build -o api1.wasm --ldflags="-X 'main.binaryName=api1.wasm'" . +GOOS=js GOARCH=wasm go build -o api2.wasm --ldflags="-X 'main.binaryName=api2.wasm'" . diff --git a/docs/hello-multiple/index.html b/docs/hello-multiple/index.html new file mode 100644 index 0000000..6d3fec4 --- /dev/null +++ b/docs/hello-multiple/index.html @@ -0,0 +1,29 @@ + + + + go-wasm-http-server hello demo + + + + + + + diff --git a/docs/hello-multiple/sw.js b/docs/hello-multiple/sw.js new file mode 100644 index 0000000..9baf478 --- /dev/null +++ b/docs/hello-multiple/sw.js @@ -0,0 +1,15 @@ +importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.25.1/lib/wasm/wasm_exec.js'); +importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@master/sw.js'); + +addEventListener('install', (event) => { + event.waitUntil(caches.open('examples').then((cache) => cache.addAll(['api1.wasm', 'api2.wasm']))); +}); + +addEventListener('activate', (event) => { + event.waitUntil(clients.claim()); +}); + +registerWasmHTTPListener('api1.wasm', { base: 'api1/1/' }); +registerWasmHTTPListener('api1.wasm', { base: 'api1/2/' }); +registerWasmHTTPListener('api2.wasm', { base: 'api2/1/' }); +registerWasmHTTPListener('api2.wasm', { base: 'api2/2/' }); diff --git a/docs/hello-sse/api.wasm b/docs/hello-sse/api.wasm index 0b408ab..4092a6a 100755 Binary files a/docs/hello-sse/api.wasm and b/docs/hello-sse/api.wasm differ diff --git a/docs/hello-sse/index.html b/docs/hello-sse/index.html index f0831d5..abb58ee 100644 --- a/docs/hello-sse/index.html +++ b/docs/hello-sse/index.html @@ -3,47 +3,46 @@ go-wasm-http-server hello sse demo diff --git a/docs/hello-sse/sw.js b/docs/hello-sse/sw.js index b883512..9880758 100644 --- a/docs/hello-sse/sw.js +++ b/docs/hello-sse/sw.js @@ -1,14 +1,14 @@ -importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.23.1/misc/wasm/wasm_exec.js') -importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v2.2.1/sw.js') +importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.25.1/lib/wasm/wasm_exec.js'); +importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@master/sw.js'); -const wasm = 'api.wasm' +const wasm = 'api.wasm'; addEventListener('install', (event) => { - event.waitUntil(caches.open('examples').then((cache) => cache.add(wasm))) -}) + event.waitUntil(caches.open('examples').then((cache) => cache.add(wasm))); +}); addEventListener('activate', (event) => { - event.waitUntil(clients.claim()) -}) + event.waitUntil(clients.claim()); +}); -registerWasmHTTPListener(wasm, { base: 'api' }) +registerWasmHTTPListener(wasm, { base: 'api' }); diff --git a/docs/hello-state-keepalive/index.html b/docs/hello-state-keepalive/index.html index 53985c5..0dd7f54 100644 --- a/docs/hello-state-keepalive/index.html +++ b/docs/hello-state-keepalive/index.html @@ -3,30 +3,30 @@ go-wasm-http-server hello with state and keepalive demo - + diff --git a/docs/hello-state-keepalive/sw.js b/docs/hello-state-keepalive/sw.js index 4d8e820..81d774d 100644 --- a/docs/hello-state-keepalive/sw.js +++ b/docs/hello-state-keepalive/sw.js @@ -1,16 +1,16 @@ -importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.23.1/misc/wasm/wasm_exec.js') -importScripts('../sw.js') +importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.25.1/lib/wasm/wasm_exec.js'); +importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@master/sw.js'); -const wasm = '../hello-state/api.wasm' +const wasm = '../hello-state/api.wasm'; -addEventListener('install', event => { - event.waitUntil(caches.open('hello-state').then((cache) => cache.add(wasm))) -}) - -addEventListener('activate', event => { - event.waitUntil(clients.claim()) -}) +addEventListener('install', (event) => { + event.waitUntil(caches.open('examples').then((cache) => cache.add(wasm))); +}); -addEventListener('message', () => {}) +addEventListener('activate', (event) => { + event.waitUntil(clients.claim()); +}); -registerWasmHTTPListener(wasm, { base: 'api' }) +addEventListener('message', () => {}); + +registerWasmHTTPListener(wasm, { base: 'api' }); diff --git a/docs/hello-state/api.wasm b/docs/hello-state/api.wasm index b5787fe..719a7a3 100755 Binary files a/docs/hello-state/api.wasm and b/docs/hello-state/api.wasm differ diff --git a/docs/hello-state/index.html b/docs/hello-state/index.html index 7a2fc69..20c7bff 100644 --- a/docs/hello-state/index.html +++ b/docs/hello-state/index.html @@ -3,27 +3,27 @@ go-wasm-http-server hello with state demo - + diff --git a/docs/hello-state/sw.js b/docs/hello-state/sw.js index 6dde0e1..9880758 100644 --- a/docs/hello-state/sw.js +++ b/docs/hello-state/sw.js @@ -1,14 +1,14 @@ -importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.23.1/misc/wasm/wasm_exec.js') -importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v2.2.1/sw.js') +importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.25.1/lib/wasm/wasm_exec.js'); +importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@master/sw.js'); -const wasm = 'api.wasm' +const wasm = 'api.wasm'; addEventListener('install', (event) => { - event.waitUntil(caches.open('examples').then((cache) => cache.add(wasm))) -}) - -addEventListener('activate', (event) => { - event.waitUntil(clients.claim()) -}) + event.waitUntil(caches.open('examples').then((cache) => cache.add(wasm))); +}); -registerWasmHTTPListener(wasm, { base: 'api' }) +addEventListener('activate', (event) => { + event.waitUntil(clients.claim()); +}); + +registerWasmHTTPListener(wasm, { base: 'api' }); diff --git a/docs/hello/api.wasm b/docs/hello/api.wasm index 3232078..70705ad 100755 Binary files a/docs/hello/api.wasm and b/docs/hello/api.wasm differ diff --git a/docs/hello/index.html b/docs/hello/index.html index 2d8e116..7e0c320 100644 --- a/docs/hello/index.html +++ b/docs/hello/index.html @@ -3,27 +3,27 @@ go-wasm-http-server hello demo - + diff --git a/docs/hello/sw.js b/docs/hello/sw.js index b883512..9880758 100644 --- a/docs/hello/sw.js +++ b/docs/hello/sw.js @@ -1,14 +1,14 @@ -importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.23.1/misc/wasm/wasm_exec.js') -importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v2.2.1/sw.js') +importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.25.1/lib/wasm/wasm_exec.js'); +importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@master/sw.js'); -const wasm = 'api.wasm' +const wasm = 'api.wasm'; addEventListener('install', (event) => { - event.waitUntil(caches.open('examples').then((cache) => cache.add(wasm))) -}) + event.waitUntil(caches.open('examples').then((cache) => cache.add(wasm))); +}); addEventListener('activate', (event) => { - event.waitUntil(clients.claim()) -}) + event.waitUntil(clients.claim()); +}); -registerWasmHTTPListener(wasm, { base: 'api' }) +registerWasmHTTPListener(wasm, { base: 'api' }); diff --git a/docs/index.html b/docs/index.html index 042b1c1..e69878f 100644 --- a/docs/index.html +++ b/docs/index.html @@ -6,11 +6,40 @@

go-wasm-http-server examples

-

See README for more information.

+

+ See README for more + information. +

diff --git a/go.mod b/go.mod index 16684a8..1273ab9 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,9 @@ module github.com/nlepage/go-wasm-http-server/v2 -go 1.18 +go 1.22 require ( github.com/hack-pad/safejs v0.1.1 - github.com/nlepage/go-js-promise v1.0.0 - github.com/tmaxmax/go-sse v0.8.0 + github.com/nlepage/go-js-promise v1.1.0 + github.com/tmaxmax/go-sse v0.11.0 ) diff --git a/go.sum b/go.sum index 27a9218..77dd91f 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,6 @@ github.com/hack-pad/safejs v0.1.1 h1:d5qPO0iQ7h2oVtpzGnLExE+Wn9AtytxIfltcS2b9KD8= github.com/hack-pad/safejs v0.1.1/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio= -github.com/nlepage/go-js-promise v1.0.0 h1:K7OmJ3+0BgWJ2LfXchg2sI6RDr7AW/KWR8182epFwGQ= -github.com/nlepage/go-js-promise v1.0.0/go.mod h1:bdOP0wObXu34euibyK39K1hoBCtlgTKXGc56AGflaRo= -github.com/tmaxmax/go-sse v0.8.0 h1:pPpTgyyi1r7vG2o6icebnpGEh3ebcnBXqDWkb7aTofs= -github.com/tmaxmax/go-sse v0.8.0/go.mod h1:HLoxqxdH+7oSUItjtnpxjzJedfr/+Rrm/dNWBcTxJFM= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= +github.com/nlepage/go-js-promise v1.1.0 h1:BfvywsIMo4cpNOKyoReBWkxEW8f9HMwXqGc45wEKPRs= +github.com/nlepage/go-js-promise v1.1.0/go.mod h1:bdOP0wObXu34euibyK39K1hoBCtlgTKXGc56AGflaRo= +github.com/tmaxmax/go-sse v0.11.0 h1:nogmJM6rJUoOLoAwEKeQe5XlVpt9l7N82SS1jI7lWFg= +github.com/tmaxmax/go-sse v0.11.0/go.mod h1:u/2kZQR1tyngo1lKaNCj1mJmhXGZWS1Zs5yiSOD+Eg8= diff --git a/serve.go b/serve.go index f4869d5..a45a842 100644 --- a/serve.go +++ b/serve.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net/http" + "os" "strings" "syscall/js" @@ -21,10 +22,8 @@ func Serve(handler http.Handler) (func(), error) { h = http.DefaultServeMux } - prefix, err := wasmhttp.GetString("path") - if err != nil { - return nil, err - } + path := os.Getenv("WASM_HTTP_PATH") + prefix := path for strings.HasSuffix(prefix, "/") { prefix = strings.TrimSuffix(prefix, "/") @@ -78,7 +77,7 @@ func Serve(handler http.Handler) (func(), error) { return nil, err } - if _, err = wasmhttp.Call("setHandler", handlerValue); err != nil { + if _, err = wasmhttp.Call("setHandler", path, handlerValue); err != nil { return nil, err } diff --git a/sw.js b/sw.js index 11e0ae0..c87c07e 100644 --- a/sw.js +++ b/sw.js @@ -1,42 +1,94 @@ -function registerWasmHTTPListener(wasm, { base, cacheName, passthrough, args = [] } = {}) { - let path = new URL(registration.scope).pathname - if (base && base !== '') path = `${trimEnd(path, '/')}/${trimStart(base, '/')}` +class WasmHTTP { + #listeners = new Map(); - const handlerPromise = new Promise(setHandler => { - self.wasmhttp = { - path, - setHandler, + setHandler(path, handler) { + const listener = this.#listeners.get(path); + if (!listener) { + throw new Error(`no listener for path "${path}"`); } - }) - const go = new Go() - go.argv = [wasm, ...args] - const source = cacheName - ? caches.open(cacheName).then((cache) => cache.match(wasm)).then((response) => response ?? fetch(wasm)) - : caches.match(wasm).then(response => (response) ?? fetch(wasm)) - WebAssembly.instantiateStreaming(source, go.importObject).then(({ instance }) => go.run(instance)) + listener.setHandler(handler); + } - addEventListener('fetch', e => { - if (passthrough?.(e.request)) { - e.respondWith(fetch(e.request)) + handle(e) { + const { pathname } = new URL(e.request.url); + + for (const [path, listener] of this.#listeners) { + if (pathname.startsWith(path)) { + listener.handle(e); return; + } + } + } + + addListener({ wasm, base = '', cacheName, passthrough, args = [], env = {} }) { + const path = new URL(trimStart(base, '/'), registration.scope).pathname; + + if (this.#listeners.has(path)) { + throw new Error(`a listener is already registered for path "${path}"`); } - const { pathname } = new URL(e.request.url) - if (!pathname.startsWith(path)) return + this.#listeners.set(path, new WasmHTTPListener({ wasm, path, cacheName, passthrough, args, env })); + } +} - e.respondWith(handlerPromise.then(handler => handler(e.request))) - }) +class WasmHTTPListener { + #handlerPromise; + #resolveHandler; + #passthrough; + + constructor({ wasm, path, cacheName, passthrough, args, env }) { + this.#handlerPromise = new Promise((resolve) => { + this.#resolveHandler = resolve; + }); + + this.#passthrough = passthrough; + + this.#run({ wasm, path, cacheName, passthrough, args, env }); + } + + async #run({ wasm, path, cacheName, args, env }) { + try { + const go = new Go(); + go.argv = [wasm, ...args]; + go.env = { ...env, WASM_HTTP_PATH: path }; + + const cache = cacheName ? await caches.open(cacheName) : caches; + const source = (await cache.match(wasm)) ?? (await fetch(wasm)); + + const { instance } = await WebAssembly.instantiateStreaming(source, go.importObject); + + await go.run(instance); + } catch (err) { + console.error(`error while running ${wasm} for path "${path}"`, err); + } + } + + setHandler(handler) { + this.#resolveHandler(handler); + } + + handle(e) { + if (this.#passthrough?.(e.request)) return; + + // FIXME return 500 if run has thrown + + e.respondWith(this.#handlerPromise.then((handler) => handler(e.request))); + } +} + +self.wasmhttp = new WasmHTTP(); + +addEventListener('fetch', (e) => { + self.wasmhttp.handle(e); +}); + +function registerWasmHTTPListener(wasm, { base, cacheName, passthrough, args, env } = {}) { + self.wasmhttp.addListener({ wasm, base, cacheName, passthrough, args, env }); } function trimStart(s, c) { - let r = s - while (r.startsWith(c)) r = r.slice(c.length) - return r -} - -function trimEnd(s, c) { - let r = s - while (r.endsWith(c)) r = r.slice(0, -c.length) - return r + let r = s; + while (r.startsWith(c)) r = r.slice(c.length); + return r; }