The future of distributed computing; transitioning microservices to WebAssembly(Wasm) via RPC.

Timothy McCallum
Wasm
Published in
9 min readNov 20, 2019

Wasm in the browser

From an in-browser perspective, Wasm is certainly basking in the glory of its recent development efforts, and for good reason.

Our most recent article “Writing and executing your Rust program as WebAssembly (Wasm) in under 5 minutes” provides a real-life demonstration of Rust to Wasm compilation, as well as, simple in-browser Wasm execution.

https://medium.com/wasm/writing-and-executing-your-rust-program-as-webassembly-wasm-in-under-5-minutes-3c6736438feb
https://medium.com/wasm/writing-and-executing-your-rust-program-as-webassembly-wasm-in-under-5-minutes-3c6736438feb

Another one of our recent articles gave examples of terrific in-browser applications and then went on to explain WebAssembly (Wasm) in quite a lot of detail.

https://medium.com/wasm/the-future-of-web-assembly-wasm-the-hardware-execution-revolution-4116eafa39a0
https://medium.com/wasm/the-future-of-web-assembly-wasm-the-hardware-execution-revolution-4116eafa39a0

Moving beyond the browser

Wasm is more than just bytecode for browsers. Wasm offers unprecedented portability, efficiency and flexibility. Ergo, we can now take a leap from writing in-browser Wasm applications, in many different languages, to distributing Wasm’s discrete units of functionality across all devices.

Wasm execution environments can include minimal shells, mobiles, desktops and IoT devices. Wasm can potentially drive everything from microchips to full-blown data-centres (Webassembly.org, 2019).

Why is moving beyond the browser important?

When you contact a modern web service you are not just interacting with a single machine, behind the scenes you are interacting with potentially thousands of machines (Arpaci-Dusseau and Arpaci-Dusseau, 2018). The more complex the site, the more expensive the operation. Microservices, which are spread over distributed systems, need to be as simple, efficient and reliable as possible. For big companies like Facebook or Google etc. one would assume that the refining these attributes would result in a favourable outcome and potentially yield savings, in terms of expended energy; ultimately reducing outgoing costs.

In addition to just the bottom line, we should also be enthusiastically experimenting to find ways in which Wasm could improve experiences for end users/consumers. One great example of this is eBay, who recently used Wasm to compliment their mobile barcode scanner implementation; positively impacting an important customer-centric metric (Tech.ebayinc.com, 2019).

Why Wasm?

Let me explain, by starting with a quick word about abstraction.

While OS abstractions turned out to be a poor choice for building distributed systems, programming language abstractions make much more sense.(Arpaci-Dusseau and Arpaci-Dusseau, 2018). As the first mainstream programming language which has been designed with formal semantics from the start, Wasm goes a step further to provide an abstraction over modern hardware (Rossberg et al., 2018).

Wasm, allows the logic of each discrete function to be written and shared amongst the most amount of source-code languages. It aligns with the best software principles and practices that we know (DRY & KISS) and provides a means to transition executable code between all devices as necessary.

Why Remote Procedure Calls?

From an inter-process communication (IPC) perspective, the most dominant abstraction is based on the idea of a Remote Procedure Call, or RPC for short (Arpaci-Dusseau and Arpaci-Dusseau, 2018).

Achieving this ubiquitous interoperability, between distributed machines, requires functionality which allows any application (written in any language) to directly call on services from any other distributed machine, as if it were simply calling its own local objects. This is precisely what Remote Procedure Call (RPC) technologies facilitate.

The goal of this article is, to use Wasm and RPC, to perform universal-language-agnostic code execution over the web.

In the next section we are going to show you how to:

  1. Write custom Rust code and compile to Wasm
  2. Set up a Remote Procedure Call (RPC) server
  3. Define custom services on the RPC server
  4. Install a Wasm Virtual Machine (WAVM)
  5. Execute your custom WebAssembly (Wasm) code remotely via HTTP Post (i.e. Curl, Python etc.)

1. Write custom Rust code and compile to Wasm

Install Rust

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shsource $HOME/.cargo/env

Create a new Rust project

cd ~
cargo new --lib add_numbers_via_wavm
cd add_numbers_via_wavm

Edit the Cargo.toml file; add the lib section & also add the dependencies as shown below

[lib]
name = "adding_lib"
path = "src/adding.rs"
crate-type =["cdylib"]
[dependencies]
serde_json = "1.0"

Add the necessary Wasm software and configuration, at the command line

rustup target add wasm32-wasi
rustup override set nightly

