Wasm Builders 🧱

Cover image for A One Line Microservice with Spin
Matt Butcher for Fermyon Technologies

Posted on

A One Line Microservice with Spin

In this post, we'll build a one-line WebAssembly microservice with Swift. From there, we will dive into Wagi, the specification for writing simple Wasm cloud apps. Finally, we will run the code with Spin, a framework for running WebAssembly microservices.

Setting Up

Swift is a programming language developed by Apple. The community-driven SwiftWasm project adds a Wasm compiler to Swift, complete with WASI support. Before running the code here, you will need to install and configure SwiftWasm.

Not a Swift fan? No big deal! There is nothing particularly special about Swift in today's post. You can just as easily use a number of other languages. There are similar examples for Python, C#, C, Go, AssemblyScript, and Zig. Or you can check out the examples in the official Spin documentation. And if you'd like more sophisticated examples of Rust, Go, and AssemblyScript, the Spin KitchenSink demos have you covered. Finally, the code in this tutorial is based on this example.

A Swift Oneliner

For this example, we'll stick with Swift because it is so concise (the Go version would, for example, require us to write four lines of code). Let's create a file named hello.swift and add a single line of code.

print("content-type: text/plain\n\nHello, World!\n")
Enter fullscreen mode Exit fullscreen mode

The above is just a print statement. Nothing fancy. It should print out some text, then a couple of line breaks, then another line of text.

We can compile it using the swiftc compiler:

$ swiftc -target wasm32-unknown-wasi hello.swift -o hello.wasm
Enter fullscreen mode Exit fullscreen mode

Once the compilation is complete, we have a file named hello.wasm that is the binary version of our simple oneliner.

Our compiled hello.wasm file will run in any Wasm engine that supports WASI.

In a moment, we are going to deploy this as a microservice. But first, we can execute it using a regular command line Wasm runtime and see the result. In this example we use wasmtime, but there are several others.

$ wasmtime hello.wasm
content-type: text/plain

Hello, World!
Enter fullscreen mode Exit fullscreen mode

What we see above is the result of our print. The last line of the output is easy enough to understand -- it just says "Hello, World!" But what's the deal with the first line? And why the empty line? These questions bring us to Wagi.

Wagi: The Nineties Called and Want Their Web Back

In the mid-1990s, the web was new. Most web pages were lovingly hand-crafted HTML. JavaScript was called LiveScript. CSS hadn't been invented. And web servers were not terribly sophisticated.

Static HTML files were fine for regular text content, but developers wanted to build dynamic web pages where the HTML was generated on the fly. And that desire resulted in the creation of the Common Gateway Interface (CGI).

With CGI, the web server received a request, then forwarded it on to a CGI program which then sent back some content. The web server directed that content back to the browser. In essence, the web server merely ran command line programs on behalf of a web browser.

The best thing about writing CGI programs is that the developer didn't have to do anything network-related at all! The incoming HTTP request was translated into environment variables and files. And any content that the program wrote to standard output (STDOUT) was sent back through the web server and on to the browser.

There was just one little caveat: At the bare minimum, a CGI program needed to tell the web server what type of content it was sending back. For example, if it generated an HTML page, it needed to send content-type: text/html as its first line of output. That line would be intercepted by the web server.

Then, to tell the web server that it was going to send the data that was intended for the browser, the CGI program would send a single blank line. After that, everything else was passed from the CGI program through the web server, and back to the web browser.

If the Swift language had been around back then, this is what a simple CGI program would look like:

print("content-type: text/plain\n\nHello, World!\n")
Enter fullscreen mode Exit fullscreen mode

And, yes, before you strain your eyes looking, it is in fact the exact same program we wrote above:

  • It writes a content-type
  • It sends a blank line to tell the server the headers are done
  • It sends back text to be displayed by the web browser

Wagi (WebAssembly Gateway Interface) is a description of how to write WebAssembly modules that work like CGI.

