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 }
|