mirror of
https://github.com/nlepage/go-wasm-http-server.git
synced 2026-01-12 10:09:12 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c561826125 | ||
|
|
897626b7d1 | ||
|
|
c93d379f20 | ||
|
|
98257b470a | ||
|
|
b2bd8679fd | ||
|
|
770d49a106 | ||
|
|
e8555180f7 | ||
|
|
8abad8cb77 | ||
|
|
74cbaf89b5 |
@@ -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.0/sw.js')
|
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v2.0.3/sw.js')
|
||||||
|
|
||||||
registerWasmHTTPListener('path/to/server.wasm')
|
registerWasmHTTPListener('path/to/server.wasm')
|
||||||
```
|
```
|
||||||
|
|||||||
Binary file not shown.
@@ -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>
|
||||||
|
|||||||
@@ -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.0/sw.js')
|
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v2.0.3/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' })
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.0/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.
@@ -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.0/sw.js')
|
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v2.0.3/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.
@@ -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.0/sw.js')
|
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v2.0.3/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' })
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
wasmhttp "github.com/nlepage/go-wasm-http-server/v2/v2"
|
wasmhttp "github.com/nlepage/go-wasm-http-server/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Demonstrates a simple hello JSON service.
|
// Demonstrates a simple hello JSON service.
|
||||||
|
|||||||
@@ -78,8 +78,8 @@ func (rs *Writer) Write(b []byte) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rs *Writer) Close() error {
|
func (rs *Writer) Close() error {
|
||||||
rs.controller.Call("close")
|
_, err := rs.controller.Call("close")
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *Writer) Context() context.Context {
|
func (rs *Writer) Context() context.Context {
|
||||||
|
|||||||
@@ -73,6 +73,14 @@ func (v Value) IndexString(i int) (string, error) {
|
|||||||
return safejs.Value(sv).String()
|
return safejs.Value(sv).String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v Value) IsNull() bool {
|
||||||
|
return safejs.Value(v).IsNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Value) IsUndefined() bool {
|
||||||
|
return safejs.Value(v).IsUndefined()
|
||||||
|
}
|
||||||
|
|
||||||
func (v Value) New(args ...any) (Value, error) {
|
func (v Value) New(args ...any) (Value, error) {
|
||||||
args = toJSValue(args).([]any)
|
args = toJSValue(args).([]any)
|
||||||
r, err := safejs.Value(v).New(args...)
|
r, err := safejs.Value(v).New(args...)
|
||||||
|
|||||||
48
request.go
48
request.go
@@ -1,10 +1,12 @@
|
|||||||
package wasmhttp
|
package wasmhttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
@@ -13,16 +15,6 @@ import (
|
|||||||
func Request(uvalue js.Value) (*http.Request, error) {
|
func Request(uvalue js.Value) (*http.Request, error) {
|
||||||
value := safejs.Safe(uvalue)
|
value := safejs.Safe(uvalue)
|
||||||
|
|
||||||
body, err := value.Get("body")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err := body.Call("getReader")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
method, err := value.GetString("method")
|
method, err := value.GetString("method")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -33,10 +25,44 @@ func Request(uvalue js.Value) (*http.Request, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body, err := value.Get("body")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var bodyReader io.Reader
|
||||||
|
|
||||||
|
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")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyReader = readablestream.NewReader(r)
|
||||||
|
}
|
||||||
|
|
||||||
req := httptest.NewRequest(
|
req := httptest.NewRequest(
|
||||||
method,
|
method,
|
||||||
url,
|
url,
|
||||||
readablestream.NewReader(r),
|
bodyReader,
|
||||||
)
|
)
|
||||||
|
|
||||||
headers, err := value.Get("headers")
|
headers, err := value.Get("headers")
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ func (r *response) WriteHeader(code int) {
|
|||||||
checkWriteHeaderCode(code)
|
checkWriteHeaderCode(code)
|
||||||
|
|
||||||
init, err := safejs.ValueOf(map[string]any{
|
init, err := safejs.ValueOf(map[string]any{
|
||||||
"code": code,
|
"status": code,
|
||||||
"headers": r.headerValue(),
|
"headers": r.headerValue(),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
10
serve.go
10
serve.go
@@ -57,19 +57,13 @@ func Serve(handler http.Handler) (func(), error) {
|
|||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
var errStr string
|
res.WriteError(fmt.Sprintf("%+v", r))
|
||||||
if err, ok := r.(error); ok {
|
|
||||||
errStr = err.Error()
|
|
||||||
} else {
|
|
||||||
errStr = fmt.Sprintf("%s", r)
|
|
||||||
}
|
|
||||||
res.WriteError(errStr)
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
req, err := Request(safejs.Unsafe(args[0]))
|
req, err := Request(safejs.Unsafe(args[0]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.WriteError(err.Error())
|
res.WriteError(fmt.Sprintf("%+v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
7
sw.js
7
sw.js
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user