Wednesday, July 16, 2008

File Upload Progress dengan PHP 5 dan APC

Beberapa hari lalu, secara nggak sengaja gue baca-baca PHP 5.2.0 Release Announcement (tumben ^^) dan ngelihat kalimat ini di daftar key features nya "Hooks for tracking file upload progress were introduced". Ho ho ho was ist das? Penasaran banget, gue coba tanya-tanya kang google, dan mampir di satu blog yang bertanya di PHP Development mailing list tentang fungsi tracking yang dijanjikan tersebut. Yang menjawab tidak lain dan tidak bukan adalah Rasmus Lerdorf sendiri, dan menyatakan bahwa fungsi tersebut masih berada pada level C dan membutuhkan fungsi pendukung untuk digunakan dalam php.
Sudah lama gue coba cari-cari cara untuk bisa menampilkan laporan tentang kemajuan proses sewaktu meng-upload file. Gue sebenarnya cari cara, dimana menulis codenya itu murni dalam PHP. Tapi sayangnya kebanyakan yang gue temuin itu menggunakan cgi dan ditulis dalam Perl. Sayangnya gue gak begitu ngerti Perl, dan gue kurang suka sama bahasanya karena "terlalu dinamis", sehingga membuat code yang ditulis cepat untuk menjadi sulit dibaca.

Gue juga sempat coba pakai Fancyupload yang ditulis oleh Harald Kirschner, sebuah multi file uploader yang bekerja menggunakan Flash. Fancyupload ini gue lihat juga dipakai sama Joomla! CMS. Sayangnya ada 2(dua) kekurangan yang menurut gue cukup penting. Fancyupload ini tidak jalan di mac, dan setiap data yang di upload content-type nya pasti "application/octet-stream", bikin sebel. Biasanya gue kalo periksa file yang di upload, gue pasti periksa berdasarkan extension dan content-type nya, kalo beda biasanya filenya tidak gue terima.

Rasmus sendiri, menurut blog tersebut, langsung menyatakan bahwa dia telah menulis fungsi pendukung tersebut di dalam Alternative PHP Cache, sebuah PHP library yang merupakan bagian dari PHP Extension Community Library (PECL). Contohnya, beserta source code nya pun langsung diberikan. Tapi, menurut gue, sialnya, dia menulis contohnya menggunakan Yahoo! User Interface Framework dan gak pake komentar. Bukannya gue gak suka sama framework. Tapi menurut gue, kurang bijaksana sekali memberikan contoh code menggunakan framework, gak peduli se-straightforward apapun contohnya. Dan untuk sebuah contoh code, menurut gue lagi, komentar itu luar biasa pentingnya untuk mengerti step-by-step apa saja yang terjadi, atau mengapa terjadi, di dalam code tersebut.

Oleh karena itu, gue mau mencoba untuk membuat versi yang lebih sederhana, tanpa framework apapun dan penuh komentar ^^. Dalam kesempatan ini gue mau coba jabarkan dari awal, mulai dari menginstall APC modul itu sendiri, mengkonfigurasi modul tersebut, hingga menggunakannya.


Instalasi dan Konfigurasi APC

Untuk menggunakan fungsi file upload tracker menggunakan APC, kita butuh php 5.2+. Gue sudah mencoba menginstall APC ini diatas Windows dan Debian based Linux. Untuk Windows kita harus mendownload APC library secara manual. PECL zip package yang disediakan oleh php.net tidak mengikutsertakan library APC dalam distribusinya.

Setelah di download php_apc.dll tersebut tinggal dimasukkan ke dalam extension directory yang telah di definisikan di dalam php.ini. Untuk menginstall APC di dalam Debian based Linux gue menggunakan tutorial yang ini. Untuk install di distribusi linux yang lain dan Operating System yang lain silahkan cari di google. Kesuksesan dan kegagalan instalasi biasanya dipengaruhi berbagai macam aspek, yang tidak semuanya gue tahu dan tidak bisa gue jabarkan panjang lebar disini.

Pokoknya setelah kalian berhasil menginstall APC tersebut, maka kita harus mengatur konfigurasinya di dalam php.ini. Yang pertama adalah mendaftarkan library tersebut dalam daftar extension supaya dikenal oleh PHP.

extension=php_apc.dll


Di linux tidak berbeda jauh

extension=apc.so


