Less talk, more code

The blog of Iskandar Soesman

Aplikasi Comet Dengan Nginx HTTP Push Module

Setelah sebelumnya kita membahas aplikasi comet pure berbasis PHP, kali ini kita akan buat aplikasi comet dengan bantuan modul Nginx_HTTP_Push_Module. Pada tutorial kali ini PHP kita gunakan sebagai interface untuk front-end.

Untuk mendapat gambaran aplikasi yang akan kita buat, silahkan coba di sini http://pushmodule.slact.net/.

Pertama-tama kita harus melakukan instalasi Nginx disertai dengan modul Nginx_HTTP_Push_Module. Dalam artikel ini OS yang digunakan adalah Ubuntu 11.04. Namun tetap bisa diterapkan pada OS lainnya dengan beberapa penyesuain.

Download source Nginx_HTTP_Push_Module di https://github.com/slact/nginx_http_push_module/archives/master

Download source Nginx dari http://wiki.nginx.org/Install

extract ke dua file tadi:

tar -xzvf nginx-1.0.4.tar.gz
tar -xzvf slact-nginx_http_push_module-v0.692-18-g5688154.tar.gz

Install library PCRE yang dibutuhkan Nginx saat proses compile:

sudo apt-get install gcc libpcre3-dev libssl-dev

Masuk ke dalam folder Nginx yang sudah diekstrak:

cd nginx-1.0.4

Compile nginx dengan modul:

sudo ./configure \
    --add-module=/LOKASI_FOLDER/nginx_http_push_module/

Secara default Nginx akan diinstal ke folder /usr/local/nginx/. Namun jika ingin menggunakan folder yang lain gunakan perintah:

sudo ./configure \
    --prefix=/PATH/FOLDER_TUJUAN/ \
    --add-module=/LOKASI_FOLDER/nginx_http_push_module/

Lakukan proses make:

make
sudo make install

Setelah proses "make" dan "install" berhasil, edit file konfigurasi Nginx:

/usr/local/nginx/conf/nginx.conf atau sesuai dengan folder yang telah ditentukan.

Edit kira-kira pada line 10 dan tambahkan:

pid     /var/run/nginx.pid;

buat sebuah file baru sebagai startup script:

sudo vim /etc/init.d/nginx

Isikan seperti berikut:

#! /bin/sh

### BEGIN INIT INFO
# Provides:          nginx
# Required-Start:    $local_fs $remote_fs $network $syslog
# Required-Stop:     $local_fs $remote_fs $network $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: starts the nginx web server
# Description:       starts nginx using start-stop-daemon
### END INIT INFO

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/sbin/nginx
NAME=nginx
DESC=nginx

test -x $DAEMON || exit 0

# Include nginx defaults if available
if [ -f /etc/default/nginx ] ; then
    . /etc/default/nginx
fi

set -e

. /lib/lsb/init-functions

test_nginx_config() {
  if nginx -t $DAEMON_OPTS
  then
    return 0
  else
    return $?
  fi
}

case "$1" in
  start)
    echo -n "Starting $DESC: "
        test_nginx_config
    start-stop-daemon --start --quiet --pidfile /var/run/$NAME.pid \
        --exec $DAEMON -- $DAEMON_OPTS || true
    echo "$NAME."
    ;;
  stop)
    echo -n "Stopping $DESC: "
    start-stop-daemon --stop --quiet --pidfile /var/run/$NAME.pid \
        --exec $DAEMON || true
    echo "$NAME."
    ;;
  restart|force-reload)
    echo -n "Restarting $DESC: "
    start-stop-daemon --stop --quiet --pidfile \
        /var/run/$NAME.pid --exec $DAEMON || true
    sleep 1
        test_nginx_config
    start-stop-daemon --start --quiet --pidfile \
        /var/run/$NAME.pid --exec $DAEMON -- $DAEMON_OPTS || true
    echo "$NAME."
    ;;
  reload)
        echo -n "Reloading $DESC configuration: "
        test_nginx_config
        start-stop-daemon --stop --signal HUP --quiet --pidfile /var/run/$NAME.pid \
            --exec $DAEMON || true
        echo "$NAME."
        ;;
  configtest)
        echo -n "Testing $DESC configuration: "
        if test_nginx_config
        then
          echo "$NAME."
        else
          exit $?
        fi
        ;;
  status)
    status_of_proc -p /var/run/$NAME.pid "$DAEMON" nginx && exit 0 || exit $?
    ;;
  *)
    echo "Usage: $NAME {start|stop|restart|reload|force-reload|status|configtest}" >&2
    exit 1
    ;;
