Living life and Make it Better

life, learn, contribute

Endy Muhardin

Software Developer berdomisili di Jabodetabek, berkutat di lingkungan open source, terutama Java dan Linux.

Aplikasi Web dengan Spring 2.5 [bagian 5]

Aplikasi web –berbeda dengan aplikasi desktop– ditakdirkan untuk stateless. Artinya dia tidak menyimpan data untuk masing-masing user yang sedang aktif. Keputusan ini menyebabkan aplikasi web bisa diakses jutaan user sekaligus. Tapi juga menyebabkan perlu teknik khusus agar data user yang sedang aktif dapat disimpan dengan baik.

Tanpa kemampuan penyimpanan state, semua data yang dikirim user akan hilang setelah dia pindah halaman. Misalnya user:

  1. mengisi form 1, kemudian menekan tombol Next
  2. mengisi form 2, kemudian menekan tombol Next
  3. tiba di form 3. Pada saat ini, data yang diisi di form 1 sudah hilang

Sebelum kita melihat kode program dengan Spring 2.5, terlebih dulu kita bahas konsepnya.

Data user yang kita bicarakan di sini adalah data sementara. Contoh klasik dari state user yang harus disimpan antara lain:

  • kita berbelanja online. Barang-barang yang sudah kita pilih, tapi belum kita pesan, ini harus disimpan oleh aplikasi. Data ini sering disebut dengan istilah shopping cart (atau troli belanja)
  • kita sudah login untuk melihat email. Selama kita belum menyatakan selesai (dengan cara logout), kita ingin aplikasi menyimpan username dan password kita. Sehingga kita tidak perlu memasukkan username dan password setiap kali pindah dari satu email ke email lain.
  • kita mengisi form secara bertahap (wizard-style). Keseluruhan rangkaian proses baru berakhir pada saat form terakhir diisi. Tanpa state management yang baik, kita tidak bisa menggunakan rangkaian form. Kita terpaksa menggunakan satu form yang sangat panjang, dan menyuruh user mengisinya sekaligus dalam satu kali proses.

Contoh yang kita sebutkan di atas membahas tentang data sementara yang harus dikelola sebelum disimpan ke database. Umurnya relatif lebih pendek daripada data permanen yang disimpan di database. Misalnya, username dan password selama user membaca email, disimpan dalam jangka waktu beberapa jam saja. Data wizard mungkin hanya disimpan beberapa menit saja, sampai form terakhir selesai dan keseluruhan data disimpan ke database. Walaupun beberapa situs, seperti Amazon, mungkin saja menyimpan data shopping-cart kita selama berbulan-bulan.

Ada beberapa cara yang dapat kita pilih untuk menyimpan state. Setidaknya ada dua pilihan lokasi :

  • di client. Dengan menggunakan mekanisme cookie, kita dapat menyimpan data di browser client. Cara ini tidak membebani server, tapi kapasitas cookie di client relatif kecil. Kita tidak bisa menyimpan data yang terlalu banyak di cookie.
  • di server. Mekanismenya mirip dengan kalau kita belanja di hypermarket. Tas dan jaket kita titipkan di pintu masuk, kemudian kita akan diberikan token. Pada waktu kita keluar, token kita tukarkan dengan barang yang kita titipkan.

Demikian juga dengan penyimpanan state. Ketika user pertama kali datang, dia akan mendapat token atau session ID. Aplikasi menyimpan state user di lokasi tertentu yang ditandai dengan session ID tersebut. Setiap request yang datang akan dilihat session IDnya dan datanya diambil untuk diproses.

Ada beberapa cara untuk menitipkan token pada user, beberapa yang sering digunakan antara lain:

  • masukkan ke cookie. Ya kita pakai cookie lagi, tapi tidak menyimpan keseluruhan data state, melainkan cuma session ID saja.
  • disisipkan di setiap link dan form. URL yang tadinya seperti ini : http://localhost/aplikasi/personlist menjadi seperti ini: http://localhost/aplikasi/personlist?session_id=12a75tj67.
  • dikirim ke server sebagai HTML input. Biasanya tipe yang dipilih adalah hidden. Cara ini memiliki kelemahan, karena tidak semua request bisa dibungkus dalam form.

