読者です 読者をやめる 読者になる 読者になる

yifeの日記

Supercalifragilisticexpialidocious

PythonでWebアプリをつくる(ApacheもWebフレームワークもなしで)

この記事は、ワープロソフトでソースコードを貼り付ける方法を知らないことから、学校の授業におけるレポートの代わりとして執筆されました。
以下に記述されている内容は、学生が学習途中に記述した内容であり、その真偽について一切の保証はないものとします。
また、この記事は「みんなのPython Webアプリ編」に従って学習を進めます。


今回は、Webブラウザを使って操作するアプリケーション(Webアプリ)を、ApacheなどのWebサーバアプリケーションや、ZopeなどのWebアプリケーションフレームワークを使わず、標準のPythonモジュールだけで作成してみたいと思います。
標準のモジュールだけを使ってシンプルなアプリケーションを構築することで、ウェブアプリがどのように動作しているかを理解することを目的とします。

0章 セットアップする

PythonでWebアプリを開発するためには、開発環境にPythonをインストールするなどして、Pythonが実行できる環境を用意する必要があります。今回は、開発環境としてMacbookAir (Late2010)を用意しました。OSはMacOS X Lionで、1月25日時点の最新アップデートをすべて適用した状態になっています。

PythonでWebアプリを開発するためには、
Pythonインタプリタ
が必要です。
Pythonインタプリタは日本Pythonユーザ会のウェブサイト(http://www.python.jp/)から取得できますが、MacOS X LionにはPythonがはじめからインストールされています。
Pythonのバージョンを確認するには、Terminalを起動してから、以下のコマンドを入力します。

f:id:yife:20120125045717p:plain

patc-air-no-MacBook-Air:~ patc$ python
Python 2.7.1 (r271:86832, Jul 31 2011, 19:30:53) 
[GCC 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.15.00)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 

これで、現在のシステムにPython2.7.1がインストールされていることがわかりました。
今回はプリインストールされたPythonをそのまま使いますが、最新のPythonリリースが存在する場合はアップデートを行うことをおすすめします。

1章 PythonでWebサーバをつくってみる

さっそく、PythonだけでWebサーバをつくってみましょう。
そもそも、Webサーバとはなんでしょうか。Wikipediaには、以下のように記されています。

Webサーバ(ウェブサーバ)は、HTTPに則り、クライアントソフトウェアのウェブブラウザに対して、HTMLやオブジェクト(画像など)の表示を提供するサービスプログラム及び、そのサービスが動作するサーバコンピュータを指す。 広義には、クライアントソフトウェアとHTTPによる通信を行うプログラム及びコンピュータ。

http://ja.wikipedia.org/wiki/Web%E3%82%B5%E3%83%BC%E3%83%90

どうやら、「クライアントから送信されてきたHTTPリクエストにもとづいて、HTMLや画像など、なんらかのデータを返すプログラム」と定義できそうです。

1.1章 SimpleHTTPServerでお手軽ウェブサーバ

HTTPリクエストに基づいて、なんらかのデータを返せばよいのですから、本格的なWebサーバをつくるまえにシンプルなWebサーバを作ってみましょう。
Pythonはバッテリーついてます(Battery Included)哲学にしたがって、よく使われるモジュールがあらかじめ含まれた状態で頒布されています。今回はそのなかから、SimpleHTTPServerを利用してみます。
SimpleHTTPServerは、本来はこれをもとにして、もっと複雑なWebサーバを構築するために用意されているものですが、単体でも最低限の機能を備えており、立派にWebサーバとして動作します。

Terminalを起動してPythonのインタラクティブシェルを起動し、以下の二行のコードを書いてみましょう。

f:id:yife:20120125050937p:plain

import SimpleHTTPServer
SimpleHTTPServer.test()

たった2行のコードで、SimpleHTTPServerが立ち上がりました。それではさっそくサーバにアクセスしてみましょう。
Webブラウザを起動して、「http://localhost:8000」にアクセスしてみてください。環境によってなにが表示されるかは異なります。私の環境では、以下のようにディレクトリリストが表示されました。
f:id:yife:20120125051323p:plain
また、Terminalには、Webサーバの稼働状態が表示されています。
f:id:yife:20120125051434p:plain

localhostは現在使用しているシステムそのもののことを指します。Webブラウザにlocalhostと入力した場合、127.0.0.1ループバックアドレス)が読み込まれます。
つまり、今回入力した「http://localhost:8000」とは、「このブラウザが開かれているPC上で起動してるWebサーバの8000番ポートにアクセスする」という意味です。

