Some time ago, a colleague I worked with at Krome Studios, Matthew Peers, put me onto using HTTP as an interactive output for debug information. I’d never touched HTTP before and didn’t realise what an amazingly easy and convenient interface it was for getting state information out of your game. Pretty much any platform that is ping-able and supports sockets interface can provide a HTTP interface for debugging information. So far I’ve used it on X360, PS3, PC and iOS devices.
For those of you who have no experience with HTTP GET protocol, I will go through a simple overview of the code involved. But before I do that, I’d like to show you exactly what this is all about and I think a short video is the quickest way to convey that information. So here is a small video of how I am currently using the interface for my iOS game magelore. This video shows the PC version running, but it works exactly the same running it on an iDevice or pretty much any other platform.

So what’s it good for?

Some things I’ve used my HTTP interface for so far:
  • profile information. Gives you somewhere to dump a copy of everything in the selected players profile at any given moment.
  • a script debugger. For our custom scripting language, it allowed you to put breakpoints into any live script and also see traces of recently run script commands and their results. It’s very satisfying being able to just connect to designer’s machines and see what scripts they are currently running and how many objects they have placed – especially if they are unaware you are doing it ;)
  • sorted and filtered general entity lists and entity details
  • screenshots. If you can get a screen shot into a recognised file format, like TGA or PNG,  then you can easily transfer it via HTTP.
  • server / client entity lists. For debugging network apps it’s great for looking at what all clients on the network are seeing.
  • download csv or log files. In my flight simulator work, I capture the entire history of all entities which can be downloaded via the debug interface to see what their velocities reached, turn rates, and what sensors where doing at any time.
Other suggestions:
  • Visual event tracker.  Its often useful for designers to capture game events like player movements, deaths, and kills for level tuning purposes. Why not generate images from this information at runtime and allow them to scrutinize a QA session while its in process. Or even open a couple of them at once if its a network game.
  • Mike Acton’s FPGA (whatever the hell that is) interface

How to set it up

Here is a very quick overview for one possible simple way of setting up the html interface. You could do a lot more elaborate interface with support for AJAX and generating java script if you want to really spend a lot of time on it. However, a very basic interface, like I present below, still gives you a lot of freedom and can be set up in an afternoon.
This is the code for Win32 sockets. Other platforms can differ slightly on how to set up non-blocking sockets, but are otherwise identical. Note that I’ve removed all error-checking for the purposes of keeping it readable for this article.
First we create a listener TCP IP socket. It will just be waiting for GET requests from any browser trying to connect to you.
  SOCKET m_listener;
 
    SOCKET m_connection;
 
  
 
    SOCKET listener = socket(PF_INET, SOCK_STREAM, 6);
 
    u_long nonBlocking = 1;
 
    ioctlsocket(listener, FIONBIO, &nonBlocking);
 
  
 
    sockaddr_in myAddr;
 
    myAddr.sin_family = AF_INET;
 
    myAddr.sin_port = htons(portNmbr);
 
    myAddr.sin_addr.s_addr = INADDR_ANY;
 
    bind(listener, (sockaddr*)&myAddr, sizeof(myAddr));
 
    listen(listener, 16);
Too easy. Now we just need to accept connections and read the GET request. From the GET request we really just want the URL since all the information about what to display will be embedded in the URL as a path and as VALUE PAIRs.
  SOCKET m_connection;
 
  
 
    m_connection = accept(m_listener, 0, 0);
 
    if (m_connection != INVALID_SOCKET)
 
    {
 
      const int BUFSIZE = 8096;
 
      char buffer[BUFSIZE+1];
 
      int received = recv(connection, buffer, BUFSIZE, 0);
 
      if (received > 0)
 
      {
 
        buffer[received] = 0; // null terminate the buffer
 
        ProcessHTTPSpaces(buffer);  // spaces may come in a %20 so convert these back to spaces
 
        char* url = (char*)strchr(buffer, ' '); // string is "METHOD URL " + lots of other stuff
 
        if (url)
 
        {
 
          url++[0] = 0; // null terminate the method, increment pointer to url
 
          char* pSpaceChar = (char*)strchr(url, ' ');
 
          if( pSpaceChar ) pSpaceChar[0] = 0; // null terminate after the second space to ignore extra stuff
 
          if( url[0] == '/' ) url++;
 
          HandleRequest(buffer, url);
 
        }
 
        CLOSE_SOCKET(m_connection);
 
      }
 
    }
The HandleRequest method has now been passed 2 parts. The first parameter will either be “GET” or “POST”.  I only support “GET” currently.
This method will extract the arguments from the URL and put them into a handy arguments class. For example a typical URL request to example an entity might look like:
  http:\\127.0.0.1:1234\ExamineEntity?id=235422;verbose=true
This is interpreted as the command “ExamineEntity”  with the arguments “id=235422″ specifying which entity to examine and “verbose=true” specifying that we want the verbose version. Users of the system would register an “ExamineEntity” callback which would then appear on the main HTML page (or could be hidden if it should only be reached by an internal link).
  HTTPServer::Instance()->RegisterCallback(SZ("ExamineEntity"), SZ("Examine an entity"), &HTTPCallback_ExamineEntity, 0);
And so here is the code for HandleRequest. Again it has been simplified to keep this article to a wieldable length:
void HTTPServer::HandleRequest(const char* method, const char* url)
 
  {
 
    // we only handle GET requests - may support POST in future
 
    if (!DMStringEqual(method, "GET"))
 
      return;
 
  
 
    // build the command and arg list
 
    DMString command = GrabCommand(url);
 
  
 
    DMHTTPArgList args;
 
    const char *pArg, *pValue;
 
    while (GrabValuePair(url, pArg, pValue))
 
    {
 
      args.push_back(HTTPArgument(pArg, pValue));
 
    }
 
  
 
    HTTPCallbackMap::iterator it = m_callbacks.find(command);
 
    if (it != m_callbacks.end())
 
    {
 
      it->second.callback(it->second.pUserData, command, args);
 
    }
 
    else
 
    {
 
      HTMLBuilder builder;
 
      builder.WriteLn("Unkown URL Command");
 
      SendResponse(builder.HTMLPage());
 
    }
 
  }
You’ll see the HTMLBuilder class in there. Your final output needs to be in HTML format so this class just enables you to do some simple logging and formatting without worrying about that aspect.  I think convenience is very important if you want all of your team to be able to get behind this debugging method and add their own HTML pages. I also have a helper class for the generation of sortable HTML tables, which I won’t go into now.
The final piece of the puzzle is the SendResponse method. This sends the HTML page back to your browser and is listed below. Note that this is hardcoding the type as text/html. You may want to customise this so you can handle other content types, but I’ve kept it simple for this article.
void HTTPServer::SendResponse(const char* pData, const char *pDataType)
 
  {
 
    const char *pHeader = "HTTP/1.0 200 OK\r\n\r\n\r\n";
 
    int dataSize = strlen(pData);
 
    send(m_connection, pHeader, strlen(pHeader), 0);
 
    send(m_connection, pData, dataSize, 0);
 
  }
So that’s basically it. You now have a bunch of game-side callbacks that get called whenever someone attempts to browse your ip address. The game callbacks just need to generate a HTML page by whatever method they like and pass it to “SendResponse”.

Conclusion

While I’ve explained my specific server callback implementation, the real purpose of the article was just to get you thinking about the idea of using HTML as a debugging interface to your application. It is simple, convenient, cross-platform, and gives you all the power of your browser for multiple windows, multiple applications without having to code your own net client. It’s now up to you to work interesting ways for it to help your project.