-
Notifications
You must be signed in to change notification settings - Fork 465
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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
1 parent
8728f8a
commit 419e296
Showing
5 changed files
with
176 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 not shown.