Update: EXPERIMEN GAGAL!!!
(Hasil yang diperoleh ternyata harus mengubah tilecache.py dan generate file .dll sendiri sesuai dengan spek wsgi tilecache untuk diload sebagai handler ISAPI IIS7)

Masih seputar soal performansi (tweaking) mapserver di platform Windows Server 2008, yang dibahas sebelumnya, tulisan kali ini fokus pada tuning TileCache yang menggunakan bahasa Python pada web server IIS7.

Karena tidak bisanya (berdasarkan pencarian atau memang aku yang tidak tahu) bagaimana menjalankan python sebagai FastCGI Module, maka pilihan jatuh pada ISAPI Module yang katanya jauh lebih baik dari CGI/FastCGI Module.

Yang perlu diketahui adalah bahwa Tilecache dibangun berdasarkan Python 2.5 dan di sini aku menggunakan Python 2.7.2. Untuk versi Python 3.x tidak disarankan karena akan error. Jelas errornya kenapa, kurang tahu karena skill python yang pas2an aja nih :D
1) Kalau belum ada, silakan unduh di situsnya dan install (disarankan di C:\Python27 tapi aku sendiri install di C:\Program Files\Python2.7 tidak ada masalah)

2) Unduh Python for Windows extensions dan install seperti biasanya

3) Aktifkan IIS 6 Management Compability (ini penting untuk plugin/library yang akan diinstall nantinya)

4) Unduh Python ISAPI-WSGI Handler dan install seperti instalasi program Windows biasanya. Installer akan mencari tempat instalasi Python dan menempatkan lokasi penginstalan folder Lib (dalam kasus ku, akan diinstall di C:\Program Files\Python2.7\Lib\site-packages\). Ingat lokasinya dan lihat seharusnya ada file _isapi_wsgi.dll (ini yang akan kita gunakan sebagai handler-nya).
Tambahan: untuk instalasi pada Windows Server 64-bit, Application Pools (lihat di sidebar kiri, atau pada masing2 website) harus diset harus “Enable 32-Bit Applications” jadi “true” melalui Advance Settings yang ada di sidebar kanannya.

5) Setelah seluruh proses instalasi selesai, kembali ke IIS Manager. Untuk memastikan silakan restart service IIS7 ini, atau kalau mau leboh pasti lagi restart Windows Server nya juga agar IIS 6 Management Compability dapat terload dengan pasti. Double-click Handler Mappings di IIS7 Manager.
Tambahkan handler untuk script python (Add Module Mapping di Action sidebar sebelah kanan) dan arahkan ke file _isapi_wsgi.dll. Akan membuka dialog box seperti berikut (aku namakan modul ini Python Module):

Proses instalasi selesai. Dengan demikian, seluruh script *.py akan ditangani oleh plugin ini. Dan sekali lagi, untuk memastikan berjalan dengan benar, restart IIS Service nya tanpa harus merestart Windows Server nya. Untuk testing apakah berjalan atau tidak, bisa buat file test.py yang di simpan di wwwroot inetpub. Isinya:

# This extension is used mainly for testing purposes - it is not
# designed to be a simple sample, but instead is a hotch-potch of things
# that attempts to exercise the framework.

from isapi import isapicon
from isapi.simple import SimpleExtension
import sys, os, stat

if hasattr(sys, "isapidllhandle"):
    import win32traceutil

# We use the same reload support as 'advanced.py' demonstrates.
from isapi import InternalReloadException
import win32event, win32file, winerror, win32con, threading

# A watcher thread that checks for __file__ changing.
# When it detects it, it simply sets "change_detected" to true.
class ReloadWatcherThread(threading.Thread):
    def __init__(self):
        self.change_detected = False
        self.filename = __file__
        if self.filename.endswith("c") or self.filename.endswith("o"):
            self.filename = self.filename[:-1]
        self.handle = win32file.FindFirstChangeNotification(
                        os.path.dirname(self.filename),
                        False, # watch tree?
                        win32con.FILE_NOTIFY_CHANGE_LAST_WRITE)
        threading.Thread.__init__(self)

    def run(self):
        last_time = os.stat(self.filename)[stat.ST_MTIME]
        while 1:
            try:
                rc = win32event.WaitForSingleObject(self.handle,
                                                    win32event.INFINITE)
                win32file.FindNextChangeNotification(self.handle)
            except win32event.error, details:
                # handle closed - thread should terminate.
                if details[0] != winerror.ERROR_INVALID_HANDLE:
                    raise
                break
            this_time = os.stat(self.filename)[stat.ST_MTIME]
            if this_time != last_time:
                print "Detected file change - flagging for reload."
                self.change_detected = True
                last_time = this_time

    def stop(self):
        win32file.FindCloseChangeNotification(self.handle)

def TransmitFileCallback(ecb, hFile, cbIO, errCode):
    print "Transmit complete!"
    ecb.close()

