某元祖 Advent Calendar 25日目 - PEP3333

Python3 Advent Calendar の記事、と見せかけて某元祖 Advent Calendar の記事らしいです。
Python3 Advent Calendar の二周目用に書いておいたのが結局24人集まって書かなかったから使い回したとかそういうことはないんだからね! 勘違いしないでよね!

WSGI について

さて、気を取り直して PEP3333 です。
と行きたいところですが、 PEP3333 の前にまずは PEP333 についての説明です。

PEP333 は、 Python のアプリケーションと Web サーバの間のインタフェイス定義である WSGI(Web Server Gateway Interface) というものを定義しています。
WSGICGIPython 向けに作り直したものと言えます。

WSGI そのものはただのインタフェイスでしかありませんが、 Web Server と Web Application を繋ぐ基本的な仕組みです。
最近の Web Application Framework であればほぼすべてが WSGI Application として動かすことができるため、 Web Application Framework を使った開発であっても裏では使われていたりします。

WSGI については私が執筆した記事を見ていただいてもいいのですが、ここでは簡単に解説します。

ここから先のソースコードは Python2.x 向けのものです。

WSGI Application とは

PEP3333 の前にまずは WSGI の定義である PEP333 についてです。

PEP333 で定義されている WSGI にはサーバとアプリケーションの二つの定義があります。
WSGI Application は Python で書かれた呼び出し可能なオブジェクト (関数・クラスなどの __call__ メソッドが定義されたオブジェクト) です。

まあグダグダ説明をするよりソースで語った方がはやいので、簡単な WSGI Application の例です。

def hello(environ, start_response):

    start_response('200 OK', [('Content-Type', 'text/plain')])

    return ["Hello, world."]

この簡単な関数が Hello, world を返すだけの WSGI Application です。

WSGI Application は入力として二つの引数を受け取ります。

environ サーバから渡される環境の辞書。リクエストされたパスやアクセス元 IP Address などの情報が入っている。
start_response クライアントへのレスポンスを開始する際に呼び出す呼び出し可能オブジェクト。

そして、返値としてイテレートすると文字列を返す Iterable を返します(文字列のリストなど)。

WSGI Application を実行する

折角アプリケーションを作ったので、実行してみます。
実行にはサーバ側の実装が必要なのですが、 Python には標準ライブラリとして wsgiref モジュールが存在し、その中に簡単なサーバが実装されているのでこれを使います。

from wsgiref import simple_server

def run():

    server = simple_server.make_server('', 8080, hello)
    server.serve_forever()

この run 関数を実行すると wsgiref.simple_server モジュールを使い、 localhost:8080 で先程作った Hello, world アプリケーションを実行します。

実行した状態で http://localhost:8080/ にアクセスすると Hello, world が表示されます。

WSGI まとめ

このように WSGI Application は簡単に作れ、実行できます。
また、 WSGI Server としても wsgiref のものだけでなく、 Apachemod_wsgi モジュールや Twisted, Meinheld, gunicorn など使って実行できるので、サービスインも楽です。

このようなサーバとアプリケーションに加え、サーバからの入力を処理し、次のアプリケーションに渡すミドルウェアというものを組み合わせることで Web Application を簡単に作ることができます。

Web Application Framework の使い方を覚えるのがめんどくさかったり、フレームワークにあわせて保守するのが面倒くさいと思いがちな私のような人間の場合は、この WSGI Middleware と WSGI Application を組み合わせるだけで Web Application を作ることが多いです。というか Web Application Framework を使った経験はないのでよくわかりません。

例えば Paste などが使えるでしょう。
また、私が作っている wsgilib なども使えるかもしれません。(とか言いつつ setup.py 書いてないし PEP8 守ってないし色々微妙)

PEP3333

前の節までは Python2.x と PEP333 の話でした。
ここからは PEP3333 の話です。

PEP3333 は Python2 のみの対応であった PEP333 を Python3 に対応させるためのものです。
と言っても PEP333 と PEP3333 の違いは Python2 から Python3 で変わった文字列の扱いに関する部分だけなので、それほど難しいことはありません。

試しに先程作った Hello, world アプリケーションは、このまま Python3 で動かし、アクセスすると

A server error occurred.  Please contact the administrator.

なんて言われてしまいます。
標準出力を見てみると

Traceback (most recent call last):
  File "/usr/local/python3.2/lib/python3.2/wsgiref/handlers.py", line 138, in run
    self.finish_response()
  File "/usr/local/python3.2/lib/python3.2/wsgiref/handlers.py", line 179, in finish_response
    self.write(data)
  File "/usr/local/python3.2/lib/python3.2/wsgiref/handlers.py", line 264, in write
    "write() argument must be a bytes instance"
AssertionError: write() argument must be a bytes instance

と出ています。
要は、 write 関数にバイト文字列ではない文字列が入力された事に文句を言っているようです。

ひとまず Python3 で動くようにするには以下のように返値をバイト文字列に変えることで動きます。

def hello(environ, start_response):

    start_response('200 OK', [('Content-Type', 'text/plain')])

    return [b'Hello, world.']

Python3 対応するには

基本的に WSGI アプリケーションを Python3 対応対応させるには、以下の点に気をつけるだけで問題なく行えます。

  • start_response callable に渡す文字列は str (Python2 での unicode)

- ただし、使える文字は latin-1 の範囲のみ
- 内部で latin-1 にエンコードするため、範囲外の文字を渡すとエラーになる

  • 返値はイテレートするとバイト文字列を返すオブジェクトを返す

これだけで簡単に Python3 対応できるので、皆様お試し下さい。

また、 WSGI についても理解しておくことで、 Web Application Framework を使わないでも Web Application 開発ができるようになりますし、各種フレームワークを使う上でもきっと役に立つと思います。