Serving WebAssembly Files with a Development Web Server

WebAssembly is a relatively recent feature of javascript and modern browsers; as a result, many web servers don’t recognize files the “.wasm” extension as Web Assembly binary files. Here’s how to configure this file type for web servers in the development environment.

Up until the introduction of WebAssembly, Javascript has been just about the only code that will run directly in a browser. It works very well for typical applications, but there are some compute-intensive scenarios like game applications, image processing, etc, where performance could be better. Web Assembly provides a way to send something similar to native level byte code to the browser, where it will execute at close to native code speeds. Source code (currently written in C, C++, or Rust) is compiled into binary code in a file with a “.wasm” extension. The “.wasm” file is fetched and executed by Javascript code in the browser, using the new WebAssembly API.

See this recent ray tracing application project (2018) for a demonstration of how fast Web Assembly code can execute compared to Javascript code.

Emscripten is a leading compiler for WebAssembly; its ’emcc’ command compiles C or C++ source code to .wasm output. An ’emrun’ utility runs a simple HTTP server to open the .html, the .js, and the .wasm files that are generated from a run of the ’emcc’ compiler:

1
2
3
4
5
6
7
8
9
$ emcc hello-world.c -O3 -s WASM=1 -o out/hello-world.html
$ ls -l out/hello-world.*
-rw-r--r--  1 curious  staff  102846 Sep  4 13:48 out/hello-world.html
-rw-r--r--  1 curious  wheel   17753 Sep  4 13:48 out/hello-world.js
-rw-r--r--  1 curious  staff     210 Sep  4 13:48 out/hello-world.wasm
 
$ emrun --no_browser --port 8000 .
Web server root directory: /Users/curious/prog/javascript/webassembly
Now listening at http://localhost:8000/

The “emcc” command is given a “.c” file to compile along with a path to a “.html” file for output. The C code is compiled to .wasm binary code and some Javascript and HTML is also created to load and run it. Running “emrun” starts a simple HTTP server; opening a browser on “http://localhost:8000” and browsing to “out/hello-world.html” will run the WASM code in a codepen-like multi-frame window.

This is good for experimenting with WASM and developing an application, but at some point I’d just like to test the WASM code in the HTML and Javascript code that I’m going to use in the final application, outside of the emscripten environment.

The Python 2 SimpleHTTPServer is easily accessible on many development environments and offers a simple and direct way to serve HTML application files. It comes with the default installation of python (available out of the box on Mac OS X and Linux, easily installed on Windows). Running it with the “python -m” option will start a simple HTTP server on port 8000:

$ python -m SimpleHTTPServer

This works very well to serve HTML, Javascript, and CSS content when testing a web application.

But fetching a WebAssembly .wasm file from a running SimpleHTTPServer results in a runtime error; a message like this is displayed in the developer’s console (this message comes from Chrome, but other browsers react similarly):

Uncaught (in promise) TypeError: Failed to execute ‘compile’ on ‘WebAssembly’:
Incorrect response MIME type. Expected ‘application/wasm’.

Looking in the Chrome developer’s console, it can be seen that a Content-Type of “application/octet-stream” was returned for the served WASM file. This is the default that’s returned from many web servers for files that appear to have binary (unreadable) data. But the WebAssembly API expects to get content of type “application/wasm”.

With a little more coding around the SimpleHTTPServer python class, it can be made to recognize the “.wasm” file type and serve the WebAssembly code in a way that the browser recognizes it. The key is to add the “.wasm” extension to the table of recognized file types extensions (otherwise known as the “mime types”) that SimpleHTTPServer will recognize. This code does the trick:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python
import BaseHTTPServer, SimpleHTTPServer
 
port=8000
print "Running on port %d" % port
 
SimpleHTTPServer.SimpleHTTPRequestHandler.extensions_map['.wasm'] =    'application/wasm' 
httpd = BaseHTTPServer.HTTPServer(('localhost', port),
    SimpleHTTPServer.SimpleHTTPRequestHandler)
 
print "Mapping \".wasm\" to \"%s\"" %
    SimpleHTTPServer.SimpleHTTPRequestHandler.extensions_map['.wasm']
httpd.serve_forever()

I put the above code in a bash script named “runserver”. Making the script executable with “chmod a+x runserver” produces a script that will run a simple web server and serve the .wasm code with the correct “Content-Type” in the response:

$ runserver
Running on port 8000
Mapping “.wasm” to “application/wasm”
127.0.0.1 – – [27/Aug/2018 08:18:28] “GET /hello-world-demo.html HTTP/1.1” 200 –
127.0.0.1 – – [27/Aug/2018 08:18:28] “GET /out/hello.wasm HTTP/1.1” 200 –

Here’s the same fix for Node/ExpressJS:

1
2
3
4
5
6
7
var express = require('express');
var app = express();
 
express.static.mime.types['wasm'] = 'application/wasm'; 
app.use(express.static(__dirname + '/'));
app.listen(8080);

(the “application/wasm” MIME type should soon be available in Express 4.17)

TL; DR

I used the python’s built-in SimpleHTTPServer to serve HTML and .wasm code to test a WebAssembly application, but found that the “Content-Type” header sent by the server wasn’t correct for the WASM code. As a result the WebAssembly javascript API couldn’t load and run the WASM. An additional mime type added to the web server, mapping “.wasm” to “application/wasm”, was all that was needed to get the WebAssembly code to run:

1
2
3
4
5
6
7
import BaseHTTPServer, SimpleHTTPServer
SimpleHTTPServer.SimpleHTTPRequestHandler.extensions_map['.wasm'] =     'application/wasm'port = 8000
httpd = BaseHTTPServer.HTTPServer(('localhost', 8000),
    SimpleHTTPServer.SimpleHTTPRequestHandler)
httpd.serve_forever()

The MIME type can be defined for ExpressJS this way:

1
2
3
4
5
6
var express = require('express');
var app = express();
express.static.mime.types['wasm'] = 'application/wasm';app.use(express.static(__dirname + '/'));
port=8000
app.listen(8000);

Versions

$ python -V
Python 2.7.10

Express version 4.16.3

$ emcc -v
emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 1.38.11
clang version 6.0.1 (emscripten 1.38.11 : 1.38.11)

Chrome Version 68.0.3440.106

2 Comments

Add a Comment