mirror of
https://github.com/nlepage/go-wasm-http-server.git
synced 2025-03-10 17:29:10 +00:00
[FEATURE] Use ReadableStream for Response (#15)
* feat: uses ReadableStream for Response * chore: rebuilds other examples
This commit is contained in:
parent
163b49702b
commit
5ec4a8d7e8
34
docs/hello-sse/api.go
Normal file
34
docs/hello-sse/api.go
Normal file
@ -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 {}
|
||||||
|
}
|
BIN
docs/hello-sse/api.wasm
Executable file
BIN
docs/hello-sse/api.wasm
Executable file
Binary file not shown.
32
docs/hello-sse/index.html
Normal file
32
docs/hello-sse/index.html
Normal file
@ -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
docs/hello-sse/sw.js
Normal file
12
docs/hello-sse/sw.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
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@v1.1.0/sw.js')
|
||||||
|
|
||||||
|
addEventListener('install', (event) => {
|
||||||
|
event.waitUntil(skipWaiting())
|
||||||
|
})
|
||||||
|
|
||||||
|
addEventListener('activate', event => {
|
||||||
|
event.waitUntil(clients.claim())
|
||||||
|
})
|
||||||
|
|
||||||
|
registerWasmHTTPListener('api.wasm', { base: 'api' })
|
Binary file not shown.
@ -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/go-wasm-http-server@v1.1.0/sw.js')
|
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v1.1.0/sw.js')
|
||||||
|
|
||||||
addEventListener('install', (event) => {
|
addEventListener('install', (event) => {
|
||||||
|
Binary file not shown.
@ -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/go-wasm-http-server@v1.1.0/sw.js')
|
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v1.1.0/sw.js')
|
||||||
|
|
||||||
addEventListener('install', (event) => {
|
addEventListener('install', (event) => {
|
||||||
|
@ -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{}
|
// Wait for webpage event or use empty select{}
|
||||||
}
|
}
|
||||||
|
8
go.mod
8
go.mod
@ -1,5 +1,9 @@
|
|||||||
module github.com/nlepage/go-wasm-http-server
|
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
go.sum
4
go.sum
@ -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 h1:K7OmJ3+0BgWJ2LfXchg2sI6RDr7AW/KWR8182epFwGQ=
|
||||||
github.com/nlepage/go-js-promise v1.0.0/go.mod h1:bdOP0wObXu34euibyK39K1hoBCtlgTKXGc56AGflaRo=
|
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
internal/jstype/types.go
Normal file
13
internal/jstype/types.go
Normal file
@ -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
internal/readablestream/readable_stream.go
Normal file
87
internal/readablestream/readable_stream.go
Normal file
@ -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
internal/safejs/bytes.go
Normal file
11
internal/safejs/bytes.go
Normal file
@ -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
internal/safejs/func.go
Normal file
26
internal/safejs/func.go
Normal file
@ -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
internal/safejs/value.go
Normal file
103
internal/safejs/value.go
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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.
|
// 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
|
package wasmhttp
|
||||||
|
94
request.go
94
request.go
@ -7,29 +7,91 @@ import (
|
|||||||
"syscall/js"
|
"syscall/js"
|
||||||
|
|
||||||
promise "github.com/nlepage/go-js-promise"
|
promise "github.com/nlepage/go-js-promise"
|
||||||
|
|
||||||
|
"github.com/nlepage/go-wasm-http-server/internal/jstype"
|
||||||
|
"github.com/nlepage/go-wasm-http-server/internal/safejs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Request builds and returns the equivalent http.Request
|
// Request builds and returns the equivalent http.Request
|
||||||
func Request(r js.Value) *http.Request {
|
func Request(ur js.Value) (*http.Request, error) {
|
||||||
jsBody := js.Global().Get("Uint8Array").New(promise.Await(r.Call("arrayBuffer")))
|
r := safejs.Safe(ur)
|
||||||
body := make([]byte, jsBody.Get("length").Int())
|
|
||||||
js.CopyBytesToGo(body, jsBody)
|
ab, err := r.Call("arrayBuffer")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
u8a, err := jstype.Uint8Array.New(promise.Await(safejs.Unsafe(ab)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
l, err := u8a.GetInt("length")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, l)
|
||||||
|
|
||||||
|
_, err = safejs.CopyBytesToGo(b, u8a)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
method, err := r.GetString("method")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
url, err := r.GetString("url")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
req := httptest.NewRequest(
|
req := httptest.NewRequest(
|
||||||
r.Get("method").String(),
|
method,
|
||||||
r.Get("url").String(),
|
url,
|
||||||
bytes.NewBuffer(body),
|
bytes.NewReader(b),
|
||||||
)
|
)
|
||||||
|
|
||||||
headersIt := r.Get("headers").Call("entries")
|
headers, err := r.Get("headers")
|
||||||
for {
|
if err != nil {
|
||||||
e := headersIt.Call("next")
|
return nil, err
|
||||||
if e.Get("done").Bool() {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
v := e.Get("value")
|
|
||||||
req.Header.Set(v.Index(0).String(), v.Index(1).String())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return req
|
headersIt, err := headers.Call("entries")
|
||||||
|
for {
|
||||||
|
e, err := headersIt.Call("next")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
done, err := e.GetBool("done")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if done {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := e.Get("value")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := v.IndexString(0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := v.IndexString(1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
}
|
}
|
||||||
|
174
response.go
Normal file
174
response.go
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
package wasmhttp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"syscall/js"
|
||||||
|
|
||||||
|
promise "github.com/nlepage/go-js-promise"
|
||||||
|
|
||||||
|
"github.com/nlepage/go-wasm-http-server/internal/jstype"
|
||||||
|
"github.com/nlepage/go-wasm-http-server/internal/readablestream"
|
||||||
|
"github.com/nlepage/go-wasm-http-server/internal/safejs"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Response interface {
|
||||||
|
http.ResponseWriter
|
||||||
|
io.StringWriter
|
||||||
|
http.Flusher
|
||||||
|
io.Closer
|
||||||
|
Context() context.Context
|
||||||
|
WriteError(string)
|
||||||
|
JSValue() js.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
type response struct {
|
||||||
|
header http.Header
|
||||||
|
wroteHeader bool
|
||||||
|
|
||||||
|
promise js.Value
|
||||||
|
resolve func(any)
|
||||||
|
|
||||||
|
rs *readablestream.Writer
|
||||||
|
body *bufio.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResponse() (Response, error) {
|
||||||
|
rs, err := readablestream.NewWriter()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
promise, resolve, _ := promise.New()
|
||||||
|
|
||||||
|
return &response{
|
||||||
|
promise: promise,
|
||||||
|
resolve: resolve,
|
||||||
|
|
||||||
|
rs: rs,
|
||||||
|
body: bufio.NewWriter(rs),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Response = (*response)(nil)
|
||||||
|
|
||||||
|
// Header implements [http.ResponseWriter].
|
||||||
|
func (r *response) Header() http.Header {
|
||||||
|
if r.header == nil {
|
||||||
|
r.header = make(http.Header)
|
||||||
|
}
|
||||||
|
return r.header
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *response) headerValue() map[string]any {
|
||||||
|
h := r.Header()
|
||||||
|
hh := make(map[string]any, len(h)+1)
|
||||||
|
for k := range h {
|
||||||
|
hh[k] = h.Get(k)
|
||||||
|
}
|
||||||
|
return hh
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write implements http.ResponseWriter.
|
||||||
|
func (r *response) Write(buf []byte) (int, error) {
|
||||||
|
r.writeHeader(buf, "")
|
||||||
|
return r.body.Write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteHeader implements [http.ResponseWriter].
|
||||||
|
func (r *response) WriteHeader(code int) {
|
||||||
|
if r.wroteHeader {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
checkWriteHeaderCode(code)
|
||||||
|
|
||||||
|
init, err := safejs.ValueOf(map[string]any{
|
||||||
|
"code": code,
|
||||||
|
"headers": r.headerValue(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := jstype.Response.New(r.rs.Value, init)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.wroteHeader = true
|
||||||
|
|
||||||
|
r.resolve(safejs.Unsafe(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteString implements [io.StringWriter].
|
||||||
|
func (r *response) WriteString(str string) (int, error) {
|
||||||
|
r.writeHeader(nil, str)
|
||||||
|
return r.body.WriteString(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush implements [http.Flusher]
|
||||||
|
func (r *response) Flush() {
|
||||||
|
if !r.wroteHeader {
|
||||||
|
r.WriteHeader(200)
|
||||||
|
}
|
||||||
|
if err := r.body.Flush(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements [io.Closer]
|
||||||
|
func (r *response) Close() error {
|
||||||
|
if err := r.body.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return r.rs.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *response) Context() context.Context {
|
||||||
|
return r.rs.Context()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *response) WriteError(str string) {
|
||||||
|
slog.Error(str)
|
||||||
|
if !r.wroteHeader {
|
||||||
|
r.WriteHeader(500)
|
||||||
|
_, _ = r.WriteString(str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *response) JSValue() js.Value {
|
||||||
|
return r.promise
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *response) writeHeader(b []byte, str string) {
|
||||||
|
if r.wroteHeader {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m := r.Header()
|
||||||
|
|
||||||
|
_, hasType := m["Content-Type"]
|
||||||
|
hasTE := m.Get("Transfer-Encoding") != ""
|
||||||
|
if !hasType && !hasTE {
|
||||||
|
if b == nil {
|
||||||
|
if len(str) > 512 {
|
||||||
|
str = str[:512]
|
||||||
|
}
|
||||||
|
b = []byte(str)
|
||||||
|
}
|
||||||
|
m.Set("Content-Type", http.DetectContentType(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
r.WriteHeader(200)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkWriteHeaderCode(code int) {
|
||||||
|
if code < 100 || code > 999 {
|
||||||
|
panic(fmt.Sprintf("invalid WriteHeader code %v", code))
|
||||||
|
}
|
||||||
|
}
|
@ -1,48 +0,0 @@
|
|||||||
package wasmhttp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http/httptest"
|
|
||||||
"syscall/js"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ResponseRecorder uses httptest.ResponseRecorder to build a JS Response
|
|
||||||
type ResponseRecorder struct {
|
|
||||||
*httptest.ResponseRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewResponseRecorder returns a new ResponseRecorder
|
|
||||||
func NewResponseRecorder() ResponseRecorder {
|
|
||||||
return ResponseRecorder{httptest.NewRecorder()}
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSResponse builds and returns the equivalent JS Response
|
|
||||||
func (rr ResponseRecorder) JSResponse() js.Value {
|
|
||||||
var res = rr.Result()
|
|
||||||
|
|
||||||
var body js.Value = js.Undefined()
|
|
||||||
if res.ContentLength != 0 {
|
|
||||||
var b, err = ioutil.ReadAll(res.Body)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
body = js.Global().Get("Uint8Array").New(len(b))
|
|
||||||
js.CopyBytesToJS(body, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
var init = make(map[string]interface{}, 2)
|
|
||||||
|
|
||||||
if res.StatusCode != 0 {
|
|
||||||
init["status"] = res.StatusCode
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(res.Header) != 0 {
|
|
||||||
var headers = make(map[string]interface{}, len(res.Header))
|
|
||||||
for k := range res.Header {
|
|
||||||
headers[k] = res.Header.Get(k)
|
|
||||||
}
|
|
||||||
init["headers"] = headers
|
|
||||||
}
|
|
||||||
|
|
||||||
return js.Global().Get("Response").New(body, init)
|
|
||||||
}
|
|
65
serve.go
65
serve.go
@ -1,57 +1,92 @@
|
|||||||
package wasmhttp
|
package wasmhttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall/js"
|
"syscall/js"
|
||||||
|
|
||||||
promise "github.com/nlepage/go-js-promise"
|
"github.com/nlepage/go-wasm-http-server/internal/safejs"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
wasmhttp = safejs.Safe(js.Global().Get("wasmhttp"))
|
||||||
)
|
)
|
||||||
|
|
||||||
// Serve serves HTTP requests using handler or http.DefaultServeMux if handler is nil.
|
// Serve serves HTTP requests using handler or http.DefaultServeMux if handler is nil.
|
||||||
func Serve(handler http.Handler) func() {
|
func Serve(handler http.Handler) (func(), error) {
|
||||||
var h = handler
|
h := handler
|
||||||
if h == nil {
|
if h == nil {
|
||||||
h = http.DefaultServeMux
|
h = http.DefaultServeMux
|
||||||
}
|
}
|
||||||
|
|
||||||
var prefix = js.Global().Get("wasmhttp").Get("path").String()
|
prefix, err := wasmhttp.GetString("path")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
for strings.HasSuffix(prefix, "/") {
|
for strings.HasSuffix(prefix, "/") {
|
||||||
prefix = strings.TrimSuffix(prefix, "/")
|
prefix = strings.TrimSuffix(prefix, "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
if prefix != "" {
|
if prefix != "" {
|
||||||
var mux = http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.Handle(prefix+"/", http.StripPrefix(prefix, h))
|
mux.Handle(prefix+"/", http.StripPrefix(prefix, h))
|
||||||
h = mux
|
h = mux
|
||||||
}
|
}
|
||||||
|
|
||||||
var cb = js.FuncOf(func(_ js.Value, args []js.Value) interface{} {
|
handlerValue, err := safejs.FuncOf(func(_ safejs.Value, args []safejs.Value) interface{} {
|
||||||
var resPromise, resolve, reject = promise.New()
|
res, err := NewResponse()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
ctx, cancel := context.WithCancel(res.Context())
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
cancel()
|
||||||
|
}()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := res.Close(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
|
var errStr string
|
||||||
if err, ok := r.(error); ok {
|
if err, ok := r.(error); ok {
|
||||||
reject(fmt.Sprintf("wasmhttp: panic: %+v\n", err))
|
errStr = err.Error()
|
||||||
} else {
|
} else {
|
||||||
reject(fmt.Sprintf("wasmhttp: panic: %v\n", r))
|
errStr = fmt.Sprintf("%s", r)
|
||||||
}
|
}
|
||||||
|
res.WriteError(errStr)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var res = NewResponseRecorder()
|
req, err := Request(safejs.Unsafe(args[0]))
|
||||||
|
if err != nil {
|
||||||
|
res.WriteError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
h.ServeHTTP(res, Request(args[0]))
|
req = req.WithContext(ctx)
|
||||||
|
|
||||||
resolve(res.JSResponse())
|
h.ServeHTTP(res, req)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return resPromise
|
return res.JSValue()
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
js.Global().Get("wasmhttp").Call("setHandler", cb)
|
if _, err = wasmhttp.Call("setHandler", handlerValue); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return cb.Release
|
return handlerValue.Release, nil
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user