Data state yang disimpan di server juga memiliki beberapa pilihan lokasi penyimpanan, antara lain:

  • di webserver. Webserver menulis satu file per satu session. Semua data disimpan di sini. Cara ini paling mudah dikonfigurasi, sehingga banyak webserver menjadikan cara ini sebagai default. Kelemahannya, metode ini menyulitkan kita untuk mengcluster webserver, karena kita kemudian harus memikirkan bagaimana mensinkronisasikan data session di masing-masing anggota cluster
  • di database. Webserver dikonfigurasi agar menyimpan datanya di database. Cara ini cukup sulit, mengingat dialek SQL masing-masing database berbeda. Banyak vendor webserver tidak mau menulis adapter untuk masing-masing merek database, sehingga kita harus periksa dulu apakah database yang kita gunakan didukung atau tidak. Bila tidak, kita terpaksa menulis sendiri kode program untuk state management.
  • di distributed cache. Ada beberapa aplikasi cache yang mengkhususkan diri untuk menyimpan data secara terdistribusi, misalnya memcached yang open source atau Oracle Coherence yang berbayar. Penyimpanan ini bisa dicluster sendiri, terpisah dari webserver. Dengan pendekatan ini, kita bisa mengcluster webserver sesuka hati, karena statenya tidak disimpan webserver, melainkan dititipkan di distributed cache. Kelemahan cara ini sama dengan metode database. Tidak semua webserver menyediakan support. Kadang kita harus tulis kodenya sendiri.
  • di upstream layer. Beberapa orang menggunakan arsitektur empat layer : client - web layer - application layer - database. Dengan arsitektur ini, semua state disimpan di application layer. Biasanya server yang digunakan untuk application layer memiliki mekanisme state management yang lebih baik daripada webserver. Terutama jika kita ingin melakukan clustering.

Sekarang setelah kita mengerti konsepnya, mari kita implementasikan pada contoh aplikasi kita. Kita akan menggunakan state management untuk menampilkan hasil upload data Person.

Kode upload kita kemarin bekerja sebagai berikut :

  1. user melakukan upload dengan mensubmit form
  2. server memproses, kemudian mengirim redirect ke halaman berikut
  3. browser menerima perintah redirect, kemudian melakukan request GET ke URL lain sesuai perintah redirect
  4. server menerima request GET, kemudian melakukan proses dan merender hasilnya
  5. browser menampilkan respon dari server

Teknik ini sering disebut Post-Redirect-Get (PRG) pattern.

Kesalahan umum programmer web pemula adalah memproses form sebagai berikut:

  1. user melakukan upload dengan mensubmit form
  2. server memproses, kemudian merender hasilnya
  3. browser menampilkan respon dari server

Bila user merefresh halaman hasil, maka yang dilakukan browser adalah mengulang dari langkah 1. Ini akan mengakibatkan form tersubmit dua kali. Berbeda dengan teknik PRG, kalau user merefresh halaman hasil, maka browser akan mengulangi dari langkah ketiga, yaitu GET untuk halaman hasil. Dengan demikian form tidak disubmit dua kali.

Kita menghadapi tantangan untuk mengimplementasikan PRG pada halaman upload result. Kalau hanya sekedar pesan sukses, kita bisa membuat redirect ke http://localhost/aplikasi/personuploadresult?msg=Sukses. Tapi yang ingin kita tampilkan adalah daftar yang memuat mana data yang sukses diimport, dan mana data yang gagal. Sulit untuk dikirim melalui URL.

Untuk itu, kita harus simpan data tersebut di session. Berikut adalah kode pemrosesan upload yang sudah dimodifikasi.

PersonUploadController.java

package tutorial.spring25.ui.springmvc;

@Controller
@RequestMapping("/personuploadform")
public class PersonUploadController {
    private static final Log LOG = LogFactory.getLog(PersonUploadController.class);
    private static final String REDIRECT_PERSONUPLOADRESULT = "redirect:personuploadresult";


    @RequestMapping(method=RequestMethod.POST) 
    public String processForm(@RequestParam("persondata") MultipartFile file, final HttpSession session) {

        // parse file into list of strings
        List<String> contents = new ArrayList<String>();
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(file.getInputStream()));
            String content = reader.readLine();
            while(content != null) {
                contents.add(content);
                content = reader.readLine();
            }
            reader.close();
        } catch (IOException e) {
            LOG.warn(e.getMessage(), e);
        }


        // parse list of strings into list of Persons
        List<Person> persons = new ArrayList<Person>();
        List<ParseError> errors = new ArrayList<ParseError>();
        personDataParser.parse(contents, persons, errors);

        for (Person person : persons) {
            personDao.save(person);
        }

        session.setAttribute(Constants.PERSONUPLOAD_ERRORS_KEY, errors);
        session.setAttribute(Constants.PERSONUPLOAD_SUCCESS_KEY, persons);
    
        return REDIRECT_PERSONUPLOADRESULT;
    }
}

