2008年8月アーカイブ
先日リリースされたMovable Type 4.2に更新しようとしたら、ここで使っているs33.xrea.comサーバは、PerlやMySQLのバージョンが古いせいで、上手くインストールできませんでした。
システム更新時のトラブルを避けるために、別のサーバで導入テストまでやったのに、そのテストしたサーバとこのサーバでは、インストールされているソフトのバージョンが違うという罠がありましたorz
元に戻そうとしたら、MySQLのバックアップファイルを誤って紛失してしまったので、Movable Typeに組み込まれているバックアップ復元機能を利用しましたが、なぜか50件の記事しか復元されませんでした。
とりあえず応急処置で、復元されなかった記事は以前に出力されたhtmlを見ながら日付を調節して1つずつ投稿しなおしておきましたが、何か不具合があるかもしれません。また、コメントは戻していないので、7月以降のコメントは消えているかもしれません。
4.0で出来たばかりのバックアップ機能ではなく、以前からあるエクスポート/インポート機能でバックアップを取ればよかったorz
CGIプログラムは通常はWebサーバから呼び出されて実行されるので、高機能なデバッガが利用できないため、とてもデバッグがしにくいです。
しかし、Pythonの標準ライブラリには、CGIのデバッグに役立つ2つのモジュールが用意されていて、デバッグがかなり楽にできます。
まず最初に紹介するのが、cgitbです。このモジュールを使うと、CGIプログラムで捕捉されない例外が発生したときに、トレースバック情報をHTMLで出力してくれます。使い方は非常に簡単で、
import cgitb; cgitb.enable()
と最初の方に書いておくだけです。後は例外が発生したときに、勝手になんとかしてくれます。
実際にcgitbの出力がどんな感じになるのか、サンプルを用意しました。
http://niisaka.s33.xrea.com/cgi-bin/test/python-cgi.cgi
このCGIは1行コメントを投稿して、10行まで保存しますが、コメント欄が空欄のまま投稿すると例外が発生します。(フォームにinput要素があっても値が空の場合はFieldStorageには登録されないため)
例外が発生すると、例外の詳細と前後のソースコードが表示されます。この場合は、form['comment']から暗黙的に呼び出された FieldStorage.__getitem__() でKeyError例外が発生しています。
ユーザにソースコードを見せたくない場合は、cgitb.enable(display=0, logdir="/tmp") のようにすると、/tmpディレクトリにファイルとして出力され、ブラウザ上には出力されなくなります。また、context引数で出力されるソースコードの行数を指定できます。(標準ではcontext=5)
もう1つとても便利なのが、CGIHTTPServerモジュールです。これはCGI実行機能付きの簡易的なWebサーバとして動くモジュールで、Apache等をインストールしなくてもローカルでCGIのテストができます。
使用方法は、コマンドプロンプトから python -m CGIHTTPServer とするだけです。 このときのカレントディレクトリがWebサーバのドキュメントルートとなります。標準ではポート8000に設定されているので、ブラウザから localhost:8000 を開くことでアクセスできます。
CGIスクリプトは、cgi-binという名前のディレクトリより下に置く必要があり、拡張子は.pyでなければなりません。また、cgi-bin以下のディレクトリでは.py以外のファイルにはアクセスできなくなっています。画像などを表示する場合は、cgi-bin以外のディレクトリに配置しなければなりません。
このモジュールには1つ困った問題があり、CGIスクリプトが実行されるときにカレントディレクトリがドキュメントルートのままになっています。通常はCGIスクリプトのあるディレクトリがカレントディレクトリになるので、実際にApache等で動かした場合と動作が異なってしまいます。CGIHTTPServerを修正してしまうか、CGIスクリプト側でカレントディレクトリを設定しなおすといいかもしれません。
このような素晴らしい機能が標準で付いてくるのもPythonの魅力ですね。
CGIと言えばいまだにPerlが主流ですが、PythonはCGI用のライブラリも標準で用意されており、特別なパッケージを別途インストールする必要無しに、簡単にCGIを作成することができます。
CGI作成に使うのは、その名の通りcgiモジュールです。
cgiモジュールの使い方は非常に簡単で、import cgiをした後に form = cgi.FieldStorage() とするだけで、フォームの内容を取得できます。メソッドがPOSTかGETかだとか、そんなことは一切気にする必要はありません。この一行だけで、後はformを介してフォームの値にアクセスできます。
例えば掲示板の名前入力欄にnameという名前を付けていれば、form['name'].valueでname欄に入力された値が取得できます。'name'が存在しない場合はKeyErrorの例外が投げられますが、getvalueメソッドを使って name = form.getvalue('name', '名無しさん') のようにすると例外の代わりにデフォルト値を返してくれます。
'name'に該当する項目が1つだけならば、form['name'].valueは文字列を返しますが、チェックボックス等は同じ名前で複数の項目を使用することがあります。その場合に同名の項目が2つ以上選択されていると、form['name'].valueはリストを返します。同名の項目数に関わらず、常に単一の文字列を返して欲しい場合はgetfirstメソッドを、逆に常にリストで返して欲しい場合はgetlistメソッドを使います。
フォームからファイルをアップロードした場合は、form['name']はファイルオブジェクトになります。アップロードされたファイルの読み取りは普通のファイルと同じように行います。オリジナルのファイル名はform['name'].filenameで、MIMEタイプはform['name'].typeで取得できます。
投稿サイズに制限を設けたい場合は、import cgiの後でcgi.FieldStorage()を呼ぶ前に、cgi.maxlen = 102400 のようにバイト単位で指定します。フォームの内容がこのサイズを超えていると、cgi.FieldStorage()がValueErrorの例外を投げます。(ただし他のエラーでもValueErrorを投げるので、厳密に判定したければエラーメッセージを調べる必要がある)
掲示板CGI等でよく使う、<>&"のエスケープもcgiモジュールに用意されていて、s = cgi.escape(s) でエスケープされた文字列が得られます。デフォルトでは"をエスケープしてくれないので、それもエスケープして欲しい場合は、 s = cgi.escape(s, True) のようにします。
Pythonの強力なライブラリのおかげで、単純な掲示板くらいならあっという間に作ることができます。CGIを配布する場合も、余計なライブラリは必要ないのでライセンス等を気にしなくていいのは気楽です。

