Skip to content

Commit

Permalink
[FEATURE] Use ReadableStream for Response (#15)
Browse files Browse the repository at this point in the history
* feat: uses ReadableStream for Response

* chore: rebuilds other examples
  • Loading branch information
nlepage authored Oct 14, 2024
1 parent 163b497 commit 5ec4a8d
Show file tree
Hide file tree
Showing 21 changed files with 634 additions and 83 deletions.
34 changes: 34 additions & 0 deletions docs/hello-sse/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package main

import (
"net/http"
"time"

"github.com/tmaxmax/go-sse"

wasmhttp "github.com/nlepage/go-wasm-http-server"
)

func main() {
s := &sse.Server{}
t, _ := sse.NewType("ping")

go func() {
m := &sse.Message{
Type: t,
}
m.AppendData("Hello world")

for range time.Tick(time.Second) {
_ = s.Publish(m)
}
}()

http.Handle("/events", s)

if _, err := wasmhttp.Serve(nil); err != nil {
panic(err)
}

select {}
}
Binary file added docs/hello-sse/api.wasm
Binary file not shown.
32 changes: 32 additions & 0 deletions docs/hello-sse/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<title>go-wasm-http-server hello demo</title>
<script>
navigator.serviceWorker.register('sw.js')
.then(registration => {
const serviceWorker = registration.installing ?? registration.waiting ?? registration.active
if (serviceWorker.state === 'activated') {
startEventSource()
} else {
serviceWorker.addEventListener('statechange', e => {
if (e.target.state === 'activated') startEventSource()
})
}
})

function startEventSource() {
const es = new EventSource('/api/events')
es.addEventListener('ping', (e) => {
const p = document.createElement('p')
p.textContent = `ping: data=${e.data}`
document.body.append(p)
})
window.addEventListener('unload', () => {
es.close()
})
}
</script>
</head>
<body></body>
</html>
12 changes: 12 additions & 0 deletions docs/hello-sse/sw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
importScripts('https://cdn.jsdelivr.net/gh/golang/[email protected]/misc/wasm/wasm_exec.js')
importScripts('https://cdn.jsdelivr.net/gh/nlepage/[email protected]/sw.js')

addEventListener('install', (event) => {
event.waitUntil(skipWaiting())
})

addEventListener('activate', event => {
event.waitUntil(clients.claim())
})

registerWasmHTTPListener('api.wasm', { base: 'api' })
Binary file modified docs/hello-state/api.wasm
Binary file not shown.
2 changes: 1 addition & 1 deletion docs/hello-state/sw.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
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.23.1/misc/wasm/wasm_exec.js')
importScripts('https://cdn.jsdelivr.net/gh/nlepage/[email protected]/sw.js')

addEventListener('install', (event) => {
Expand Down
Binary file modified docs/hello/api.wasm
Binary file not shown.
2 changes: 1 addition & 1 deletion docs/hello/sw.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
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.23.1/misc/wasm/wasm_exec.js')
importScripts('https://cdn.jsdelivr.net/gh/nlepage/[email protected]/sw.js')

addEventListener('install', (event) => {
Expand Down
6 changes: 5 additions & 1 deletion example_json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ func Example_json() {
}
})

defer wasmhttp.Serve(nil)()
release, err := wasmhttp.Serve(nil)
if err != nil {
panic(err)
}
defer release()

// Wait for webpage event or use empty select{}
}
8 changes: 6 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
module github.com/nlepage/go-wasm-http-server

go 1.13
go 1.18

require github.com/nlepage/go-js-promise v1.0.0
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
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +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=
13 changes: 13 additions & 0 deletions internal/jstype/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package jstype

import (
"syscall/js"

"github.com/nlepage/go-wasm-http-server/internal/safejs"
)

var (
ReadableStream = safejs.Safe(js.Global().Get("ReadableStream"))
Response = safejs.Safe(js.Global().Get("Response"))
Uint8Array = safejs.Safe(js.Global().Get("Uint8Array"))
)
87 changes: 87 additions & 0 deletions internal/readablestream/readable_stream.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package readablestream

import (
"context"
"io"

"github.com/nlepage/go-wasm-http-server/internal/jstype"
"github.com/nlepage/go-wasm-http-server/internal/safejs"
)

type Writer struct {
Value safejs.Value
controller safejs.Value
ctx context.Context
}

var _ io.WriteCloser = (*Writer)(nil)

func NewWriter() (*Writer, error) {
var start safejs.Func
var controller safejs.Value

start, err := safejs.FuncOf(func(_ safejs.Value, args []safejs.Value) any {
defer start.Release()
controller = args[0]
return nil
})
if err != nil {
return nil, err
}

var cancel safejs.Func
ctx, cancelCtx := context.WithCancel(context.Background())

cancel, err = safejs.FuncOf(func(_ safejs.Value, _ []safejs.Value) any {
defer cancel.Release()
cancelCtx()
return nil
})
if err != nil {
return nil, err
}

source, err := safejs.ValueOf(map[string]any{
"start": safejs.Unsafe(start.Value()),
"cancel": safejs.Unsafe(cancel.Value()),
})
if err != nil {
return nil, err
}

value, err := jstype.ReadableStream.New(source)
if err != nil {
return nil, err
}

return &Writer{
Value: value,
controller: controller,
ctx: ctx,
}, nil
}

func (rs *Writer) Write(b []byte) (int, error) {
chunk, err := jstype.Uint8Array.New(len(b)) // FIXME reuse same Uint8Array?
if err != nil {
return 0, err
}

n, err := safejs.CopyBytesToJS(chunk, b)
if err != nil {
return 0, err
}

_, err = rs.controller.Call("enqueue", chunk)

return n, err
}

func (rs *Writer) Close() error {
rs.controller.Call("close")
return nil
}

func (rs *Writer) Context() context.Context {
return rs.ctx
}
11 changes: 11 additions & 0 deletions internal/safejs/bytes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package safejs

import "github.com/hack-pad/safejs"

func CopyBytesToGo(dst []byte, src Value) (int, error) {
return safejs.CopyBytesToGo(dst, safejs.Value(src))
}

func CopyBytesToJS(dst Value, src []byte) (int, error) {
return safejs.CopyBytesToJS(safejs.Value(dst), src)
}
26 changes: 26 additions & 0 deletions internal/safejs/func.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package safejs

import (
"github.com/hack-pad/safejs"
)

type Func safejs.Func

func FuncOf(fn func(this Value, args []Value) any) (Func, error) {
r, err := safejs.FuncOf(func(this safejs.Value, args []safejs.Value) any {
args2 := make([]Value, len(args))
for i, v := range args {
args2[i] = Value(v)
}
return fn(Value(this), []Value(args2))
})
return Func(r), err
}

func (f Func) Release() {
safejs.Func(f).Release()
}

func (f Func) Value() Value {
return Value(safejs.Func(f).Value())
}
103 changes: 103 additions & 0 deletions internal/safejs/value.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package safejs

import (
"syscall/js"

"github.com/hack-pad/safejs"
)

type Value safejs.Value

func Safe(v js.Value) Value {
return Value(safejs.Safe(v))
}

func Unsafe(v Value) js.Value {
return safejs.Unsafe(safejs.Value(v))
}

func ValueOf(value any) (Value, error) {
v, err := safejs.ValueOf(value)
return Value(v), err
}

func (v Value) Call(m string, args ...any) (Value, error) {
args = toJSValue(args).([]any)
r, err := safejs.Value(v).Call(m, args...)
return Value(r), err
}

func (v Value) Get(p string) (Value, error) {
r, err := safejs.Value(v).Get(p)
return Value(r), err
}

func (v Value) GetBool(p string) (bool, error) {
bv, err := v.Get(p)
if err != nil {
return false, err
}

return safejs.Value(bv).Bool()
}

func (v Value) GetInt(p string) (int, error) {
iv, err := v.Get(p)
if err != nil {
return 0, err
}

return safejs.Value(iv).Int()
}

func (v Value) GetString(p string) (string, error) {
sv, err := v.Get(p)
if err != nil {
return "", err
}

return safejs.Value(sv).String()
}

func (v Value) Index(i int) (Value, error) {
r, err := safejs.Value(v).Index(i)
return Value(r), err
}

func (v Value) IndexString(i int) (string, error) {
sv, err := v.Index(i)
if err != nil {
return "", err
}

return safejs.Value(sv).String()
}

func (v Value) New(args ...any) (Value, error) {
args = toJSValue(args).([]any)
r, err := safejs.Value(v).New(args...)
return Value(r), err
}

func toJSValue(jsValue any) any {
switch value := jsValue.(type) {
case Value:
return safejs.Value(value)
case Func:
return safejs.Func(value)
case map[string]any:
newValue := make(map[string]any)
for mapKey, mapValue := range value {
newValue[mapKey] = toJSValue(mapValue)
}
return newValue
case []any:
newValue := make([]any, len(value))
for i, arg := range value {
newValue[i] = toJSValue(arg)
}
return newValue
default:
return jsValue
}
}
2 changes: 0 additions & 2 deletions package.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
// Package wasmhttp (github.com/nlepage/go-wasm-http-server) allows to create a WebAssembly Go HTTP Server embedded in a ServiceWorker.
//
// It is a subset of the full solution, a full usage is available on the github repository: https://github.com/nlepage/go-wasm-http-server
package wasmhttp
Loading

0 comments on commit 5ec4a8d

Please sign in to comment.