Perhatikan dua baris session.setAttribute di bagian bawah. Setelah menyimpan data di session, kita redirect ke halaman hasil.

Pada halaman hasil, terlebih dulu kita pindahkan data hasil upload dari session ke request. Kemudian data yang ada di session kita hapus, agar tidak membebani memori ataupun kapasitas penyimpanan server. Lalu baru kita render tampilannya.

Berikut adalah kode untuk memindahkan data dari session ke request. Terletak di class PersonController, dalam method uploadResult.

PersonController

package tutorial.spring25.ui.springmvc;

@Controller
public class PersonController {	

    @RequestMapping("/personuploadresult")
    public ModelMap uploadResult(final HttpSession session){
        final ModelMap modelMap = new ModelMap();

        if (session.getAttribute(Constants.PERSONUPLOAD_SUCCESS_KEY) != null) {

            modelMap.addAttribute(
                session.getAttribute(Constants.PERSONUPLOAD_SUCCESS_KEY)
            );

            session.removeAttribute(Constants.PERSONUPLOAD_SUCCESS_KEY);
        }

        if (session.getAttribute(Constants.PERSONUPLOAD_ERRORS_KEY) != null) {

            modelMap.addAttribute(
                session.getAttribute(Constants.PERSONUPLOAD_ERRORS_KEY)
            );

            session.removeAttribute(Constants.PERSONUPLOAD_ERRORS_KEY);
        }

        return modelMap;
    }
}

Dan ini adalah template yang digunakan untuk menampilkan hasil upload.

personuploadresult.html

<html>

  <head><title>:: Person Upload Result ::</title></head>

  <body>

    <h1>Person Upload Result</h1>

    <h2>Successfully Imported Person Data</h2>
    #if (!$personList ||  ${personList.isEmpty()})

      <h3>No Data</h3>

    #else

    <table border="0" cellpadding="2" cellspacing="2">
      <tr>
        <th>Name</th>
        <th>Email</th>
        <th> </th>
      </tr>
      #foreach($person in $personList)
      <tr>
        <td>$person.Name</td>
        <td>$person.Email</td>
        <td>
          <a href="personform?id=$person.Id">edit</a> | 
          <a href="persondetail?id=$person.Id">view</a>
        </td>
      </tr>
      #end
    </table>

    #end


    <h2>Erroneous Data</h2>
    #if (!$parseErrorList ||  ${parseErrorList.isEmpty()})
 
    <h3>No Data</h3>

    #else

    <table border="0" cellpadding="2" cellspacing="2">
      <tr>
        <th>Row</th>
        <th>Data</th>
        <th>Reason</th>
      </tr>

      #foreach($error in $parseErrorList)
      <tr>
        <td>$error.Line</td>
        <td>$error.Text</td>
        <td>$error.Reason</td>
      </tr>
      #end
    </table>
    #end

  </body>
</html>

Di framework Ruby on Rails, sudah ada dukungan untuk kegiatan ini, namanya flash message. Flash message akan menyimpan data di session, kemudian menghapusnya pada request berikutnya.

Pada artikel ini kita sudah melakukan state management sederhana. Untuk kemampuan yang lebih canggih seperti pengelolaan shopping cart atau wizard, kita bisa menggunakan framework Spring Web Flow. Dengan SWF, flow aplikasi bisa diedit secara grafis. Ini akan memudahkan kita untuk mendokumentasikan flow navigasi aplikasi.

Spring Web Flow Editor

Gambar diambil dari situsnya SWF.


Another Subversion Backup Script

Dulu saya pernah menulis tentang backup script Subversion untuk Linux, maupun untuk Windows. Sayangnya, script tersebut hanya bisa digunakan untuk satu repository saja.

Biasanya saya menghosting beberapa Subversion repository sekaligus, dipublikasikan menggunakan Apache dengan konfigurasi SVNParentPath. Setidaknya ada 10 repository yang saya kelola, sehingga untuk mengkonfigurasi backup otomatisnya cukup melelahkan juga.

Oleh karena itu, saya membuat backup script lagi. Kali ini mampu menangani satu folder yang berisi banyak repository. Berikut scriptnya, masih dalam bahasa Ruby.

subversion-repos-full-backup.rb

require 'zlib'

if ARGV.length < 2
  puts "Usage : ruby full-backup.rb <SVNParentPath folder> <backupfolder>"  
  exit
end

# some configuration
svn_parent_path = ARGV[0]
backup_folder = ARGV[1]

# variable initialization
current_date = Time.now.strftime("%Y%m%d")

