はじめに
第1回では
しかし、
MessageBoardアプリケーション
今回作成するアプリケーションは、
アプリケーションのソースコードはこちらにZIP圧縮版があります。まずは、
サンプルソースmessage.
具体的な修正箇所は、
- listMessagesで文字をエスケープする際のUnicodeへの変換処理で、
変換エラーを無視するようにした - listMessagesで出力するHTMLにmetaタグでcharsetを指定した
- listMessagesの HTTPレスポンスヘッダのContent-type に charset=utf-8を追加した
- listMessagesで出力しているformタグにAcceptEncoding="utf-8"を追加した
上記修正に伴い、
$ python message.py
message.http://
にブラウザでアクセスすると、

フォームに名前、

今回は、
ソースの内容
前回のHello, world.アプリケーションでは1つの関数を定義していましたが、__
というメソッドが定義されています。
特殊メソッド
__
のようにメソッド名の前後にアンダースコアが2つ付いているメソッドは、
特殊メソッドを定義することで、
__
が定義されたクラスのインスタンスは、
__
と同じように定義されている__
はクラスのコンストラクタで、
>>> instance = someclass() # __call__ メソッドを持ったsomeclass クラスのインスタンスを作成
>>> instance(argument) # instance.__call__(argument) と等価な処理
処理の流れ
メッセージボードの大まかな処理の内容です。
- __
call__での処理 - Webサーバからリクエストを受け取り、
リクエストメソッドによってlistMessagesかaddMessageに処理を振り分けます。 - GETリクエストの場合
- listMessagesを呼び出して今までに書き込まれたメッセージの一覧と入力フォームを出力します。入力フォームのデータは、
同じURLにPOSTで送信され、 __
でaddMessageに振り分けられます。call__ - POSTリクエストの場合
- POSTされたデータからメッセージを生成し、
messagesリストに追加します。追加した後は、 同じURLにリダイレクトするようにレスポンスを返します。
それでは、
__call__
まずWebサーバにリクエストが届くと、__
が呼ばれます。
__
の定義は次のようになっています。
def __call__(self, environ, start_response):
''' WSGI アプリケーション '''
# リクエストメソッドを取得
method = environ['REQUEST_METHOD']
if method == 'GET':
# GET の場合
return self.listMessages(environ, start_response)
elif method == 'POST':
# POST の場合
return self.addMessage(environ, start_response)
else:
# それ以外の場合は 501 を返す
start_response('501 NotImplemented', [('Content-type', 'text/plain')])
return 'Not Implemented'
メソッドの引数にselfという引数が付いています。これは、__
の呼び出しに使われたインスタンスそのものです。Pythonでは、
__
の中では、
GETであればself.
addMessage
続いて、
addMessageの定義は以下のようになっています。
def addMessage(self, environ, start_response):
''' メッセージを追加する '''
# POST データを取得
inpt = environ['wsgi.input']
length = int(environ.get('CONTENT_LENGTH', 0))
# 取得したデータをパースして辞書オブジェクトに変換
query = dict(cgi.parse_qsl(inpt.read(length)))
# POST メッセージを保存
msg = {'name':query['name'],
'title':query['title'],
'body':query['body'],
'date':datetime.datetime.now()}
self.messages.append(msg)
# リダイレクトを行う
start_response('302 Found', [('Content-type', 'text/plain'),
('Location', util.request_uri(environ))])
return ''
POSTされたデータは、
POSTされたデータのパースにはcgiモジュールのparse_
保存するメッセージは、
最後に、
request_
listMessages
最後に、
以下がlistMessagesのソースです。
def listMessages(self, environ, start_response):
''' 一覧表示 '''
fp = StringIO.StringIO()
# ヘッダを出力
fp.write(r'''<html>
<head><title>Message Board</title>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
</head>
<body>
''')
# メッセージ数分繰り返し
for msg in reversed(self.messages):
esc = saxutils.escape
tmp = {}
# 入力を全てエスケープして出力
for key, value in msg.iteritems():
value = str(value)
tmp[key] = str(esc(unicode(value, 'utf-8', 'ignore')))
# メッセージの内容を書き出す
fp.write('''<dl>
<dt>title</dt>
<dd>%(title)s</dd>
<dt>name</dt>
<dd>%(name)s</dd>
<dt>date</dt>
<dd>%(date)s</dd>
<dt>message</dt>
<dd>%(body)s</dd>
</dl><hr />''' % tmp)
# 書込み用フォームを出力
fp.write('''<form action="%s" method="POST" AcceptEncoding="utf-8">
<dl>
<dt>name</dt>
<dd><input type="text" name="name"/></dd>
<dt>title</dt>
<dd><input type="text" name="title"/></dd>
<dt>body</dt>
<dd><textarea name="body"></textarea></dd>
</dl>
<input type="submit" name="save" value="Post" />
</form>
</body></html>''' % util.request_uri(environ))
# シーク位置を先頭にしておく
fp.seek(0)
start_response('200 OK', [('Content-type', 'text/html; charset=utf-8')])
return fp
レスポンス本文の生成には、
まず、
フォームのポスト先URLは、
listMessagesの返り値には、
for x in fp:
...
と書いた場合、
ファイルオブジェクトと同様にStringIOもiterableです。なので、
まとめ
以上が、
次回は、