# The ISAPI extension - handles requests in our virtual dir, and sends the
# response to the client.
class Extension(SimpleExtension):
    "Python test Extension"
    def __init__(self):
        self.reload_watcher = ReloadWatcherThread()
        self.reload_watcher.start()

    def HttpExtensionProc(self, ecb):
        # NOTE: If you use a ThreadPoolExtension, you must still perform
        # this check in HttpExtensionProc - raising the exception from
        # The "Dispatch" method will just cause the exception to be
        # rendered to the browser.
        if self.reload_watcher.change_detected:
            print "Doing reload"
            raise InternalReloadException

        if ecb.GetServerVariable("URL").endswith("test.py"):
            file_flags = win32con.FILE_FLAG_SEQUENTIAL_SCAN  | win32con.FILE_FLAG_OVERLAPPED
            hfile = win32file.CreateFile(__file__, win32con.GENERIC_READ,
                                         0, None, win32con.OPEN_EXISTING,
                                         file_flags, None)
            flags = isapicon.HSE_IO_ASYNC | isapicon.HSE_IO_DISCONNECT_AFTER_SEND | \
                    isapicon.HSE_IO_SEND_HEADERS
            # We pass hFile to the callback simply as a way of keeping it alive
            # for the duration of the transmission
            try:
                ecb.TransmitFile(TransmitFileCallback, hfile,
                                 int(hfile),
                                 "200 OK",
                                 0, 0, None, None, flags)
            except:
                # Errors keep this source file open!
                hfile.Close()
                raise
        else:
            # default response
            ecb.SendResponseHeaders("200 OK", "Content-Type: text/html\r\n\r\n", 0)
            print >> ecb, "" print >> ecb, "The root of this site is at", ecb.MapURLToPath("/") print >> ecb, ""
            ecb.close()
        return isapicon.HSE_STATUS_SUCCESS

    def TerminateExtension(self, status):
        self.reload_watcher.stop()

# The entry points for the ISAPI extension.
def __ExtensionFactory__():
    return Extension()

# Our special command line customization.
# Pre-install hook for our virtual directory.
def PreInstallDirectory(params, options):
    # If the user used our special '--description' option,
    # then we override our default.
    if options.description:
        params.Description = options.description

# Post install hook for our entire script
def PostInstall(params, options):
    print
    print "The sample has been installed."
    print "Point your browser to /PyISAPITest"

# Handler for our custom 'status' argument.
def status_handler(options, log, arg):
    "Query the status of something"
    print "Everything seems to be fine!"

custom_arg_handlers = {"status": status_handler}

if __name__=='__main__':
    # If run from the command-line, install ourselves.
    from isapi.install import *
    params = ISAPIParameters(PostInstall = PostInstall)
    # Setup the virtual directories - this is a list of directories our
    # extension uses - in this case only 1.
    # Each extension has a "script map" - this is the mapping of ISAPI
    # extensions.
    sm = [
        ScriptMapParams(Extension="*", Flags=0)
    ]
    vd = VirtualDirParameters(Name="PyISAPITest",
                              Description = Extension.__doc__,
                              ScriptMaps = sm,
                              ScriptMapUpdate = "replace",
                              # specify the pre-install hook.
                              PreInstall = PreInstallDirectory
                              )
    params.VirtualDirs = [vd]
    # Setup our custom option parser.
    from optparse import OptionParser
    parser = OptionParser('') # blank usage, so isapi sets it.
    parser.add_option("", "--description",
                      action="store",
                      help="custom description to use for the virtual directory")

    HandleCommandLine(params, opt_parser=parser,
                              custom_arg_handlers = custom_arg_handlers)

Fungsinya untuk testing fitur2 yang penting untuk load Python sebagai ISAPI-WSGI, diperoleh dari bawaan hasil instalasi. Coba dibuka di browser, http://localhost/test.py dan seharusnya menghasilkan tulisan:

Hello world from isapi!

Lalu ubah tilecache.cgi atau tilecache.fcgi menjadi tilecache.py. Silakan test satu persatu. Lebih baik menggunakan tilecache.fcgi, kalau tidak jalan, baru gunakan tilecache.cgi sebagai tilecache.py. Kini tilecache sudah dapat berjalan sebagai ISAPI Module.

 

Lalu bagaimana dengan isu utama jumlah konkuren koneksi (instance) per user ke server?

Karena ini berjalan layaknya script ASP yang juga berjalan sebagai ISAPI Module, maka koneksi dapat dibatasi per AppPool di daftar Sites di sidebar kiri IIS7 Manager. Juga dapat dibatasi di proxy, di gateway ataupun di konfigurasi network (router). Intinya, konfigurasi soal koneksi menjadi lebih fleksibel untuk diaturnya, tidak hanya mentok di server ini saja. Untuk hal ini, tidak akan dibahas lebih lanjut, silahkan Googling saja kalau ingin membatasi jumlah koneksi di IIS7 secara keseluruhan. Tapi sekali lagi ingat, aplikasi GIS (web dan desktop) membutuhkan resource yang jauh lebih besar daripada service web pada umumnya. Kalau memang load terlalu besar, mungkin solusi load balancing patut dipertimbangkan.

 

Hasil ujicoba nya?

Belum ditest :-D
Ntar kalau ada datanya akan dipost juga. Kalau ada yang masih error atau perbaikan, jangan lupa komen ya.

Update: EXPERIMEN GAGAL!!!
(Hasil yang diperoleh ternyata harus mengubah tilecache.py dan generate file .dll sendiri sesuai dengan spek wsgi tilecache untuk diload sebagai handler ISAPI IIS7)

Freelance Web Developer