Dir.foreach(svn_parent_path) { |repo| 
    next if('.' == repo || '..' == repo)
    puts "Start to process folder : "+repo

    puts "Performing svndump"
    repo_name = svn_parent_path + File::SEPARATOR + repo
    dumpfile_name = repo + '-' +current_date + '.dmp'
    dumpfile = backup_folder + File::SEPARATOR + dumpfile_name

    `svnadmin dump #{repo_name} > #{dumpfile}`

    puts "Compressing dumpfile"
    Zlib::GzipWriter.open(dumpfile+".gz") do |gz|
      gz.write(File.read(dumpfile))
    end


    puts "Deleting uncompressed file"
    File.delete(dumpfile)
}

Cara menjalankannya tidak sulit, cukup panggil dia melalui command prompt:

ruby subversion-repos-full-backup.rb /path/ke/repos /path/ke/backup-folder

Semoga bermanfaat


Aplikasi Web dengan Spring 2.5 [bagian 4]

Belajar membuat aplikasi web belum lengkap tanpa tahu caranya mengupload file dan mengelola state. Pada artikel ini kita akan belajar tentang cara menangani upload file dengan Spring MVC versi 2.5. Di artikel selanjutnya baru kita akan bahas tentang state management.

Studi kasus kita kali ini sederhana saja. Kita sudah punya aplikasi buku alamat sederhana pada rangkaian artikel sebelumnya. Kita sudah bisa menampilkan daftar data orang, mengedit data yang sudah ada atau menambah data baru, serta menggunakan template untuk header dan footer. Kali ini kita akan membuat fasilitas import data berupa text file berformat Comma Separated Value (CSV).

File yang akan kita import kira-kira berbentuk seperti ini:

Endy Muhardin,endy.muhardin@gmail.com
Hadikusuma Wahab,dhiku@artivisi.com
Ifnu Bima,ifnubima@gmail.com

Setelah file tersebut diupload, kita akan memasukkan masing-masing data ke dalam database. Setelah itu, user akan kita arahkan ke halaman daftar data orang, sehingga data yang baru saja diupload bisa dilihat.

Untuk melakukan upload, kita harus membuat HTML form dengan encoding type multipart form data. Artinya, data kita akan dikirim melalui HTTP POST dalam beberapa bagian (multipart). Kira-kira mekanismenya mirip dengan mengirim attachment melalui email.

Berikut adalah kode program HTMLnya.

personuploadform.html

<html>

<head><title>:: Upload Person Data ::</title></head>

<body>

<h1>Upload Person Data</h1>

<form method="POST" enctype="multipart/form-data">

<table>  
  <tr>
    <td><label for="file">Person Data</label></td>
    <td><input type="file" name="persondata"></td>
  </tr><tr>
  	<td> </td>
  	<td><input type="submit" class="inputbutton" value="Upload"/></td>
  </tr>
</table>

</form>

</body>

</html>

Perhatikan tag form. Di sana ada satu hal yang berbeda, yaitu enctype="multipart/form-data". Semua aplikasi web yang ingin mengupload file harus membuat tag form seperti itu, apapun bahasa pemrograman yang digunakan. Ini sering menimbulkan kebingungan di kalangan pemula.

Saya sudah ikuti semua instruksi, tapi kenapa file yang diupload tidak terbaca?

Untuk menerima file, kita gunakan input type="file".

Selanjutnya masuk ke kode Java. Untuk menampilkan dan memproses form ini, kita buatkan class PersonUploadController. Bentuknya mirip dengan PersonFormController yang kemarin sudah kita buat. Ada method untuk menampilkan halaman form, dan ada method untuk memproses data yang dikirim user. Berikut class PersonUploadController.

PersonUploadController.java

package tutorial.spring25.ui.springmvc;

@Controller
@RequestMapping("/personuploadform")
public class PersonUploadController {
  private static final Log LOG = LogFactory.getLog(PersonUploadController.class);
  private PersonCSVParser personDataParser;
  private PersonDao personDao;

  @Autowired(required=true)
  public void setPersonDao(PersonDao personDao) {
    this.personDao = personDao;
  }

  @Autowired(required=true)
  public void setPersonDataParser(PersonCSVParser personDataParser) {
    this.personDataParser = personDataParser;
  }
		
  @RequestMapping(method=RequestMethod.GET)
  public ModelMap displayForm(){
    return new ModelMap();
  }
	
