From 64837955f58f1ccacb61be55f81956aef64b0ab8 Mon Sep 17 00:00:00 2001 From: anthdm Date: Wed, 10 Jan 2024 08:53:51 +0100 Subject: [PATCH 1/6] fixed js runtime --- cmd/api/main.go | 4 ++-- examples/js/index.js | 19 +++++++++++++++-- internal/actrs/runtime.go | 16 ++++++++++++--- internal/runtime/runtime.go | 14 +------------ internal/runtime/runtime_test.go | 35 +++++++++++++++++++++++++++++++- 5 files changed, 67 insertions(+), 21 deletions(-) diff --git a/cmd/api/main.go b/cmd/api/main.go index 4827f4a..7cf9b0a 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -55,13 +55,13 @@ func main() { } func seedEndpoint(store storage.Store, cache storage.ModCacher) { - b, err := os.ReadFile("examples/go/app.wasm") + b, err := os.ReadFile("examples/js/index.js") if err != nil { log.Fatal(err) } endpoint := &types.Endpoint{ ID: uuid.MustParse("09248ef6-c401-4601-8928-5964d61f2c61"), - Runtime: "go", + Runtime: "js", Name: "Catfact parser", Environment: map[string]string{"FOO": "bar"}, CreatedAT: time.Now(), diff --git a/examples/js/index.js b/examples/js/index.js index ce7630f..4588159 100644 --- a/examples/js/index.js +++ b/examples/js/index.js @@ -1,8 +1,23 @@ +function generateEndHex(length, status) { + var buffer = new ArrayBuffer(8); + var view = new DataView(buffer); + + view.setUint32(0, status, true); + view.setUint32(4, length, true); + + var hexString = ""; + for (var i = 0; i < buffer.byteLength; i++) { + var hex = view.getUint8(i).toString(16); + hexString += hex.padStart(2, "0"); + } + + return hexString; +} + // This should come from the official SDK. // But there is no official SDK yet, so we keep it here. function respond(res, status) { - console.log(res) - console.log(status) + putstr(res+generateEndHex(res.length, status)) } respond("

From my Raptor application


some other stuff here
", 200) \ No newline at end of file diff --git a/internal/actrs/runtime.go b/internal/actrs/runtime.go index caab648..8ad007f 100644 --- a/internal/actrs/runtime.go +++ b/internal/actrs/runtime.go @@ -12,6 +12,7 @@ import ( "github.com/anthdm/hollywood/actor" "github.com/anthdm/raptor/internal/runtime" "github.com/anthdm/raptor/internal/shared" + "github.com/anthdm/raptor/internal/spidermonkey" "github.com/anthdm/raptor/internal/storage" "github.com/anthdm/raptor/internal/types" "github.com/anthdm/raptor/proto" @@ -39,6 +40,7 @@ type Runtime struct { runtime *runtime.Runtime repeat actor.SendRepeater stdout *bytes.Buffer + script []byte } func NewRuntime(store storage.Store, cache storage.ModCacher) actor.Producer { @@ -95,10 +97,18 @@ func (r *Runtime) initialize(msg *proto.HTTPRequest) error { args := runtime.Args{ Cache: modCache, DeploymentID: deploy.ID, - Blob: deploy.Blob, Engine: msg.Runtime, Stdout: r.stdout, } + + switch args.Engine { + case "js": + r.script = deploy.Blob + args.Blob = spidermonkey.WasmBlob + default: + args.Blob = deploy.Blob + } + run, err := runtime.New(context.Background(), args) if err != nil { return err @@ -118,9 +128,9 @@ func (r *Runtime) handleHTTPRequest(ctx *actor.Context, msg *proto.HTTPRequest) return } - var args []string = nil + args := []string{} if msg.Runtime == "js" { - args = []string{"", "-e", string(r.runtime.Blob())} + args = []string{"", "-e", string(r.script)} } req := bytes.NewReader(b) diff --git a/internal/runtime/runtime.go b/internal/runtime/runtime.go index da7c428..ed6f755 100644 --- a/internal/runtime/runtime.go +++ b/internal/runtime/runtime.go @@ -6,7 +6,6 @@ import ( "io" "os" - "github.com/anthdm/raptor/internal/spidermonkey" "github.com/google/uuid" "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" @@ -41,14 +40,7 @@ func New(ctx context.Context, args Args) (*Runtime, error) { } wasi_snapshot_preview1.MustInstantiate(ctx, r.runtime) - switch args.Engine { - case "js": - r.blob = spidermonkey.WasmBlob - default: - r.blob = args.Blob - } - - mod, err := r.runtime.CompileModule(ctx, r.blob) + mod, err := r.runtime.CompileModule(ctx, args.Blob) if err != nil { return nil, fmt.Errorf("runtime failed to compile module: %s", err) } @@ -57,10 +49,6 @@ func New(ctx context.Context, args Args) (*Runtime, error) { return r, nil } -func (r *Runtime) Blob() []byte { - return r.blob -} - func (r *Runtime) Invoke(stdin io.Reader, env map[string]string, args ...string) error { modConf := wazero.NewModuleConfig(). WithStdin(stdin). diff --git a/internal/runtime/runtime_test.go b/internal/runtime/runtime_test.go index d3aee80..c72f9f6 100644 --- a/internal/runtime/runtime_test.go +++ b/internal/runtime/runtime_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/anthdm/raptor/internal/shared" + "github.com/anthdm/raptor/internal/spidermonkey" "github.com/anthdm/raptor/proto" "github.com/google/uuid" "github.com/stretchr/testify/require" @@ -15,7 +16,39 @@ import ( pb "google.golang.org/protobuf/proto" ) -func TestRuntime(t *testing.T) { +func TestRuntimeInvokeJSCode(t *testing.T) { + b, err := os.ReadFile("../../examples/js/index.js") + require.Nil(t, err) + + req := &proto.HTTPRequest{ + Method: "get", + URL: "/", + Body: nil, + } + breq, err := pb.Marshal(req) + require.Nil(t, err) + + out := &bytes.Buffer{} + args := Args{ + Stdout: out, + DeploymentID: uuid.New(), + Blob: spidermonkey.WasmBlob, + Engine: "js", + Cache: wazero.NewCompilationCache(), + } + r, err := New(context.Background(), args) + require.Nil(t, err) + + scriptArgs := []string{"", "-e", string(b)} + require.Nil(t, r.Invoke(bytes.NewReader(breq), nil, scriptArgs...)) + + _, status, err := shared.ParseRuntimeHTTPResponse(out.String()) + require.Nil(t, err) + require.Equal(t, http.StatusOK, status) + require.Nil(t, r.Close()) +} + +func TestRuntimeInvokeGoCode(t *testing.T) { b, err := os.ReadFile("../../examples/go/app.wasm") require.Nil(t, err) From 22edb8d0813ab911ce865d05d54260130ea6f99f Mon Sep 17 00:00:00 2001 From: anthdm Date: Wed, 10 Jan 2024 10:59:38 +0100 Subject: [PATCH 2/6] user logs --- cmd/api/main.go | 4 +- examples/go/main.go | 2 + examples/js/index.js | 26 ++++---- internal/actrs/runtime.go | 2 +- internal/runtime/runtime_test.go | 4 +- internal/shared/shared.go | 25 ++++++++ internal/shared/shared_test.go | 105 ++++++++++++++++++++++++++----- sdk/sdk.go | 6 +- 8 files changed, 137 insertions(+), 37 deletions(-) diff --git a/cmd/api/main.go b/cmd/api/main.go index 7cf9b0a..4827f4a 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -55,13 +55,13 @@ func main() { } func seedEndpoint(store storage.Store, cache storage.ModCacher) { - b, err := os.ReadFile("examples/js/index.js") + b, err := os.ReadFile("examples/go/app.wasm") if err != nil { log.Fatal(err) } endpoint := &types.Endpoint{ ID: uuid.MustParse("09248ef6-c401-4601-8928-5964d61f2c61"), - Runtime: "js", + Runtime: "go", Name: "Catfact parser", Environment: map[string]string{"FOO": "bar"}, CreatedAT: time.Now(), diff --git a/examples/go/main.go b/examples/go/main.go index 461718f..88d50dd 100644 --- a/examples/go/main.go +++ b/examples/go/main.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "net/http" raptor "github.com/anthdm/raptor/sdk" @@ -20,5 +21,6 @@ func main() { // router := chi.NewMux() // router.Get("/dashboard", handleDashboard) // router.Get("/login", handleLogin) + fmt.Println("user log") raptor.Handle(http.HandlerFunc(handleLogin)) } diff --git a/examples/js/index.js b/examples/js/index.js index 4588159..bb4fc09 100644 --- a/examples/js/index.js +++ b/examples/js/index.js @@ -1,23 +1,21 @@ -function generateEndHex(length, status) { +// This should come from the official SDK. +// But there is no official SDK yet, so we keep it here. +function respond(res, status) { var buffer = new ArrayBuffer(8); var view = new DataView(buffer); view.setUint32(0, status, true); - view.setUint32(4, length, true); - - var hexString = ""; - for (var i = 0; i < buffer.byteLength; i++) { - var hex = view.getUint8(i).toString(16); - hexString += hex.padStart(2, "0"); - } + view.setUint32(4, res.length, true); - return hexString; + var bytes = new Uint8Array(buffer); + print(res) + print(bytes) } -// This should come from the official SDK. -// But there is no official SDK yet, so we keep it here. -function respond(res, status) { - putstr(res+generateEndHex(res.length, status)) -} +console.log("user log here") +console.log("user log here") +console.log("user log here") +console.log("user log here") +console.log("user log here") respond("

From my Raptor application


some other stuff here
", 200) \ No newline at end of file diff --git a/internal/actrs/runtime.go b/internal/actrs/runtime.go index 8ad007f..f01f734 100644 --- a/internal/actrs/runtime.go +++ b/internal/actrs/runtime.go @@ -140,7 +140,7 @@ func (r *Runtime) handleHTTPRequest(ctx *actor.Context, msg *proto.HTTPRequest) return } - res, status, err := shared.ParseRuntimeHTTPResponse(r.stdout.String()) + _, res, status, err := shared.ParseStdout(r.stdout) if err != nil { respondError(ctx, http.StatusInternalServerError, "invalid response", msg.ID) return diff --git a/internal/runtime/runtime_test.go b/internal/runtime/runtime_test.go index c72f9f6..198a976 100644 --- a/internal/runtime/runtime_test.go +++ b/internal/runtime/runtime_test.go @@ -42,7 +42,7 @@ func TestRuntimeInvokeJSCode(t *testing.T) { scriptArgs := []string{"", "-e", string(b)} require.Nil(t, r.Invoke(bytes.NewReader(breq), nil, scriptArgs...)) - _, status, err := shared.ParseRuntimeHTTPResponse(out.String()) + _, _, status, err := shared.ParseStdout(out) require.Nil(t, err) require.Equal(t, http.StatusOK, status) require.Nil(t, r.Close()) @@ -71,7 +71,7 @@ func TestRuntimeInvokeGoCode(t *testing.T) { r, err := New(context.Background(), args) require.Nil(t, err) require.Nil(t, r.Invoke(bytes.NewReader(breq), nil)) - _, status, err := shared.ParseRuntimeHTTPResponse(out.String()) + _, _, status, err := shared.ParseStdout(out) require.Nil(t, err) require.Equal(t, http.StatusOK, status) require.Nil(t, r.Close()) diff --git a/internal/shared/shared.go b/internal/shared/shared.go index 31432e6..ae2eb8e 100644 --- a/internal/shared/shared.go +++ b/internal/shared/shared.go @@ -13,8 +13,33 @@ import ( "github.com/anthdm/raptor/proto" ) +const magicLen = 8 + var errInvalidHTTPResponse = errors.New("invalid HTTP response") +func ParseStdout(stdout io.Reader) (logs []byte, resp []byte, status int, err error) { + stdoutb, err := io.ReadAll(stdout) + if err != nil { + return + } + outLen := len(stdoutb) + if outLen < magicLen { + err = fmt.Errorf("mallformed HTTP response missing last %d bytes", magicLen) + return + } + magicStart := outLen - magicLen + status = int(binary.LittleEndian.Uint32(stdoutb[magicStart : magicStart+4])) + respLen := binary.LittleEndian.Uint32(stdoutb[magicStart+4:]) + if int(respLen) > outLen-magicLen { + err = fmt.Errorf("response length exceeds available data") + return + } + respStart := outLen - magicLen - int(respLen) + resp = stdoutb[respStart : respStart+int(respLen)] + logs = stdoutb[:respStart] + return +} + func ParseRuntimeHTTPResponse(in string) (resp string, status int, err error) { if len(in) < 16 { err = fmt.Errorf("misformed HTTP response missing last 16 bytes") diff --git a/internal/shared/shared_test.go b/internal/shared/shared_test.go index 89c9da8..a37cb72 100644 --- a/internal/shared/shared_test.go +++ b/internal/shared/shared_test.go @@ -1,27 +1,104 @@ package shared import ( + "bytes" "encoding/binary" "encoding/hex" "fmt" + "log" "testing" "github.com/stretchr/testify/require" ) +func BenchmarkParseStdout(b *testing.B) { + userResp := "

This is the actual response

" + statusCode := uint32(200) + userLogs := ` +the big brown fox +the big brown fox +the big brown fox +the big brown fox +the big brown fox +` + builder := &bytes.Buffer{} + + buf := make([]byte, 8) + binary.LittleEndian.PutUint32(buf[0:4], statusCode) + binary.LittleEndian.PutUint32(buf[4:8], uint32(len(userResp))) + + b.ResetTimer() + b.StartTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + builder.WriteString(userLogs) + builder.WriteString(userResp) + builder.Write(buf) + if _, _, _, err := ParseStdout(builder); err != nil { + log.Fatal(err) + } + builder.Reset() + } + b.StopTimer() +} + +func TestParseWithoutUserLogs(t *testing.T) { + userResp := "

This is the actual response

" + statusCode := uint32(200) + builder := &bytes.Buffer{} + builder.WriteString(userResp) + + // += userResp + buf := make([]byte, 8) + binary.LittleEndian.PutUint32(buf[0:4], statusCode) + binary.LittleEndian.PutUint32(buf[4:8], uint32(len(userResp))) + builder.Write(buf) + + logs, resp, status, err := ParseStdout(builder) + require.Nil(t, err) + require.Equal(t, int(statusCode), status) + require.Equal(t, userResp, string(resp)) + require.Equal(t, []byte{}, logs) +} + +func TestParseWithUserLogs(t *testing.T) { + userResp := "

This is the actual response

" + statusCode := uint32(200) + userLogs := ` +the big brown fox +the big brown fox +the big brown fox +the big brown fox +the big brown fox +` + builder := &bytes.Buffer{} + builder.WriteString(userLogs) + builder.WriteString(userResp) + + // += userResp + buf := make([]byte, 8) + binary.LittleEndian.PutUint32(buf[0:4], statusCode) + binary.LittleEndian.PutUint32(buf[4:8], uint32(len(userResp))) + builder.Write(buf) + + logs, resp, status, err := ParseStdout(builder) + require.Nil(t, err) + require.Equal(t, int(statusCode), status) + require.Equal(t, userResp, string(resp)) + require.Equal(t, userLogs, string(logs)) +} + func TestParseRuntimeHTTPResponse(t *testing.T) { - t.Run("multiline response", func(t *testing.T) { - text := "This is the best.\nBut not always correct.\nThe big brown fox." - statusCode := uint32(500) - - buf := make([]byte, 8) - binary.LittleEndian.PutUint32(buf[0:4], statusCode) - binary.LittleEndian.PutUint32(buf[4:8], uint32(len(text))) - - in := fmt.Sprintf("%s%s", text, hex.EncodeToString(buf)) - resp, status, err := ParseRuntimeHTTPResponse(in) - require.Nil(t, err) - require.Equal(t, int(statusCode), status) - require.Equal(t, text, resp) - }) + text := "This is the best.\nBut not always correct.\nThe big brown fox." + statusCode := uint32(500) + + buf := make([]byte, 8) + binary.LittleEndian.PutUint32(buf[0:4], statusCode) + binary.LittleEndian.PutUint32(buf[4:8], uint32(len(text))) + + in := fmt.Sprintf("%s%s", text, hex.EncodeToString(buf)) + resp, status, err := ParseRuntimeHTTPResponse(in) + require.Nil(t, err) + require.Equal(t, int(statusCode), status) + require.Equal(t, text, resp) } diff --git a/sdk/sdk.go b/sdk/sdk.go index 7631c93..c50b0d4 100644 --- a/sdk/sdk.go +++ b/sdk/sdk.go @@ -3,8 +3,6 @@ package run import ( "bytes" "encoding/binary" - "encoding/hex" - "fmt" "io" "log" "net/http" @@ -46,12 +44,12 @@ func Handle(h http.Handler) { r.Header[k] = v.Fields } h.ServeHTTP(w, r) // execute the user's handler - fmt.Print(w.buffer.String()) + os.Stdout.Write(w.buffer.Bytes()) buf := make([]byte, 8) binary.LittleEndian.PutUint32(buf[0:4], uint32(w.statusCode)) binary.LittleEndian.PutUint32(buf[4:8], uint32(w.buffer.Len())) - fmt.Print(hex.EncodeToString(buf)) + os.Stdout.Write(buf) } type ResponseWriter struct { From a03b3662edc86a5989990b415d2e3c73e04e6178 Mon Sep 17 00:00:00 2001 From: anthdm Date: Wed, 10 Jan 2024 11:17:39 +0100 Subject: [PATCH 3/6] runtime log actor --- cmd/wasmserver/main.go | 1 + internal/actrs/runtime.go | 14 ++++++++++---- internal/actrs/runtime_log.go | 23 +++++++++++++++++++++++ internal/types/metrics.go | 6 ++++++ 4 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 internal/actrs/runtime_log.go diff --git a/cmd/wasmserver/main.go b/cmd/wasmserver/main.go index 1181ae1..117414b 100644 --- a/cmd/wasmserver/main.go +++ b/cmd/wasmserver/main.go @@ -54,6 +54,7 @@ func main() { c.RegisterKind(actrs.KindRuntime, actrs.NewRuntime(store, modCache), &cluster.KindConfig{}) c.Engine().Spawn(actrs.NewMetric, actrs.KindMetric, actor.WithID("1")) c.Engine().Spawn(actrs.NewRuntimeManager(c), actrs.KindRuntimeManager, actor.WithID("1")) + c.Engine().Spawn(actrs.NewRuntimeLog, actrs.KindRuntimeLog, actor.WithID("1")) c.Start() server := actrs.NewWasmServer( diff --git a/internal/actrs/runtime.go b/internal/actrs/runtime.go index f01f734..cbaa545 100644 --- a/internal/actrs/runtime.go +++ b/internal/actrs/runtime.go @@ -140,7 +140,7 @@ func (r *Runtime) handleHTTPRequest(ctx *actor.Context, msg *proto.HTTPRequest) return } - _, res, status, err := shared.ParseStdout(r.stdout) + logs, res, status, err := shared.ParseStdout(r.stdout) if err != nil { respondError(ctx, http.StatusInternalServerError, "invalid response", msg.ID) return @@ -154,7 +154,7 @@ func (r *Runtime) handleHTTPRequest(ctx *actor.Context, msg *proto.HTTPRequest) ctx.Respond(resp) r.stdout.Reset() - // only send metrics when its a request on LIVE + // only send metrics and logs when its a request on LIVE if !msg.Preview { metric := types.RequestMetric{ ID: uuid.New(), @@ -164,8 +164,14 @@ func (r *Runtime) handleHTTPRequest(ctx *actor.Context, msg *proto.HTTPRequest) RequestURL: msg.URL, StatusCode: status, } - pid := ctx.Engine().Registry.GetPID(KindMetric, "1") - ctx.Send(pid, metric) + metricPID := ctx.Engine().Registry.GetPID(KindMetric, "1") + ctx.Send(metricPID, metric) + + runtimeLogPID := ctx.Engine().Registry.GetPID(KindRuntimeLog, "1") + runtimeLog := types.RuntimeLogEvent{ + Data: logs, + } + ctx.Send(runtimeLogPID, runtimeLog) } } diff --git a/internal/actrs/runtime_log.go b/internal/actrs/runtime_log.go new file mode 100644 index 0000000..ade7821 --- /dev/null +++ b/internal/actrs/runtime_log.go @@ -0,0 +1,23 @@ +package actrs + +import ( + "github.com/anthdm/hollywood/actor" + "github.com/anthdm/raptor/internal/types" +) + +const KindRuntimeLog = "runtime_log" + +type RuntimeLog struct{} + +func NewRuntimeLog() actor.Receiver { + return &RuntimeLog{} +} + +func (rl *RuntimeLog) Receive(c *actor.Context) { + switch msg := c.Message().(type) { + case actor.Started: + case actor.Stopped: + case types.RuntimeLogEvent: + _ = msg + } +} diff --git a/internal/types/metrics.go b/internal/types/metrics.go index 6d55af2..5a0918a 100644 --- a/internal/types/metrics.go +++ b/internal/types/metrics.go @@ -21,3 +21,9 @@ type RequestMetric struct { Duration time.Duration `json:"duration"` StatusCode int `json:"status_code"` } + +// RuntimeLogEvent holds the logs that where written out +// during runtime invocation of a script. +type RuntimeLogEvent struct { + Data []byte +} From b1b68386794b2cfccbdf1a54dacc056bf43355ac Mon Sep 17 00:00:00 2001 From: anthdm Date: Wed, 10 Jan 2024 17:21:03 +0100 Subject: [PATCH 4/6] js resp borking --- examples/js/index.js | 5 ++++- internal/actrs/runtime.go | 2 +- internal/shared/shared.go | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/js/index.js b/examples/js/index.js index bb4fc09..442ec19 100644 --- a/examples/js/index.js +++ b/examples/js/index.js @@ -8,8 +8,11 @@ function respond(res, status) { view.setUint32(4, res.length, true); var bytes = new Uint8Array(buffer); + print(res) - print(bytes) + for (let i = 0; i < bytes.length; i++) { + putstr(String.fromCharCode(bytes[i])) + } } console.log("user log here") diff --git a/internal/actrs/runtime.go b/internal/actrs/runtime.go index cbaa545..b4d3c0b 100644 --- a/internal/actrs/runtime.go +++ b/internal/actrs/runtime.go @@ -81,7 +81,7 @@ func (r *Runtime) Receive(c *actor.Context) { func (r *Runtime) initialize(msg *proto.HTTPRequest) error { r.deploymentID = uuid.MustParse(msg.DeploymentID) - // TODO: this could be coming from a Redis cache instead of Postres. + // TODO: this could be coming from a Redis cache instead of Postgres. // Maybe only the blob. Not sure... deploy, err := r.store.GetDeployment(r.deploymentID) if err != nil { diff --git a/internal/shared/shared.go b/internal/shared/shared.go index ae2eb8e..362ccb3 100644 --- a/internal/shared/shared.go +++ b/internal/shared/shared.go @@ -30,6 +30,7 @@ func ParseStdout(stdout io.Reader) (logs []byte, resp []byte, status int, err er magicStart := outLen - magicLen status = int(binary.LittleEndian.Uint32(stdoutb[magicStart : magicStart+4])) respLen := binary.LittleEndian.Uint32(stdoutb[magicStart+4:]) + fmt.Println(status) if int(respLen) > outLen-magicLen { err = fmt.Errorf("response length exceeds available data") return From cbe43ecff7975f37f9c3c14fb86220fa7d12682d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D1=83=D0=BB=D0=B0=D1=82=20=D0=A5=D0=B0=D0=BD=D0=B3?= =?UTF-8?q?=D0=B8=D0=BB=D1=8C=D0=B4=D0=B8=D0=BD?= Date: Wed, 10 Jan 2024 21:39:41 +0300 Subject: [PATCH 5/6] Somehow cracked js with hex encoding values --- examples/js/index.js | 30 +++++++++++++++++++----------- internal/actrs/runtime.go | 3 ++- internal/runtime/runtime.go | 12 ++++++++++++ internal/runtime/runtime_test.go | 4 ++-- 4 files changed, 35 insertions(+), 14 deletions(-) diff --git a/examples/js/index.js b/examples/js/index.js index 442ec19..e6af6fc 100644 --- a/examples/js/index.js +++ b/examples/js/index.js @@ -1,18 +1,26 @@ -// This should come from the official SDK. +// This should come from the official SDK. // But there is no official SDK yet, so we keep it here. -function respond(res, status) { - var buffer = new ArrayBuffer(8); - var view = new DataView(buffer); +function hexLog(s) { + for (let i = 0; i < s.length; i++) { + putstr(s.charCodeAt(i).toString(16).padStart(2, "0")) + } + putstr("0a") +} - view.setUint32(0, status, true); - view.setUint32(4, res.length, true); +console.log = hexLog - var bytes = new Uint8Array(buffer); +function respond(res, status) { + var buffer = new ArrayBuffer(8); + var view = new DataView(buffer); + view.setUint32(0, status, true); + view.setUint32(4, res.length, true); - print(res) - for (let i = 0; i < bytes.length; i++) { - putstr(String.fromCharCode(bytes[i])) - } + for (let i = 0; i < res.length; i++) { + putstr(res.charCodeAt(i).toString(16).padStart(2, "0")) + } + for (let i = 0; i < view.buffer.byteLength; i++) { + putstr(view.getUint8(i).toString(16).padStart(2, "0")); + } } console.log("user log here") diff --git a/internal/actrs/runtime.go b/internal/actrs/runtime.go index b4d3c0b..09f4ed9 100644 --- a/internal/actrs/runtime.go +++ b/internal/actrs/runtime.go @@ -140,7 +140,8 @@ func (r *Runtime) handleHTTPRequest(ctx *actor.Context, msg *proto.HTTPRequest) return } - logs, res, status, err := shared.ParseStdout(r.stdout) + stdout := runtime.GetStdout(msg.Runtime, r.stdout) + logs, res, status, err := shared.ParseStdout(stdout) if err != nil { respondError(ctx, http.StatusInternalServerError, "invalid response", msg.ID) return diff --git a/internal/runtime/runtime.go b/internal/runtime/runtime.go index ed6f755..6834f35 100644 --- a/internal/runtime/runtime.go +++ b/internal/runtime/runtime.go @@ -2,6 +2,7 @@ package runtime import ( "context" + "encoding/hex" "fmt" "io" "os" @@ -65,3 +66,14 @@ func (r *Runtime) Invoke(stdin io.Reader, env map[string]string, args ...string) func (r *Runtime) Close() error { return r.runtime.Close(r.ctx) } + +func GetStdout(runtime string, stdout io.Reader) io.Reader { + switch runtime { + case "go": + return stdout + case "js": + return hex.NewDecoder(stdout) + default: + return stdout + } +} diff --git a/internal/runtime/runtime_test.go b/internal/runtime/runtime_test.go index 198a976..35b0b29 100644 --- a/internal/runtime/runtime_test.go +++ b/internal/runtime/runtime_test.go @@ -42,7 +42,7 @@ func TestRuntimeInvokeJSCode(t *testing.T) { scriptArgs := []string{"", "-e", string(b)} require.Nil(t, r.Invoke(bytes.NewReader(breq), nil, scriptArgs...)) - _, _, status, err := shared.ParseStdout(out) + _, _, status, err := shared.ParseStdout(GetStdout("js", out)) require.Nil(t, err) require.Equal(t, http.StatusOK, status) require.Nil(t, r.Close()) @@ -71,7 +71,7 @@ func TestRuntimeInvokeGoCode(t *testing.T) { r, err := New(context.Background(), args) require.Nil(t, err) require.Nil(t, r.Invoke(bytes.NewReader(breq), nil)) - _, _, status, err := shared.ParseStdout(out) + _, _, status, err := shared.ParseStdout(GetStdout("go", out)) require.Nil(t, err) require.Equal(t, http.StatusOK, status) require.Nil(t, r.Close()) From 29d76ee3a7afac9046b36c1cd3538147259deb96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D1=83=D0=BB=D0=B0=D1=82=20=D0=A5=D0=B0=D0=BD=D0=B3?= =?UTF-8?q?=D0=B8=D0=BB=D1=8C=D0=B4=D0=B8=D0=BD?= Date: Wed, 10 Jan 2024 22:05:46 +0300 Subject: [PATCH 6/6] Decoding stdout inside `shared.ParseStdout` --- internal/actrs/runtime.go | 3 +-- internal/runtime/runtime.go | 12 ------------ internal/runtime/runtime_test.go | 4 ++-- internal/shared/shared.go | 15 +++++++++++++-- internal/shared/shared_test.go | 6 +++--- 5 files changed, 19 insertions(+), 21 deletions(-) diff --git a/internal/actrs/runtime.go b/internal/actrs/runtime.go index 09f4ed9..10b6758 100644 --- a/internal/actrs/runtime.go +++ b/internal/actrs/runtime.go @@ -140,8 +140,7 @@ func (r *Runtime) handleHTTPRequest(ctx *actor.Context, msg *proto.HTTPRequest) return } - stdout := runtime.GetStdout(msg.Runtime, r.stdout) - logs, res, status, err := shared.ParseStdout(stdout) + logs, res, status, err := shared.ParseStdout(msg.Runtime, r.stdout) if err != nil { respondError(ctx, http.StatusInternalServerError, "invalid response", msg.ID) return diff --git a/internal/runtime/runtime.go b/internal/runtime/runtime.go index 6834f35..ed6f755 100644 --- a/internal/runtime/runtime.go +++ b/internal/runtime/runtime.go @@ -2,7 +2,6 @@ package runtime import ( "context" - "encoding/hex" "fmt" "io" "os" @@ -66,14 +65,3 @@ func (r *Runtime) Invoke(stdin io.Reader, env map[string]string, args ...string) func (r *Runtime) Close() error { return r.runtime.Close(r.ctx) } - -func GetStdout(runtime string, stdout io.Reader) io.Reader { - switch runtime { - case "go": - return stdout - case "js": - return hex.NewDecoder(stdout) - default: - return stdout - } -} diff --git a/internal/runtime/runtime_test.go b/internal/runtime/runtime_test.go index 35b0b29..eca7211 100644 --- a/internal/runtime/runtime_test.go +++ b/internal/runtime/runtime_test.go @@ -42,7 +42,7 @@ func TestRuntimeInvokeJSCode(t *testing.T) { scriptArgs := []string{"", "-e", string(b)} require.Nil(t, r.Invoke(bytes.NewReader(breq), nil, scriptArgs...)) - _, _, status, err := shared.ParseStdout(GetStdout("js", out)) + _, _, status, err := shared.ParseStdout("js", out) require.Nil(t, err) require.Equal(t, http.StatusOK, status) require.Nil(t, r.Close()) @@ -71,7 +71,7 @@ func TestRuntimeInvokeGoCode(t *testing.T) { r, err := New(context.Background(), args) require.Nil(t, err) require.Nil(t, r.Invoke(bytes.NewReader(breq), nil)) - _, _, status, err := shared.ParseStdout(GetStdout("go", out)) + _, _, status, err := shared.ParseStdout("go", out) require.Nil(t, err) require.Equal(t, http.StatusOK, status) require.Nil(t, r.Close()) diff --git a/internal/shared/shared.go b/internal/shared/shared.go index 362ccb3..3e1a804 100644 --- a/internal/shared/shared.go +++ b/internal/shared/shared.go @@ -17,8 +17,8 @@ const magicLen = 8 var errInvalidHTTPResponse = errors.New("invalid HTTP response") -func ParseStdout(stdout io.Reader) (logs []byte, resp []byte, status int, err error) { - stdoutb, err := io.ReadAll(stdout) +func ParseStdout(runtime string, stdout io.Reader) (logs []byte, resp []byte, status int, err error) { + stdoutb, err := io.ReadAll(GetDecodedStdout(runtime, stdout)) if err != nil { return } @@ -89,3 +89,14 @@ func makeProtoHeader(header http.Header) map[string]*proto.HeaderFields { } return m } + +func GetDecodedStdout(runtime string, stdout io.Reader) io.Reader { + switch runtime { + case "go": + return stdout + case "js": + return hex.NewDecoder(stdout) + default: + return stdout + } +} diff --git a/internal/shared/shared_test.go b/internal/shared/shared_test.go index a37cb72..bed02fd 100644 --- a/internal/shared/shared_test.go +++ b/internal/shared/shared_test.go @@ -34,7 +34,7 @@ the big brown fox builder.WriteString(userLogs) builder.WriteString(userResp) builder.Write(buf) - if _, _, _, err := ParseStdout(builder); err != nil { + if _, _, _, err := ParseStdout("go", builder); err != nil { log.Fatal(err) } builder.Reset() @@ -54,7 +54,7 @@ func TestParseWithoutUserLogs(t *testing.T) { binary.LittleEndian.PutUint32(buf[4:8], uint32(len(userResp))) builder.Write(buf) - logs, resp, status, err := ParseStdout(builder) + logs, resp, status, err := ParseStdout("go", builder) require.Nil(t, err) require.Equal(t, int(statusCode), status) require.Equal(t, userResp, string(resp)) @@ -81,7 +81,7 @@ the big brown fox binary.LittleEndian.PutUint32(buf[4:8], uint32(len(userResp))) builder.Write(buf) - logs, resp, status, err := ParseStdout(builder) + logs, resp, status, err := ParseStdout("go", builder) require.Nil(t, err) require.Equal(t, int(statusCode), status) require.Equal(t, userResp, string(resp))