6

Comet/HTTP push with nginx

October 2 by Rasmus Andersson, tagged nginx, comet, http, push and pubsub, filed under software

One of the most cumbersome problems of implementing some kind of HTTP push a.k.a. Comet functionality is that the client (website) need to be served from the same host and on the same port as the actual push (long-polling or multipart response) mechanism. Now, as we need to maintain a high number of concurrent client connections we can not use traditional server-side applications like PHP or Ruby on Rails. Using PHP for instance would require one PHP process per client connection — the main memory would quickly become saturated and we’ll most likely hit some scary limit of fds and processes the kernel handles without being a sad little kernel.

For a little project I’m involved in I needed to have the best of two worlds and to a cheap price…

Cheap? Yes, as in not expensive. I refrain from running two or more separate hosts exploiting the “iframe domain relaxness” (for instance like Facebook do) because it’s expensive. Hey, I don’t want to spare a cute little IPv4 address just for this thing.

Nginx is a russian workhorse (uhm, no actually it’s a really neat web server by Igor Sysoev) which have become a popular choice for running large websites with high load. And I really like it, both as a user and as a code-pornographer (it’s written in a very pretty C).

Now, let’s not eat the cream off of the cake. First I’d like to tell you what happened before I settled with nginx. The most apparent solution is to…not solve it. Not by yourself I mean. Let someone else do the job — find ready-made software. After a night of Googling and evaluation I’ve read about and tried a few of the most popular choices: Orbited, Meteor, CometD, GlassFish, ErlyComet, and numerous others (see end of this article for a few more links). Too complex and all of them basically needs to be running on their own host in an expensive >2-host setup (exploiting the previously mentioned “iframe domain relaxness”). I also refuse to run Java — call me old and grumpy, I just don’t trust a Java app to stay under 1 GB RAM at 10,000 connections.

It’s fun to write software, so what the hell — I wrote my own little comet (pub/sub on HTTP channels, basically) using my fave async I/O library libevent. I called it cometps. But… it basically suffered from the first problem mentioned with the “big ones” — it wasn’t a real battle-tested web server at the same time, which is a requirement for the little project I’m working on. At least I discovered how neat it is to use YAML for configuring C apps :).

Let’s take a reading break and look at some pretty ASCII art:

            sender
              ||
              ||
           [message]
              ||
              \/
    ----------------------
      channel {$push_id}
    ----------------------
      ||      ||      ||
      \/      ||      \/
   listener   ||   listener
              \/
           listener

Then yesterday I stumbled upon NginxHttpPushModule by Leo Ponomarev. This module is basically a chat server and I need a broadcast model, where all subscribers to a “channel” gets all messages published in that channel. So I forked it, rewrote some parts and added broadcasting functionality — voilá! A publish-subscribe server… oh wait, it’s Nginx at the same time! Awesome!

And it works really well.

Never write software without a real-life context. To honour that sentence (I just made up) I wrote a simple Cocoa (OS X) application which grabs an image from my laptops camera, POSTs it to a PHP script which saves it to disk and then the Cocoa app publishes the newly created image’s URL to a NginxHttpPushModule channel. In the other end there is a ridiculously simple HTML client (jQuery and XHR) which listens to the same channel and when receiving (JSON) from the camera app, updates an image. Here’s a video of the thing, running on a live remote server.

I thew in old dirty Internet Explorer 6 just for kicks. Note that 95% of the delay is caused by uploading the photo over my slow connection at home — the publishing and propagation is faster then both Batman and the Phantom.

Related reading


6 comments

  • Avatar

    Can’t say I agree with claimed memory footprint of a Java-based Comet server. Even if it’s not possible below 1 GB of RAM, I still don’t consider this a major issue. With the memory available in any standard workstation, not much will get me jumping down to C. True, if you’re a shop full of low-level C programmers, it might make sense. But that’s becoming increasingly rare these days.

    Some other thoughts..
    How do you handle extended disconnects/connects?
    If I disconnect (by reloading the page, clicking a link), get sent 20 messages and then return 10 seconds later. What messages will I receive? What happens if I have multiple tabs open?

    Is this intended only for public channels? Will it support private channels?

    Sorry if I come off as the one bashing your newborn. Not the intent, just curious :)
    Nice to see more people falling in love with Nginx. What happened to lighttpd that was recommended for use with Smisk? Dumped it favor of Nginx? Also, great to hear about more YAML advocates. Been adopting it for config files too! :)

    (Psst, increase the size of the comment text area, or make it auto-grow.)

  • Avatar

    Don’t you think the user experience could be become a bit peculiar if only some of your open browser session (tabs) would receive sent messages?
    (Tab A receiving all messages, while tab B and C receive none even though they’re in the same session)

    Handling multiple users like that might not be a bad idea as you say, combining session ID + username. If I recall correctly, that’s how XMPP handles multiple (concurrent) clients of the same user - by identifying each client. If one were to view each tab as a separate client it would make a good deal of sense.

    Speaking of XMPP, have you given that any thought? XMPP bridges/BOSH libraries seem to be popping up everywhere. With the advent of Google Wave, XMPP will make it mainstream in my opinion. The times they are a-changin’..

    Also, if you haven’t given Richard Jones’ (of Last.fm) Comet chronicles a read, I highly recommend them.
    http://www.metabrew.com/article/a-million-user-comet-application-with-mochiweb-part-1/

    And psst again, I would change to Safari/Chromium in a heartbeat if they ever would support vertical tabs :)

  • Avatar

    Cool. Entity tags is clever, I like it. Staying true to HTTP :)
    I’ll be following the progress of both of you.

  • Avatar

    Have you found any nice COMET implementations made in Java

Comment