  @RequestMapping(method=RequestMethod.POST) 
  public String processForm(@RequestParam("persondata") MultipartFile file) {
    // parse file into list of strings
    List contents = new ArrayList();
    try {
      BufferedReader reader = new BufferedReader(new InputStreamReader(file.getInputStream()));
      String content = reader.readLine();
      while(content != null) {
					
        if("".equals(content)) {
          content = reader.readLine();
          continue;
        }
        contents.add(content);
        content = reader.readLine();
      }
      reader.close();
    } catch (IOException e) {
      LOG.warn(e.getMessage(), e);
    }
			
    // parse list of strings into list of Persons
    List persons = new ArrayList();
    List errors = new ArrayList();
    personDataParser.parse(contents, persons, errors);

    for (Person person : persons) {
      personDao.save(person);
    }

    return "redirect:personlist"
  }
}

Logika kode di atas tidak sulit. Untuk mendisplay form, praktis tidak ada yang perlu dilakukan. Kita cukup memberikan model kosong, karena form upload tidak membutuhkan data apa-apa.

Pada saat memproses form, kita menerima parameter MultipartFile. Ini adalah file yang sudah diparsing oleh Spring. Parameter ini diambil dari input form dengan nama persondata.

Supaya Spring bisa memisahkan file yang diupload dari keseluruhan HttpRequest, kita harus menyediakan resolver. Ada dua library yang biasa digunakan orang untuk memproses file upload, yaitu:

Spring mendukung kedua library. Saya biasanya menggunakan Jakarta Commons. Untuk mengaktifkan dukungan ini, kita perlu menambahkan resolver di konfigurasi DispatcherServlet.

tutorial-servlet.xml

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
  <property name="maxUploadSize" value="1000000"/>
</bean>

Setelah file didapat, kita lalu membaca isinya. Hanya operasi I/O standar di sini. Kita membuka InputStream yang dibungkus dengan BufferedReader yang memiliki method readLine yang praktis. Kemudian kita looping setiap baris. Jangan lupa untuk memeriksa baris kosong. Hasil pembacaan file kita simpan ke List untuk pemrosesan selanjutnya.

Pemrosesan String menjadi Person kita lakukan di class PersonCSVParser supaya ada pembagian tanggung jawab yang jelas. Memisahkan parser di class tersendiri akan memudahkan kita untuk mengetes kode parser tersebut.

PersonCSVParser tidak ditampilkan di sini. Bagi yang ingin melihat kode programnya dapat langsung pergi ke Github. Demikian juga kelengkapannya, seperti:

Setelah data diproses menjadi kumpulan Person, kita looping lagi untuk menyimpan hasilnya ke database dengan menggunakan personDao.

Terakhir, setelah semua selesai, kita redirect ke halaman daftar orang. Seharusnya kita menampilkan hasil upload. Mana data yang error, dan mana data yang sukses diimpor. Tapi materi ini membutuhkan pengetahuan tentang session, yang akan kita bahas pada artikel mendatang.

Selamat mencoba.


Continuous Integration

Tools untuk Continuous Integration sudah sering dibahas di sini. Ada CruiseControl, Luntbuild, dan Hudson. Apa yang begitu pentingnya dari proses ini sehingga saya meluangkan waktu untuk melihat setiap tools yang ada?

Continuous Integration, menurut Martin Fowler, begini:

Continuous Integration is a software development practice where members of a team integrate their work frequently, usually each person integrates at least daily - leading to multiple integrations per day.

Each integration is verified by an automated build (including test) to detect integration errors as quickly as possible.

Many teams find that this approach leads to significantly reduced integration problems and allows a team to develop cohesive software more rapidly.

Ada beberapa keyword di sini:

  • integrate their work
  • frequently
  • verified by automated build (including test)
  • detect errors as quickly as possible
  • significant reduced problems
  • develop .. rapidly

Mari kita bahas lebih jauh.

Sebagai contoh, kita membuat aplikasi dengan bahasa pemrograman Java. Langkah-langkah yang harus dilakukan sampai suatu kode program bisa digunakan antara lain:

  1. tulis source code
  2. kompilasi
  3. coba dijalankan sendiri (unit-test)
  4. coba gabungkan dengan kode lain (integration-test)
  5. deploy / install
  6. coba fitur-fiturnya
  7. kalau semua OK, cepat-cepat diarsip

Cukup banyak kegiatan yang harus dilakukan untuk menambah satu fitur. Penggunaan bahasa dinamik (seperti PHP atau Ruby) hanya mengurangi langkah kedua. Sisanya sama.

Kegiatan yang banyak ini akan sangat membosankan kalau dikerjakan secara manual. Bukan cuma boring, tapi juga rawan error. Di tengah tekanan deadline, sangat mungkin kita akan melewatkan test yang sudah pernah dijalankan sebelumnya.

Hei, dulu saja jalan .. kenapa sekarang tidak?

