Nix may not be a general purpose programming language, but it is a pretty decent special purpose programming language. It is pretty good at describing managing sets of package descriptions and — as it turns out — CGI programming. To be precise the following properties make it pretty feasible:
-
builtins.getEnv
allows us very easy access to the environment which is the main way the web server communicates with a CGI application - The powerful string interpolation Nix offers makes using a templating engine practically obsolete and rendering HTML not feel like a chore.
- Attribute set can be used to make routing logic very simple and comprehensible, both in expression and implementation. For example:
route = path: routes."${path}" or notFound
.
With bubblegum a simple CGI application looks like this:
{ depot, ... }:
let
inherit (depot.web.bubblegum)
respond
;
in
respond "OK" {
"Content-type" = "text/html";
# further headers…
} ''
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>hello world</title>
</head>
<body>
hello world!
</body>
</html>
''
Of course applications are somewhat limited due to the nature of nix:
- It is pretty slow: Currently it uses a simple wrapper around
nix-instantiate
to interpret CGI scripts which already takes between 200ms and 300ms for trivial scripts and well over 1.5s for scripts using import from derivation where the output is already present in/nix/store
. - We can’t cause any effects except for adding files and directories to the nix store, building derivations and performing GET requests using
builtins.fetchurl
(although this probably only works with tarball TTL 0 and would be very slow). - Error handling is very limited as
builtins.tryEval
doesn’t catch all errors nix evaluation generates.
However even in spite of that bubblegum
can be used to write useful CGI applications. If you want to see for yourself you can run a few example scripts:
> git clone https://code.tvl.fyi/ depot && cd depot
> nix-build -A web.bubblegum && ./result
# navigate to http://localhost:9000
This distribution of examples contains a working file based blog and a SVG which is built on demand from a derivation.
Future improvements would be supporting request bodies and thus POST
and the introduction of streaming (maybe even multiplexed) I/O using a custom interpreter and emulated lazy lists via attribute sets.
Full source code can be browsed in cgit here.