さて、SimpleHTTPServerはうまく動いたので、いったんサーバを終了することにします。実行中のスクリプトを停止させるには、MacではControl + Cを押します。Terminal上に「>>>」が表示され、ユーザの入力を受け付ける状態に戻れば大丈夫です。うまく終了できない場合は、そのままTerminalを終了することでも、強制的にスクリプトを停止させることができます。
f:id:yife:20120125051929p:plain
上の画面のようになったら、このセクションは完了です。

1.2章 HTMLファイルを表示する

さきほどSimpleHTTPServerモジュールをそのままWebサーバとして利用したとき、WebブラウザにはPCのディレクトリ構造が表示されました。これは、SimpleHTTPServerが、起動したときのカレントフォルダを表示するようになっているからです。つまり、さきほどはPythonのインタラクティブシェルを起動したときのカレントフォルダ(~)がそのまま表示されていたわけですね。
このように、Webサーバがファイルを扱う起点となるフォルダのことを、ドキュメントルートと呼ぶことがあります。
Webサーバとして公開するからには、HTMLファイルを表示したいですね。その方法はとても簡単で、表示したいHTMLを作成して、ドキュメントルートに置くだけで完了します。

まずはドキュメントルートにするディレクトリを作成しましょう。場所はどこでもかまいませんが、今回は~/web/というディレクトリを作成し、そこをドキュメントルートとすることにしました。
f:id:yife:20120125052628p:plain
つぎに、表示したいHTMLを作成します。とりあえず、最低限のHTMLを作成することにしました。
以下のソースコードを、index.htmlという名前のテキストファイルに保存してください。保存するときには、テキストエンコーディングUTF-8になっていることを確認してください。

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>SampleHTML</title>
  </head>
  <body>
    <p>Python is awesome!</p>
  </body>
</html>

これで、表示するHTMLはできあがりました。

ところで、SimpleHTMLServerを起動するたびに、「import SimpleHTTPServer」「SimpleHTTPServer.test()」とインタプリタに入力するのは面倒です。
HTMLを作成したついでに、SimpleHTTPServerを起動するスクリプトファイルも生成しておきましょう。
以下のソースコードを、simpleserver.pyという名前のテキストファイルに保存してください。保存するときは、テキストエンコーディングUTF-8になっていることを確認してください。

import SimpleHTTPServer
SimpleHTTPServer.test()

作成した2つのファイルを、さきほど作成したドキュメントルートにおいてください。私の場合は、以下のようになりました。
f:id:yife:20120125053531p:plain

ファイルを設置し終えたら、Webサーバを起動します。Terminalを起動してドキュメントルートまで移動し、以下のようにコマンドを入力します。

python simpleserver.py

これでSimpleHTTPServerが立ち上がりました。先ほどと同じように、Webブラウザに「http://localhost:8000」(または「http://127.0.0.1:8000」)と入力してアクセスします。すると、index.htmlの内容が表示されるはずです。
f:id:yife:20120125053952p:plainf:id:yife:20120125053954p:plain

これで、「HTTPリクエストに対してHTMLファイルを返す」ことができましたので、これ以上ないほどシンプルとはいえ、立派にWebサーバと呼んでいいアプリケーションが完成しました。今回作成したindex.htmlファイルはとてもシンプルですが、HTMLファイルに画像を追加したり、CSSで装飾したりすることで、より複雑なWebページを表示することができます。

2章 CGIHTTPServerを使う

さきほどまで使用していたSimpleHTTPServerでは、ファイルや画像を取り扱う機能しかありません。つまり、静的コンテンツにしか対応してないということです。
Web「アプリケーション」を作るからには、ユーザからの入力によって、出力が変わる仕組みがほしいですよね。
そこで、次は、おなじく標準ライブラリに含まれているCGIHTTPServerを使います。

2.1章 CGIHTTPServerを準備する

CGIHTTPServerは、SimpleHTTPServerと同じように、Pythonだけあれば利用できます。
それでは、さっそく起動スクリプトを書いてみましょう。
以下のソースコードを、cgiserver.pyという名前のテキストファイルに保存してください。保存するときは、テキストエンコーディングUTF-8になっていることを確認してください。

import CGIHTTPServer
CGIHTTPServer.test()

