Skip to content

Commit

Permalink
middleware: add wasm basic (#1747)
Browse files Browse the repository at this point in the history
* middleware: add wasm basic

Signed-off-by: Loong <loong.dai@intel.com>

* Update middleware/http/wasm/basic/basic.go

* Update middleware/http/wasm/basic/basic.go

Co-authored-by: Taction <zchao9100@gmail.com>
Co-authored-by: Yaron Schneider <schneider.yaron@live.com>
Co-authored-by: Bernd Verst <4535280+berndverst@users.noreply.github.com>
  • Loading branch information
4 people committed Jun 13, 2022
1 parent 8728f8a commit 419e296
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,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 @@ -1798,6 +1798,8 @@ github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG
github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I=
github.com/tebeka/strftime v0.1.3/go.mod h1:7wJm3dZlpr4l/oVK0t1HYIc4rMzQ2XJlOMIUJUJH6XQ=
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/tevid/gohamcrest v1.1.1/go.mod h1:3UvtWlqm8j5JbwYZh80D/PVBt0mJ1eJiYgZMibh0H/k=
github.com/tidwall/gjson v1.2.1/go.mod h1:c/nTNbUr0E0OrXEhq1pwa8iEgc2DOt4ZZqAt1HtCkPA=
github.com/tidwall/gjson v1.8.1 h1:8j5EE9Hrh3l9Od1OIEDAb7IpezNA20UdRngNAj5N0WU=
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.getNativeMetadata(metadata)
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) getNativeMetadata(metadata middleware.Metadata) (*middlewareMetadata, error) {
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.

0 comments on commit 419e296

Please sign in to comment.