Ya belum tentu. Membangun aplikasi mirip seperti membangun rumah-rumahan dari kartu. Sedikit penambahan bisa merobohkan seluruh bangunan. Jadi sangat penting kita untuk sering-sering melakukan integrasi. Dan yang juga penting, test yang sudah ada harus dijalankan semuanya.

Sekarang sudah ada tools yang membantu kita melakukannya. Cukup investasi waktu 10 menit untuk melakukan instalasi dan konfigurasi. Investasi 10 menit ini akan menghasilkan:

  • build scheduler
  • laporan kemajuan project setiap saat
  • notifikasi setiap ada error
  • arsip build

Lalu apa manfaat dari fitur-fitur ini?

Banyak, mari kita bahas satu persatu.

Build Scheduler

Semua kegiatan build kita tadi, mulai dari checkout dari version control sampai membuat installer aplikasi bisa dilakukan secara terjadwal. Misalnya satu jam sekali, satu hari tiga kali, dan sebagainya.

Kita juga bisa mengkonfigurasi macam-macam jadwal. Misalnya untuk test dengan data sedikit bisa dijalankan sering-sering. Test yang melibatkan data banyak –seperti test End Of Day process atau simulasi performance aplikasi dengan satu tahun data– bisa dijalankan di malam hari, waktu semua orang sedang tidur.

Laporan Kemajuan Project

Pernahkah Anda, sedang asyik coding, puluhan variabel beterbangan di dalam otak, tiba-tiba dipanggil oleh Project Manager.

Fitur XX sudah selesai?

Kita jawab, “Sudah”. Kemudian balik lagi ke kode program, hanya untuk menyadari bahwa seluruh konsentrasi sudah buyar, sehingga butuh waktu 10 menit untuk mengingat-ingat apa yang tadi sedang dikerjakan.

Dengan adanya tools Continuous Integration, Project Manager bisa langsung melihat kemajuan project di websitenya. Sehingga dia tidak perlu mengganggu konsentrasi orang lain.

Notifikasi Error

Misalnya kita mengubah nama kolom di database, ternyata menyebabkan fungsi-fungsi lain jadi error. Akan lebih cepat untuk memperbaiki hal ini jika antara kita mengubah nama kolom dan saat error ketahuan selisihnya singkat. Bagaimana cara supaya error cepat ketahuan? Ada dua cara:

  1. test suite harus lengkap, sehingga mencakup seluruh bagian kode.
  2. test suite harus sering-sering dijalankan.

Tools CI membantu kita di item #2. Tetapi test suite tetap harus kita yang membuatnya.

Arsip Build

Project Manager lagi-lagi mengganggu konsentrasi kita. Kali ini dia mau presentasi ke customer dan mau menunjukkan aplikasi terbaru. Sialnya, tadi pagi kita baru saja mulai mengganti framework, tadinya pakai IceFaces mau diganti jadi RichFaces. Wow, kenapa harus diganti?? Hmm .. sayangnya itu diluar pembahasan artikel ini.

Karena ada perombakan arsitektur besar-besaran, maka aplikasi saat ini sedang berantakan. Tidak mungkin kita memberikan aplikasi terbaru ini untuk dibawa presentasi. Bisa-bisa Project Manager kita mendapat malu karena bukannya mendemokan aplikasi, malah mendemokan stack trace.

Untuk mengambil versi sebelum dirombak juga belum tentu pekerjaan mudah. Tergantung apakah Anda menggunakan Subversion atau tidak. Dan apakah kita rajin membuat tag untuk menandai titik-titik penting.

Continuous Integration Tools to the rescue ….

Langsung saja buka arsip build. Cari build terakhir yang sukses, dan berikan pada bos. Jangan lupa diberi pesan, “Kapan-kapan kalau butuh yang seperti ini lagi, langsung saja buka URL ini ya.”

Bila proses CI ini berjalan lancar, hidup programmer jadi jauh lebih nyaman. Dia tinggal:

  • coding
  • buat unit test dan integration test
  • jalankan dua-duanya
  • kalau OK, commit
  • balik lagi ke coding

Sekarang kita kembali ke daftar keywordnya Martin Fowler di atas. Agar proses CI ini bisa berjalan baik, semua orang harus integrate their work. Setiap kode yang ditulis harus segera digabungkan dengan kode yang lainnya (yang ditulis anggota tim lainnya).

Ini harus dilakukan frequently, sering-sering. Ini agar error yang muncul bisa segera terdeteksi.

Seluruh kegiatan mulai dari kompilasi sampai bikin installer harus diotomasi. Kalau sudah otomatis, CI tools akan bisa mengeksekusinya dengan mudah.

