10 Commits

Author SHA1 Message Date
Nicolas Lepage
3cf36c41e2 chore: v2.0.5 2024-12-09 23:12:31 +01:00
Nicolas Lepage
30a6ef67f9 fix: avoids closing cancelled readablestream 2024-12-09 23:11:17 +01:00
Nicolas Lepage
669f82020d chore: rebuilds hello-sse example 2024-12-09 22:46:00 +01:00
Nicolas Lepage
c94dcd965d chore: v2.0.4 2024-11-27 22:33:50 +01:00
Nicolas Lepage
2d786bdb14 fix: listen for ReadableStream cancellation 2024-11-27 22:32:19 +01:00
Nicolas Lepage
c561826125 chore: v2.0.3 2024-10-17 01:12:34 +02:00
Nicolas Lepage
897626b7d1 fix: Firefox does not have request.body ReadableStream 2024-10-17 01:11:41 +02:00
Nicolas Lepage
c93d379f20 feat: improves examples 2024-10-16 14:01:11 +02:00
Nicolas Lepage
98257b470a feat: allow using cache for wasm binary 2024-10-16 14:01:11 +02:00
Nicolas Lepage
b2bd8679fd fix: error management in readablestream writer close method 2024-10-16 13:19:16 +02:00
14 changed files with 106 additions and 35 deletions

View File

@@ -97,7 +97,7 @@ Create a ServiceWorker file with the following code:
📄 `sw.js` 📄 `sw.js`
```js ```js
importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.18.4/misc/wasm/wasm_exec.js') importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.18.4/misc/wasm/wasm_exec.js')
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v2.0.2/sw.js') importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v2.0.5/sw.js')
registerWasmHTTPListener('path/to/server.wasm') registerWasmHTTPListener('path/to/server.wasm')
``` ```

Binary file not shown.

View File

@@ -1,32 +1,57 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>go-wasm-http-server hello demo</title> <title>go-wasm-http-server hello sse demo</title>
<script> <script>
navigator.serviceWorker.register('sw.js') navigator.serviceWorker.register('sw.js')
.then(registration => { .then(registration => {
const serviceWorker = registration.installing ?? registration.waiting ?? registration.active const serviceWorker = registration.installing ?? registration.waiting ?? registration.active
if (serviceWorker.state === 'activated') { if (serviceWorker.state === 'activated') {
startEventSource() document.querySelector('#open-button').disabled = false
} else { } else {
serviceWorker.addEventListener('statechange', e => { serviceWorker.addEventListener('statechange', e => {
if (e.target.state === 'activated') startEventSource() if (e.target.state === 'activated') {
document.querySelector('#open-button').disabled = false
}
}) })
} }
}) })
function startEventSource() { window.addEventListener('DOMContentLoaded', () => {
const es = new EventSource('/api/events') let es;
es.addEventListener('ping', (e) => {
const p = document.createElement('p') document.querySelector('#open-button').addEventListener('click', () => {
p.textContent = `ping: data=${e.data}` if (es && es.readyState === es.OPEN) return
document.body.append(p) es = new EventSource('api/events')
es.addEventListener('ping', (e) => {
const li = document.createElement('li')
li.textContent = `ping: data=${e.data}`
document.querySelector('ul').append(li)
})
}) })
document.querySelector('#close-button').addEventListener('click', () => {
if (!es) return
es.close()
})
document.querySelector('#clear-button').addEventListener('click', () => {
document.querySelector('ul').innerHTML = ''
})
window.addEventListener('unload', () => { window.addEventListener('unload', () => {
if (!es) return
es.close() es.close()
}) })
} })
</script> </script>
</head> </head>
<body></body> <body>
<p>
<button id="open-button" disabled>Open event source</button>
<button id="close-button">Close event source</button>
<button id="clear-button">Clear events</button>
</p>
<ul></ul>
</body>
</html> </html>

View File