まだ動かすCGIプログラムを用意していませんが、ひとまずcgiserver.pyを実行して、CGIHTTPServerを立ち上げてみましょう。このスクリプトを実行するには、Terminalでドキュメントルートまで移動した後に、以下のように実行するのでしたね。

python cgiserver.py

スクリプトを実行すると、SimpleHTTPServerを立ち上げたときと同じようなメッセージが表示されます。ためしに「http://localhost:8000」というアドレスにアクセスしてみると、SimpleHTTPServerと全く同じようにindex.htmlが表示されることがわかります。
f:id:yife:20120125101147p:plain
これでCGIHTTPServerの用意はできました。次は、実際にアプリケーションをPythonで書いてみましょう。

2.2章 Webアプリケーションを設置する

CGIHTTPServerは、index.htmlのような静的ファイルだけでなく、ユーザからの入力によって結果の変わる動的ファイルもあつかうことができます。
そのためには、ドキュメントルートに新しく「cgi-bin」という名前のフォルダを作る必要があります。
f:id:yife:20120125101525p:plain
これから作成するWebプログラムは、かならずcgi-binフォルダの中に置かれることになります。

それでは、手始めにシンプルなWebアプリケーションを作成してみましょう。
以下のソースコードを、test.pyという名前のテキストファイルに保存してください。保存するときは、テキストエンコーディングUTF-8になっていることを確認してください。

#!/usr/bin/env python

print "Content-type: text/html\n"
print "<html><body>This is my first WebApp. IT'S WORKS!</body></html>"

f:id:yife:20120125102146p:plain
ファイルをcgi-binフォルダ内に置いたら、Terminalからファイルの実行権限を与えてやる必要があります。Terminalでcgi-binフォルダまで移動し、「chmod 755 test.py」というコマンドを入力してください。
f:id:yife:20120125102158p:plain
これでWebアプリケーションの設置は完了です。

2.3章 Webアプリケーションを動かしてみる

さきほど設置したtest.pyを動かすためには、「http://localhost:8000」ではなく、「http://localhost:8000/cgi-bin/test.py」というURLにアクセスする必要があります。新しく追加された「/cgi-bin/test.py」という部分を、特にリクエストと呼びます。Webサーバに「/cgi-bin/test.py」がほしいからちょうだい、とメッセージを送るわけですね。リクエストを受け取ったWebサーバは、あらかじめ設定された動作に従ってリクエスト文字列を解析し、処理します。たいていのばあい、リクエスト文字列に指定されたディレクトリに配置されたファイルを返すことになるでしょう。

Terminalを起動してドキュメントルートに戻り、cgiserver.pyを実行してCGIHTTPServerを立ち上げておきます。
その状態で「http://localhost:8000/cgi-bin/test.py」にアクセスすると、さきほどと違う画面が表示されるはずです。
f:id:yife:20120125102913p:plain
これで、Webアプリ作成はうまくいきましたね。

2.4章 test.pyを見てみる

さきほど作成したWebアプリケーション、test.pyについて、詳しく見てみましょう。

#!/usr/bin/env python

print "Content-type: text/html\n"
print "<html><body>This is my first WebApp. IT'S WORKS!</body></html>"

プログラムはとても単純にできていて、実質的に2行しかありません。重要なのは、Print文を使って文字列を表示している、ということです。通常、Print文は標準入出力に対して文字列を出力しますが、今回はWebサーバを伝ってWebブラウザに表示されました。この仕組みを使えば、もっと複雑なHTMLも表示することができます。つまり、CSSや画像を使った、リッチなインターフェイスが可能だということです。
もう1つ気をつけないといけないのが、3行目にある「print "Content-type: text/html\n"」の部分です。Webアプリケーションでは、HTTPに従って、かならずこういったヘッダを返す必要があります。

2.5章 もうすこし複雑なWebアプリをつくってみる

test.pyでは、なんどアクセスしてみても同じ内容しか表示されません。そこで、アクセスされるたびに変化するWebアプリを作ってみましょう。
test.pyを改造して、アクセスされた日時を表示するプログラムを使います。日時の計算には、python標準のdatetimeモジュールが使えそうです。
以下のソースコードを、nowdate.pyという名前のテキストファイルに保存し、cgi-binフォルダの中においてください。保存するときは、テキストエンコーディングUTF-8になっていることを確認してください。

#!/usr/bin/env python

import datetime

html_body="""
<html><body>
%d/%d/%d %d:%d:%d
</body></html>"""

now=datetime.datetime.now()

print "Content-type: text/html\n"
print html_body % (now.year, now.month, now.day, now.hour, now.minute, now.second)