Kalau semua ini dilakukan, Martin Fowler berjanji bahwa kegiatan coding akan menjadi lebih rapid dan significantly reduces problem.

Nah, investasinya cuma 5-10 menit instalasi dan konfigurasi, keuntungannya banyak sekali. Lalu kenapa tidak banyak yang mengimplementasikannya?

Oh iya, saya lupa kasi tau. Implementasi CI sendiri mungkin 5-10 menit. Tapi proses ini punya ketergantungan terhadap tools dan praktek lain, yaitu:

  • Automated Testing
  • Version Control

Dua praktek ini mengharuskan kita mengubah pola pikir dan kebiasaan, mirip seperti orang yang mau kurus atau berhenti merokok.

So … Good Luck

:D


Konfigurasi Shorewall di Ubuntu Gutsy

Bila kita ingin mempublish komputer di internet, hal pertama yang kita pikirkan adalah firewall. Bagaimana membatasi akses hanya ke port-port yang kita ijinkan.

Di Linux, firewall diimplementasikan dengan menggunakan aplikasi iptables. Aplikasi ini sangat powerful dan canggih. Menurut Peter Parker, with great power comes great responsibility. Akan tetapi, menurut saya, with great power comes great complexity. Untuk bisa mengoperasikan iptables dengan benar, setidaknya kita harus memahami konsep chain dan table. Setelah itu, baru kita bisa belajar tentang jenis-jenis protokol, state, port, dan hal-hal TCP/IP lainnya.

Menjelaskan konsep ini saja butuh waktu setidaknya setengah hari pada rata-rata orang yang sudah memahami konsep dasar jaringan komputer. Daripada saya bolak-balik menjelaskan konsep chain dan table, baiklah kita cari saja aplikasi front end yang sederhana.

Untungnya di Ubuntu tidak kekurangan aplikasi front end untuk iptables. Untuk komputer personal, kita bisa gunakan Firestarter. Aplikasi ini sangat mudah digunakan. Tinggal jalankan wizardnya, dan dia akan segera mendeteksi segala perangkat jaringan yang kita miliki. Beri tahu Firestarter mana perangkat yang terhubung ke internet, dan mana perangkat yang terhubung ke jaringan lokal. Kemudian tentukan layanan atau port berapa yang ingin kita buka. Selesai sudah. Begitu mudah.

Selain Firestarter, kita juga bisa menggunakan GuardDog.

Sayangnya, kali ini saya ingin menginstal di server. Firestarter walaupun ampuh tapi kurang sesuai, karena dia berbasis GUI. Harus install Gnome dulu, kemudian kalau mau setting harus menggunakan X tunneling agar Firestarter bisa tampil di komputer yang me-remote.

Setelah tanya kanan kiri, Bos Ari memberikan petuah agar sebaiknya saya pakai Shorewall saja. Selain Shorewall, masih ada beberapa alternatif, seperti Arno’s Firewall yang direkomendasikan Anton. Tapi melihat sekilas dari tutorialnya, nampaknya Shorewall adalah yang paling intuitif untuk digunakan.

Baiklah, mari kita install dan konfigurasi Shorewall. Sebagai gambaran, komputer yang ingin saya bentengi cuma memiliki satu kartu jaringan yang langsung terhubung ke internet. Saya ingin membuka layanan SSH di port 22 dan HTTP di port 80.

Langkah pertama tentunya adalah menginstal Shorewall. Di Ubuntu tidak sulit, langsung saja

sudo apt-get install shorewall 

Shorewall akan segera terinstal, tapi tidak aktif. Kita harus mengkonfigurasi dulu, baru kemudian mengaktifkannya.

Konfigurasi Shorewall ada di folder /etc/shorewall.

Setelah Shorewall terinstal, pertama kita tentukan dulu jaringan yang terhubung ke komputer kita. Untuk kasus saya tidak sulit, cuma ada satu jaringan terhubung ke eth0.

Jaringan komputer, dalam dunia Shorewall disebut dengan istilah zone. Konfigurasinya ditulis di file bernama zones. Karena kita cuma punya satu jaringan, yaitu internet, berikut adalah isi file tersebut.

fw	firewall
net	ipv4

Shorewall ingin menaruh dirinya sendiri ke dalam zone terpisah. Oleh karena itu kita punya dua zone di file tersebut, yaitu net dan fw. ipv4 adalah jenis zone. Ada tiga jenis zone dalam shorewall, yaitu ipv4, firewall, dan ipsec. Bila kita menggunakan koneksi terenkripsi, kita bisa menggunakan opsi ipsec.

