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/cast v1.3.1 // indirect | ||||||
| 	github.com/spf13/pflag v1.0.5 // indirect | 	github.com/spf13/pflag v1.0.5 // indirect | ||||||
| 	github.com/stretchr/objx v0.3.0 // 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/tidwall/match v1.0.3 // indirect | ||||||
| 	github.com/tjfoc/gmsm v1.3.2 // indirect | 	github.com/tjfoc/gmsm v1.3.2 // indirect | ||||||
| 	github.com/tklauser/go-sysconf v0.3.6 // 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/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/tebeka/strftime v0.1.3/go.mod h1:7wJm3dZlpr4l/oVK0t1HYIc4rMzQ2XJlOMIUJUJH6XQ= | ||||||
| github.com/testcontainers/testcontainers-go v0.9.0/go.mod h1:b22BFXhRbg4PJmeMVWh6ftqjyZHgiIl3w274e9r3C2E= | 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/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.2.1/go.mod h1:c/nTNbUr0E0OrXEhq1pwa8iEgc2DOt4ZZqAt1HtCkPA= | ||||||
| github.com/tidwall/gjson v1.8.1 h1:8j5EE9Hrh3l9Od1OIEDAb7IpezNA20UdRngNAj5N0WU= | 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