Setelah ini, kita harus menyalakan fungsi RFC 1867, dan tergantung kalian ingin mencoba file upload dimana, lokal server atau remote server, ada beberapa perbedaan dalam konfigurasinya supaya nanti codenya bisa di test. Gue akan kasih lihat disini konfigurasi lokal server gue yang gue sadur dari IBM yang membahas thema yang sama.

...
post_max_size = 400M
...
upload_max_filesize = 400M
...
[APC]
apc.rfc1867 = on
apc.max_file_size = 200M
...


Mari kita lihat, gue merubah nilai maximum file size diatas 200M, kenapa? Karena apabila kita mengupload file di lokal server, maka file tersebut ter-upload begitu cepatnya, sehingga APC tidak sempat bereaksi untuk menghitung kemajuannya. Oleh karena itu untuk mengetest di lokal server kita butuh file yang cukup besar, biasanya diatas 150M. Oleh karena itu juga, kita butuh merevisi nilai maximum file size di php.ini. Untuk remote server atau produktif server, kita hanya perlu menyalakan fungsi RFC 1867 saja, dalam arti, kita hanya perlu menambahkan "apc.rfc1867 = on" di dalam php.ini kita. Selebihnya bisa dibiarkan sesuai nilai standardnya.

Untuk mengetest apakah semua instalasi dan konfigurasi berjalan dengan baik dan benar, maka setelah restart apache kita harung menengok phpinfo(). Apabila disitu terdapat informasi tentang APC seperti screenshot dibawah, maka apc sudah terinstall dengan benar dan kita bisa mulai menggunakannya.



Kalau kalian ingin tahu lebih lanjut tentang arti dan fungsi dari masing-masing konfigurasi APC silahkan membaca dokumentasinya. Sekarang kita bisa memulai untuk bereksperimen menampilkan informasi proses upload file. Sekedar informasi, semua code yang akan gue tulis, baik itu php, html atau javascript code, berada dalam satu file, yaitu upload_progress.php. Hal ini gue lakukan sebenarnya hanya untuk kemudahan saja dalam mengerti penggunaan APC. Kalau kalian sudah mengerti, silahkan implementasikan dengan cara kalian sendiri-sendiri, dan gunakan Framework apa saja yang kalian suka.


Upload Form

Mari kita bikin upload form yang sederhana saja.

<form action="upload_progress.php" enctype="multipart/form-data" 
      id="upload_form" method="post" 
      onsubmit="postForm('upload_form'); 
      return false;" target="hiddenframe">
<input id="progress_key" name="APC_UPLOAD_PROGRESS" 
       value="<?php echo uniqid()?>" type="hidden">
<input id="test_file" name="test_file" type="file">
<input value="Upload!" type="submit">
</form>
<div id="progress_win">
</div>
<iframe name="hiddenframe" style="display: none;"></iframe>


So, selesai. Sekarang mari lihat satu-satu. di baris pertama untuk form tag, kita punya atribut action yang mengacu pada dirinya sendiri apabila form tersebut di submit. Atribute enctype, karena kita ingin mengupload file, atribut method seperti biasa, post. Kita memiliki atribut Id yang pada atribut onsubmit di berikan sebagai parameter kepada fungsi javascript postForm. Dan kita memiliki atribut target, yang memiliki nilai "hiddenframe". Nilai ini mengacu pada nama iFrame yang didefinisikan di baris 8(delapan).

Dengan menggunakan atribut target pada form tag, maka apabila form tersebut di submit, proses submit tersebut akan dijalankan di Iframe, sehingga halaman dimana form tersebut berada tidak akan di reload. Dengan atribut onsubmit, maka sebelum form tersebut di submit, maka fungsi yang ada di dalam onsubmit tersebut dijalankan. Return false sebenarnya menyatakan bahwa setelah menjalankan fungsi tersebut maka gagalkan operasi submit form. Kenapa digagalkan? nanti gue kasih tahu di bagian javascript.

