Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

middleware: add wasm basic #1747

Merged
merged 8 commits into from
Jun 13, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ require (
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.3.0 // indirect
github.com/tetratelabs/wazero v0.0.0-20220425003459-ad61d9a6ff43
github.com/tidwall/match v1.0.3 // indirect
github.com/tjfoc/gmsm v1.3.2 // indirect
github.com/tklauser/go-sysconf v0.3.6 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1269,6 +1269,8 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69
github.com/supplyon/gremcos v0.1.0 h1:kZdC3P6m8dkfBO4ZLB2XmzHrPu/Z5enwgz6/x8MTIhc=
github.com/supplyon/gremcos v0.1.0/go.mod h1:ZnXsXGVbGCYDFU5GLPX9HZLWfD+ZWkiPo30KUjNoOtw=
github.com/testcontainers/testcontainers-go v0.9.0/go.mod h1:b22BFXhRbg4PJmeMVWh6ftqjyZHgiIl3w274e9r3C2E=
github.com/tetratelabs/wazero v0.0.0-20220425003459-ad61d9a6ff43 h1:o/PS34ksCpw72GtxUKad1jDMPsgGn6zVRG0BFIOUrsU=
github.com/tetratelabs/wazero v0.0.0-20220425003459-ad61d9a6ff43/go.mod h1:Y4X/zO4sC2dJjZG9GDYNRbJGogfqFYJY/BbyKlOxXGI=
github.com/tidwall/gjson v1.2.1/go.mod h1:c/nTNbUr0E0OrXEhq1pwa8iEgc2DOt4ZZqAt1HtCkPA=
github.com/tidwall/gjson v1.8.1 h1:8j5EE9Hrh3l9Od1OIEDAb7IpezNA20UdRngNAj5N0WU=
github.com/tidwall/gjson v1.8.1/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk=
Expand Down
139 changes: 139 additions & 0 deletions middleware/http/wasm/basic/basic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package basic

import (
"encoding/json"
"log"
"os"
"path/filepath"

"github.com/pkg/errors"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/wasi"
"github.com/valyala/fasthttp"

"github.com/dapr/components-contrib/middleware"
"github.com/dapr/kit/logger"
)

type middlewareMetadata struct {
Path string `json:"path"`
Runtime string `json:"runtime"`
}

// Middleware is an wasm basic middleware.
type Middleware struct {
logger logger.Logger
}

// NewMiddleware returns a new wasm basic middleware.
func NewMiddleware(logger logger.Logger) *Middleware {
return &Middleware{logger: logger}
}

// GetHandler returns the HTTP handler provided by wasm basic middleware.
func (m *Middleware) GetHandler(metadata middleware.Metadata) (func(h fasthttp.RequestHandler) fasthttp.RequestHandler, error) {
var (
meta *middlewareMetadata
err error
)
meta, err = m.getNativeetadata(metadata)
berndverst marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, errors.Wrap(err, "failed to parse wasm basic metadata")
}
if meta.Runtime != "wazero" {
return nil, errors.Wrap(err, "only support wazero runtime")
}
path, err := filepath.Abs(meta.Path)
if err != nil {
return nil, errors.Wrap(err, "failed to find wasm basic file")
}
return func(h fasthttp.RequestHandler) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
// Create a new WebAssembly Runtime.
r := wazero.NewRuntime()

// TinyGo needs WASI to implement functions such as panic.
wm, err := wasi.InstantiateSnapshotPreview1(ctx, r)
if err != nil {
log.Fatal(err)
}
defer wm.Close(ctx)

wasmByte, err := os.ReadFile(path)
if err != nil {
log.Fatalf("wasm file %v", err)
}

mod, err := r.InstantiateModuleFromCode(ctx, wasmByte)
if err != nil {
log.Fatal(err)
}
defer mod.Close(ctx)

// These are undocumented, but exported. See tinygo-org/tinygo#2788
malloc := mod.ExportedFunction("malloc")
free := mod.ExportedFunction("free")

uriFunc := mod.ExportedFunction("run")
if uriFunc == nil {
log.Fatal(err)
}

uri := ctx.RequestURI()
uriLength := uint64(len(uri))

// Instead of an arbitrary memory offset, use TinyGo's allocator. Notice
// there is nothing string-specific in this allocation function. The same
// function could be used to pass binary serialized data to Wasm.
results, err := malloc.Call(ctx, uriLength)
if err != nil {
log.Fatal(err)
}
uriPtr := results[0]
// This pointer is managed by TinyGo, but TinyGo is unaware of external usage.
// So, we have to free it when finished
defer free.Call(ctx, uriPtr)

// The pointer is a linear memory offset, which is where we write the value.
if !mod.Memory().Write(ctx, uint32(uriPtr), uri) {
log.Fatalf("Memory.Write(%d, %d) out of range of memory size %d",
uriPtr, uriLength, mod.Memory().Size(ctx))
}

// Now, we can call "uriFunc", which reads the string we wrote to memory!
ptrSize, err := uriFunc.Call(ctx, uriPtr, uriLength)
// if err != nil {
if err != nil {
log.Fatal(err)
}

// Note: This pointer is still owned by TinyGo, so don't try to free it!
retPtr := uint32(ptrSize[0] >> 32)
retSize := uint32(ptrSize[0])
// The pointer is a linear memory offset, which is where we write the name.
if bytes, ok := mod.Memory().Read(ctx, retPtr, retSize); !ok {
log.Fatalf("Memory.Read(%d, %d) out of range of memory size %d",
retPtr, retSize, mod.Memory().Size(ctx))
} else {
ctx.Request.SetRequestURIBytes(bytes)
}

h(ctx)
}
}, nil
}

func (m *Middleware) getNativeetadata(metadata middleware.Metadata) (*middlewareMetadata, error) {
berndverst marked this conversation as resolved.
Show resolved Hide resolved
b, err := json.Marshal(metadata.Properties)
if err != nil {
return nil, err
}

var data middlewareMetadata
err = json.Unmarshal(b, &data)
if err != nil {
return nil, err
}

return &data, nil
}
34 changes: 34 additions & 0 deletions middleware/http/wasm/basic/basic_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package basic

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/valyala/fasthttp"

"github.com/dapr/components-contrib/middleware"
"github.com/dapr/kit/logger"
)

type testHandler struct{}

func (t *testHandler) handle(ctx *fasthttp.RequestCtx) {
}

func TestGetHandler(t *testing.T) {
meta := middleware.Metadata{Properties: map[string]string{
"path": "./hello.wasm",
"runtime": "wazero",
}}
log := logger.NewLogger("wasm.basic.test")
handler, err := NewMiddleware(log).GetHandler(meta)
assert.Nil(t, err)

var ctx fasthttp.RequestCtx
ctx.Request.SetRequestURI("/v1.0/hi")

th := &testHandler{}
handler(th.handle)(&ctx)

assert.Equal(t, "/v1.0/hello", string(ctx.RequestURI()))
}
Binary file added middleware/http/wasm/basic/hello.wasm
Binary file not shown.