Dapr

    First you need to install , Rust, , WasmEdge, and the compiler tool.

    Next, fork or clone the demo application from Github. You can use this repo as your own application template.

    The demo has 3 Dapr sidecar applications.

    • The image-api-rs project provides a WasmEdge microservice to turn an input image into a grayscale image using the function. It demonstrates the use of Rust SDKs for Dapr and WasmEdge.
    • The image-api-go project provides a WasmEdge microservice to recognize and classify the object on an input image using the function. It demonstrates the use of Go SDKs for Dapr and WasmEdge.

    Dapr sidecar microservices in the demo application.

    You can follow the instructions in the README to start the sidecar services. Here are commands to build the WebAssembly functions and start the 3 sidecar services.

    # Build the classify and grayscale WebAssembly functions, and deploy them to the sidecar projects cd functions/grayscale ./build.sh cd ../../ cd functions/classify ./build.sh cd ../../ # Build and start the web service for the application UI cd web-port go build ./run_web.sh cd ../ # Build and start the microservice for image processing (grayscale) cd image-api-rs cargo build ./run_api_rs.sh cd ../ # Build and start the microservice for tensorflow-based image classification cd image-api-go go build --tags "tensorflow image" ./run_api_go.sh cd ../

    Finally, you should be able to see the web UI in your browser.

    dapr-wasmedge

    The demo application in action.

    While our example WebAssembly functions are written in Rust, you can compile functions written in C/C++, Swift, Kotlin, and AssemblyScript to WebAssembly. WasmEdge also provides support for functions written in JavaScript and DSLs.

    The function is a Rust program that reads image data from STDIN and writes the grayscale image into STDOUT.

    We use rustwasmc to build it and then copy it to the sidecar.

    cd functions/grayscale rustup override set 1.50.0 rustwasmc build --enable-ext cp ./pkg/grayscale.wasm ../../image-api-rs/lib

    The classify function is a Rust function that takes a byte array for image data as input and returns a string for the classification. It uses the .

    We use rustwasmc to build it and then copy it to the sidecar.

    cd functions/classify rustup override set 1.50.0 rustwasmc build --enable-ext cp ./pkg/classify_bg.wasm ../../image-api-go/lib/classify_bg.wasm

    In the next three sections, we will look into those three sidecar services.

    The image-api-rs sidecar application is written in Rust. It should already have the WebAssembly function lib/grayscale.wasm installed from the previous step. Please refer to the script to install the WasmEdge Runtime binary and its dependencies.

    The sidecar microservice runs a Tokio-based event loop that listens for incoming HTTP requests at the path /api/image.

    Once it receives an image file in the HTTP POST request, it invokes a WebAssembly function in WasmEdge to perform the image processing task. It creates a WasmEdge instance to interact with the WebAssembly program.

    The following Dapr CLI command starts the microservice in the Dapr runtime environment.

    The image-api-go sidecar application is written in Go. It should already have the WebAssembly function lib/classify\_bg.wasm installed from the previous step. Please refer to the script to install the WasmEdge Runtime Go SDK.

    The sidecar microservice runs an event loop that listens for incoming HTTP requests at the path /api/image.

    func main() { s := daprd.NewService(":9003") if err := s.AddServiceInvocationHandler("/api/image", imageHandlerWASI); err != nil { log.Fatalf("error adding invocation handler: %v", err) } if err := s.Start(); err != nil && err != http.ErrServerClosed { log.Fatalf("error listenning: %v", err) } }

    Once it receives an image file in the HTTP POST request, it invokes a WebAssembly function in WasmEdge to perform the Tensorflow-based image classification task. It utilizes the Go API for WasmEdge to interact with the WebAssembly program.

    func imageHandlerWASI(_ context.Context, in *common.InvocationEvent) (out *common.Content, err error) { image := in.Data var conf = wasmedge.NewConfigure(wasmedge.REFERENCE_TYPES) conf.AddConfig(wasmedge.WASI) var vm = wasmedge.NewVMWithConfig(conf) var wasi = vm.GetImportObject(wasmedge.WASI) wasi.InitWasi( os.Args[1:], // The args os.Environ(), // The envs []string{".:."}, // The mapping directories []string{}, // The preopens will be empty ) // Register WasmEdge-tensorflow and WasmEdge-image var tfobj = wasmedge.NewTensorflowImportObject() var tfliteobj = wasmedge.NewTensorflowLiteImportObject() vm.RegisterImport(tfobj) vm.RegisterImport(tfliteobj) var imgobj = wasmedge.NewImageImportObject() vm.RegisterImport(imgobj) vm.LoadWasmFile("./lib/classify_bg.wasm") vm.Validate() vm.Instantiate() res, err := vm.ExecuteBindgen("infer", wasmedge.Bindgen_return_array, image) ans := string(res.([]byte)) vm.Delete() conf.Delete() out = &common.Content{ Data: []byte(ans), ContentType: in.ContentType, DataTypeURL: in.DataTypeURL, } return out, nil }

    The following Dapr CLI command starts the microservice in the Dapr runtime environment.

    The web UI service web-port is a simple web server written in Go. It serves static HTML and JavaScript files from the static folder and sends images uploaded to /api/hello to the or classify sidecars’ /api/image endpoints.

    func main() { http.HandleFunc("/static/", staticHandler) http.HandleFunc("/api/hello", imageHandler) println("listen to 8080 ...") log.Fatal(http.ListenAndServe(":8080", nil)) } func staticHandler(w http.ResponseWriter, r *http.Request) { // ... read and return the contents of HTML CSS and JS files ... } func imageHandler(w http.ResponseWriter, r *http.Request) { // ... ... api := r.Header.Get("api") if api == "go" { daprClientSend(body, w) } else { httpClientSend(body, w) } } // Send to the image-api-go sidecar (classify) via the Dapr API func daprClientSend(image []byte, w http.ResponseWriter) { // ... ... resp, err := client.InvokeMethodWithContent(ctx, "image-api-go", "/api/image", "post", content) // ... ... } // Send to the image-api-rs sidecar (grayscale) via the HTTP API func httpClientSend(image []byte, w http.ResponseWriter) { // ... ... req, err := http.NewRequest("POST", "http://localhost:3502/v1.0/invoke/image-api-rs/method/api/image", bytes.NewBuffer(image)) // ... ... }

    The JavaScript in simply uploads images to the web-port sidecar’s /api/hello endpoint and the will request the classify or grayscale microservice based on the request header api.

    function runWasm(e) { const reader = new FileReader(); reader.onload = function (e) { setLoading(true); var req = new XMLHttpRequest(); req.open("POST", '/api/hello', true); req.setRequestHeader('api', getApi()); req.onload = function () { // ... display results ... }; const blob = new Blob([e.target.result], { type: 'application/octet-stream' }); req.send(blob); }; console.log(image.file) reader.readAsArrayBuffer(image.file); }

    The following Dapr CLI command starts the web service for the static UI files.

    That’s it. You now have a three part distributed application written in two languages!