Conrad Bailey

Giving a Web Interface to a Command Line Application

This post gives some of the implementation details surrounding the Easter egg I've hidden in this website. So, spoiler alert. There's only one hint (read boldface instruction) about actually finding the egg and I've put that way way down at the bottom.

The Problem

So you wrote some interesting command line utility in C++ or something. Maybe it's even interactive. And now you want to share that utility on the web. You do some googling, but conclude there's no natural way of doing this.

This is the situation I found myself in when I wanted to add an Easter egg to my website. I had a little C++ text-based game laying around that I thought would be a fun thing to stumble across.

The Plan

I am not a web developer, but I've played enough with Flask to know my way around. I was also pretty familiar with the subprocess Python module. I figured I could serve the web interface with Flask and use subprocess to run the native binary upon request.

I was imagining a very literal translation of the game: a terminal like interface where the user provides a line of input and the result is reflected in some sort of scrolling window above the input area.

However, there were two big black-boxes in my design:

  1. The user shouldn't have to refresh the page to get results, but I didn't know how to change page content dynamically. In other words, I didn't have any experience with AJAX.
  2. I wasn't sure how to handle running the binary. As it was, the process continues to run until the player dies or wins. I had a fuzzy idea about running a process per session in the background, but that was doomed as you'll read in a bit.

AJAX Requests From Client

After some googling I found that AJAX requests are the typical method by which dynamic content is added to a page without refreshing. Then I found this great blog-post about integrating AJAX requests and responses into a Flask app. So I created a simple HTML scaffolding for rough drafts: A <div id="terminal"> for output, an <input id="prompt"> for input, and a link like <a href="javascript:trigger('#prompt, '#terminal');"> for issuing the AJAX request.

That AJAX request depends on a Javascript/JQuery function that I named trigger. Look at the "AJAX From The Client" section of the aforementioned blog post, or inspect the source of the Easter egg, to see how I did it. trigger is a lot like his translate function, except the only arguments necessary are a source/input element and a dest/output element.

AJAX Request From Server

Once again, see "AJAX From The Server" from the other blog post for a rough outline of how to make Flask serve up a reasonable response to an AJAX request.

For my purposes, I needed to issue whatever command just came in from the user to an instance of the text game, and respond with the result.

Communicating with the Binary

Incorrect First Step

My first approach was to construct a Popen object with subprocess and store it in the sessions object. That didn't work. Next I tried the g object, and that didn't work. I even tried server side sessions and that again failed. Clearly this approach wasn't working, and taking more shots in the dark would be dumb. The problem was getting the Popen objects to persist between requests; they don't pickle and I didn't want to do a deep dive of Flask's internal state. On top of that, providing input to and extracting output from a running, interactive process is kind of clumsy at best.

A Better Re-Framing

Perhaps some readers out there have a solution to the persistence problem, but I think I found a better solution to my overall objective: record every command issued during a session and for every request spin up a new instance of the game, replay all of the commands, and return the result. This solved both the persistence and I/O problems.

Necessary Modifications

Earlier I had been communicating through read, readuntil, and write methods on Popen.stdout and Popen.stdin, but I found that clumsy. I figured the cleanest way to retrieve output from my game would be with the Popen.communicate method. The problem with that is that communicate() blocks until the process terminates; in an interactive process, like a game, that will not happen after every command, like those not resulting in death or victory. So I had to modify the game to recognize a special string as an "exit code" such that it would immediately flush buffers and exit, like how pilots say "over" at the end of a transmission. Then whenever you communicate() with the game process, append that command to the end of the command list.

Sanitization must also be done to turn your textual output into good HTML. For instance, I needed to convert newlines into </br> and > into &gt;.

The Interface

That covers the nuts and bolts of the backend. A lot of my time went into fiddling with the CSS and Jinja template to get the terminal to act the way I wanted: centered, scroll bar on the outside, command entering with the "Enter" key, etc.

Wiring it All Up

Finally, I used this DigitalOcean post as a guide for serving Flask apps with Gunicorn on Nginx. My justification for choosing Gunicorn is twofold.

  1. I like the name
  2. I found that blog post

The Hint

Okay, hover over the spoiler text if you are not clever enough to find the East egg on your own.

Look at the favicon