用意し終わったら、忘れずに実行権限を与えてやる必要があります。Terminalからnowdate.pyのあるディレクトリまで移動して、「chmod 755 nowdate.py」を実行してください。
実行権を与えたら、「http://localhost:8000/cgi-bin/nowdate.py」にアクセスしてみてください。現在の日付と時刻が表示されたら成功です。
f:id:yife:20120125104552p:plain
簡単であっけなく思われるかもしれませんが、ユーザの入力(HTTPリクエスト)に対して、毎回違う出力が行われますから、立派なWebアプリケーションの完成といっていいでしょう。

2.6章 nowdate.pyを見てみる

さきほど作成したWebアプリケーション、nowdate.pyについて、詳しくて見てみましょう。

#!/usr/bin/env python

import datetime

html_body="""
<html><body>
%d/%d/%d %d:%d:%d
</body></html>"""

now=datetime.datetime.now()

print "Content-type: text/html\n"
print html_body % (now.year, now.month, now.day, now.hour, now.minute, now.second)

3行目からわかるとおり、このアプリケーションはdatetimeモジュールを利用しています。Pythonの標準モジュールであるdatetimeモジュールを使えば、簡単に現在の日時などを調べられるだけでなく、日付や時間の四則演算も可能になる便利なモジュールです。
また、レスポンスとして返すHTMLを組み立てるために、Pythonの文字列フォーマット機能を使っています。毎回その場ですべてのHTMLを組み立てるのではなく、あらかじめテンプレートをつくっておいて、そこに現在の日付をはめこんでいるのです。

ところでこのアプリケーションはうまく動いているのですが、時計として見てみると、ブラウザをリロードしないと現在の日付がわからなくて不便ですね。
Webアプリケーションは、クライアントからのリクエストを受けて、はじめてレスポンスを返します。このことはWebアプリケーションの大きな特徴であり、よく覚えておく必要があるでしょう。

3章 Webアプリケーションに値を入力する

ここまでで、Webアプリケーションを作成して出力を行う方法については理解しました。次は、ユーザからの入力を受け取る方法について説明します。

3.1章 GETリクエスト

Webアプリケーションに値を渡すには、大別して2つの方法があります。「GETリクエスト」と「POSTリクエスト」です。ここでは、GETリクエストについて説明します。
http://example.com/homu/homu/prpr/」といったURLがリクエストとして送られた場合、WebサーバはまずURLをドメインとパスに分解します。この場合、ドメインは「example.com」、パスは「/homu/homu/prpr/」となります。
このパスをどう取り扱うかは、Webサーバにゆだねられています。パスとして指定されたところに特にWebアプリケーションが存在すれば、Webサーバはアプリケーションを実行し、その結果を返します。
また、URLでは、「http://example.com/homu/homu/prpr?wehihi」のように、クエスチョンマークで区切ってさらに値をつけくわえることができます。ためしに、さきほど作成したWebアプリケーションに対して、URLの末尾に「?hogehoge」とでも付け加えてアクセスしてみてください。?以降の文字列が何であろうと、問題なくWebアプリが表示されることが確認できると思います。このことから、URLにおいて、クエスチョンマーク以降はパスと別に取り扱われていることがわかりますね。
?マーク以降の部分を、クエリと呼びます。クエリを使うと、Webアプリケーションに値を渡すことができます。クエリは、キーと値のペアを=で結んだ形で表現されます。複数のペアを渡したいときは、&でつなぎます。このようにして指定されたデータは文字列としてWebアプリに渡されることになります。

3.2章 CGIモジュール

Pythonには、URL上でクエリとして指定された文字列を受け取るためのモジュールが、標準で含まれています。cgiモジュールです。このモジュールを使うと、クライアントから渡されたクエリを分解して、Pythonで使いやすい形にしてくれます。

それでは、実際にURLからクエリを渡し、Webアプリで受け取ってみましょう。
以下のソースコードを、querytest.pyという名前のテキストファイルに保存し、cgi-binフォルダの中においてください。保存するときは、テキストエンコーディングUTF-8になっていることを確認してください。

#!/usr/bin/env python

import datetime

html_body="""
<html><body>
foo=%s
</body></html>"""

import cgi
form=cgi.FieldStorage()

print "Content-type: text/html\n"
print html_body % form['foo'].value

終わったら、忘れずにTerminalから「chmod 755 querytest.py」を実行して、実行権限を与えてくださいね。

