Memakai Server-Sent Events API Di HTML5


Pada artikel Memakai Reverse Ajax Dengan Spring Web MVC 3.2, saya mengimplementasikan Reverse Ajax dengan menggunakan teknik long polling. Pada artikel tersebut, di sisi client, saya secara recursive terus memanggil jQuery.getJSON() untuk melakukan polling. Pada artikel ini, saya akan memakai cara yang lebih sederhana dengan menggunakan Server-Sent Events (SSE) API. Walaupun membuat semuanya menjadi sederhana, SSE hanya membolehkan komunikasi satu arah (yaitu dari server ke client). Selain itu, data tidak boleh dalam bentuk sembarangan melainkan harus mengikuti format text/event-stream. Setidaknya ini masih lebih mudah diterapkan bila dibandingkan menggunakan WebSocket yang mengharuskan server khusus.

Sebagai latihan, saya akan mulai dengan membuat sebuah proyek Laravel baru dengan memberikan perintah:

composer create-project laravel/laravel latihan_sse --prefer-dist

Setelah itu, saya akan membuat sebuah view sederhana yang melakukan proses login dengan nama login.php di folder view yang isinya seperti berikut ini:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>
    <form method="post" action="<?php echo action('LoginController@login'); ?>">
        <div>
            <label for="nama">Nama</label>
            <input type="text" name="nama" maxlength="50" required />
        </div>
        <div>
            <label for="password">Password</label>
            <input type="password" name="password" required />
        </div>
        <div>
            <input type="submit" value="Login" />
        </div>
    </form>
</body>
</html>

View di atas akan memanggil LoginController.login() bila tombol submit di-klik.

Berikutnya, saya membuat sebuah view lagi dengan nama Logout.php di folder view yang isinya seperti berikut ini:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>
    Selamat datang, <strong><?php echo $nama; ?></strong>.
    <?php 
        echo link_to_action('LoginController@logout', 
            'Klik disini untuk logout', array('nama'=>$nama));
    ?>
</body>
</html>

View di atas akan memanggil LoginController.logout() dengan menyertakan parametr nama user bila link untuk logout di-klik oleh pengguna.

Setelah view selesai dibuat, saya akan lanjut dengan membuat sebuah controller sederhana dengan nama LoginController.php di folder controller yang isinya seperti berikut ini:

<?php

class LoginController extends BaseController {

    public function login() {       
        if (Request::has('nama') && Request::has('password')) {
            $nama = Request::get('nama');
            $events = Cache::get('events', function() { return []; });
            $events[] = ['nama'=>$nama, 'aksi'=>'login'];
            Cache::forever('events', $events);            
            return Response::view('logout', array('nama' => $nama));
        } else {
            return Redirect::to('login');
        }
    }

    public function logout() {
        $nama = Request::get('nama');
        $events = Cache::get('events', function() { return []; });
        $events[] = ['nama'=>$nama, 'aksi'=>'logout'];
        Cache::forever('events', $events);        
        return Redirect::to('login');
    }

}

?>

Kode program sederhana di atas akan menyimpan log aktifitas login dan logout ke dalam sebuah array. Karena PHP tidak memungkinkan sebuah penyimpanan yang permanen untuk session berbeda, tidak seperti variabel static di Java EE yang dapat diakses kapan saja, maka saya akan menggunakan Cache dari Laravel. Secara default, Laravel akan menulis isi Cache ke sebuah file sehingga saya dapat memperoleh isi Cache yang sama kapan saja pada session apapun.

Langkah terakhir untuk membuat proses login dan logout bekerja adalah menambahkan kode program PHP berikut ini di routes.php:

Route::get('login', function() {
    return View::make('login');
});

Route::post('login', 'LoginController@login');

Route::get('logout', 'LoginController@logout');

Sekarang, bila saya membuka URL untuk /login, saya dapat login dengan nama pengguna apa saja, kemudian memilih logout. Di balik layar, PHP akan menyimpan aktifitas (event) login atau logout tersebut pada Cache milik Laravel.

Sekarang, saya akan mengimplementasikan bagian yang membaca event login atau logout dari Cache. Halaman tersebut akan memakai SSE: sebuah halaman yang menampilkan status user, apakah sedang online atau offline. Dengan Reverse Ajax, server akan menghubungi client bila ada user yang login atau logoff sehingga client hanya perlu bekerja memperbaharui tampilan jika ada event yang perlu diproses.

Saya akan mulai dengan membuat sebuah view bernama User.php di folder view yang isinya terlihat seperti berikut ini:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
    <style type="text/css">
        table {
            color: #666; font-size: 20px; text-shadow: 1px 1px 0px #fff;
            background: #eaebec; margin: 20px; border: #ccc 1px solid;
            border-radius: 3px; box-shadow: 0 1px 2px #d1d1d1;          
        }
        table td {
            padding-left: 20px; border-top: 1px solid #fff;
            border-bottom: 1px solid #e0e0e0;

        }
        .offline { color: red;  }       
        .online { color: green; }
    </style>  
