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
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 にはなんでも突っ込めるようなので、関数オブジェクトをそのまま突っ込んでみましたが、なかなか使いやすいです。あと、 methods は GET
だけを指定した場合、勝手に 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 は初期設定で .wsgi を Python コードとして認識してくれません。 .py にしちゃえばいいといえばそれで終わりですが、右クリックしてメニューから [ファイルを開くアプリケーションの選択] で Python Editor を既定値にすると、 Python コードとして書けるようになります。 IntelliSense も効きます。
まとめ
Werkzeug があれば Python で web アプリつくるの怖くない!(← Django 使えよ)
これで負担軽減ができるのか、 img.azyobuzi.net のベータテスト終了が楽しみですね。
というわけで、 Happy Coding in Python with Visual Studio!!