djangoをmod_wsgiやcgiで使うためのpythonスクリプト

mod_wsgiを使うことでも、djangoアプリケーションが使えます:

とはいえ、このドキュメントも、.htaccessで使うためのものはなく、いろいろな環境の対策が混在してるために自環境に合わせる部分の識別が難しいものになっています。

しかも、django(1.0.2)のWSGIHandler自体にも、runserver等と振る舞いが一致しない、という問題があります。
以下は、この問題とその解決法を示し、環境を含めmod_wsgi上で動かすための構成を示し、さらにCGIとして使うため方法も示します。

Problems about: django.core.handlers.wsgi.WSGIHandler

django-1.0.2のWSGIHandlerは、wsgirefのPATH_INFOを元にurlの解決を行います。しかしこのせいで、URLの解釈方法に、runserverやmod_pythonでの通常のhandlerとの違いが出てしまいます。

具体的には、以下のREQUEST_URIになるようなURLでアクセスしたとき、各実装では以下のようなデータで、viewsの関数の引数に渡ることになります:

  • REQUEST_URI
    • '/foo/%7ebar//buzz'
  • runserver, modpython
    • '/foo/~bar//buzz'
  • mod_wsgi, cgi(via wsgiref)
    • '/foo/~bar/buzz'

以下のスクリプトは、runserverやmodpythonとの差をなくすワークアラウンドを入れた、cgiやmod_wsgiでWSGIHandler経由でdjangoを使うための起動スクリプトです。

index.wsgi/index.cgi

#!/usr/bin/env python
# -*- coding: utf-8 mode: python -*-
import sys
import os

# modify if you put "index.wsgi" into different directory
# apps = os.path.dirname(os.path.join("where", "to", "myproject", "settings.py"))
apps = os.path.dirname(__file__)
project = os.path.dirname(apps)
sys.path.append(apps)
sys.path.append(project)

# modify if you use custom setting
# os.environ["DJANGO_SETTINGS_MODULE"] = "myproject.mysql_settings"
os.environ["DJANGO_SETTINGS_MODULE"] = "myproject.settings"


import django.core.handlers.wsgi
# application = django.core.handlers.wsgi.WSGIHandler()

#
# django.core.handlers.wsgi.WSGIRequest uses PATH_INFO for urlresolvers
# but it has diffrent semantics against runserver/mod_python .
#
# for example: REQUEST_URI: '/foo/%7ebar//buzz'
#   runserver/modpython : '/foo/~bar//buzz'
#                  wsgi : '/foo/~bar/buzz'
#
# WORKAROUND:
#   set PATH_INFO = REQUEST_URI - SCRIPT_NAME - ("?" + QUERY_STRING)
def application(environ, start_response):
    import urllib
    path = urllib.splitquery(environ["REQUEST_URI"])[0]
    path = urllib.unquote(path)
    path_info = path[len(environ["SCRIPT_NAME"]):]
    environ["PATH_INFO"] = path_info
    return django.core.handlers.wsgi.WSGIHandler()(environ, start_response)

if __name__ == "__main__":
    from wsgiref.handlers import CGIHandler
    CGIHandler().run(application)
    pass

wsgiref.handlers.CGIHandlerを使用することで、cgiでもmod_wsgiでも同じコードで動くようにしています。

Usage: mod_wsgi

ディレクトリ構成
 /where/to/myproject/settings.py
                     manage.py
                     ...

 ~/public_html/myproject/.htaccess
 ~/public_html/myproject/index.wsgi
.htaccess
# .htaccess
AddHandler wsgi-script .wsgi
Options +ExecCGI
DirectoryIndex index.wsgi
index.wsgi

上記pythonコードを "index.wsgi" という名前で置きます。

index.wsgiの中を適切に書き換えます。

  • appsのファイルパスをsettings.pyのあるPATHに変更します。
  • settingsをデータベース等の環境を合わせたものを使う場合、DJANGO_SETTINGS_MODULEを適切に書き換えます

Usage: cgi

ディレクトリ構成
 /where/to/myproject/settings.py
                     manage.py
                     ...

 ~/public_html/myproject/.htaccess
 ~/public_html/myproject/index.cgi
.htaccess
# .htaccess
AddHandler cgi-script .cgi
Options +ExecCGI
DirectoryIndex index.cgi
index.cgi

上記pythonコードを "index.cgi" という名前で置き、chmod 755 index.cgi しておきます。

mod_wsgiのときと同様、index.cgiの中を適切に書き換えます。

  • appsのファイルパスをsettings.pyのあるPATHに変更します。
  • settingsをデータベース等の環境を合わせたものを使う場合、DJANGO_SETTINGS_MODULEを適切に書き換えます

Appendix: env.wsgi/env.cgi

#!/usr/bin/env python
# -*- coding: utf-8 mode: python -*-

import os

def to_table(dic):
    keys = [key for key in dic]
    keys.sort()
    code = "<table style='border:solid'>"
    for key in keys:
        code += "<tr><th>%s</th><td>%s</td></tr>" % (key, dic[key])
        pass
    code += "</table>"
    return code

def application(environ, start_response):
    status = "200 OK"
    headers = [('Content-Type', "text/html;charset=UTF-8")]
    code = "<html><body>"
    code += "<h3>environ</h3>"
    code += to_table(environ)
    code += "<h3>os.environ</h3>"
    code += to_table(os.environ)
    code += "</body></html>"
    start_response(status, headers)
    return [code]

if __name__ == "__main__":
    from wsgiref.handlers import CGIHandler
    CGIHandler().run(application)
    pass