April 22, 2022

Wasm, WASI, Wagi: What are they?

Matt Butcher Matt Butcher

wasi wagi wasm webassembly spin

Wasm, WASI, Wagi: What are they?

Any new technology comes with a litany of new terminology. WebAssembly is no different. In this post, we take a look at how Wasm (short for WebAssembly), WASI (the WebAssembly System Interface) and Wagi (WebAssembly Gateway Interface) work together. The Fermyon Spin framework makes use of all three of these.

As of Oct. 2023, we have some updates on this material. Read the latest on Wasm, WASI, and the Component Model

Wasm

In How to Think About WebAssembly, we drew on a quote from WebAssembly creator Luke Wagner, when he said

WebAssembly… defines a portable, size- and load-time-efficient format and execution model specifically designed to serve as a compilation target for the Web.

We went on to show how while the original target was the web, WebAssembly’s design and key features made it a great fit for a variety of other cases, including cloud applications. There are a number of WebAssembly execution environments that run on the command line. Wasmtime, Wamr, and WasmEdge are a few examples. And there are some executors that specialize in being embedded in other environments. wasm3 is great for embedding in highly constrained environments (e.g. smaller than a Raspberry Pi), while wasmer can be embedded in languages like PHP and V.

All of these (and browsers, too) can run vanilla WebAssembly modules that follow the official specification.

But by default, vanilla WebAssembly modules cannot access external resources. No files, no environment variables, no network connections; nothing. As a result, developers are somewhat limited in what they can do with vanilla Wasm.

In the browser, there are mechanisms for interaction between a WebAssembly module and JavaScript. A JS developer can export functions to the WebAssembly module, allowing the module to then call those functions.

Now there is a group of specifications, collectively called WASI, that allows non-browser WebAssembly modules to securely talk to a few select system resources.

WASI, the WebAssembly System Interface

WASI will eventually support many features. The current list of features includes everything from access to the system’s random generator to machine learning extensions. These burgeoning standards are still actively being defined, and are not broadly implemented. (The Wasmtime runtime typically stays close to the forefront of WASI.)

That said, there are a few WASI specifications that enjoy broad implementation:

  • Filesystem access
  • Environment variable support
  • Support for clocks and random number generators

Other popular system functions, like networking and HTTP support, are coming along rapidly. But at the moment, only limited networking capabilities are common, and until WebAssembly supports threading, it is not a good candidate for writing low-level socket code.

Fermyon engineers have long been interested in writing web applications and microservices. And we are sometimes an impatient lot. So we chose not to wait for networking support before writing an HTTP server that runs WebAssembly modules. And we drew inspiration from an unlikely place.

Wagi: WebAssembly Gateway Interface

We initially thought we were caught in a dilemma:

  • If we wrote our own networking stack, we would be working around the goals and process of the WASI working group (which we are part of). That didn’t seem like good behavior.
  • But, without networking in WebAssembly, we had no way to write services that could load WebAssembly modules to handle HTTP requests.

Or did we? That last supposition, we realized, was not entirely true. We did not actually require networking inside of the WebAssembly module. Instead, we could handle the networking part in the host runtime and pass the data into our Wasm modules using existing WASI features. Not only that, but there is even a standard for doing this! RFC 3875 defined exactly such a mechanism. And this technology has enjoyed broad adoption among almost all major web browsers for the last few decades, though it was better known as Common Gateway Interface (CGI).

CGI works like this:

  • The webserver receives a request that is destined for a CGI program
  • The webserver parses the HTTP header and body, and starts the appropriate CGI program
  • The webserver stores each HTTP header in an environment variable
  • The request body (if any) is passed to the CGI program via the STDIN file handle
  • As the CGI program runs, anything it prints to the STDOUT file handle is sent back to the browser, and anything sent to the STDERR file handle is sent to the error log

When the webserver receives a request for http://example.com/foo.cgi, it starts an external program named foo.cgi and passes it all of the relevant HTTP information using only environment variables, arguments, and file handles. When the CGI program returns data to the webserver, the server reformats that information as an HTTP response and sends the response to the browser.

The important thing to note here is that the CGI program never needs network access.

These features matched our requirements for how to run WebAssembly prior to WASI networking support. And so we wrote Wagi.

Wagi abides by the CGI 1.1 spec (RFC 3875). It defines a few extra environment variables unique to Wagi, but compatible with the specification.

The Wagi server works analogously to CGI webservers, though with some substantial performance improvements. Wagi maps an HTTP path to a Wasm module. For example, with Wagi, one could map https://example.com/foo/bar to the Wasm module hello.wasm. When /foo/bar is requested, Wagi loads the hello.wasm module and executes it just like CGI executes its apps.

In the end, Wagi is the easiest way to write a WebAssembly web application. Here, for example, is a Wagi “Hello World” program written in Swift:

print("content-type: text/plain\n")
print("\n")
print("Hello!\n")

In Rust, it’s not much more complicated:

fn main() {
    println("content-type: text/plain");
    println("");
    println("Hello World");
}

In essence, the main difference between writing a Wagi app and any other stand-alone app is that the first line it prints to STDOUT is the content-type of the data it is about to write. After the header section, an app must print an empty line before starting on the main content. This is exactly the way that CGI works.

Wagi started out as an implementation of a protocol (CGI 1.1). But as we’ve moved forward into building newer tooling, we at Fermyon have started using the term “Wagi support” to mean that tooling works with any app that supports Wagi’s CGI style. That’s what we mean when we say that a tool “supports Wagi applications” or “is Wagi compatible.” This is important for one reason: While the WASI standard upon which Wagi is built has continued to evolve, not all languages are able to keep up. WASI is an entire suite of standards. But Wagi needs the support of only a few of these standards. Saying something is “Wagi compatible” is the shortest way for us to say “supports at least the features of WASI that are needed to write a Wagi web app.”

When we introduced Spin, our framework for building WebAssembly microservices and web apps, we added support for Wagi. That means you can write Spin apps in any language that can be compiled to WebAssembly and has basic WASI support. That includes compiled languages like C, Rust, and C# as well as scripting languages like Python and Ruby.

For some languages, like Rust and Go, Spin supports a richer executor that can not only do HTTP, but can also listen for Redis events. It’s even possible to write custom triggers.

Conclusion

Wasm, WASI, Wagi: three terms we use frequently when talking about our WebAssembly support. This article defines the three, but also explains how we use the three in conjunction.

If you are reading this article on our website, then you are using a Wagi-based web application. Fermyon.com is managed by our micro-CMS named Bartholomew, which is compiled to a Wagi-compatible WebAssembly binary and executed by Wagi. Our Wasm CMS uses WASI’s file support to read content off of a local volume. It also uses WASI clock support to handle dates and times. For example, an article may by pushed into WASI’s file system, but not published until a particular date and time. And then Bartholomew relies upon Wagi to translate incoming requests into environment variables and files, which it then reads before writing its response back to STDOUT.

But not all of our Wagi programs are quite that complex. Earlier we covered another of our services written in Go. If you are interested in a practical example, that article is a good place to start.


🔥 Recommended Posts


Quickstart Your Serveless Apps with Spin

Get Started