More easy IPC
Last time I talked about .NET Remoting and concluded that it was very nice for .NET to .NET communication, but not much use for anything else - and it's deprecated too. How's a lazy programmer like me meant to get other sorts of applications talking to each other with minimal effort? I still think that WCF is far too heavyweight for most things I want to do, and raw sockets are far too fiddly (but necessary if performance requires that level of control), so what else lives in the space in between?
HttpListener is a pretty cool class hiding in the depths of the .NET Framework: as the name suggests, it provides an HTTP listener (the core of a mini web server) and can decode web requests sent to it. There's nothing special about these web requests - they can come from any application you want and, in fact, a web browser or the wget utility are quite useful when testing an application hosting this sort of service. And did I mention that it's very easy to use.
Here's how to create one and start listening:
this.listener = new HttpListener();
this.listener.Prefixes.Add(string.Format("https://localhost:17765/"));
this.listener.Start();
this.listener.BeginGetContext(ListenerCallback, null);
The "prefixes" are what addresses the listener pays attention to - the significant thing here is the port number, and just pick something that doesn't collide with other services. (Since I do in fact have IIS on my development PC, I want to avoid port 80.) The last line above starts the listener waiting for and processing an incoming request, the completion of which will occur in the ListenerCallback method. Note that this isn't registering a handler for any incoming message, but initiating processing of a single received message, i.e. , you have to keep calling this to keep receiving messages.
Before talking about the callback, here's how to shut the thing down when you're done with it:
this.listener.Stop();
this.listener.Close();
The callback function is where everything happens, in this case, a trivial bit of code that reacts to /key?k=<something> requests (the beginning of a key injection application, if you must know):
private void ListenerCallback(IAsyncResult result)
{
if (!this.listener.IsListening)
return;
try
{
var context = this.listener.EndGetContext(result);
var request = context.Request;
HttpStatusCode responseCode;
if (!request.IsLocal)
responseCode = HttpStatusCode.Forbidden;
else
{
switch (request.Url.AbsolutePath)
{
case "/key":
var key = request.QueryString["k"];
if (key == null)
responseCode = HttpStatusCode.BadRequest;
else
{
Debug.Print("Received key " + key);
bool success = true;
responseCode = success ? HttpStatusCode.OK : HttpStatusCode.ServiceUnavailable;
}
break;
default:
responseCode = HttpStatusCode.NotFound;
break;
}
}
context.Response.StatusCode = (int)responseCode;
context.Response.Close();
// Wait for the next request
this.listener.BeginGetContext(ListenerCallback, null);
}
catch(IOException)
{
}
}
Let's examine this in detail. First, a check that the listener is still active - when stopped, IsListening will be false. (Note that if the listener is torn down while the callback is running, an IO exception is thrown - I really ought to avoid this instead of wrapping the handler in a try...catch block.) Having determined that the listener is running, EndGetContext completes the reception of an HTTP request. Since, as I said above, I want to handle only communications within the PC, I reject any incoming requests where IsLocal is false (the HttpListener can be used across machines too, of course, though configuration is a bit of a chore).
Next, I "parse" the URL in the request, looking for path "/key" and the "k" query parameter. All I do here is print out the received message.
I close the request's response to let the client clean up correctly - there's just an error status and no content (though I could send something back - an HTML help page, for example, when given an unrecognised request URL).
Finally, as mentioned above, the callback is not registered as a continuous handler, but as the completion function for a single request, so I need to initiate another request-to-receive before returning.
That's all there is to a simple mini web server. To test it, run and then fire up a web browser and navigate to "https://localhost:17765/key?k=F" and the debug console will show the expected message.
Here's a mindtaxingly complex .NET client which does the same thing:
var req = WebRequest.CreateHttp("https://localhost:17765/key?k=F");
var resp = req.GetResponse();
I could examine the response for the error code (or any content passed back) but I lazily don't care here.
Since this was all so simple, here's the same thing in native C++:
auto inet = InternetOpen(L"MyAppClientId", INTERNET_OPEN_TYPE_PRECONFIG, nullptr, nullptr, 0);
auto conn = InternetOpenUrl(inet, L"https://localhost:17765/key?k=F", nullptr, 0, 0, 0);
InternetCloseHandle(conn);
InternetCloseHandle(inet);
I was brought up on raw sockets many years ago, and it still feels kinda astonishing that a web request can require so little C++.
So there you have it. Not as clever as .NET Remoting, in that you have to pack and unpack message arguments, but still very simple and quite performant.