The Mills · Demos

emley-moor

An HTTP server written in Shoddy — mills/emley-moor

A transmitting mast on the moor, broadcasting

Summary

emley-moor is a web server. Point a browser at http://127.0.0.1:8080/ and it answers, in Shoddy, over a socket it opened itself. A socket is the connection a program opens to talk over a network.

cd mills/emley-moor
./build.sh          # serve
./build.sh test     # the routing tests — no network at all

Named for the mast on Emley Moor, a few miles from Huddersfield in the same West Riding as the mills this language is named for. The mast is the tallest freestanding structure in the country. Its entire job is to take a signal in and put it out again to whoever is listening. So does this.

The whole server is one pure function

This is the only interesting thing about the mill, and it is worth the whole page:

Def Respond(request As String) As String

Raw request text in, raw response text out. No socket, no clock, no state. Routing, page building and escaping all live behind that one signature. That means every route can be graded by calling it with a string:

Assert(HttpStatus(Respond(Get("/"))) = 200, "THE INDEX IS THERE")
Assert(HttpStatus(Respond(Get("/nothing-here"))) = 404, "AND ANYTHING ELSE IS A 404")

test.shoddy does exactly that, twenty-odd times, with no network, no --allow-net and no server running. What is left outside the function is accept, read, write, close — about twenty lines, in emley-moor.shoddy, with nothing in them to get wrong.

It is the same seam mungo-caverns draws when it makes a turn a function rather than a loop that prints. A cave adventure and a web server are the same shape of problem: something arrives, something goes back, and the interesting part is the middle. Put the socket at the edge and the middle becomes testable.

What it serves

RouteWhat you get
/An index page, styled in the mill's own colours.
/echoThe request you just sent, taken apart — method, path, every header, and the raw text.
/plaintext/plain, for curl — the command-line tool that fetches web pages.
anything elseA 404 — the web's standard "not found" answer — to prove it can.

The /echo page is the one that earns its keep. It parses — reads and takes apart — the incoming request with HttpHeaders from https, a machine written to read a reply. A request has the same header block a reply does, so the words read one without knowing the difference. The mill proves it every time you load the page.

Under the Hood

Escaping is not optional

The path is a stranger's text, and it goes on the page. A server that pastes it in raw has written its own cross-site scripting hole — a security flaw where a visitor's text runs as code in someone else's browser. So everything echoed back goes through Esc first — ampersand before angle brackets, or the escapes get escaped. There is a test that fetches /<script>alert(1)</script> and asserts the tags do not survive into the body.

HTTP/1.0 and Connection: close

Every reply says HTTP/1.0 and Connection: close. That is a promise: the server hangs up when it has finished. The hang-up is what makes read to end of input the correct rule for whoever is reading — including net's own RecvAll. The reply also carries a Content-Length — the byte count of the body. One of the tests asserts that number is the actual length of the body it sent.

One at a time, on the loopback

One connection is accepted, answered and closed before the next is looked at. No keep-alive, no threads, no chunked encoding. Accept is non-blocking — it returns at once instead of waiting — and it returns 0 when nobody is at the door. So the idle path is a poll around Sleep (check, nap, check again) rather than a spin that burns the processor doing nothing.

It binds 127.0.0.1 only — reachable from this machine and nowhere else. ListenOn is there if you disagree, and the risk is then yours. This is a plaintext HTTP server — nothing it sends is encrypted — written to demonstrate a seam, not to face the internet.

No HTTPS, and why not

Shoddy has a TLS client and no TLS server. TLS is the encryption layer that makes a web connection private — the S in HTTPS. On the client side, TcpSecure upgrades a connected socket; Accept always hands back a plain socket. Server-side TLS drags in certificate provisioning — getting and renewing the identity papers a secure server must show. That is a larger question than this mill wants to answer. So it speaks plaintext and stays on the loopback, where that is nobody's problem.

Not named Loop

The accept loop is ServeForever. It was Loop for about four minutes, until the program failed with unknown word: LOOP at the call site. Loop is a reserved word, so a Def of it is simply not there when you come to call it. Worth knowing, because the error names the caller rather than the definition.

The Machines It Uses

MachineWhy
fileIncluded for the serving-from-disk route that is not written yet.
httpsHttpHeaders reads the incoming request, and pulls in net underneath.
netListen, Accept, WaitRecvFor, Send, Close — the whole socket half.
strJoin and Replace build every page and escape the parts a stranger wrote.