WebAssembly Server

/app/cmd/wasmjs.go (3.5 KB)

  1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
//go:build js

package cmd

import (
"context"
"syscall/js"

"github.com/muesli/coral"
"github.com/pkg/errors"
"github.com/valyala/fasthttp"
)

const keyWASM = "wasm"

var _router fasthttp.RequestHandler

func wasmCmd() *coral.Command {
short := "Starts the server and exposes a WebAssembly application to scripts"
f := func(*coral.Command, []string) error { return startWASM(_flags) }
ret := &coral.Command{Use: keyWASM, Short: short, RunE: f}
return ret
}

func startWASM(flags *Flags) error {
initScript()
if err := initIfNeeded(); err != nil {
return errors.Wrap(err, "error initializing application")
}

st, r, logger, err := loadServer(flags, _logger)
if err != nil {
return err
}
logger.Infof("Started WASM server")
defer func() { _ = st.Close(context.Background(), _logger) }()
_router = r

select {}
}

func initScript() {
js.Global().Set("goFetch", js.FuncOf(FetchJS))
}

func FetchJS(_ js.Value, args []js.Value) any {
if len(args) != 3 {
return emsg("must provide exactly three arguments, an HTTP request as used by fetch, an array of headers, and a string representation of the body")
}
ret, err := WASMProcess(args[0], args[1], args[2])
if err != nil {
return emsg(err.Error())
}
return ret
}

func emsg(s string) string {
return "WASM_ERROR:" + s
}

func WASMProcess(req js.Value, headers js.Value, reqBody js.Value) (js.Value, error) {
rc := &fasthttp.RequestCtx{Request: fasthttp.Request{}, Response: fasthttp.Response{}}
err := populateRequest(req, headers, reqBody, rc)
if err != nil {
return js.Null(), err
}
err = runRequest(rc)
if err != nil {
return js.Null(), err
}
return createResponse(rc)
}

func populateRequest(req js.Value, headers js.Value, reqBody js.Value, rc *fasthttp.RequestCtx) (e error) {
defer func() {
if x := recover(); x != nil {
if err, ok := x.(error); ok {
e = err
} else {
panic(x)
}
}
}()

url := req.Get("url").String()
rc.Request.URI().Update(url)

rc.Request.Header.SetHost(string(rc.Request.URI().Host()))

method := req.Get("method").String()
rc.Request.Header.SetMethod(method)

if !headers.IsNull() && !headers.IsUndefined() {
for i := 0; i < headers.Length(); i++ {
entry := headers.Index(i)
k, v := entry.Index(0).String(), entry.Index(1).String()
rc.Request.Header.Set(k, v)
}
}

if reqBody.IsNull() || reqBody.IsUndefined() {
return nil
}

body := reqBody.String()
if body == "" {
return nil
}
println("REQ BODY [" + body + "]")
rc.Request.SetBody([]byte(body))
return nil
}

func runRequest(rc *fasthttp.RequestCtx) (e error) {
if _router == nil {
return errors.New("didn't start app through the WASM action, no active router")
}
defer func() {
if x := recover(); x != nil {
if err, ok := x.(error); ok {
e = err
} else {
panic(x)
}
}
}()
_router(rc)
return nil
}

func createResponse(rc *fasthttp.RequestCtx) (js.Value, error) {
rspClass := js.Global().Get("Response")
hd := js.Global().Get("Headers").New()
for _, b := range rc.Response.Header.PeekKeys() {
hd.Call("set", js.ValueOf(string(b)), js.ValueOf(string(rc.Response.Header.Peek(string(b)))))
}
hd.Call("set", js.ValueOf("Content-Type"), js.ValueOf(string(rc.Response.Header.ContentType())))
rspBytes := rc.Response.Body()
opts := map[string]any{"status": rc.Response.StatusCode(), "headers": hd}
x := rspClass.New(js.ValueOf(string(rspBytes)), js.ValueOf(opts), js.ValueOf(map[string]any{"credentials": "same-origin"}))
return x, nil
}