middleware: add wasm basic (#1747)
* 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>
This commit is contained in:
parent
8728f8a4c9
commit
419e296d35
1
go.mod
1
go.mod
|
|
@ -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
|
||||
|
|
|
|||
2
go.sum
2
go.sum
|
|
@ -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=
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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 not shown.
Loading…
Reference in New Issue