Webアプリケーションに値を渡す
これまで、Pythonを使ってWebアプリケーションの「出力」を行う方法について解説しました。アプリケーションの2つの要素は「出力」と「入力」です。ここでは、もう1つの重要な要素「入力」を行う方法について解説します。
Webアプリケーションでは、Webブラウザにアプリケーションの利用者(ユーザ)が利用するインターフェース(UI)を置きます。Webブラウザから、「リクエスト」という形でWebサーバになんらかの指示を与えます。
Webアプリケーションでは、2つの方法を使ってクライアントからサーバに指示を与えることができます。1つは「GETリクエスト」と呼ばれています。もう1つは「POSTリクエスト」と呼ばれています。ここでは、順を追って2種類のリクエストについて解説します。
URLを使ってWebサーバに命令を渡す
Webアプリケーションに関する解説をした節で、「URLはリクエストの一種である」と書きました。URLの後半部分は「パス」に分解されます。Webサーバがパスを解釈して、どのようなレスポンスを返すべきかを判断します。パスとして指定した部分にプログラムがある場合は、プログラムの実行結果がレスポンスとして返されます。
URLには、パスの他にパラメータと呼ばれる情報を含めることができます。パスの最後にクエスチョンマーク(?)を置くと、その後がリクエストの中で特別に扱われます。
試しに、「http://127.0.0.1:8000/cgi-bin/test.py?foobarbaz」というURLをWebブラウザに入力してみてください。Pythonで作ったWebサーバを立ち上げた状態でURLを入力します。すると、以前と同じようにPythonのプログラムが出力した結果が表示されたはずです。クエスチョンマークの後ろを別の文字に変えても同じ結果になります。URLの中で、クエスチョンマーク以降がパスと別に扱われているわけです。
クエスチョンマーク以降はクエリと呼ばれています。クエリ(Query)とは、「問い合わせ」や「質問」という意味の英語です。リクエストに添える問い合わせ、というような意味ととらえてください。クエリを使うと、Webサーバ上のプログラムに値を渡すことができます。 クエリには、キーと値のペアをイコール(=)で挟んだ形でデータを記述できます。複数の値を渡したいときには、キーと値のペアを「アンド(&)」で区切ります。データの構造はPythonの辞書によく似ています。
このようにしてURLに埋め込んだ値は、文字列としてWebサーバ上のプログラムに渡されます。サーバ上のプログラムでクエリを受け取ることで、クライアントからの指示を受け取ることができるのです。
プログラムで値を受け取る
Pythonには、URL上にクエリとして渡された値を受け取るためのモジュールが用意されています。それがcgiモジュールです。このモジュールを使うと、クライアントから渡されたリクエストを分割して、Pythonで扱いやすい形に変換できます。
では、実際にURLからクエリを渡し、サーバ上で走るPythonのプログラムで受け取ってみましょう。まずは、以下のプログラムをcgi-binフォルダに置いてください。LinuxやMacOS Xでは、「chmod 755 querytest.py」としてファイルに実行権限を与えるのを忘れないようにしてください。
List01 querytest.py
:::python
#!/usr/bin/env python
html_body = """
<html><body>
foo = %s
</body></html>"""
import cgi
form=cgi.FieldStorage() # (1)
print "Content-type: text/html¥n"
print html_body % foo # (2)
このプログラムは、「クエリとして渡されたfooというキーを表示する」という単純なプログラムです。日付表示のプログラムと同じように、Pythonの文字列フォーマット機能を使って結果を表示しています。プログラムの中ほどでは、cgiモジュールをインポートしています。
直後の行で使っているcgiモジュールのFieldStorage()という関数がポイントです(1)。この関数を使うと、クエリとして渡された値をPythonのプログラムで扱えるように変換できます。変換されたオブジェクトはフィールドストレージ(FieldStorage)と呼ばれています。フィールドストレージには「form['foo']」のようにクエリのキーを添えて辞書風にアクセスします。辞書風にアクセスして返ってくるオブジェクトからキーの値を得るためには、「value」というアトリビュートを利用します。2では、「form['foo'].value」のようにしてfooというキーに対応する値を取得しています。
スクリプトを設置したら、PythonのWebサーバを立ち上げた状態で次のURLにアクセスしてください。
:::url
http://127.0.0.1:8000/cgi-bin/querytest.py?foo=bar
すると、ブラウザ上に「foo」というクエリに渡した値「bar」という文字列が表示されるはずです。URLの「foo=」の後を書き換えてアクセスしてみてください。Webブラウザに表示される文字列が変わるはずです。
クエリのキーをスマートに扱う
ところで、querytest.pyというプログラムでは、クエリに必ず「foo」というキーがあることを期待するようになっています。試しに、「cgi-bin/querytest.py?bar=bar」のようにしてキーを変えてみてください。エラーが起こるので、Webブラウザには何も表示されないはずです。かわりにWebサーバを立ち上げているシェルにエラーが表示されているはずです。
このプログラムを、もう少し気の利いたものにしたいときはどうすればいいでしょうか。さまざまな状況で問題なく動くことは良いプログラムの条件です。クエリにキーが存在しなかった場合はエラーになってしまうのでは良いプログラムとは言えません。キーが存在していない場合でも、適切に動くプログラムの方がより良いプログラムと言えます。
組み込み型の辞書型で、キーが存在する場合でも存在しない場合でもスマートに値を取り出したい場合には辞書型のメソッドget()を利用します。このメソッドには、第2引数としてキーが存在しなかったときに値として返す「デフォルト値」を設定できます。get()メソッドを使うと、キーの存在を確認する「if文」などを使わずに、辞書型のキーをスマートに扱うことができます。
辞書型のget()メソッドに似たメソッドがフィールドストレージにあります。getvalue()というメソッドを使うと、クエリのキーをよりスマートに扱えます。このメソッドには、辞書型のget()と同じように2種類の引数を渡します。1つ目はクエリのキーとなる文字列です。もう1つはオプションの引数で、クエリにキーが存在しなかったときに利用するデフォルト値です。
querytest.pyの最後の行を以下のように書き換えてみてください。クエリを与えないときにもエラーが発生せず、代わりに「N/A」という表示をします。「N/A」というのは「該当なし(Not Applicable)」という意味の略語です。
:::python
print html_body % form.getvalue('foo', 'N/A')
「13日の金曜日」を探すWebアプリケーション
さて、URLを使ってWebサーバで動くプログラムに値を受け渡すことができることが分かりました。ここでは、もう少し複雑な機能を持ったプログラムを作ってみましょう。
西暦を受け取り、その年に「13日の金曜日」がいくつあるかを調べて出力するプログラムを作ってみようと思います。とてもシンプルなプログラムですが、ユーザからの入力に応じて出力を返す機能を持っています。入力と出力があるという意味では、立派なWebアプリケーションと呼べます。
プログラムの機能をまとめると、以下のようになります。
- 西暦はURLにクエリとして埋め込んで渡します
- クエリのキーは「year」とします
- クエリを受け取って、その年にある「13日の金曜日」を探し出します
- 結果として、総数と日付を返します
- クエリには数字以外の文字列が渡ってくることも考慮します
cgi-binフォルダに、以下のスクリプトを設置してみてください。条件分岐やループがあり、これまでのサンプルに比べるとかなり本格的なプログラム になっています。
このスクリプトファイルはUTF-8で編集、保存してください。LinuxやMac OS Xでは、「chomd 755 find13f.py」として実行権限を与えてください。
プログラムを設置したら、Webブラウザで「cgi-bin/find13f.py?year=3000」というアドレスにアクセスしてみてください。クエリの数字を書き換えると、別の西暦に対する結果を計算して表示します。
List02 find13f.py
:::python
#!/usr/bin/env python
# coding: utf-8
import cgi
from datetime import datetime
html_body = u"""
<html><head>
<meta http-equiv="content-type"
content="text/html;charset=utf-8">
</head>
<body>
%s
</body>
</html>"""
content=''
form=cgi.FieldStorage()
year_str=form.getvalue('year', '')
if not year_str.isdigit():
content=u"西暦を入力してください"
else:
year=int(year_str)
friday13=0
for month in range(1, 13):
date=datetime(year, month, 13)
if date.weekday()==4:
friday13+=1
content+=u"%d年%d月13日は金曜日です" % (year, date.month)
content+=u"<br />"
if friday13:
content+=u"%d年には合計%d個の13日の金曜日があります" % (year, friday13)
else:
content+=u"%d年には13日の金曜日がありません"
print "Content-type: text/html;charset=utf-8\n"
print (html_body % content).encode('utf-8')
図01 西暦3000年には13日の金曜日が1つだけある
このプログラムでポイントとなるのは2箇所です。
1つ目はクエリに渡された値が数値かどうかを判別する部分です(1)。クエリに渡される値はすべて文字列型となります。文字列型を数値に変換するには、組み込み関数のint()を使います。ただし、int()に数値以外の文字列を与えるとエラーになってしまいます。そこでこのプログラムでは、Pythonの文字列型が持つisdigit()メソッドを使って事前にチェックを行っています。このメソッドは、文字列が数字だけで構成されている場合に真(True)を返します。ですので、クエリ文字列をint()関数の引数として与えて、エラーになるかどうかを検査できるわけです。クエリが数値以外の文字列を含んでいた場合には、警告の表示を、そうでない場合には13日の金曜日を探す処理を進めます。
もう1つのポイントは、クエリとして渡された西暦に「13日の金曜日」が何日あるかを数える部分です(2)。このプログラムでは、datetimeモジュールを活用しています。1月から12月までの13日に相当するdatetimeオブジェクトを作り、weekday()メソッドで曜日を調べています。月曜日が0となりますので、4に相当する日が金曜日となります。
13日の金曜日が見つかったら、ループの中でレスポンスの一部となる文字列を組み立てていきます。最後にPythonの文字列フォーマットを使ってレスポンス全体となる文字列を組み立て、print文で出力しています。