nullpobug勉強会04 "wsgiってなに"

またも間が空いてしまったnullpobug勉強会と、そのまとめ。
今回のテーマはwsgiについて。

講師:id:nullpobug
生徒:id:feiz

wsgiってなに

web server gateway interfaceの略。
webサーバーとアプリケーション間の通信規約を定めたもの。
PEP-333に仕様が記述されている。

具体的に

サーバー側のきまりごと
  1. 環境変数をdict形式で提供するよ。
  2. アプリを
 application(env, start_response)

って形で呼び出すよ。

  1. envは辞書形式の環境変数、start_responseはレスポンスヘッダを返すためのコールバックだよ。
アプリ側のきまりごと

envとstart_responseの2つの引数を受け取るcallableとして実装するよ。
envはサーバー側で作った環境変数のdictで、start_responseはレスポンスヘッダをセットする関数だよ
返り値としてiterableな文字列を返すよ

基本的にこれだけ。他にも環境変数には〜〜のキーが存在することを保証するとか、レスポンスヘッダが送出の寸前まで書き換え可能な事を保証するとかの細かい事も色々規定されてる。

どう便利?

要するにWSGIに準拠してアプリを書けば

  1. WSGIを実装してるサーバー上ならどこでも動く
  2. WSGIミドルウェアがやってくれること(環境変数の提供とかレスポンスが返る事の保証とか)を考えなくてよくなる

ということ。

うごかしてみる

動かさなければ始まらない。
python2.5以降にはwsgirefというパッケージがあって、wsgiハンドラとかデモアプリとかが用意されてるのでそれを使ってみる。

In [1]: from wsgiref.handlers import CGIHandler
In [2]: from wsgiref.simple_server import demo_app
In [3]: handler = CGIHandler()
In [4]: handler.run(demo_app)
Status: 200 OK
Content-Type: text/plain
Content-Length: 1209

Hello world!

COMMAND_MODE = 'unix2003'
HOME = '/Users/feiz'
LANG = 'ja_JP.UTF-8'
(中略)
wsgi.multiprocess = True
wsgi.multithread = False
wsgi.run_once = True
wsgi.url_scheme = 'http'
wsgi.version = (1, 0)

でけた。
CGIHandlarはCGI動作するwsgiの実装クラスで、run(application)を呼び出す事でアプリを実行する。
demo_appはHello Worldとenvを返すだけのwsgiアプリ。

wsgiミドルウェアを書く

環境変数にこっそりほげを入れ込むミドルウェアを書いてみる

In [5]: def hoge_middleware(env, start_response):
   ...:     env["HOGE"] = "HOGE MIDDLEWARE"
   ...:     return demo_app(env, start_response)

できた。以下実行結果。

In [6]: handler.run(hoge_middleware)
Status: 200 OK
Content-Type: text/plain
Content-Length: 1234

Hello world!

COMMAND_MODE = 'unix2003'

HOGE = 'HOGE MIDDLEWARE'

HOME = '/Users/feiz'
LANG = 'ja_JP.UTF-8'
(中略)
wsgi.multithread = False
wsgi.run_once = True
wsgi.url_scheme = 'http'
wsgi.version = (1, 0)

なんかいる。

DjangoWSGI

DjangoWSGIに対応してます。
ということでハンドラがどうなってるのか見てみる。
core/wsgi.py

class WSGIHandler(base.BaseHandler):
    initLock = Lock()
    request_class = WSGIRequest

    def __call__(self, environ, start_response):
        """以下略"""

environとstart_responseを受け取ってるっぽい。
mod_wsgiからこのハンドラを呼び出すための下準備をするスクリプト

import os
import sys

PROJECT_BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_NAME = os.path.basename(PROJECT_BASE)
SITE_BASE = os.path.dirname(PROJECT_BASE)

sys.path.append(SITE_BASE)
sys.path.append(PROJECT_BASE)

os.environ['PYTHON_EGG_CACHE'] = os.path.join(SITE_BASE, '.python-eggs')
os.environ['DJANGO_SETTINGS_MODULE'] = PROJECT_NAME + '.settings'

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

Djangoで必要なpathとかを設定して、最後に変数"application"にWSGIHandlerをセットしておしまい。

まとめ

WSGIに準拠するとアプリケーション、フレームワークを作るときにすごい楽できるよ!