Newer
Older

Dynamic websites are easy, and you can make one too

Date: 2025-02-15 17:15
Tags: this-web-server, just-tell-the-computer-what-you-want-it-to-do

In the last few years there's been a movement back towards static HTML sites, as a reaction to the extremely bloated websites with 2MB of minified Javascript on every page, and that's cool. There are advantages of static sites - they can be hosted anywhere, even file://. And they guarantee that every request can be served quickly. There's been a proliferation of static site generators such as Hugo and others.

But I've also seen them viewed as something fundamental, a lower layer on top of which everything else sits, like the Internet equivalent of assembly code, like a good set of static files is mechanically sympathetic. This is not actually the case. Although a static site is a valid paradigm, and a pretty good one, it's not actually the assembly code of the web. The web's fundamental paradigm is that a client sends an HTTP request to a server, the server sends an HTTP response, and the response body is rendered in a browser.

Some running process on the server must interpret the request and produce a response. A static lump of data inherently cannot do this - it has to be a process. By default, almost any web server program can translate a static lump of data into a way of responding to requests, but this is just a fraction of their capabilities. If you have to run a process anyway, then why hamstring it to behave like it's not one? You can take full advantage of the fact that you're running code on the server.

(There are some good reasons of course — for example, that you want to host the same site as an IPFS directory or a downloadable zip file. But the majority of websites do not have such requirements. And you can still produce a separate static variant of your website if you want to. You might even find it most convenient to do this using the same process that generates pages dynamically.)


This website (small as it is) mostly consists of an application server written in bare C with an SCGI interface, and an nginx server in front of that. (I considered the much more complicated FastCGI, but saw no advantage when requests and responses are buffered anyway.) Feel free to go and read the specification of SCGI. I bet you could implement it in an afternoon. It's much tighter than HTTP. Of course, web browsers don't speak SCGI, so you need a real web server to receive requests and convert them to SCGI, which is nginx.

With nginx buffering both requests and responses (which is the default), the workflow is like a synchronous RPC call. Nginx receives and parses the whole HTTP request, then opens a connection to the application server, sends the request in one big block of data, receives the response in one big block of data, closes the connection and then sends the response back to the client at the client's own speed. It's almost like the application server implements a function (pure or not): byte[] getResponse(Map metadata, byte[] body). In fact, the same concept could have literally been implemented as a function within an nginx module - but using a separate process is a good idea, because it creates a fault isolation boundary. If the application server crashes, nginx will return a "502 Bad Gateway" response to that request, and can still handle other requests that might be routed to different backends. It's also impossible for any exploit in the application server to read TLS private keys from the memory of nginx.

Buffering the entire response works fine for web pages, since they are not very big and it is very convenient. For larger files such as images, the application server returns an internal redirect (X-Accel-Redirect), which tells nginx to go and do another path lookup and fetch something else, possibly from a different backend. Locations marked as internal; in the nginx config file can only match internal redirects, so you can prevent bypassing the application server. And since it's a different backend (such as a static directory, instead of SCGI) it isn't subject to the constraints of SCGI such as having to fully buffer the response. (Pedantically, SCGI does support streaming, if configured, but why spend effort on that just to serve a static file?)

You could write your application server in C like mine, but if you want to actually be productive, I'd recommend Python instead. You could use an SCGI library, but as I said earlier, the protocol is so simple you could also implement it yourself in an afternoon. You could also use FastCGI or WSGI (though you'll need a third-party nginx module for WSGI) or HTTP itself (though you lose some metadata and increase the probability of suffering injection vulnerabilities when using an HTTP backend).