Project: WasmEdge Runtime (Runtime / Container Runtime)
File: dapr.md

In this article, I will demonstrate how to use WasmEdge as a sidecar application runtime for Dapr. There are two ways to do this: Standalone WasmEdge is the recommended approach* is to write a microservice using or , and run it in WasmEdge. The WasmEdge application serves web requests and communicates with the sidecar via sockets using the Dapr API. In this case, we can . Alternatively, Embedded WasmEdge is to create a simple microservice in Rust or Go to listen for web requests and communicate with the Dapr sidecar. It passes the request data to a WasmEdge runtime for processing. The business logic of the microservice is a WebAssembly function created and deployed by an application developer. While the first approach (running the entire microservice in WasmEdge) is much preferred, we are still working on a fully fledged Dapr SDKs for WasmEdge. You can track their progress in GitHub issues -- and . First you need to install and . and are optional for the standalone WasmEdge approach. However, they are required for the demo app since it showcases both standalone and embedded WasmEdge approaches. Fork or clone the demo application from Github. You can use this repo as your own application template. ```bash git clone https://github.com/second-state/dapr-wasm ```` The demo has 4 Dapr sidecar applications. The project provides a public web service for a static HTML page. This is the applications UI. From the static HTML page, the user can select a microservice to turn an input image into grayscale. All 3 microsoervices below perform the same function. They are just implemented using different approaches. Standalone WasmEdge approach:* The project provides a standalone WasmEdge sidecar microservice that takes the input image and returns the grayscale image. The microservice is written in Rust and compiled into WebAssembly bytecode to run in WasmEdge. Embedded WasmEdge approach #1: The project provides a simple Rust-based microservice. It embeds a to turn an input image into a grayscale image. Embedded WasmEdge approach #2: The project provides a simple Go-based microservice. It embeds a to turn an input image into a grayscale image. You can follow the instructions in the to start the sidecar services. Here are commands to build the WebAssembly functions and start the sidecar services. The first set of commands deploy the static web page service and the standalone WasmEdge service written in Rust. It forms a complete application to turn an input image into grayscale. ```bash cd web-port go build ./run_web.sh cd ../ cd image-api-wasi-socket-rs cargo build --target wasm32-wasi cd ../ cd image-api-wasi-socket-rs ./runapiwasisocketrs.sh cd ../ ``` The second set of commands create the alternative microservices for the embedded WasmEdge function. ```bash cd functions/grayscale ./build.sh cd ../../ cd image-api-rs cargo build --release ./runapirs.sh cd ../ cd image-api-go go build ./runapigo.sh cd ../ ``` Finally, you should be able to see the web UI in your browser. The starts a non-blocking TCP server inside WasmEdge. The TCP server passes incoming requests to `handleclient()`, which passes HTTP requests to `handlehttp()`, which calls `grayscale()` to process the image data in the request. ```rust fn main() -> std::io::Result<()> { let port = std::env::var("PORT").unwrapor(9005.tostring()); println!("new connection at {}", port); let listener = TcpListener::bind(format!("127.0.0.1:{}", port))?; loop { let = handleclient(listener.accept()?.0); } } fn handle_client(mut stream: TcpStream) -> std::io::Result<()> {

... } fn handle_http(req: Request<Vec<u8>>) -> bytecodec::Result<Response<String>> { ... ... } fn grayscale(image: &[u8]) -> Vec<u8> { let detected = image::guess_format(&image); let mut buf = vec![]; if detected.is_err() { return buf; } let imageformatdetected = detected.unwrap(); let img = image::loadfrommemory(&image).unwrap(); let filtered = img.grayscale(); match imageformatdetected { ImageFormat::Gif => { filtered.write_to(&mut buf, ImageOutputFormat::Gif).unwrap(); } _ => { filtered.write_to(&mut buf, ImageOutputFormat::Png).unwrap(); } }; return buf; } ``` Work in progress: It will soon interact with the Dapr sidecar through the . Now, you can build the microservice. It is a simple matter of compiling from Rust to WebAssembly. ```bash cd image-api-wasi-socket-rs cargo build --target wasm32-wasi ``` Deploy the WasmEdge microservice in Dapr as follows. ```bash dapr run --app-id image-api-wasi-socket-rs \ --app-protocol http \ --app-port 9005 \ --dapr-http-port 3503 \ --components-path ../config \ --log-level debug \ wasmedge ./target/wasm32-wasi/debug/image-api-wasi-socket-rs.wasm ``` The embedded WasmEdge approach requires us to create a WebAssembly function for the business logic (image processing) first, and then embed it into simple Dapr microservices. The is simple. It uses the macro to makes it easy to call the function from a Go or Rust host embedding the WebAssembly function. It takes and returns base64 encoded image data for the web. ```rust pub fn grayscale(image_data: String) -> String { let imagebytes = imagedata.split(",").map(|x| x.parse::<u8>().unwrap()).collect::<Vec<u8>>(); return grayscale::grayscaleinternal(&imagebytes); } ``` The Rust function that actually performs the task is as follows. ```rust pub fn grayscaleinternal(imagedata: &[u8]) -> String { let imageformatdetected: ImageFormat = image::guessformat(&imagedata).unwrap(); let img = image::loadfrommemory(&image_data).unwrap(); let filtered = img.grayscale(); let mut buf = vec![]; match imageformatdetected { ImageFormat::Gif => { filtered.write_to(&mut buf, ImageOutputFormat::Gif).unwrap(); } _ => { filtered.write_to(&mut buf, ImageOutputFormat::Png).unwrap(); } }; let mut base64_encoded = String::new(); base64::encodeconfigbuf(&buf, base64::STANDARD, &mut base64_encoded); return base64encoded.tostring(); } ``` The embeds the above imaging processing function in WasmEdge. The is a web server and utilizes the Dapr Go SDK. ```go 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 listening: %v", err) } } ``` The `imageHandlerWASI()` function and calls the image processing (grayscale) function in it via . Build and deploy the Go microservice to Dapr as follows. ```bash cd image-api-go go build dapr run --app-id image-api-go \ --app-protocol http \ --app-port 9003 \ --dapr-http-port 3501 \ --log-level debug \ --components-path ../config \ ./image-api-go ``` The embeds the above imaging processing function in WasmEdge. The is a Tokio and Warp based web server. ```rust pub async fn run_server(port: u16) { prettyenvlogger::init(); let home = warp::get().map(warp::reply); let image = warp::post() .and(warp::path("api")) .and(warp::path("image")) .and(warp::body::bytes()) .map(|bytes: bytes::Bytes| { let v: Vec<u8> = bytes.iter().map(|&x| x).collect(); let res = imageprocesswasmedge_sys(&v); let _encoded = base64::encode(&res); Response::builder() .header("content-type", "image/png") .body(res) }); let routes = home.or(image); let routes = routes.with(warp::cors().allowanyorigin()); let log = warp::log("dapr_wasm"); let routes = routes.with(log); warp::serve(routes).run((Ipv4Addr::UNSPECIFIED, port)).await } ``` The `imageprocesswasmedge_sys()` function and calls the image processing (grayscale) function in it via . Build and deploy the Rust microservice to Dapr as follows. ```bash cd image-api-rs cargo build --release dapr stop image-api-rs export LDLIBRARYPATH=/home/coder/.wasmedge/lib64/ dapr run --app-id image-api-rs \ --app-protocol http \ --app-port 9004 \ --dapr-http-port 3502 \ --components-path ../config \ --log-level debug \ ./target/release/image-api-rs ``` That's it! your cool Dapr microservices in WebAssembly!