[TurboGears] メンテナンス中のとき、別画面を表示する方法

環境

この記事の内容は、Ubuntu Linux 6.10, TurboGears 1.0.1で確認しました。

概要

ウェブアプリケーションを運用していると、例えばデータベースのスキーマを変更しているときなど、他のユーザからはアクセスされたくないときがあります。そんなとき、メンテナンス中である旨を表示する画面に、すべてのアクセスをリダイレクトできると便利です。この記事では、TurboGearsでそれを実現する方法を紹介します。

仕様

この機能の仕様は、次のようにします。

1. メンテナンス中のときは、カレントディレクトリにmaintenanceという名前のファイルを作成する。
2. カレントディレクトリにmaintenanceという名前のファイルがあるとき、/static/maintenance.htmlにすべてのアクセスをリダイレクトする。

コード

以下のコードを記述します。ファイルは、proj/filters/__init__.pyとします。

# -*- coding: utf-8 -*-

from turbogears import url
from cherrypy.filters import basefilter
import os.path
import cherrypy

class MaintenanceFilter(basefilter.BaseFilter):
    def __init__(self, redirect_to=None, except_path=None):
        if redirect_to is not None:
            self.redirect_to = redirect_to
        else:
            self.redirect_to = "/static/maintenance.html"

        if except_path is not None:
            self.except_path = except_path
        else:
            self.except_path = ["/static"]

    def before_main(self):
        if not os.path.exists("maintenance"):
            return

        redirect = True
        for path in self.except_path:
            if cherrypy.request.path.startswith(url(path)):
                redirect = False
                break

        if redirect:
            raise cherrypy.HTTPRedirect(url(self.redirect_to))

# vim: tabstop=4 shiftwidth=4 expandtab

Rootコントローラに、以下のコードを記述します。

import proj.filters

class Root(controllers.RootController):
    _cp_filters = [proj.filters.MaintenanceFilter()]

以上です。

詳細

CherryPyがリクエストを処理するコードは、/usr/lib/python2.4/site-packages/CherryPy-2.2.1-py2.4.egg/cherrypy/_cphttptools.pyの、Requestクラスの_runメソッドです。

    def _run(self):

        try:
            # This has to be done very early in the request process,
            # because request.object_path is used for config lookups
            # right away.
            self.processRequestLine()

            try:
                applyFilters('on_start_resource', failsafe=True)

                try:
                    self.processHeaders()

                    applyFilters('before_request_body')
                    if self.processRequestBody:
                        self.processBody()

                    # Loop to allow for InternalRedirect.
                    while True:
                        try:
                            applyFilters('before_main')
                            if self.execute_main:
                                self.main()
                            break
                        except cherrypy.InternalRedirect, ir:
                            self.object_path = ir.path

                    applyFilters('before_finalize')
                    cherrypy.response.finalize()
                except cherrypy.RequestHandled:
                    pass
                except (cherrypy.HTTPRedirect, cherrypy.HTTPError), inst:
                    # For an HTTPRedirect or HTTPError (including NotFound),
                    # we don't go through the regular mechanism:
                    # we return the redirect or error page immediately
                    inst.set_response()
                    applyFilters('before_finalize')
                    cherrypy.response.finalize()
            finally:
                applyFilters('on_end_resource', failsafe=True)
        except (KeyboardInterrupt, SystemExit):
            raise
        except:
            if cherrypy.config.get("server.throw_errors", False):
                raise
            cherrypy.response.handleError(sys.exc_info())

applyFilters関数は、CherryPyのコントローラに設定されているフィルタの、指定したメソッドを実行します。すなわち、

                            applyFilters('before_main')

というのは、フィルタのbefore_mainメソッドを呼び出すことを意味します。さて上のコードから、コントローラのメソッドからだけでなく、フィルタから例外cherrypy.HTTPRedirectを生成しても、リダイレクトされることが分かります。よって、冒頭のbefore_mainメソッドでリダイレクトするフィルタを記述すれば、希望する動作を実現できます。また、Rootコントローラにフィルタを設定しておけば、その下のすべてのURLにおいて、有効となります。