短期集中:PythonでWebおみくじを作る(XAMPPでCGI)

Python
この記事は約16分で読めます。

Python勉強企画、5日目はWebプログラミングです。まずはちょっと昔懐かしい感じのするCGIです。

準備

CGIを使用する場合、Apacheの設定を多少いじる必要があります。Python ファイルがリクエストされた場合は、そのまま中身を返送するのではなくCGIとして実行する、という設定です。

httpd.confの設定

以下は、XAMPPでApacheをインストールしている場合の例です。

  1. 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で手軽に試せるのも学習用には都合が良いですね。

コメント

タイトルとURLをコピーしました