@@ -1,12 +1,14 @@
importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.23.1/misc/wasm/wasm_exec.js') 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.0.2/sw.js') importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v2.0.5/sw.js')
const wasm = 'api.wasm'
addEventListener('install', (event) => { addEventListener('install', (event) => {
event.waitUntil(skipWaiting()) event.waitUntil(caches.open('examples').then((cache) => cache.add(wasm)))
}) })
addEventListener('activate', event => { addEventListener('activate', (event) => {
event.waitUntil(clients.claim()) event.waitUntil(clients.claim())
}) })
registerWasmHTTPListener('api.wasm', { base: 'api' }) registerWasmHTTPListener(wasm, { base: 'api' })

View File

@@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>go-wasm-http-server hello with state demo</title> <title>go-wasm-http-server hello with state and keepalive demo</title>
<script> <script>
navigator.serviceWorker.register('sw.js').then(registration => { navigator.serviceWorker.register('sw.js').then(registration => {
const sw = registration.active ?? registration.installing ?? registration.waiting const sw = registration.active ?? registration.installing ?? registration.waiting

View File

@@ -1,8 +1,10 @@
importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.23.1/misc/wasm/wasm_exec.js') 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.0.2/sw.js') importScripts('../sw.js')
const wasm = '../hello-state/api.wasm'
addEventListener('install', event => { addEventListener('install', event => {
event.waitUntil(skipWaiting()) event.waitUntil(caches.open('hello-state').then((cache) => cache.add(wasm)))
}) })
addEventListener('activate', event => { addEventListener('activate', event => {
@@ -11,4 +13,4 @@ addEventListener('activate', event => {
addEventListener('message', () => {}) addEventListener('message', () => {})
registerWasmHTTPListener('../hello-state/api.wasm', { base: 'api' }) registerWasmHTTPListener(wasm, { base: 'api' })

Binary file not shown.

View File

@@ -1,12 +1,14 @@
importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.23.1/misc/wasm/wasm_exec.js') 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.0.2/sw.js') importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v2.0.5/sw.js')
const wasm = 'api.wasm'
addEventListener('install', (event) => { addEventListener('install', (event) => {
event.waitUntil(skipWaiting()) event.waitUntil(caches.open('examples').then((cache) => cache.add(wasm)))
}) })
addEventListener('activate', event => { addEventListener('activate', (event) => {
event.waitUntil(clients.claim()) event.waitUntil(clients.claim())
}) })
registerWasmHTTPListener('api.wasm', { base: 'api' }) registerWasmHTTPListener(wasm, { base: 'api' })

Binary file not shown.

View File

@@ -1,12 +1,14 @@
importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.23.1/misc/wasm/wasm_exec.js') 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.0.2/sw.js') importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v2.0.5/sw.js')
const wasm = 'api.wasm'
addEventListener('install', (event) => { addEventListener('install', (event) => {
event.waitUntil(skipWaiting()) event.waitUntil(caches.open('examples').then((cache) => cache.add(wasm)))
}) })
addEventListener('activate', event => { addEventListener('activate', (event) => {
event.waitUntil(clients.claim()) event.waitUntil(clients.claim())
}) })
registerWasmHTTPListener('api.wasm', { base: 'api' }) registerWasmHTTPListener(wasm, { base: 'api' })

View File

@@ -12,11 +12,14 @@ type Writer struct {
Value safejs.Value Value safejs.Value
controller safejs.Value controller safejs.Value
ctx context.Context ctx context.Context
cancelled bool
} }
var _ io.WriteCloser = (*Writer)(nil) var _ io.WriteCloser = (*Writer)(nil)
func NewWriter() (*Writer, error) { func NewWriter() (*Writer, error) {
var rs *Writer
var start safejs.Func var start safejs.Func
var controller safejs.Value var controller safejs.Value
@@ -34,6 +37,7 @@ func NewWriter() (*Writer, error) {
cancel, err = safejs.FuncOf(func(_ safejs.Value, _ []safejs.Value) any { cancel, err = safejs.FuncOf(func(_ safejs.Value, _ []safejs.Value) any {
defer cancel.Release() defer cancel.Release()
rs.cancelled = true
cancelCtx() cancelCtx()
return nil return nil
}) })
@@ -54,14 +58,20 @@ func NewWriter() (*Writer, error) {
return nil, err return nil, err
} }
return &Writer{ rs = &Writer{
Value: value, Value: value,
controller: controller, controller: controller,
ctx: ctx, ctx: ctx,
}, nil }
return rs, nil
} }
func (rs *Writer) Write(b []byte) (int, error) { func (rs *Writer) Write(b []byte) (int, error) {
if rs.cancelled {
return 0, nil
}
chunk, err := jstype.Uint8Array.New(len(b)) // FIXME reuse same Uint8Array? chunk, err := jstype.Uint8Array.New(len(b)) // FIXME reuse same Uint8Array?
if err != nil { if err != nil {
return 0, err return 0, err
@@ -78,8 +88,12 @@ func (rs *Writer) Write(b []byte) (int, error) {
} }
func (rs *Writer) Close() error { func (rs *Writer) Close() error {
rs.controller.Call("close") if rs.cancelled {
return nil return nil
}
_, err := rs.controller.Call("close")
return err
} }
func (rs *Writer) Context() context.Context { func (rs *Writer) Context() context.Context {

View File

@@ -6,6 +6,7 @@ import (
"net/http/httptest" "net/http/httptest"
"syscall/js" "syscall/js"
promise "github.com/nlepage/go-js-promise"
"github.com/nlepage/go-wasm-http-server/v2/internal/readablestream" "github.com/nlepage/go-wasm-http-server/v2/internal/readablestream"
"github.com/nlepage/go-wasm-http-server/v2/internal/safejs" "github.com/nlepage/go-wasm-http-server/v2/internal/safejs"
) )
@@ -30,7 +31,26 @@ func Request(uvalue js.Value) (*http.Request, error) {
} }
var bodyReader io.Reader var bodyReader io.Reader
if !body.IsUndefined() && !body.IsNull() {
if !body.IsNull() {
// WORKAROUND: Firefox does not have request.body ReadableStream
if body.IsUndefined() {
blobp, err := value.Call("blob")
if err != nil {
return nil, err
}
blob, err := promise.Await(safejs.Unsafe(blobp))
if err != nil {
return nil, err
}
body, err = safejs.Safe(blob).Call("stream")
if err != nil {
return nil, err
}
}
r, err := body.Call("getReader") r, err := body.Call("getReader")
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -136,6 +136,7 @@ func (r *response) Context() context.Context {
func (r *response) WriteError(str string) { func (r *response) WriteError(str string) {
slog.Error(str) slog.Error(str)
if !r.wroteHeader { if !r.wroteHeader {
r.Header().Set("Content-Type", "text/plain")
r.WriteHeader(500) r.WriteHeader(500)
_, _ = r.WriteString(str) _, _ = r.WriteString(str)
} }

7
sw.js
View File

@@ -1,4 +1,4 @@
function registerWasmHTTPListener(wasm, { base, args = [] } = {}) { function registerWasmHTTPListener(wasm, { base, cacheName, args = [] } = {}) {
let path = new URL(registration.scope).pathname let path = new URL(registration.scope).pathname
if (base && base !== '') path = `${trimEnd(path, '/')}/${trimStart(base, '/')}` if (base && base !== '') path = `${trimEnd(path, '/')}/${trimStart(base, '/')}`
@@ -11,7 +11,10 @@ function registerWasmHTTPListener(wasm, { base, args = [] } = {}) {
const go = new Go() const go = new Go()
go.argv = [wasm, ...args] go.argv = [wasm, ...args]
WebAssembly.instantiateStreaming(fetch(wasm), go.importObject).then(({ instance }) => go.run(instance)) 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))
addEventListener('fetch', e => { addEventListener('fetch', e => {
const { pathname } = new URL(e.request.url) const { pathname } = new URL(e.request.url)