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')
|
||||
|
||||
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')
|
||||
|
||||
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{}
|
||||
}
|
||||
|
8
go.mod
8
go.mod
@ -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
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/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.
|
||||
//
|
||||
// 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
|
||||
|
94
request.go
94
request.go
@ -7,29 +7,91 @@ import (
|
||||
"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/safejs"
|
||||
)
|
||||
|
||||
// Request builds and returns the equivalent http.Request
|
||||
func Request(r js.Value) *http.Request {
|
||||
jsBody := js.Global().Get("Uint8Array").New(promise.Await(r.Call("arrayBuffer")))
|
||||
body := make([]byte, jsBody.Get("length").Int())
|
||||
js.CopyBytesToGo(body, jsBody)
|
||||
func Request(ur js.Value) (*http.Request, error) {
|
||||
r := safejs.Safe(ur)
|
||||
|
||||
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(
|
||||
r.Get("method").String(),
|
||||
r.Get("url").String(),
|
||||
bytes.NewBuffer(body),
|
||||
method,
|
||||
url,
|
||||
bytes.NewReader(b),
|
||||
)
|
||||
|
||||
headersIt := r.Get("headers").Call("entries")
|
||||
for {
|
||||
e := headersIt.Call("next")
|
||||
if e.Get("done").Bool() {
|
||||
break
|
||||
}
|
||||
v := e.Get("value")
|
||||
req.Header.Set(v.Index(0).String(), v.Index(1).String())
|
||||
headers, err := r.Get("headers")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
69
serve.go
69
serve.go
@ -1,57 +1,92 @@
|
||||
package wasmhttp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"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.
|
||||
func Serve(handler http.Handler) func() {
|
||||
var h = handler
|
||||
func Serve(handler http.Handler) (func(), error) {
|
||||
h := handler
|
||||
if h == nil {
|
||||
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, "/") {
|
||||
prefix = strings.TrimSuffix(prefix, "/")
|
||||
}
|
||||
|
||||
if prefix != "" {
|
||||
var mux = http.NewServeMux()
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle(prefix+"/", http.StripPrefix(prefix, h))
|
||||
h = mux
|
||||
}
|
||||
|
||||
var cb = js.FuncOf(func(_ js.Value, args []js.Value) interface{} {
|
||||
var resPromise, resolve, reject = promise.New()
|
||||
handlerValue, err := safejs.FuncOf(func(_ safejs.Value, args []safejs.Value) interface{} {
|
||||
res, err := NewResponse()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
ctx, cancel := context.WithCancel(res.Context())
|
||||
|
||||
defer func() {
|
||||
cancel()
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
if err := res.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
var errStr string
|
||||
if err, ok := r.(error); ok {
|
||||
reject(fmt.Sprintf("wasmhttp: panic: %+v\n", err))
|
||||
errStr = err.Error()
|
||||
} 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()
|
||||
})
|
||||
|
||||
js.Global().Get("wasmhttp").Call("setHandler", cb)
|
||||
|
||||
return cb.Release
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err = wasmhttp.Call("setHandler", handlerValue); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return handlerValue.Release, nil
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user