A Humans.txt File That a Human Can Appreciate
For our My City Lives humans.txt file, I decided to get a little bit creative. It always irked me that humans.txt was in a format that only a machine could love -- plain text. But, with constraints come creativity. How could I use this plain text format for our humans.txt file and not be hamstrung by its limitations (i.e., no colour, no HTML, etc.)?
A lot of creativity has sprung from people forced to use the plain text format. Everyone is familiar with ASCII art and emoticons. So my first thought was to do some fancy ASCII art. But that's been done before. I wanted to try something a little more minimalistic and subtle. So I enumerated what levers I had at my disposal and came up with one particular lever that I wanted to take advantage of:
- Control of the plaintext stream while in transit from server to browser.
"This should be easy", I thought. Back in the 1990s I had used a 2400 baud modem, so seeing text files chunk out to the browser was a common occurrence. Of course, if it turned out to be this easy, I would not be writing this blog post.
I whipped up a quick node.js script that chained an array of functions. Each function used setTimeout with some timeout chosen for presentational aesthetic, to call the next link in the chain. Within each function, I write to the buffer. node.js makes HTTP chunked-transfer encoding trivially easy. I was testing this out in a Linux virtual machine which only had FireFox installed. I tested out the file in browser and thought this was a nice, simple, clever hack.
So I deployed it to our server cluster, but immediately ran into my first stumbling block. Our web server was different from the server which has node.js on it. I setup Nginx to proxy requests for /humans.txt to this server. Of course, if you have experience in this area, you already know what problem I ran into. I hit up the humans.txt file on my browser and it took the full length of my "presentation" before it displayed. Then it displayed all in one go. Nginx was waiting for the node.js process to send the entire response before it passed it onto my browser.
The solution to this was pretty straightforward. In Nginx, I set proxy_buffering to off for that particular location. I reloaded the configuration, loaded it up in FireFox, and all was well! Then I tried Chrome. And I got some weird behaviour. It seemed to buffer 3/4 of the file, displayed it out in one go, then output the remainder as I expected. Testing in Safari exhibited similar behaviour. I'm not exactly sure what WebKit is doing, but my first thought was "Paul what have you done? You've managed to create a browser incompatibility working with plain text files!"
I dropped down to telnet and verified that Nginx was in fact chunking the HTTP response. So this was definitely a WebKit issue. Lots of experimentation and googling led me to the HTTP Content-Type header being the culprit. I was using a value of text/plain. FireFox words as expected with this Content-Type header and displays everything fine. But WebKit seems to want to buffer up at least the first part of the response -- perhaps a side effect of an optimization? Things diverged even more when I tried changing the MIME type. In Safari 5.1, I could use text/event-stream which is meant for an XHR chunked response. That made Safari work just fine and display things as expected. But in Chrome, the file wouldn't display. It would download instead. So I had to find a MIME type that was for a plain text format but that would display in-browser. What I chose was text/javascript. That was the one header that I experimented with which worked consistently across all 3 browser.
So, there you have it! A nice little clever hack that I put together to thank everyone involved in building My City Lives.