</head>
<body>
    <table id='tabel'>
        <tbody>
            <tr id='solid snake'>
                <td>solid snake</td>
                <td class='offline'>OFFLINE</td>
            </tr>
            <tr id='liquid snake'>
                <td>liquid snake</td>
                <td class='offline'>OFFLINE</td>
            </tr>
            <tr id='big boss'>
                <td>big boss</td>
                <td class='offline'>OFFLINE</td>
            </tr>
            <tr id='mei ling'>
                <td>mei ling</td>
                <td class='offline'>OFFLINE</td>
            </tr>
        </tbody>
    </table>
    <script type="text/javascript">     
        var source = new EventSource("<?php echo action("EventController@proses"); ?>");      
        source.addEventListener('login', function(e) {
            var td = document.querySelector("table tr[id='" + e.data + "'] td:nth-child(2)");
            td.className = 'online';
            td.textContent = 'ONLINE';    
        }, false);
        source.addEventListener('logout', function(e) {
            var td = document.querySelector("table tr[id='" + e.data + "'] td:nth-child(2)");
            td.className = 'offline';
            td.textContent = 'OFFLINE';
        }, false);      
    </script>
</body>
</html>

Pada view di atas, penggunaan SSE membuat kode program JavaScript terlihat sederhana. Saya hanya perlu membuat sebuah object EventSource. Setelah itu, saya menggunakan addEventListener untuk menentukan apa yang akan dilakukan bila server mengirim event tertentu. Pada contoh di atas, bila event login (custom event) terjadi, maka JavaScript akan mengisi kolom kedua untuk user bersangkutan dengan ‘ONLINE’. Sebaliknya, bila event logout (custom event) dikirim oleh server, maka JavaScript akan mengisi kolom kedua untuk user bersangkutan menjadi ‘OFFLINE’.

Bagian yang lebih sulit adalah membuat kode program di sisi server-nya. Saya wajib mengembalikan respon dalam bentuk seperti:

event: login
data: solid snake

event: login
data: liquid snake

event: logout
data: liquid snake

event: logout
data: solid snake

...

Untuk itu, saya membuat sebuah controller baru dengan nama EventController.php di folder controllers yang isinya seperti berikut ini:

<?php

class EventController extends BaseController {

    public function proses() {
        header('Content-Type: text/event-stream');
        header('Cache-Control: no-cache');
        set_time_limit(0);
        while(1) {
            $events = Cache::get('events', function() { return []; });                    
            if (count($events) > 0) {
                $event = array_shift($events);
                echo "event: {$event['aksi']}n";
                echo "data: {$event['nama']}n";
                echo "n";
                ob_flush();
                flush();
            }           
            Cache::forever('events', $events);            
            sleep(1);           
        }
    }

}

?>

Pada kode program di atas, saya memberikan header Content-Type secara manual. Untuk mengembalikan respon untuk SSE, nilai Content-Type wajib berisi text/event-stream. Setelah itu, saya akan memeriksa isi Cache di Laravel untuk menentukan apakah event yang perlu dikirim ke browser. Perhatikan bahwa saya melakukan ini terus menerus dan function proses() tidak akan pernah selesai. Tidak semua server bisa bertahan bila dipaksa untuk seperti ini! Untuk keperluan produksi saat aplikasi diakses banyak orang secara bersamaan, sebaiknya gunakan server yang mendukung seperti Node.js atau server JEE yang mendukung Servlet 3.0 (asynchronous servlet).

Langkah terakhir adalah mendaftarkan routes di routes.php dengan menambahkan kode program berikut ini:

Route::get('user', function() {
    return View::make('user');
});

Route::get('event', 'EventController@proses');

Sekarang, saya bisa mencoba menjalankan aplikasi web sederhana ini dengan membuka URL http://localhost/latihan_sse/public/user. Semua user pada awalnya akan memiliki status offline. Setelah itu, pada tab browser yang berbeda, saya dapat membuka satu atau lebih URL http://localhost/latihan_sse/public/login untuk melakukan login sesuai dengan nama user yang diinginkan. Perhatikan bahwa status di halaman user akan langsung diperbaharui, seperti yang terlihat pada gambar berikut ini:

Animasi yang menunjukkan isi tabel diperbaharui secara otomatis

Animasi yang menunjukkan isi tabel diperbaharui secara otomatis

Perihal Solid Snake
I'm nothing...

2 Responses to Memakai Server-Sent Events API Di HTML5

  1. Irham Syah mengatakan:

    terima kasih atas sharingnya…semoga berkah ilmunya

  2. Fer A mengatakan:

    terima kasih tutorialnya…
    saya mau nanya mas…

    untuk
    source.addEventListener(‘login’, function(e) {

    dan
    source.addEventListener(‘logout’, function(e) {

    maksud login dan logout pada source tersebut apakah event default dari HTML 5 atau bisa kita buat sendiri ?🙂

Apa komentar Anda?

Please log in using one of these methods to post your comment:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout / Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout / Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout / Ubah )

Foto Google+

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s

%d blogger menyukai ini: