Friday, April 5, 2013

OliWeb and IvySox

One of the goals that I've had for the pi project is to run a cgi-enabled web server to enable me to browser-connect and invoke shell scripts or programs remotely.  I wanted something very small and lightweight that would serve up content and invoke scripts and potentially to embed video / snapshot content from the camera.

So which software packages were considered for this job?  None of them, actually.  I know there's probably seven different things out there that would suit my purposes filling every possible niche between command-line socket listener and Apache, but I wanted to write my own.  I've always wanted to learn how low-level socket programming works but haven't had a really good excuse to invest the time until now.  While this has been a bit of a quixotic (quixidiotic?) detour from the main thrust of the project (to the tune of about 2 weeks), I'll borrow in my defense the brilliantly-tautological, debate-silencing quip of my former roommate Gary: "what's fun for me is fun for me".

It turns out that socket programming is really not all that difficult, apart from all the weird old c-language structs with quasi-polymorphic casting and old-school error handling and inscrutably-abbreviated variable names.  Having resources like Beej available also helped tremendously - this is a great and very readable resource with solid examples without which this would have been significantly more painful.  I'm ordering one of his hard copies and making a PayPal donation out of gratitude and suggest you do the same.

So this project yielded two main artifacts.  The first is IvySox, which is a C++ wrapper for the socket stuff with, you know, modern, readable variable names and method calls.  It provides some simple interfaces for things like "listen on this port", "accept inbound connection", "receive message", "send message" and the like, and has some helper classes for managing connections that mask all the scary c-structs.  It's pretty messy right now and I need to do a whole bunch of stuff like adding more methods for client-side comms (since I was building a web server, it's pretty server-side focused), adding full IPV6 support (it's partway there, but haven't verified that it's 100% compatible), and general code cleanup and pruning of all the abortive false-start stubs that are still hanging around.  

The web server itself is a class called OliWeb, which uses IvySox interfaces to listen on a chosen port, receive inbound connections, and serve up files.  It's lean and mean, can invoke CGI scripts or programs and display their (stdout) output, logs all inbound traffic, etc.  OliWeb started out single-threaded, but I bit the bullet and refactored to multithread with a single detached pthread servicing each inbound HTTP request.  Here it is!  This is the home page (index.html) as viewed from Safari on my iPhone (I'm not clearing any shelf space for Placards Proclaiming Greatness in the Field of Web Design just yet):


Here's the snapshot interface where I've got a CGI script set up to take a pic (using fswebcam at the moment) from the attached webcam (sitting on a bedside table, taking thrilling pics of a bookshelf and the just-glimpsed reflection of the ceiling fan... anyone for starting a meme that involves surreptitious midday photos of household furniture and partial views of idle home appliances?). 


Yesterday a co-worker and I pounded it with a bunch of these in parallel:

while true; 
do curl http://10.10.48.141:8077/snapshot/snapshot.jpeg > /dev/null; 
done

while simultaneously using the browser and navigating around.  It's holding up great... now.  One snag to be aware of when setting up a server (which I learned the hard way) is that a client disconnecting mid-stream (like hitting CTRL-C on the above script or hitting "X" or stop on your browser while it's still loading images) will instantly and unceremoniously terminate your server.  After a little internet research, we discovered that your program has to provide a handler to intercept SIGPIPE signals (lucky #13!).  The always helpful (if not particularly friendly) Stack Overflow pretty much sums it up - handle as no-op and go on about your business (I'm detecting the error after the signal now though so I don't continue trying to push bits down the broken pipe).

For this particular application, detached pthreads work great.  I tried to go down a more complicated path involving a managed thread pool with join logic and monitoring and what have you, but at the end of the day it's a lot easier to fire and forget: just pthread_create() the handler threads in a detached state and have them off themselves when they're done:

void OliWeb::handleInboundRequest()
{
    // Accept inbound request from socket listener
    InboundRequest *request = new InboundRequest();
    request->oliWebPtr = (void *)this;
    request->socketNumber = ivySox.acceptInbound(&request->inbound);

    // Create a detached thread to handle inbound request
    pthread_t aThread;
    pthread_attr_t threadAttribute;
    pthread_attr_init(&threadAttribute);
    pthread_attr_setdetachstate(&threadAttribute, 
                                PTHREAD_CREATE_DETACHED);
    int result = pthread_create( &aThread, &threadAttribute, 
                                 threadEntryPoint, (void *) request);
    writeLog("Thread launch result = " + toString(result));
}


void *threadEntryPoint(void *requestVoid)
{
    InboundRequest *request = (InboundRequest *) requestVoid;
    OliWeb *oliWeb = (OliWeb *)request->oliWebPtr;
    oliWeb->threadRequestHandler(request);
    cout << "Deleteding request handler!!" << endl;
    delete request;
    cout << "EXITING THREAD!!" << endl;
    pthread_exit(NULL);
}

I've still got a weird thing going on with parallel handler threads right now where one of them closing its connection after sending its message seems to kill all of the other threads that are still mid-stream, which I've "solved" (i.e. bottlenecked) with a mutex lock on all send operations.  Figuring this out is on my (akin-to-reorganizing-my-sock-drawer-in-level-of-current-interest) to-do list.

I wanted an extensible XML configuration file as well, so I looked at a number of available options and picked TinyXML-2 which is just about perfect for my purposes (light! fast! reasonably-compliant!).  It's overkill right now since I've only got like 5 configuration options in total (like where is the root web directory, where does the CGI-BIN live, what port to use, and two other ones that I can't remember offhand), but I wanted to have it "just in case" and may have need of a lightweight XML parser for other aspects of the pi project.

"But wait!" (one might say), "Since you're already doing a ground-up web server with the sockets and the pthreads and the what have you, why not go whole-hog and write your own XML parser while you're at it?".  To which I would reply "I already did one like six years ago.  It was an STL-based thing that was also light and fast, but kinda sloppy and basically swiss cheese from a compliance perspective.  So instead of starting from scratch or trying to polish that turd, I just decided to grab one of the abundant readily-available options.  And, really, I don't have to defend my choices to you, voice in my head - see 'Gary', above."

So: awesome.  I'm really pleased with how it came out and it allows me to do pretty much anything on the Pi remotely by invoking CGI scripts.  Detour over (cleanup notwithstanding) - I can now go back to futzing with motion .conf files to try to get the video streaming bit to work.