Less talk, more code

The blog of Iskandar Soesman

Aplikasi Comet dengan PHP

Sudah cukup lama saya mendengar tentang istilah comet ini di dalam aplikasi web. Namun baru beberapa hari belakangan tertarik untuk mencobanya lebih jauh dan kemudian menuangkannya dalam tulisan ini.

Comet adalah model aplikasi web dimana client dalam hal ini web browser melakukan long HTTP requst terhadap web server untuk mendapatkan informasi terbaru atau melakukan push data tanpa diminta secara eksplisit oleh browser http://en.wikipedia.org/wiki/Comet_(programming). Ada banyak istilah yang digunakan untuk comet ini seperti server push, ajax push, HTTP streaming dll.

Comet berguna untuk aplikasi yang didisain agar user mendapatkan update secara real-time atau ketika seorang user memberikan input kemudian butuh untuk segera di-broadcast ke user-user lainnya. Contohnya adalah aplikasi chating, aplikasi kolaborasi, multi-player game dll. Contoh layanan yang sudah menggunakan model ini adalah Google Talk Web, Google Plus, Google Wave, Facebook, Meebo dll.

Web server dan bahasa pemrograman yang digunakan menjadi perhatian utama pada comet. Hal ini disebabkan server harus bisa menangani koneksi yang terus menerus (persistent) tanpa melakukan bloking terhadap koneksi yang lain. Kekhasan ini kemudian memunculkan beberapa aplikasi yang secara khusus dibuat untuk model seperti ini seperti, Tornado, Node.js, APE (Ajax Push Engine), Google App Engine The Channel API dll.

Ada anggapan bahwa PHP bukan bahasa yang tepat untuk aplikasi comet. Namun menurut saya hal ini tidak sepenuhnya benar. Contoh sukses yang menggunakan PHP untuk comet dengan sekala besar adalah Meebo. Yang perlu diperhatikan adalah web server apa yang akan dugunakan untuk mendampingi PHP.

Apache masih bisa kita gunakan pada level developement. Namun, untuk production sangat tidak disarankan untuk menggunakan Apache. Alternatif web server yang bisa kita gunakan adalah Nginx. Khusus untuk model comet ini telah tersedia modul yang bisa digunakan, yaitu NGiNX HTTP Push Module ataupun Nginx Push Stream Module. Implementasi NGiNX HTTP Push Module secara khusus akan dibahas pada artikel yang lain. Selanjutnya fokus tulisan ini lebih pada pemrograman PHP sebagai broadcaster dan Javascript sebagai listener.

Sebelum membahas lebih jauh, berikut adalah demo dari apa yang akan kita bahas. Gunakan beberapa browser untuk melihat bagaimana aplikasinya bekerja.

Chat Box Collaboration Webnews dan CMS-nya

Setidaknya dibutuhkan Memory Object Storage seperti APC ataupun Memcache dalam tutorial ini. Pastikan environment development yang Anda gunakan sudah mendukung salah satu ekstensi ini.

Pertama-tama buat sebuah file dengan nama response.php yang isinya seperti berikut:

<?php
/**
 *
 * Simple comet response.
 *
 * Media yang digunakan sebagai tempat penyimpanan sementara
 * adalah APC. Object storage yang bisa digunakan selain APC
 * adalah Memcached, eAccelerator (versi Release-0.9.5 ke bawah),
 * xcache ataupun Zend Data Cache.
 *
 * Matrix fungsi antara APC dan Memcached:
 * apc_fetch() = memcache_get()
 * apc_store() = memcache_set()
 * apc_delete() = memcache_delete()
 * 
 */

// Prefix namespace untuk penghapusan bersamaan.
$namespace  = 'namespace';

// Object key
$cache_key  = 'comet_chat';

// Client unik id
$client_id  = $_GET['client_id'];

// Buat object namespace
if( ! $ns_key = apc_fetch($namespace) ){
    $ns_key = time();
    apc_store($namespace, $ns_key);
}

// Client id dengan nilai dari namespace prefix
$client_id = 'client_'.$ns_key.'_'.$client_id;

// Jika client id belum pernah ada, buat baru.
if( ! apc_fetch($client_id) )
    apc_store( $client_id, rand() );

// Menerima request post
if( isset($_POST['text']) ){

    apc_store( $cache_key, $_GET['client_id'].': '.$_POST['text'] );
    apc_delete($namespace);

    die( json_encode( array('status' => true) ) );
}

// 20 detik dari sekarang.
$next_time = strtotime('+ 20 sec');

while(1){

    // Buat ulang object namespace jika belum ada
    if( ! $ns_key = apc_fetch($namespace) ){
        $ns_key = time();
        apc_store($namespace, $ns_key);
    }

    // Dapatkan client id yang sudah ditambah dengan value namespace
    $client_id = 'client_'.$ns_key.'_'.$_GET['client_id'];

    // Jika client dengan namespace ini tidak ada, maka
    // client akan dikirimkan data terbaru.
    if( ! apc_fetch($client_id) ){
        apc_store( $client_id, rand() );

        $respnose = array(
            'status' => true,
            'content' => apc_fetch($cache_key)
        );

        die( json_encode($respnose) );
    }

    // Jika waktu lebih dari 20 detik dari yg sudah ditentukan,
    // stop eksekusi.
    if( $next_time < time() ){
        flush();
        die( json_encode( array('status' => 'continue') ) );
    }
}

flush();
die( json_encode( array('status' => 'continue') ) );

Buat sebuah file simple.html yang digunakan untuk menerima sekaligus mengirimkan data ke file response.php. Berikut adalah isinya:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
    "http://www.w3.org/TR/html4/strict.dtd"
    >
<html lang="en">
<head>
    <title>Chat Comet Demo</title>
</head>
<body>

<div id="result">Loading...</div>
<textarea name="text"></textarea>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
<script type="text/javascript">

settings = {
    // Generate unik id
    client_id:new Date().getTime(),
    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+'?client_id='+settings.client_id,{'text':text}, callback, 'json');

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

        }
    });

    // Listener untuk mendapatkan data dari server dan menampilkan data terbaru ke dalam element.
    $.listener = function(){
        $.getJSON(settings.socket, {client_id:settings.client_id}, function(data) {
            if(data.status == 'continue'){
                $.listener();
            }
            else{
                $('#result').append(data.content+'<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>

Tempatkan kedua file ini dalam direktori yang sama. Sekarang akses file simple.html dan ketikan sesuatu. Jika tidak ada masalah, kini contoh aplikasi comet sudah berjalan.

Terakhir pastikan web server yang digunakan sudah diset http header keep-alive untuk memastikan koneksi yang digunakan tetap persistent.

Selamat ber-comet-ria!