Baris berikutnya kita punya satu hidden input field. Field ini WAJIB ada apabila kalian ingin menggunakan APC untuk mendeteksi kemajuan upload file. Atribut Id pada input field ini bisa kalian definisikan sesuka kalian, gue definisikan sebagai progress_key. Atribut name harus sesuai dengan konfigurasi APC pada apc.rfc1867_name yang bisa dilihati di phpinfo(). setelah itu kita memiliki atribut value yang gue beri nilai yang unik. Nilai yang unik ini penting untuk orientasi APC ketika memproses kemajuan upload file. Apabila pada waktu bersamaan ada beberapa orang yang mengupload file, katakanlah user A dan B dan kalian memberikan id pada atribut value nya sama, maka APC bisa bingung, 2 progress dari 2 orang berbeda, tapi idnya sama, yang mana punya siapa? ^^

Di baris 6 kita punya div tag. Ini fungsinya sebagai container untuk menampilkan progress bar nya. Disini gue gak akan menjabarkan cara membentuk progress bar, karena untuk itu sudah banyak tutorialnya, gue hanya akan menunjukkan total prosesnya dalam persen. Sekarang mari kita lihat javascriptnya.


Javascript

Karena kita akan bekerja menggunakan AJAX, maka mari kita buat fungsi yang mempersiapkan sebuah instance dari HTTP Request Object, supaya nanti tinggal dipakai. Thema AJAX sendiri gak akan gue bahas panjang lebar. Contoh dan Tutorial bisa lihat di salah satu artikel dalam blog gue atau cari di google.

function createRequestObject()
   var requestObject;
   requestObject = false;
   if( window.ActiveXObject ){
       for( var i = 5; i; i-- ){
           try{
              if( i == 2 ){
               requestObject = 
                     new ActiveXObject( "Microsoft.XMLHTTP" );   
           }else{       
               requestObject =
                     new ActiveXObject( "Msxml2.XMLHTTP." + i + ".0" );
           }
           break;
         }catch( excNotLoadable ){                       
               requestObject = false;
         }
       }
   }else{
       requestObject = new XMLHttpRequest();
   }
   return requestObject;
}

var http = createRequestObject();


So, dengan ini kita sudah mempersiapkan sebuah instance dari HTTP Request Object. Sekarang kita buat fungsi postForm() yang akan dipanggil ketika html form yang barusan kita buat di submit.

function postForm(formName){
 document.getElementById(formName).submit();
 setTimeout('updateProgress()', 100);
}


Isinya mudah saja. HTML form dengan nama yang didefinisikan di submit. setelah itu kita menunggu 100ms sebelum menjalankan fungsi updateProgress(). Kenapa harus menunggu? Karena file yang di upload butuh waktu untuk mencapai server. Apabila kita tidak menunggu, maka APC tidak memiliki informasi apapun untuk dikirimkan kembali ke kita, dalam hal ini APC akan mengirimkan nilai false. Dan itu sangat tidak baik.

Interval 100ms gue gunakan hanya untuk lokal server. Untuk remote server ada baiknya untuk mendefinisikan interval yang lebih besar, satu hingga dua detik (2000ms) karena pengiriman data dari client hingga ke server bisa memakan waktu lama tergantung kecepatan internet.

Sekarang mari kita lihat apa isi di dalam fungsi updateProgress().

function updateProgress(){
 progress_key = document.getElementById('progress_key').value;
 http.open("GET", 'upload_progress.php?progress_key='+progress_key, true);
   http.onreadystatechange = function () {
       if (http.readyState == 4) {
           if (http.status == 200) {
             the_object = eval("(" + http.responseText + ")");           
             if(!the_object.done){
               result = Math.round(
                         (the_object.current/the_object.total) * 100);
               document.getElementById('progress_win').innerHTML = 
                         result +'%';
               setTimeout("updateProgress()",500);
             }else{
               document.getElementById('progress_win').innerHTML = '100%';
             }
           }
       }
   }
 http.send(null);
}


Disini kita lihat bahwa kita mengirimkan GET Request ke server dengan parameter progress_key yang diambil dari input hidden field yang unik tadi. Server nantinya akan mengirimkan response text berupa JSON. Response text tersebut akan dirubah menjadi javascript object dengan eval(). Lalu kita check apakah object atribut "done" didefinisikan dalam object. Apabila tidak, maka hitung kemajuannya dalam persen, lalu tampilkan nilainya dalam div container. Setelah itu tunggu beberapa saat, dalam hal ini gue memasang interval 500ms, dan ulangi proses tersebut.

Server akan menambahkan atribut "done" apabila proses upload sudah selesai. Dan apabila ini terjadi, maka angka persen di div container akan ditulis "100%". Mengenai definisi return array oleh APC silahkan baca manualnya pada bagian RFC1867. Dengan ini javascript kita kelar. Sekarang kita bisa lihat PHP scriptnya.