Create a new file called ~/.cargo/config and place the following build text into this newly created config file

[build]
target = "wasm32-wasi"

Write a custom Rust program with a couple of different functions that we can call. In our case below the functions “double” and “triple” which take an integer each and multiply by 2 and 3 respectively.

use serde_json::json;pub extern fn print(answer: i32){
let the_answer = json!({
"Result": answer});
println!("{}", the_answer.to_string());
}
#[no_mangle]
pub extern fn double(x: i32){
let z = x * 2;
print(z);
}
#[no_mangle]
pub extern fn triple(x: i32){
let z = x * 3;
print(z);
}

The above code can be compile with the following command

cargo build --release

2. Set up a Remote Procedure Call (RPC) server

I found a neat C++ RPC Server called rpcsrv. The aim here is to use this C++ RPC Server to accept HTTP POST and convert them into system calls via C++.

sudo apt-get update
sudo apt-get -y upgrade
sudo apt-get install autoconf
sudo apt install libevent-2.1-6
sudo apt-get install libevent-dev
sudo apt-get install libtool
cd ~
git clone https://github.com/jgarzik/rpcsrv.git
cd ~/rpcsrv
git submodule update --init
./autogen.sh
./configure
make
sudo make install

Start the RPC using the following commands

sudo ./rpcsrvd --listen-port 8080

3. Define custom services on the RPC server

Before we go any further I would just like to briefly discuss the use of JSON. I briefly explored a brilliant concept of the univalue. UniValue is high performance RAII C++ JSON library and universal value object class. One which I need to find more time to investigate thoroughly. For convenience in this demonstration, I used a combination of UniValue and rapidjson. Again, more time is need to explore this to ensure the best method of data interchange and interoperability.

The following code was used to install rapidjson.

cd ~
https://github.com/Tencent/rapidjson.git

cd ~/rapidjson
git submodule update --init
mkdir build
cd ~/rapidjson/build/
cmake ..
sudo cp -rp ~/rapidjson/include/rapidjson /usr/include/

After installing rapidjson, I then modified the top of the original C++ API file to include the rapidjson functionality in the rpcsrv codebase.

#include "rapidjson/document.h"
#include "rapidjson/writer.h"
#include "rapidjson/stringbuffer.h"
#include <iostream>
using namespace rapidjson;

At this stage we were able to go ahead and use the rapidjson functionality in our C++ code. Below is an example of how the original echo function was modified for this demonstration

//
// RPC "echo"
//
static UniValue myapi_1_echo(const UniValue & jreq,
const UniValue & params) {
// Receive and parse incoming parameters
string s = params.write();
const char * theJson = s.c_str();
Document d;
d.Parse(theJson);
// Assign parameters to C++ variables and print to console
Value & theService = d["Service Name"];
Value & theType = d["Type"];
Value & theFunctionName = d["Execution"]["FunctionName"];
Value & theArgument = d["Execution"]["Argument"];
cout << endl;
cout << "Received a new request to execute a service on Wasm Virtual
Machine..." << endl;
cout << s.c_str() << endl;
cout << endl;
cout << "Service Name is: " << theService.GetString() << endl;
cout << "Service Type is: " << theType.GetString() << endl;
cout << "Wasm function is: " << theFunctionName.GetString() << endl;
cout << "Wasm function argument: " << theArgument.GetString() <<
endl;

// Construct and execute the call to Wasm Virtual Machine
string commandString = "wavm run --abi=wasi --function=";
commandString += theFunctionName.GetString();
commandString += " ";
commandString += "~/add_numbers_via_wavm/target/wasm32
wasi / release / adding_lib.wasm ";
commandString += " ";
commandString += theArgument.GetString();
cout << "\n";
cout << "Executing command ... " << endl;
string theWasmResults =
execute_function_and_return_output(commandString);

// Print the result to console
cout << "Results are as follows ...: " << endl;
cout << theWasmResults << endl;
UniValue result(theWasmResults);
cout << "Finished." << endl;
// Return the results back to the caller of the RPC
return jrpcOk(jreq, result);
}

4. Install a Wasm Virtual Machine (WAVM)

WAVM uses LLVM to compile WebAssembly code to machine code with close to native performance.

The following instructions were used to install WAVM

sudo apt-get install gcc
sudo apt-get install clang
wget https://github.com/WAVM/WAVM/releases/download/nightly%2F2019-11-04/wavm-0.0.0-prerelease-linux.deb
sudo apt install ./wavm-0.0.0-prerelease-linux.deb

