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")
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
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!
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")
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¶m2=val2
) are put into theargs
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 aprint
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" }
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"
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" }
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 athttp://localhost:3000/
. If we made this/hello
then the app would be available athttp://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
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
(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!
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.
- If Go is more your thing, Fermyon has a blog post on writing a Favicon server in Go and running it in Spin. (Also posted here at Wasm.Builders)
- And we've also got a post on writing Wagi apps in Python
- Bartholomew is a Content Management System (CMS) that compiles to WebAssembly. We run Fermyon.com on Bartholomew.
- The Spin documentation goes into much more detail about Spin apps, and you can see some sample code in the Spin Kitchen Sink
- Fermyon is tracking the progress of WebAssembly support in popular languages. Check to see if your favorite language supports Wasm
Top comments (1)
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