January 25, 2023

Serving Static Content via WebAssembly

Mikkel Mørk Hegnhøj Mikkel Mørk Hegnhøj

Serving Static Content via WebAssembly

As we’ve written about in previous blogs, we run all our websites using Spin. To achieve that, there are a few Spin components, which we tend to use repeatedly for a set of generic features.

This blog post will talk about those features, particularly the Spin Fileserver, which we’ve come to rely on for serving static content via WebAssembly.

In Spin 0.7.0, two new features were added to make implementing the Spin Fileserver very easy:

  • First, the addition of the spin add command facilitated adding a new component; to an existing application through the use of a pre-made template.
  • Second, the addition of the static-fileserver and redirect templates.

Why Do You Need a Fileserver?

A very typical use-case for a fileserver is to serve static content to a client when rendering a website. The website https://developer.fermyon.com/ uses the Spin Fileserver to serve an install script for Spin and to serve static content for it (e.g., images, style-sheets, javascript for the client, etc.). This site, like other Fermyon websites, is based on the Bartholomew CMS template. The use-case here is like almost all websites, which use the client’s browser to render images, execute javascript, and even serve the good-old favicon.

How Do You Use the Spin Fileserver?

If you’re not using the Bartholomew CMS template, but have any other scenario where you need to serve static files directly to the client, this is how it’s done with Spin.

Let’s build a Spin application serving a static image to our client, just for simplicity. The following steps assume you have already installed Spin on your machine.

  1. Make sure the latest Spin templates are installed on your machine, by running spin templates install --git https://github.com/fermyon/spin --update --branch main

    This command updates all templates which are present locally, plus adds templates not available locally, from the main branch in the Git repo https://github.com/fermyon/spin.

  2. Run spin new and choose the static-fileserver template, and fill in the values you’ll be prompted with.

    There’s a shortcut command to this, which is spin new static-fileserver my_pictures

    spin new

    The result of this command is a new directory my_pictures containing a spin.toml file, which defines the Spin application.

  3. Next, we need to create a directory called assets. Once we have the assets directory, we can add an image that we would like to serve, via the Spin Fileserver. For example mkdir assets && cp ~/image_to_serve.png assets

  4. To run the application, simply run spin up – we don’t need to run spin build since we have no code to compile.

    spin up

    When navigating to http://127.0.0.1:3000/static/image_to_serve.png, we see the image.

    screenshot

That was fairly straightforward, but let’s dive a bit further into how this Spin application is configured. Everything is in the spin.toml file, in the application root directory.

spin toml

In the [[component]] section of the file, you’ll see how the template configured the file server component, with a reference to the pre-built WebAssembly module hosted on GitHub (line 9).

You will also see the mapping between the folder assets and the URL route / (root) in line 11. And finally, the URL route, which the module serves /static/..., is in line 13. The three dots means that this module will serve any request going to any resource under /static/.

The combination of those configurations make it such that any file in the assets folder, or any subfolder, will be served under http://<url>/static/ when the server runs:

  • assets/image-one.png —> http://<url>/static/image-one.png
  • assets/collection/image-two.png—> http://<url>/static/collection/image-two.png

If you want to serve files on multiple routes, you can have one file server component serve all routes by changing its route configuration (line 13) to /.... Note that if you have multiple components that could potentially handle the same request based on their defined routes, the last component defined in spin.toml takes precedence. You can see an example of this on here.

Here is a reference to the Spin manifest.

This is really all you need to know about how to use the Spin Fileserver. If you’re interested in learning more about the Fermyon Cloud technology stack (Fileserver, Spin, and Wasmtime), we’ll go into those details in the next section.

How Does This All Work?

First, let’s look at how the WebAssembly code generated by the Spin component you wrote gets access to the files we are serving.

The Spin Fileserver itself does very little. It’s a single Rust source file, which reads files from a filesystem and serves them over HTTP, adding compression and caching headers for efficiency. One could call this a typical Spin component (or microservice) as it does one specific thing in a small and easily manageable codebase.

The responsibility of providing access to a filesystem for any Spin component is implemented in Spin using WASI (WebAssembly System Interface) in Wasmtime. WASI standardizes Wasm outside the browser. Now there are many things to get a hold of here, but let’s walk through what happens when a client requests that image from the Spin application.

Using the diagram below, this is the order of what happens:

  1. The client sends an HTTP request, which is received by the OS, and is handed off to the Spin process, which hosts the Spin application. Remember when we configured the Spin application and we configured what routes to listen on? This is now being evaluated by the Spin host and the request is forwarded to the appropriate Spin component (the Fileserver in this case).
  2. Now that the Fileserver has received the request, it evaluates what file to pick and tries to open the file from disk, just as if a filesystem was available to the process. That access is provided through Wasmtime, which ultimately enables the Spin WebAssembly component to read the file.

diagram

WebAssembly, and in particular WASI, promises strong security using a capability-based security model. This means that any code running in a WASI-compliant WebAssembly runtime does not have any capabilities outside the sandbox in which it runs unless the runtime specifically grants it those capabilities. So for the above to happen, Spin needs to configure Wasmtime with the appropriate capabilities for the WebAssembly module running inside Spin. This is what the files configuration in your spin.toml file does. The Bytecode Alliance provides a good example of what sandboxing capabilities look like with Wasmtime here.

How Do I Mount the Files When Deploying My Application?

Now that we’ve walked through how access to files is being granted, a natural next question is how can we move these files so they are accessible to our Spin application when we deploy it to hosting platforms like Fermyon Cloud?

When you use the spin deploy command, all the files needed to run your Spin application are packaged using Bindle (OCI support coming soon), and thus become a data payload to the Spin application. So it’s important to remember that the size of the files you add to be served from your application directly impacts the size of the deployed package when you deploy your application to Fermyon Cloud. There is currently a limit of 100MB for the total application package being deployed to Fermyon Cloud.

What Use-Cases Are There for a Fileserver With Spin?

How does serving static content fit in with Server-Side WebAssembly, and the functions-type execution model of Spin? Because of the nearly instant start-up time of a WebAssembly module, the difference between running a long-running vs. a reactive process has diminished to be unnoticeable for most cases but still measurable. But this enables a whole variety of workloads for this model, e.g., hosting a regular website as a Spin application. We’ve seen examples of people hosting a static front-end with an API backend using Spin, someone hosting a client-side strategy game using Prolog and NextJS sites, and as mentioned earlier, we run our websites using Spin.

The main limitation of using the fileserver for serving static content via WebAssembly is the current deployment model relying on Bindle packages to carry all the files, as opposed to mounting those from central storage. This quickly taps into the area of persistent state and Spin applications, where there is a lot of work going on for relational DBs, non-relational DBs, BLOB storage, etc. Enabling serving static content from a BLOB store is definitely something to consider as a feature enhancement.

As a closing remark, we start to see so many awesome use-cases show up around Spin, some more surprising than others. We do believe that the developer experience Spin provides, combined with the speed to get things up and running in the cloud, is such a compelling and capable workflow, that only the imagination will set the limit for what can be done with this technology stack. We’re very much looking forward to seeing even more of these, and if you have some, we’d highly encourage you to share those in our Discord for the benefit of your peers in the community.


🔥 Recommended Posts


Quickstart Your Serveless Apps with Spin

Get Started