esac

exit 0

Sekarang kita cek apakah Nginx sudah berjalan sesuai dengan yang kita harapkan. Nginx yang kita install berjalan di port 80, maka pastikan tidak ada aplikasi lain yang berjalan di port ini:

sudo /etc/init.d/nginx start

Jika pada tahap ini Nginx sudah berhasil jalan, langkah berikutnya adalah melakukan konfigurasi HTTP Push Module di dalam file konfigurasi Nginx. Berikut adalah contoh konfigurasinya:

worker_processes  1;
pid /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  text/plain;

    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       80;
        server_name  localhost;

        # Lokasi root folder aplikasi,
        # sesuaikan dengan kondisi aplikasi Anda.
        location / {
        root   /var/www/public_html;
            index  index.html index.htm;
            autoindex on;
            allow all;
        }

        # Blok setting HTTP_Push_Module
    location /broadcast {

            # Blok ini baiknya dijadikan privte
            #dan hanya bisa diakses oleh PHP.
            location = /broadcast/sub {

                set $push_channel_id $arg_channel;
                push_subscriber;
                push_subscriber_concurrency broadcast;
                push_channel_group broadcast;

            }

            # Blok ini bisa direquest langsung oleh user,
            # atau melalui PHP.
            location = /broadcast/pub {

                set $push_channel_id $arg_channel;
                push_publisher;
                push_message_timeout 5s;
                push_channel_group broadcast;
                push_store_messages off;

            }
    }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        location ~ \.php$ {
            root           html;
            fastcgi_pass   127.0.0.1:9000;
            fastcgi_index  index.php;
            fastcgi_param  SCRIPT_FILENAME  /var/www/public_html$fastcgi_script_name;
            include        fastcgi_params;
        }

        location ~ /\.ht {
            deny  all;
        }
    }
}

Restart Nginx setelah melakukan konfigurasi:

sudo /etc/init.d/nginx restart

Sekarang buka browser dan masukan alamat:

http://localhost/broadcast/sub?channel=123

Jika browser loading seperti tidak selesai maka bisa dipastikan Nginx sudah berjalan dengan baik. Untuk pengujian proses publish/submit kita akan membuat file test.html yang berisi form submit. Berikut adalah contohnya:

<html lang="en">
<head>
    <title></title>
</head>
<body>
    <form action="http://localhost/broadcast/pub?channel=123" method="post">
        <textarea name="text"></textarea><br />
        <input type="submit" name="submit" />
    </form>
</body>
</html>

Buka file test.html dan buka juga halaman http://localhost/broadcast/sub?channel=123 pada tab browser yang berbeda. Isi form file test.html dan submit. Jika di halaman http://localhost/broadcast/sub?channel=123 memunculkan string yang diisikan pada halaman test.html, maka konfigurasi HTTP Push Module sudah berjalan dengan baik.

Pada pengujian ini kita melihat nilai argument 123. Ini adalah identifier unik komunikasi channel antara publisher dan subscriber. Anda bisa menentukan nilai ini sesuai dengan kebutuhan.

Agar Nginx bisa berinteraksi dengan PHP, dibutuhkan sebuah interface FastCGI, untuk menginstallnya silahkan ketik:

sudo apt-get install php5-fpm php5-cgi php5-dev php5-curl

Aktifkan interface ini:

sudo service php5-fpm start

Sekarang kita akan membuat file PHP sebagai front-end interface. Berikut adalah isi filenya:

<?php
/**
 * PHP Comet with Nginx Http Push Module Handler
 *
 * @author <k4ndar at yahoo dot com>
 */

// Set unlimited timeout
set_time_limit(0);

// Id channel
$channel_id             = '123';

// Private internal Nginx url
$nginx_url_publish      = 'http://localhost/broadcast/pub?channel='.$channel_id;
$nginx_url_subscribe    = 'http://localhost/broadcast/sub?channel='.$channel_id;

//Handler untuk requst publisher
if( isset($_POST['text']) && ! empty($_POST['text']) ){

    $request_headers = array(
        'Content-type: application/json; charset=UTF-8',
        'Accept: application/json'
    );

    $_POST['status'] = true;
    $response = send_request($nginx_url_publish, 'POST', json_encode($_POST), $request_headers  );

    header('Content-type: application/json');
    die( $response );
}