このプログラムは、クエリとして渡されたキー(foo)に対応する値を取得して表示するものです。日付表示のプログラムと同じように、Pythonの文字列フォーマット機能を利用しています。また、プログラムなかほどで、新しくcgiモジュールをimportしています。
直後の行で使用している、cgi.FieldStrage()という関数がポイントです。この関数を使うと、クエリとして渡されたキーと値のセットを、Pythonで扱いやすいように変換してくれます。変換されたオブジェクトはフィールドストレージと呼ばれ、フィールドストレージには「form['foo'」のように、クエリのキーを指定して辞書風にアクセスします。

スクリプトを設置したら、CGIHTTPServerを立ち上げた上で、「http://localhost:8000/cgi-bin/querytest.py?foo=bar」にアクセスしてください。
f:id:yife:20120125111344p:plain
foo=barが表示されたら、今度はURLのbarの部分をいろいろ変えて試してみてください。そのたびに、ページの内容が変化するはずです。

3.3章 エラーに対応する

ところで、querytest.pyでは、?以降にかならず「foo=○○」というクエリがくることが前提になっています。ためしに、?以降を消してアクセスしてみてください。Terminalにエラーが起こったことが表示され、Webページにはなにも表示されないはずです。
クエリにキーが存在しなかった場合でも、エラーにならずきちんと動作させるほうがいいですよね。辞書型からデータを取り出すときに、さきほどはvalue()を使っていましたが、キーがないときでもエラーにならずに取り出すには、辞書型の組み込みメソッドget()を利用します。get()メソッドには、第二引数としてキーが存在しなかった場合の返値を指定することができ、if文などを使わずにシンプルにエラーに対応させることができます。
フィールドストレージにも、辞書型のget()によく似たgetvalue()というメソッドが用意されています。これを使って、さきほどのプログラムを書き換えてみましょう。
querytest.pyの最後の行を、以下のように書き換えてください。

print html_body % form.getvalue('foo','N/A')

改良後のプログラムに、クエリにfooキーを指定しないままにアクセスしても、エラーにならずにN/Aが表示されるようになりました。
f:id:yife:20120125112201p:plain

3.4章 「13日の金曜日」を探すアプリケーション

それではここまでのまとめとして、クエリに西暦を受け取り、その年に何回「13日の金曜日」があるかを調べるアプリを作ってみましょう。シンプルなアプリですが、きちんと入力と出力を備えたWebアプリです。
実装する機能をまとめると、以下のようになります。

  • 西暦はURLにクエリとして埋め込んで渡す
  • クエリのキーは「year」とする
  • アプリは、クエリから値を受け取り、その年にある「13日の金曜日」を探す
  • 結果として、総数と日付を返す
  • クエリには数字以外の文字列が入ってくることも考慮する

それでは、実際のプログラムを書いていきましょう。いままでのものと比べるとずいぶん長くなります。
以下のソースコードを、find13f.pyという名前のテキストファイルに保存し、cgi-binフォルダの中においてください。保存するときは、テキストエンコーディングUTF-8になっていることを確認してください。

#!/usr/bin/env python
# coding: utf-8

import cgi
from datetime import datetime

html_body="""
<html><meta charset="utf-8"><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')

find13f.pyに実行権限を与えてから、ブラウザで「http://localhost:8000/cgi-bin/find13f.py?year=4000」とアクセスしてみてください。また、4000の部分をいろいろな数字に変えたり、数字以外を入力したりしてみてください。エラーにならずにきちんと動作するはずです。
f:id:yife:20120125114423p:plain

このプログラムでポイントとなるのは2箇所あります。
まず、クエリに渡されるのはかならず文字列なので、そのままでは数値として取り扱えません。そのため、組み込み関数のint()を用いて数字に変換しています。ただし、int()に数字文字列以外を与えるとエラーになってしまうので、まずは文字列型のメソッドisdigit()を使って事前にチェックしてから、int()で数値に変換しています。
2つめは、13日の金曜日を探す部分です。ここではdatetimeモジュールを活用しています。まず、1月から12月までの13日に相当するdatetimeモジュールを作成し、weekday()メソッドで曜日を調べています。月曜日が0なので、金曜日は4となります。
最後に、処理結果を文字列フォーマットを使ってHTMLに整形し、print文によって出力しているところは、これまでと同じです。

おわり

以上、駆け足でしたが、Pythonの標準モジュールだけを使ってWebアプリケーションを作成することができました。