アジョブジ星通信

進捗が出た頃に更新されるブログ。

img.azyobuzi.net で始める WSGI 入門

×始める
○始めた

こんにちは、テスト期間真っ盛りな azyobuzin です。さて今日 img.azyobuzi.net の WSGI 化が完了したので(テスト期間に何やってるんだか)、そのお話を。

WSGI とは

Python 製 web アプリケーションの標準みたいなものです。完全に Python の文化なので、 CGI みたいにいろんな環境に対応するために標準入出力でごにょごにょみたいなのがなくてスッキリしてたりします。

ついでに、読み方は「ウィズギー」です。どこかの誰かさん*1みたいに「うえすぎ」とか読まないように。

前提事項・環境とメリット

Apache + mod_wsgi + Python 2.7 のお話です。

今まで img.azyobuzi.net では、生 CGI で動いてた(今も動いてる)のですが、少しでも負担を減らそうと、プロセスを使いまわせる mod_wsgi を導入しようという発想に至って、 WSGI に手を出してみた次第です。 CGI より高パフォーマンス・省メモリになる(らしい)ので、設定いじくりまわせるサーバーなら使わない手はないでしょ!*2

とにかく Hello, world

まず、環境設定と動作テストです。

Python でハロワコードを書く

mod_wsgi では application 関数(クラスでもOK?)を呼び出されるので、その中にコードを書いていきます。

参考:WSGI でなんかつくってみる #python_adv — Python Web Framework Advent Calendar 2012 1.0.0 documentation

def application(env, start_response):
    start_response("200 OK", [("Content-Type", "text/html")])
    return ["<!DOCTYPE html><html>Hello, work</html>"]

こんなコードググったらいくらでも出てきますね。動作を確認するだけなので深くは突っ込みません。*3

Apache の設定

Ubuntuなら

sudo apt-get install libapache2-mod-wsgi

としておけばなんとかなります。

あとは Apache の設定ファイルに

WSGIScriptAlias Location コードのパス
#例:WSGIScriptAlias /2.0/beta_api /var/www/imgazyobuzi/2.0/beta_api/api.wsgi

を書き込んで restart すれば、動き出すはずです。もし、 500 エラーが出たら、 Apache のエラーログを見ると幸せになれます。

Werkzeug で快適なコーディング環境に

素の WSGI だとパスの振り分けとかダルいので、ユーティリティライブラリのお世話になります。

Welcome | Werkzeug (The Python WSGI Utility Library)

インストールは pip で一発ですね。

使い方はこのへん見ると、大体わかります
「werkzeug」でWSGIアプリをつくろう! — PythonMatrixJp

さっきのハロワを Werkzeug のお力で美しくしてみるとこんな感じに

from werkzeug.wrappers import Request, Response

@Request.application
def application(request):
    return Response("<!DOCTYPE html><html>Hello, work</html>", mimetype="text/html")

なんとなく Tips

Response クラスのコンストラクタについて、公式ドキュメントにこんな記載が

Special note for mimetype and content_type: For most mime types mimetype and content_type work the same, the difference affects only ‘text’ mimetypes. If the mimetype passed with mimetype is a mimetype starting with text/ it becomes a charset parameter defined with the charset of the response object. In contrast the content_type parameter is always added as header unmodified.

Request / Response Objects — Werkzeug 0.9-dev documentation

Google 翻訳が意味不明な回答をしてきましたが、つまり content_type に突っ込むとそのまま、 mimetype に突っ込むと text/ のときに良い感じにしてくれるよってことですかね?とりあえず、私は mimetype に突っ込むようにしてます。

パスで振り分けて API を実装していく

これがやりたくて Werkzeug を導入しました。流れとしては、 Map クラスで振り分けを定義して、 bind_to_environ(environ)環境変数に合わせた MapAdapter を作成して、 match() で振り分けという感じです。

from werkzeug.routing import Map, Rule
from werkzeug.wrappers import Request, Response

def regex(request):
    #処理省略
    return Response(~)

url_map = Map([
    Rule("/regex.json", endpoint=regex, methods=["GET"])
])

@Request.application
def application(request):
    try:
        endpoint, values = url_map.bind_to_environ(request.environ).match()
        return endpoint(request)
    except NotFound as e:
        #そんな振り分けルールはなかった
    except MethodNotAllowed as e:
        #許可されていないメソッドだった

match() の戻り値は、 Rule で指定した endpoint と振り分けルールで指定した変数(今回は使用してない)が入った values です。 endpoint にはなんでも突っ込めるようなので、関数オブジェクトをそのまま突っ込んでみましたが、なかなか使いやすいです。あと、 methodsGET だけを指定した場合、勝手に HEAD にも対応してくれます。

これで基本的な使い方は終わりです。ここまで作れば、あとは振り分けルールを増やしていくだけです。 CGI でやってたときよりずっと手軽。

URI のクエリ部分の取得方法

クエリの取得には Request.args 、フォームの取得には Request.form を使います。 cgi.FieldStorage のようにどちらも対応というのはないみたい(要出典)。型は Werkzeug オリジナルの ImmutableMultiDict というのが返ってきます。使い方は、 dic.get("key", "default") といたってシンプルです。

FieldStorage を使いたい場合は、さっき参考に上げたページに書いてあるように、 cgi.FieldStorage(environ=request.environ, fp=request.environ['wsgi.input']) としてあげれば OK です。

カレントディレクトリのお話

CGI ではカレントディレクトリがスクリプトのディレクトリになっていましたが、 mod_wsgi ではそうもいかないようです。なので、別ファイルに逃したコードにアクセスできない状況に。。 Apache の設定ファイルに WSGIPythonPath を書き込めば変えられるようですが、うまく行かなかったので、 sys.path.append(path) するしかありませんでした。環境依存かな?

追記: __file__ オブジェクトから取得すると、楽そうです
自分の設置場所を認識するようにした。 · a7fc129 · azyobuzin/img.azyobuzi.net · GitHub

Visual Studio での開発

今回、 img.azyobuzi.net の再開発では、 Visual Studio 2012 に Python Tools を入れてコード書いてました。 DreamSpark 使えるように本当になってよかった。

で、ポイントですが、 Python Tools は初期設定で .wsgiPython コードとして認識してくれません。 .py にしちゃえばいいといえばそれで終わりですが、右クリックしてメニューから [ファイルを開くアプリケーションの選択] で Python Editor を既定値にすると、 Python コードとして書けるようになります。 IntelliSense も効きます。

まとめ

Werkzeug があれば Python で web アプリつくるの怖くない!(← Django 使えよ)

これで負担軽減ができるのか、 img.azyobuzi.net のベータテスト終了が楽しみですね。

というわけで、 Happy Coding in Python with Visual Studio!!

おまけ

Werkzeug の読み方がわからないのでググってみたらこの有様


正しい読み方は、「ウェルクツォィグ」だそうです。
Google 翻訳

*1:ほんと、誰だろうね

*2:普通その上に Django を使う気がしますが、今回は今のソースを使いまわしたかったので生 WSGI です。

*3:そもそも素の WSGI はほとんどいじらなかったのでわからない…