//Handler untuk requst subscriber
else {

    $request_headers = array(
        'Accept: application/json',
        'Connection: keep-alive',
        'Cache-Control: max-age=0'
    );

    if( ! $response = send_request($nginx_url_subscribe, 'GET', null, $request_headers) )
        $response = json_encode(array('status' => 'continue'));

    // Pastikan browser tidak men-cache halaman ini.
    header('Content-type: application/json');
    header('Cache-Control: no-store, no-cache, must-revalidate');
    header('Cache-Control: post-check=0, pre-check=0', FALSE);
    header('Pragma: no-cache');
    header('Last-Modified: ' . gmdate( 'D, j M Y H:i:s' ) . ' GMT' );

    die( $response );
}

/**
 * HTTP POST/GET requet menggunakan cUrl
 *
 * @param string $uri Alamat url akses API
 * @param string $method HTTP method POST | GET | PUT | DELETE
 * @param array $data Data yang dikirimkan pada saat melakukan request
 * @param array $request_headers Header string yang ingin ditambahkan saat melakukan request
 * @param bool $response_output_header Flag untuk menampilkan string header atau tidak
 * @param int $timeout Waktu maksimum hingga koneksi timeout
 * @return string jika berhasil atau false jika gagal
 */
function send_request( $uri, $method = 'GET', $data = array(), $request_headers = array(), $timeout = 30, $response_output_header = false ){

    $request_headers[]  = 'User-Agent: Comet Client/0.1';
    $method     = strtoupper($method);
    $url_separator  = ( parse_url( $uri, PHP_URL_QUERY ) ) ? '&' : '?';
    $uri        = ( $method == 'GET' && ! empty($data) ) ? $uri . $url_separator . http_build_query($data) : $uri;
    $c          = curl_init();

    curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($c, CURLOPT_URL, $uri);
    curl_setopt($c, CURLOPT_TIMEOUT, $timeout);

    if($response_output_header)
        curl_setopt($c, CURLOPT_HEADER, true);

    if( ! empty($data) && $method != 'GET' ) {

        if( is_array($data) )
            $data   = http_build_query($data);

        $request_headers[]  = 'Content-Length: ' . strlen($data);

        if( $method == 'POST' )
            curl_setopt($c, CURLOPT_POST, true);

        if( $method == 'PUT' || $method == 'DELETE' )
            curl_setopt($c, CURLOPT_CUSTOMREQUEST, $method);

        curl_setopt($c, CURLOPT_POSTFIELDS, $data);
    }

    curl_setopt($c, CURLOPT_HTTPHEADER, $request_headers);

    $contents = curl_exec($c);

    curl_close($c);

    if($contents)
        return $contents;

    return false;
}

Kita juga akan membuat satu file index.html beserta Javascript-nya untuk menerima interaksi dari user. Berikut adalah isi filenya:

<html lang="en">
<head>
    <title>Chat Comet Demo - Nginx Http Push Module</title>
</head>
<body>

<div id="result">Loading...</div>
<textarea name="text"></textarea>
<!-- Silahkan sesuaikan Lokasi file jQuery ini-->
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
<script type="text/javascript">

settings = {
    socket:'response.php'
};

$(document).ready( function() {

    // Mengirimkan text ke server ketika user menekan enter.
    $('textarea').keydown(function(event) {
        if (event.keyCode == 13) {
            event.preventDefault();

            var text = $(this).val();

            if( text == '' || text == null )
                return false;

            var callback = function (data){};
            $.post(settings.socket,{'text':text}, callback, 'json');

            // Kosongkan isi textarea.
            $(this).val('');

        }
    });

    // Listener untuk mendapatkan data dari server dan menampilkan data terbaru ke dalam element.
    $.listener = function(){

        subscribe = {url:settings.socket};
        // Hilangkan comment di bawah jika ingin mendapatkan data langsung dari nginx tanpa lewat PHP.
        //subscribe.url = 'http://localhost/broadcast/sub?channel=123';

        $.getJSON(subscribe.url, function(data) {
            if(data.status == 'continue'){
                $.listener();
            }
            else{
                $('#result').append(data.text+'<br />');
                $.listener();
            }
        }).error(function() {
            $.listener();
        });
    }

    // Inisail proses
    $.init = function(){
        $('#result').html('');
        $.listener();
    }

    // Start aplikasi 200 ms setelah semua komponen selesai diload.
    window.onload = function() {
        setTimeout ( '$.init()', 200 );
    }
});
</script>
</body>
</html>

Buka browser dan jalankan file index.html. Jika loading sudah selesai ketikkan sesuatu pada bagian textarea dan tekan enter. Jika nilai yang Anda masukkan muncul maka comet sudah berjalan dengan baik. Buka juga satu tab baru dan kembali jalan kan file index.html ini. Lihat bagimana aplikasi comet ini bekerja.

Selamat mencoba!