Comet with Nginx and jQuery

This is an introduction into a basic Comet setup with Nginx and jQuery. You will have to recompile Nginx with NGiNX_HTTP_Push_Module to enable the HTTP Push/Comet functionality. “NHPM” is based on the Basic HTTP Push Relay Protocol and turns Nginx into a very efficient Comet server. This simple recipe will enable you to create live asynchronous web applications utilizing long polling without the complexity of the Bayeux protocol.

First you will need to recompile Nginx with NHPM. If you run into dependency problems please review the Nginx Install Options.

Compiling Nginx with Nginx_HTTP_Push_Module:

NGINX_PUSH_NAME=nginx_http_push_module-0.692
NGINX_NAME=nginx-0.7.67

cd /usr/local/src/archive
wget http://pushmodule.slact.net/downloads/$NGINX_PUSH_NAME.tar.gz
wget http://nginx.org/download/$NGINX_NAME.tar.gz
cd ..
tar zxvf archive/$NGINX_PUSH_NAME.tar.gz
tar zxvf archive/$NGINX_NAME.tar.gz
cd $NGINX_NAME
./configure --prefix=/usr/local/$NGINX_NAME \
--add-module=/usr/local/src/$NGINX_PUSH_NAME
make
sudo make install

Next we will configure a virtual host in Nginx to enable our publisher and subscriber on a single channel called “cheetah”. This configuration allows the queue to hold up to 10 items, and those items will timeout after 5 seconds.

Configuration: (/usr/local/nginx-0.7.67/conf/nginx.conf)

server {
    listen  81;
    server_name cheetah.example.com;

    root /var/www/cheetah.example.com;

    location /cheetah {
        push_channel_group pushmodule_cheetah;
        location /cheetah/pub {
            set $push_channel_id cheetah;
            push_publisher;
            push_message_timeout 5s;        # Give the clients time
            push_message_buffer_length 10;  # to catch up
        }
        location /cheetah/sub {
            set $push_channel_id cheetah;
            push_subscriber;
            send_timeout 3600;
        }
    }
}

Start nginx:

sudo /usr/local/nginx-0.7.67/sbin/nginx

Here is the code for the subscriber that will listen for incoming messages and simply display them. Notice the the use of “If-None-Match” and “If-Modified-Since”. These headers are used to traverse the messages in the queue. The ETAG header is used to uniquely identify messages with the same modification time.

Listen.html: (/var/www/cheetah.example.com/listen.html)

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
    <head>
        <title>Listen</title>
        <script type="text/javascript" src="http://www.google.com/jsapi"></script>
        <script type="text/javascript">
        /* <![CDATA[ */
   google.load("jquery", "1.4.2");

   function listen(last_modified, etag) {
       $.ajax({
           'beforeSend': function(xhr) {
               xhr.setRequestHeader("If-None-Match", etag);
               xhr.setRequestHeader("If-Modified-Since", last_modified);
           },
           url: '/cheetah/sub',
           dataType: 'text',
           type: 'get',
           cache: 'false',
           success: function(data, textStatus, xhr) {
               etag = xhr.getResponseHeader('Etag');
               last_modified = xhr.getResponseHeader('Last-Modified');

               div = $('<div class="msg">').text(data);
               info = $('<div class="info">').text('Last-Modified: ' + last_modified + ' | Etag: ' + etag);

               $('#data').prepend(div);
               $('#data').prepend(info);

               /* Start the next long poll. */
               listen(last_modified, etag);
           },
           error: function(xhr, textStatus, errorThrown) {
               $('#data').prepend(textStatus + ' | ' + errorThrown);
           }
       });
   };

   google.setOnLoadCallback(function() {
       /* Start the first long poll. */
       /* setTimeout is required to let the browser know
          the page is finished loading. */
       setTimeout(function() {
           listen('Thu, 1 Jan 1970 00:00:00 GMT', '0');
       }, 500);
   });
        /* ]]> */
        </script>
        <style type="text/css">
            #data {
                margin: .5em;
            }

            #data .info {
                font-weight: bold;
                font-size: 14px;
            }

            #data .msg {
                white-space: pre;
                font-family: courier;
                font-size: 14px;
                margin-bottom: .5em;
                margin-left: .5em;
            }
        </style>
    </head>
    <body>
        <div id="data"></div>
    </body>
</html>

This is the publisher that will put messages into the queue.

Send.html: (/var/www/cheetah.example.com/send.html)

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
    <head>
        <title>Send</title>
        <script type="text/javascript" src="http://www.google.com/jsapi"></script>
        <script type="text/javascript">
        /* <![CDATA[ */
    google.load("jquery", "1.4.2");

    function showResult(status, response) {
        $('#result').html('<strong>status:</strong> ' + status +
        '<br /><strong>response:</strong><br />' + response);
    };

    google.setOnLoadCallback(function() {
        $('#pub').submit(function() {
            message = $('#message').val();

            /* Do not send empty message */
            if (message == '') {
                return false;
            }

            $.ajax({
                url: '/cheetah/pub',
                data: message,
                dataType: 'text',
                type: 'post',
                success: function(responseText, textStatus, xhr) {
                    showResult(textStatus, responseText);
                },
                error: function(xhr, textStatus, errorThrown) {
                    showResult(textStatus, errorThrown);
                }
            });
            return false;
        });
    });
        /* ]]> */
        </script>

    </head>
    <body>
        <form id="pub" method="post" action="/cheetah/pub">
            <input type="text" class="message" name="message" id="message" />
            <input class="submit" type="submit" value="send" />
        </form>
        <div id="result"></div></div>
    </body>
</html>

Example Screenshots:

http://cheetah.example.com:81/send.html

Comet Send Screenshot

http://cheetah.example.com:81/listen.html

Comet Listen Screenshot

Other Methods:

PHP Publish

<?php
$ch = curl_init('http://cheetah.example.com:81/cheetah/pub');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, "Hello World!");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$ret = curl_exec($ch);
curl_close($ch);

Bash Publish

curl -d 'Hello World' http://cheetah.example.com:81/cheetah/pub

All rights reserved