Zone dan device dihubungkan di file yang namanya interfaces. Isinya adalah sebagai berikut

net     eth0            detect          tcpflags,logmartians,nosmurfs,norfc1918

Kolom pertama menyatakan bahwa zone net dilayani oleh kartu jaringan eth0 yang disebutkan di kolom kedua. Selain kartu jaringan, kita juga bisa mendaftarkan perangkat lain seperti modem.

Kolom ketiga adalah konfigurasi broadcast. Satu perangkat jaringan bisa saja memiliki banyak IP address, atau terhubung dengan koneksi peer to peer. Dengan demikian satu perangkat bisa memiliki banyak alamat broadcast. Untuk memudahkan konfigurasi, kita bisa suruh Shorewall mencari alamat broadcast yang sesuai. Inilah arti dari konfigurasi detect.

Kolom terakhir paling kanan memuat opsi. Banyak opsi yang disediakan, saya cuma jelaskan opsi yang digunakan di atas saja.

  • tcpflags : ini memeriksa paket-paket yang memiliki kombinasi flags tidak lazim.
  • logmartians : ini artinya kita akan mencatat paket yang alamat asalnya aneh.
  • nosmurfs : ini mengatasi paket yang alamat asalnya sama dengan alamat broadcast.
  • norfc1918 : ini artinya kita akan mengabaikan semua paket dari dan menuju alamat private yang disebutkan di RFC 1918. Contoh alamat private antara lain adalah 192.168.0.1

Setelah selesai dengan zone dan interface, kita menentukan kebijakan global atau policy. Kebijakan ini berlaku apabila tidak ada aturan yang spesifik terhadap suatu paket. Biasanya, kebijakan global yang kita gunakan adalah:

  • mesin kita ini boleh menghubungi siapa saja. Semua paket keluar diijinkan (ACCEPT)
  • paket yang berasal dari luar menuju firewall akan diabaikan (DROP)
  • paket yang berasal dari luar menuju komputer dibalik firewall akan diabaikan (DROP)
  • selain itu, tolak semua (REJECT)

Konfigurasi kebijakan di atas ditulis di file bernama policy. Berikut isinya

#SOURCE		DEST		POLICY		LOG LEVEL	LIMIT:BURST
$FW		net		ACCEPT
net		$FW		DROP		info
net		all		DROP		info
# The FOLLOWING POLICY MUST BE LAST
all		all		REJECT		info
#LAST LINE -- ADD YOUR ENTRIES ABOVE THIS LINE -- DO NOT REMOVE

Terakhir, baru kita buat pengecualian terhadap kebijakan di atas. Seperti disebutkan di awal artikel, saya ingin membuka akses untuk web server di port 80 dan ssh server di port 22. Sebagai tambahan pengamanan untuk mencegah flooding, kita juga akan menolak paket ping yang masuk. Tapi kita ingin tetap bisa ping keluar.

Shorewall sudah punya konfigurasi standar (disebut dengan istilah macro) untuk aplikasi server yang umum digunakan. Kita tulis aturan ini di file rules. Berikut isinya

Ping/REJECT	net		$FW

# Permit all ICMP traffic FROM the firewall TO the net zone
ACCEPT		$FW		net		icmp

# Open SSH Service
SSH/ACCEPT	net		$FW

# Open Web Server
Web/ACCEPT	net		$FW

Ping/REJECT, SSH/ACCEPT dan Web/ACCEPT di atas adalah macro. Sebetulnya kita juga bisa membuka akses webserver di port 80 tanpa macro dengan konfigurasi seperti ini

ACCEPT    net       $FW             tcp  80

Tapi biasanya ada beberapa layanan yang membuka beberapa port sekaligus, misalnya FTP. Daripada harus menghafalkan port berapa saja yang harus dibuka untuk FTP, akan lebih mudah kalau kita gunakan macro FTP/ACCEPT

Selanjutnya, kita edit file shorewall.conf. Ini adalah konfigurasi global. Isi konfigurasi yang disediakan Ubuntu Gutsy sudah cukup bagus. Saya cuma mengubah baris ini

STARTUP_ENABLED=No

menjadi

STARTUP_ENABLED=Yes

Selain itu, juga ada satu file lagi, yaitu /etc/default/shorewall. Ganti baris

startup=0

menjadi

startup=1

Semuanya sudah siap. Silahkan nyalakan firewall Anda.

sudo /etc/init.d/shorewall start

Oh iya, berhati-hatilah kalau menginstal shorewall secara remote. Salah konfigurasi bisa menyebabkan kita terkunci di luar.