mod_rewriteを使ってCGI環境で超高速にキャッシュを返す

キャッシュしてもいいHTMLを返すCGIであれば、以下のようにすることでレスポンスがとても高速になる
coreserverとかcoreserverとか、fastcgiが使えないので考えた苦し紛れではあるが用途によってはかなり効果がありそう

  1. mod_rewriteでキャッシュがあるかを判別し、あればキャッシュを返し、なければCGIに処理が渡る
  2. CGIはHTMLを生成し、キャッシュを出力してからHTMLを返す
  3. キャッシュはCronで定期的に削除される


メリットは「キャッシュがあればCGIを立ち上げる必要がない」こと
mod_rewriteapacheでの処理なので、キャッシュのレスポンスは静的なHTMLと同じ


また要求があった際にのみCGIでHTMLが生成されるので、ページ数が大量であっても全部を最初に生成したりする必要がない


リクエストが 〜/hoge/[0-9a-z]/ の場合

/.htaccess
/hoge.py
/cache/hoge/[0-9a-z]/[0-9a-z]/

があるとして


.htaccess

Options +ExecCGI
AddHandler cgi-script .py

RewriteEngine On
#とりあえずキャッシュのパスに書き換える
RewriteRule   ^hoge/([0-9a-z]*([0-9a-z])([0-9a-z]))/$   cache/hoge/$2/$3/$1
#mime-typeを設定
RewriteRule   ^cache   -   "[T=text/html;charset=utf-8]"

#キャッシュが存在していないなら
RewriteCond   %{DOCUMENT_ROOT}%{REQUEST_URI}   !-s
#hoge.pyにパスを書き換え
RewriteRule   ^cache/hoge/[0-9a-z]/[0-9a-z]/([0-9a-z]+)$   hoge.py&page=$1   [L]


hoge.py

def putCache(data, key): #keyは36進数
    key = '0' + key
    import re
    cpath = re.search( '^[0-9a-z]*([0-9a-z])([0-9a-z])$', key )
    cp1 = cpath.group(1)
    cp2 = cpath.group(2)
    import os
    root, d = os.path.split( os.path.abspath(__file__) )
    path    = os.path.join( root, 'cache', 'hoge', cp1, cp2, key )
    fo = file( path, 'wb' )
    fo.write( data )
    fo.close()


こんな感じで


gzipもついでにしておく場合は

def genGZ(html):
    import cStringIO
    import gzip
    gzbuf = cStringIO.StringIO()
    gz = gzip.GzipFile( mode = 'wb',  fileobj = gzbuf, compresslevel = 9 )
    gz.write( html )
    gz.close()
    data = gzbuf.getvalue()
    return data

def resGZ(data):
    print 'Status: 200 OK'
    print 'Content-Type: text/html; charset=utf-8' #rewriteのForceMIMEで上書きされる
    print 'Content-Encoding: gzip'
    print 'Content-Length: %d' % (len(data))
    print ''
    print data


この場合、キャッシュはContent-Encodingが出力されないので化ける
/cache/.htaccess

Header set Content-Encoding "gzip"

を作るとOK


デメリットは

  1. htaccessを触るのでこのあたりを書き換えるときにミスがあるとサイト全体が500になる
  2. Cronで定期的にキャッシュを削除しないといけない

ぐらいかな


おまけ
/cache/〜/[0-9a-z]/[0-9a-z]/を作る

dirs = ['hoge', 'foo', 'bar']
root = os.path.dirname( os.path.abspath(__file__) )
for dir in dirs:
    for i in xrange(36):
        for j in xrange(36):
            path = os.path.join( root, 'cache', dir, to36(i),  to36(j) ) #to36で36進数に
            os.makedirs(path)