5. Execute your custom WebAssembly (Wasm) code remotely via HTTP Post (i.e. Curl, Python etc.)

The execution can be performed by any mechanism which is capable of producing HTTP POST. For example everything from GUIs like Postman right through to Linux curl command and of course both interpreted and compiled code bases like Python and Java respectively.

Here is an example of the calling code using Curl in the linux command line

Curl - passing in valid JSON

tpmccallum$ curl --header "Content-Type: application/json"   --request POST   --data '{"jsonrpc":"2.0","method":"echo","params":{"Service Name": "Double the digits","Type": "Execution","Execution": {"FunctionName": "double","Argument": "10"}}, "id":1}'   http://123.456.78.9:8080/rpc/1

When looking at this calling code, remember that our Rust program, from which the Wasm originates, has two functions; “double” and “triple”. Our additional layer of RPC means that these original functions are now defined as two individual services.

As you can see above, we are not only specifying which of the services we wish to call, but also specifying the single argument required. When this POST is executed across the web the RPC server directly calls the WAVM and then a JSON result object is returned to the calling code.

Returns valid JSON

{
"jsonrpc": "2.0",
"result": {
"Result": 20
},
"id": 1
}

The return object is completely configurable, this is just a simple example which returns the result of the computation.

RPC Server Output

This RPC server output is optional and was just created for demonstration purposes. It still demonstrates that the RPC server has a hand in passing the JSON back and forth. There are opportunities for additional formatting etc. to be built into the RPC layer (which are over and above the Rust and Wasm code)

Received a new request to execute a service on Wasm Virtual Machine... {"Service Name":"Double the digits","Type":"Execution","Execution":{"FunctionName":"double","Argument":"10"}}Service Name is: Double the digits
Service Type is: Execution
Wasm function is: double
Wasm function argument: 10
Executing command ...Results are as follows ...:
{"Result":20}
Finished.

Python - passing in valid JSON

System setup

sudo apt-get install python-pip
pip install json-rpc
pip install requests

We now pass in the JSON, to the Python code, in a way that describes which service we want. In this case we want to double the number 10 i.e. calling “FunctionName”: “double” with “Argument”: “10”.

>>>import requests
>>>import json

>>>url = "http://123.456.78.9:8080/rpc/1"
>>>payload = {
"jsonrpc":"2.0","method":"echo","params":{"Service Name":"Double the digits","Type": "Execution","Execution": {"FunctionName": "double","Argument": "10"}}, "id":1
}
>>>response = requests.post(url, json=payload).json()

As we can now see, the response returns the result of the Wasm execution i.e. “Result”:20.

>>> print response{u'jsonrpc': u'2.0', u'result': u'{"Result":20}, u'id': 1}

We can try this again, by calling the other service i.e. “FunctionName”: “triple” with “Argument”: “10”

>>>url = "http://123.456.78.9:8080/rpc/1"
>>>payload = {
"jsonrpc":"2.0","method":"echo","params":{"Service Name":"Triple the digits","Type": "Execution","Execution": {"FunctionName": "triple","Argument": "10"}}, "id":1
}
>>>response = requests.post(url, json=payload).json()

Again, we can see that this response is the correct result for the chosen service.

>>> print response{u'jsonrpc': u'2.0', u'result': u'{"Result":30}', u'id': 1}

This article has demonstrated the use of Wasm via RPC. By day, I am a passionate open source blockchain software researcher and core developer for SecondState (Dallas, Austin, Beijing, Shanghai, HongKong and Taipei). If you would like to learn more about Wasm and other technologies which can improve your operation please get in touch via email or GitHub.

References

Arpaci-Dusseau, R.H. and Arpaci-Dusseau, A.C., 2018. Operating systems: Three easy pieces. Arpaci-Dusseau Books LLC.

Rossberg, A., Titzer, B., Haas, A., Schuff, D., Gohman, D., Wagner, L., Zakai, A., Bastien, J. and Holman, M. (2018). Bringing the web up to speed with WebAssembly. Communications of the ACM, 61(12), pp.107–115.

Tech.ebayinc.com. (2019). WebAssembly at eBay: A Real-World Use Case. [online] Available at: https://tech.ebayinc.com/engineering/webassembly-at-ebay-a-real-world-use-case/ [Accessed 20 Nov. 2019].

--

--

Timothy McCallum
Wasm
Editor for

I'm a technical writer and copy editor exploring WebAssembly (Wasm), software automation, and Artificial Intelligence (AI) while mastering Rust, Python, & Bash.