Wagi functions the same as CGI:

  • HTTP headers are put into environment variables
  • Other information about the web server and the client is put into predefined environment variables
  • The query parameters (the part of the URL that looks like ?param1=val1&param2=val2) are put into the args array
  • If the browser sent body content (like a form submission), that data is sent into the program's standard input (STDIN)
  • Anything written to STDOUT (like a print statement) is sent back to the server, and on to the web browser

In fact, the only real difference between Wagi and CGI is that Wagi requires that the code be compiled into WebAssembly with WASI (often described as wasm32-unknown-wasi).

Now that we understand what we wrote, let's run it as a microservice.

Spin: Running a Wagi Application

Spin is a framework for WebAssembly applications such as microservices and web applications.

With Spin, you can do a lot of things, like attach an application to a Redis channel so that it runs every time a new message is sent. But for us, its most important feature is its Wagi compatibility.

For this part, you will need to install Spin.

There are other Wagi-capable runtimes, including the Wagi project (which we wrote before Spin) and Wagi.Net, a version of Wagi that runs inside of a .NET application.

Before we can run our app in Spin, we need to describe the app to Spin. And to do that, we need to write a simple spin.toml file:

spin_version = "1"
name = "spin-hello"
trigger = { type = "http", base = "/" }
version = "1.0.0"

[[component]]
id = "hello"
source = "hello.wasm"
[component.trigger]
route = "/"
executor = { type = "wagi" }
Enter fullscreen mode Exit fullscreen mode

A spin.toml describes an application. And in Spin, an application is built out of one or more components. For our purposes a component is a microservice--a small piece of code that does one single task.

The first part of a spin.toml describes the application:

spin_version = "1"
name = "spin-hello"
trigger = { type = "http", base = "/" }
version = "1.0.0"
Enter fullscreen mode Exit fullscreen mode

For the most part, you only need to worry about name and version. Those are the name and version of your app. spin_version is always 1. And the trigger describes what sort of thing Spin is listening for. (In a Redis application, the trigger describes how Spin should listen to Redis.)

After the top section, every [[component]] section describes another microservice. We only have one, though we could declare multiple microservices using multiple [[component]] sections.

[[component]]
id = "hello"
source = "hello.wasm"
[component.trigger]
route = "/"
executor = { type = "wagi" }
Enter fullscreen mode Exit fullscreen mode

The id is just a unique ID within the spin.toml file. The source must point to the Wasm module we compiled. Then the trigger tells Spin two things:

  • What path (route) the app is mounted to. In this case, / means the root. so when we start our server, hello.wasm will be available at http://localhost:3000/. If we made this /hello then the app would be available at http://localhost:3000/hello
  • What executor to use. For us, we use wagi. (There are other executors that you can read about)

That's about it. At this point, our project directory looks like this:

$ tree           
.
├── hello.swift
├── hello.wasm
└── spin.toml
Enter fullscreen mode Exit fullscreen mode

Inside that directory, we just run spin up and it will start a web server for us:

$ spin up
Serving HTTP on address http://127.0.0.1:3000
Enter fullscreen mode Exit fullscreen mode

(At any time you can quit spin with CTRL-C.)

With Spin running, we can use a web browser to see the result. Here, we use curl:

$ curl localhost:3000

Hello, World!
Enter fullscreen mode Exit fullscreen mode

And that's it! We have written one line of Swift code and turned it into a very tiny web microservice.

Conclusion

This article began with a short Swift program. From there, we looked at Wagi--an updated version of CGI. And then we concluded by running our Swift program inside of Spin.

This is just the start, though. There is much more you can do with Spin and WebAssembly.

Oldest comments (1)

Collapse
 
deepanshu1484 profile image
Deepanshu Arora • Edited

Hi Matt,
Thanks for the detailed rundown on using this.

Update: I earlier had a query but resolved it, that's why edited this comment
Thanks again