Python勉強企画、5日目はWebプログラミングです。まずはちょっと昔懐かしい感じのするCGIです。
準備
CGIを使用する場合、Apacheの設定を多少いじる必要があります。Python ファイルがリクエストされた場合は、そのまま中身を返送するのではなくCGIとして実行する、という設定です。
httpd.confの設定
以下は、XAMPPでApacheをインストールしている場合の例です。
- XAMPPコントロールパネルを表示します。Apache は Start せずに(既に実行されている場合にはいちど停止してから作業します)、Configボタンをクリックします。
2. 表示されたサブメニューから『Apache(httpd.conf)』を選択します。
3. テキストエディタ(デフォルトではメモ帳)が起動し、httpd.conf の内容が表示されます。
『AddHandler cgi-script (いくつかの拡張子)』という行を探します。
この例では『 AddHandler cgi-script .cgi .pl .asp 』という行があります。
4. 探した行の最後に .py を追加します。
前項の例なら『 AddHandler cgi-script .cgi .pl .asp .py 』と修正します。
xamppのApacheでは、最初からドキュメントルートに対してExecCGIオプションが指定されているので、上記の設定だけでどこに拡張子 .py のファイルを配置してもCGIとして実行されるようになります。
5. メモ帳を閉じ、XAMPPコントロールパネルでStartをクリックしてApacheを起動します。
動作テストを兼ねて、はじめてのCGI
ソースコード
Apacheのドキュメントルート(XAMPPをデフォルトでインストールした場合には C:\xampp\htdocs)の下にPythonというフォルダを作成し、その中に CGITest.py という名前で以下の内容のファイルを作成します。
動作テストも兼ねているので、下のソースコードをコピー&ペーストする方が、入力ミスによる不具合が発生しなくてよいでしょう。
ただし、1行目は、python.exe のパスを表していますので、各自の環境に合わせて書き換えて下さい。(コード解説の項を参照)
#!C:/Programs/Python/python.exe
import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
print("Content-Type: text/html; charset=utf-8")
print()
body = """<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>Pythonテスト</title>
</head>
<body>
<h1>Pythonテスト</h1>
正常に動作しています
</body>
</html>
"""
print(body)
実行結果
ブラウザを起動し、URL欄に http://localhost/Python/CGITest.py と入力しましょう。以下のように表示されたら設定成功です。
コード解説
CGI必須の記述
#!C:/Programs/Python/python.exe
ファイルの先頭行には、Python実行ファイル python.exe のパスを記述します。
上の例は、python.exe のパスが
C:\Programs\Python\python.exe
である場合です。
判らない場合は、Windowsならばコマンドプロンプトで
where python.exe
を実行すると探すことができます。
文字コード設定
import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
この3行は、Pythonの 実行結果を UTF-8 で出力するためのものです。Python でプログラムを作成する場合のソースコードは基本的に UTF-8 で記述しますが、出力が UTF-8 になるとは限らないのです。
ヘッダ出力
print("Content-Type: text/html; charset=utf-8")
print()
PHPのようなWeb特化言語と異なり、Pythonではhttpヘッダを明示的に出力する必要があります。ここではhtml形式、文字コードUTF-8 と設定しています。
※httpヘッダとは、Webサーバからブラウザに送信するデータについての情報(データの種類はhtml文書なのかプレーンテキストなのかjpeg画像なのか、サイズは何バイトか、最終更新はいつか、など)を記述したもので、本体のデータの前に付加されて送出されます。最低でもデータの種類を表す “Content-Type”の行がないと、ブラウザで正常に表示できません。
httpでは、httpヘッダと本体のデータ(この場合はhtml文書)の間を空行で区切る、という規則になっているので、中身なしの print() 関数で空行を出力しています。
複数行文字列
body = """<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>Pythonテスト</title>
</head>
<body>
<h1>Pythonテスト</h1>
正常に動作しています
</body>
</html>
"""
print(body)
“”” は複数行の文字列を表します。次に ””” が現れるまで、改行文字も含めてすべて1つの文字列となります。一行分ずつ print() で表示をしてもいいのですが、長い html を出力するのならこの方が判りやすいでしょう。
もしうまく動かなかったら
”Object not found” ”Error 404″ と表示された場合 は、ファイル名やURLの入力ミス、ファイル保存場所の間違いだと思われます。
・ファイルの保存場所は ドキュメントルート以下のPythonディレクトリの中(デフォルトでは C:\xampp\htdocs\Python\ の中)ですか?
・ファイル名は CGITest.py ですか?
・URLは http://localhost/Python/CGITest.py ですか?
“Server error” “Error 500” と表示された場合 は、スクリプトのどこかが間違っていて、Pythonプログラムとして正常に実行できていません。本ブログからコピー&ペーストしたにもかかわらずこれがでる場合は、
・1行目の #! 以下の記述は正しいですか?
・#! の行はちゃんと1行目になっていますか?前に空行があっても動作しません。
を確認して下さい。
コマンドプロンプトから、CGITest.pyを実行して、
C:\xampp\htdocs\Python> python.exe CGItest.py
Content-Type: text/html; charset=utf-8
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>Pythonテスト</title>
</head>
<body>
<h1>Pythonテスト</h1>
正常に動作しています
</body>
</html>
と表示されるか確認してみましょう。
おみくじ
もう少し複雑なCGI、おみくじを作ってみましょう。これもいろいろなWeb開発の入門書でよく見る例ですが、フォームから送信されたデータの受け取りという重要な処理を試すことができます。
ソースコード
入力画面
こちらはhtmlのファイルです。
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>Webおみくじ</title>
</head>
<body>
<h1>Webおみくじ</h1>
<form action="Omikuji.py" method="post">
お名前を入力して下さい <input type="text" name="name">
<button type="submit">占う</button>
</form>
</body>
</html>
結果表示画面
こちらが処理本体です。
#!C:/Programs/Python/python.exe
import sys
import io
import cgi
import random
def displayResult(name,result):
print("Content-Type: text/html; charset=utf-8")
print()
print("""<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>Webおみくじの結果</title>
</head>
<body>
<h1>Webおみくじの結果</h1>
%sさんの運勢は%sです
</form>
</body>
</html>
""" % (name, result))
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
form = cgi.FieldStorage()
if "name" in form:
omikuji = ["大吉", "中吉", "小吉", "吉", "凶"]
random.seed()
result = random.randrange(0,5,1)
displayResult(form["name"].value, omikuji[result])
else:
print("Location: Omikuji.html")
print()
実行結果
入力画面
ブラウザのURL欄に http://localhost/Python/Omikuji.html と入力します。
下の画面が表示されたら、入力欄に名前を入力し、『占う』ボタンをクリックします。
結果画面
結果画面が表示されます。入力された名前と、ランダムに選ばれたおみくじの結果が表示されます。
コード解説
冒頭部分
#!C:/Programs/Python/python.exe
import sys
import io
import cgi
import random
1行目は最初の例と同様、 #! の後に Python の実行ファイル python.exe のパスを記述します。
import は最初の例にもあった sysモジュール と ioモジュールの他、フォームから送信された値を読み取るなどのCGIの基本処理を行う cgiモジュールと、おみくじを選ぶ乱数生成のための randomモジュールを指定しています。
結果画面表示
def displayResult(name,result):
print("Content-Type: text/html; charset=utf-8")
print()
print("""<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>Webおみくじの結果</title>
</head>
<body>
<h1>Webおみくじの結果</h1>
%sさんの運勢は%sです
</body>
</html>
""" % (name, result))
この部分は結果画面を表示するための関数です。複数文字列を使ってまるごとHTMLを記述しているため長たらしいですが、実は print()メソッドが 1つあるだけの簡単な作りです。
print()メソッドの中の文字列をごく単純化すると、
“~~%s~~%s~~” % (値1, 値2)
となっています。この部分は C などの sprintf() に相当し、文字列中に変数の値を埋め込むことができます。関数・メソッドではなく%演算子で記述するのが特徴です。
前半の “~” で囲まれた文字列(書式文字列)の中に %s が2つありますが、この部分に、後に記述した値1、値2が順に当てはめられます。
他言語の sprintf() と同様、書式文字列中には %s 以外に %d や %f も使えます。それぞれの意味は下の表の通りです。
%s | 文字列 |
%d | 整数 |
%f | 浮動小数点数 |
“~” で囲まれた文字列中に %sや %d や %f をいくつ記述してもいいですが、後に値を同じ数だけ記述する必要があります。なお、 %sや %d や %f が1つしかない場合は
“~~%s~~” % 値1
のように、値を(…)で囲まないで記述することもできます。
また、%文字 での置換を利用する文字列で % 文字そのものを表示したい場合は、文字列中には %% と記述します。
たとえば、
“本日は食品全品 %d %% 引き!” % 20
と記述すると、
本日は商品全品 20 % 引き!
となります。
フォームから送信されたデータの読みとり
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
form = cgi.FieldStorage()
io.TextIOWrapper() は文字化け対策です。
form = cgi.FieldStorage() はフォームから送信されたデータを読み取り、変数formに代入しています。フォームに入力された値は、form[キー].value で取得できます。キーはフォームの各入力欄につけられた名前で、inputタグなどのname属性の値です。
変数の存在チェックと結果表示
if "name" in form:
omikuji = ["大吉", "中吉", "小吉", "吉", "凶"]
random.seed()
result = random.randrange(0,5,1)
displayResult(form["name"].value, omikuji[result])
else:
print("Location: Omikuji.html\n")
print()
if “name” in form: は、変数 form の中に”name”というキーが存在するかどうかを判別しています。フォームからデータ送信されていない場合(入力画面を経由せずに直接 Omikuji.pyにアクセスした場合など)はこの条件が不成立となり、else節が実行されます。
条件が成立した場合は、乱数で大吉~凶のいずれかを選び、フォームで入力された名前とともに displayResult()関数を呼び出して結果画面を表示します。
条件が不成立の場合は、print(“Location: Omikuji.html\n”) が実行されます。HTTPヘッダとして
Location: Omikuji.html
(空行)
と出力することにより、自動的に入力画面に遷移するようにしています。
ファイルを1つにしてみる
以下のように改造すると、Omikuji.pyだけで入力フォームと結果画面の両方を表示できます。
#!C:/Programs/Python/python.exe
import sys
import io
import cgi
import random
def displayForm():
print("Content-Type: text/html; charset=utf-8")
print()
print("""<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>Webおみくじ</title>
</head>
<body>
<h1>Webおみくじ</h1>
<form action="" method="post">
お名前を入力して下さい <input type="text" name="name">
<button type="submit">占う</button>
</form>
</body>
</html>
""")
def displayResult(name,result):
print("Content-Type: text/html; charset=utf-8")
print()
print("""<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>Webおみくじの結果</title>
</head>
<body>
<h1>Webおみくじの結果</h1>
%sさんの運勢は%sです
<hr>
<a href="">入力に戻る</a>
</body>
</html>
""" % (name, result))
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
form = cgi.FieldStorage()
if "name" in form:
omikuji = ["大吉", "中吉", "小吉", "吉", "凶"]
random.seed()
result = random.randrange(0,5,1)
displayResult(form["name"].value, omikuji[result])
else:
displayForm()
とりあえずここまで。
CGIは、いまとなっては古い形式ですが、それだけに使える環境が多いのが利点です。xamppで手軽に試せるのも学習用には都合が良いですね。
コメント