Extism?
Extism is a WASM framework that implements the WASI specification. Extism offers two types of SDKs:
Host SDKs for writing "host" applications (i.e. applications that can run WASM module functions) in multiple languages such as Rust, Go, C, C++, Erlang, Elixir, Haskell, .Net, Node.js, OCaml, PHP, Python, Ruby, Zig and... drumroll: Java!
And finally, Plugin SDKs (or PDKs) for coding WASM modules that host applications can call. The PDKs exist for Rust, Go (TinyGo), Haskell, AssemblyScript, C, and Zig.
In summary, the Extism project makes writing applications that can execute WASM functions easy. For example, we can create a Kotlin (and therefore Java) application server with Vert-x that can, thanks to Extism, load WASM modules and execute the module's functions (coded in Rust or Go, or more specifically TinyGo because it can compile to a WASI target and other languages).
GraalVM was supposed to do this (execute WASM), but for now, the WASM support seems to be limited (no implementation of the WASI specification apparently), the documentation is succinct, and no helper is offered to work around the limitations of WASM such as the problem of data types (WASM supports a limited set of data types, including 32 and 64-bit integers, 32 and 64-bit floats).
So, the Graal of Java+WASM, today it is Extism 🥰
Install Extism
First, you need to install the Extism CLI:
sudo apt-get update -y
sudo apt-get install -y pkg-config
sudo apt install python3-pip -y
pip3 install poetry
pip3 install git+https://github.com/extism/cli
echo "export EXTISM_HOME=\"\$HOME/.local\"" >> ${HOME}/.bashrc
echo "export PATH=\"\$EXTISM_HOME/bin:\$PATH\"" >> ${HOME}/.bashrc
source ${HOME}/.bashrc
extism install latest
Extism documentation: https://extism.org/docs/install
Create a TinyGolang WASM function
Creating a WASM plugin with the Extism Go PDK is straightforward:
Create a GoLang project
Create a
main.go
file
package main
import (
"github.com/extism/go-pdk"
)
//export helloWorld
func helloWorld() int32 {
input := pdk.Input()
output := `{"message": "👋 Hello World 🌍","input": "` + string(input) + `"}`
mem := pdk.AllocateString(output)
// zero-copy output to host
pdk.OutputMemory(mem)
return 0
}
func main() {}
To build the WASM plugin, use the below command:
tinygo build -o hello-world.wasm -target wasi main.go
Create a Kotlin Vert-x project
I used the https://start.vertx.io to generate the skeleton of the project with the below command:
curl -G https://start.vertx.io/starter.zip \
-d "groupId=garden.bots" \
-d "artifactId=starter" \
-d "vertxVersion=4.3.7" \
-d "vertxDependencies=vertx-web" \
-d "language=kotlin" \
-d "jdkVersion=17" \
-d "buildTool=maven" \
--output starter.zip
unzip starter.zip
pom.xml
Once the project is generated, downloaded and unzipped, change the pom.xml
file by adding the below dependency to the dependencies
node:
<!-- Extism -->
<dependency>
<groupId>org.extism.sdk</groupId>
<artifactId>extism</artifactId>
<version>0.1.0</version>
</dependency>
<!-- Extism -->
MainVerticle.kt
Change the source code of MainVerticle.kt
like this:
package garden.bots.starter
import io.vertx.core.AbstractVerticle
import io.vertx.core.Promise
import org.extism.sdk.Context
import org.extism.sdk.manifest.Manifest
import org.extism.sdk.wasm.WasmSourceResolver
import java.nio.file.Path
import kotlin.system.exitProcess
class MainVerticle : AbstractVerticle() {
override fun start(startPromise: Promise<Void>) {
val httpPort = System.getenv("HTTP_PORT")
val functionName = System.getenv("FUNCTION_NAME")
//! Load the wasm file
//! Get an instance of the plugin
val resolver = WasmSourceResolver()
val manifest = Manifest(resolver.resolve(Path.of(System.getenv("WASM_FILE"))))
val extismCtx = Context()
val plugin = extismCtx.newPlugin(manifest, true)
vertx
.createHttpServer()
.requestHandler { req ->
//! Get the query parameter
req.body().andThen({ asyncRes ->
var name = asyncRes.result().toString()
//! Call the wasm function
val output = plugin.call(functionName, name)
req.response()
.putHeader("content-type", "application/json; charset=utf-8")
.end(output)
})
}
.listen(Integer.parseInt(httpPort)) { http ->
when (http.succeeded()) {
true -> {
startPromise.complete()
println("HTTP server started on port ${httpPort}")
}
false -> {
startPromise.fail(http.cause())
}
}
}
}
}
The most important parts are:
//! Load the wasm file
//! Get an instance of the plugin
val resolver = WasmSourceResolver()
val manifest = Manifest(resolver.resolve(Path.of(System.getenv("WASM_FILE"))))
val extismCtx = Context()
val plugin = extismCtx.newPlugin(manifest, true)
We use the Extism framework to load the WASM file, create a context and then get an instance of an Extism plugin.
Thanks to the instance of the Extism plugin, now; you can call the WASM function of the module:
//! Call the wasm function
val output = plugin.call(functionName, name)
Serve the WASM module
To build and run the Vert-x application server, use this command:
LD_LIBRARY_PATH="/usr/local/lib" \
WASM_FILE="/home/ubuntu/samples/extism-vert-x-kotlin/hello-world/hello-world.wasm" \
HTTP_PORT="8888" \
FUNCTION_NAME="helloWorld" \
./mvnw clean compile exec:java
When the HTTP server is running (and listening), you can query the WASM microservice:
curl -X POST -d 'Jane Doe' http://localhost:8888
{"message": "👋 Hello World 🌍","input": "Jane Doe"}
curl -X POST -d 'John Doe' http://localhost:8888
{"message": "👋 Hello World 🌍","input": "John Doe"}
The possibilities for using WebAssembly with Java using Extism are vast; as you can see, it is extremely easy to create a function launcher. The WASM/Java world of possibilities is expanding.