PHP script

Disini script kita akan meng-handle 2 macam proses, yang pertama adalah upload file itu sendiri, dan yang kedua adalah membaca kemajuan proses upload.

if($_SERVER['REQUEST_METHOD']=='POST') {
  //implement your upload code here
  //e.g. @move_uploaded_file($filename, $destination)
  exit;
} else if(isset($_GET['progress_key'])) {
  $status = apc_fetch(ini_get('apc.rfc1867_prefix').
                         $_GET['progress_key']);
  echo json_encode($status);
  exit;
}else{
  //you can implement some code to display error here
}


Scriptnya mudah saja apabila request method yang diterima adalah post, maka upload filenya. disini kita bisa memasukkan code yang biasa kita implementasikan kalau kita ingin mengupload file.

Apabila request method nya bukan post, maka periksa apakah GET parameter dengan nama "progress_key" di definisikan, jika ya maka ambil informasi tentang status upload file dengan apc_fetch(). Fungsi ini_get() disini adalah untuk mengambil prefix yang didefinisikan di konfigurasi apc (bisa dilihat di phpinfo()). Setiap file yang di upload dan memiliki hidden field "APC_UPLOAD_PROGRESS" pada formnya, APC bisa mengidetentifisikan kembali melalui prefix yang didefinisikan di php.ini dan value yang didefinisikan di hidden field tersebut.

Fungsi apc_fetch() akan mengirimkan array atau boolean false. Apabila dia mengirimkan array, maka kita akan merubah array tersebut menjadi JSON dengan json_encode() function dan mengirimkannya kembali ke client dalam bentuk plain text.

Jadi deh. Tinggal di test. Source code yang kumplit bisa kalian dapatkan disini dan di bawah ini hasilnya, kalian bisa langsung coba ^^.

Happy coding.

11 comments:

Indonesia Lover said...

maknyuzzzzzzz... mantab!
kyknya klo APC ni udah banyak dikenal dan udah banyak dipakai y? jadi, hosting2 bakal banyak yg nyediain extension ini.

David Andriansyah said...

tob!, rangkaian kata lo jg indah bro... http://www.davidandriansyah.co.cc

Arthur Purnama said...

bwahahahaha, bisa aja loe :P

stieven said...

Sama nih mas, saya juga dari dulu lagi cari2 UploadProgress yg pure PHP dan memang kebanyakan harus pake CGI/Perl udah coba yang ini bleum ?
http://pecl.php.net/package/uploadprogress (Ext for Linux + Contoh Source) untuk windows http://pecl4win.php.net/ext.php/php_uploadprogress.dll referencesnya
http://blog.liip.ch/archive/2006/09/28/upload-progress-meter-extension-for-php-5-2.html Sejauh ini saya coba di mesin linux Slackware 12.0 berjalan dengan baik. (belum nyoba di windows). Cuma belum sempat di dokumentasikan :)

RIEFHID said...

keren.......

rifqi fauzi said...

inspirasi baru. ini yang ku cari dari dulu

Infolusion-Admin said...

mas saya masih belum paham, bisa bantu saya ? gak mas cara bikin progress bar, mungkin ada alternatif lain ? kirim emailnya ke fherry(dot)ngeblog@gmail.com

Benny said...

coba bro pakai uploadify (www.uploadify.com) jquery+flash upload. selain bisa multi upload, kotak dialog browse file di hdd bisa dilimit juga cuma bisa cari file type tertentu.
setiap file yg diupload ada progress bar nya.

Arthur Purnama said...

Thanks bro,

Tapi ini kan solusinya supaya gak bergantung sama flash :)

Linux Media said...

Maaf mas, hehhe
saya masih newbie PHP.
sewaktu saya download php_apc.dll, dan di letakkan di folder ext/, lalu di definisikan di php.ini.
sesaat di restart apache nya, timbul error message. "<--Unable to load dynamic library 'C:\xampp\php\ext\php_apc.dll' - The specified procedure couldnot be found.-->"
apakah masalahnya tu kk??
klu bisa krim ke email enda_toni@yahoo.com.
Thx sebelumnya. :)

megaloblast.blogspot.com said...

link untuk download nya expired mas bro. mintaa link baru ding :)