Python WSGI でフォームのデータを取得

TL; DR

POST

query = env['wsgi.input'].read(int(env.get('CONTENT_LENGTH', 0))).decode('utf-8')

GET

query = env.get('QUERY_STRING', '')

parse and split

# dictionary
form = urllib.parse.parse_qs(query)
# list
form = urllib.parse.parse_qsl(query)

経緯

フォームから送られたデータを扱うとき、CGI では cgi.FieldStorage() を取得していました。

import cgi

form = cgi.FieldStorage()

title = form.getfirst('title', 'Empty Title')
content = form.getfirst('content')

WSGI ではお作法が変って、クエリ文字列を一旦取得してから処理するのが定石のようです。

POST と GET ではやや取得の仕方が異なりますが、その後の処理が共通なのでまとめて記述したサンプルを載せます。

サンプルプログラム

# coding: utf-8

from urllib.parse import parse_qs

def application(env, res):
	res('200 OK', [('Content-type', 'text/html; charset=utf-8')])

	# Form
	yield '<form method="get" accept-charset="UTF-8"><p><input type="text" name="get" value=""><input type="submit" value="GET"></form>'.encode('utf-8')
	yield '<form method="post" accept-charset="UTF-8"><p><input type="text" name="post" value=""><input type="submit" value="POST"></form>'.encode('utf-8')

	# Request method
	method = env.get('REQUEST_METHOD')
	yield '<p>Method: {}</p>'.format(method).encode('utf-8')

	# Get query string
	if method == 'POST':
		wsgi_input = env['wsgi.input']
		length = int(env.get('CONTENT_LENGTH', 0))
		query = wsgi_input.read(length).decode('utf-8')
	else:
		query = env.get('QUERY_STRING', '')
	yield '<p>Query: {}</p>'.format(query).encode('utf-8')

	# Output form fields
	form = parse_qs(query)
	for k, v in form.items():
		yield '<p>{} = {}</p>'.format(k, v).encode('utf-8')
サンプルプログラム - 初回アクセス
サンプルプログラム - POST送信

簡単な解説

method = env.get('REQUEST_METHOD')

環境変数 REQUEST_METHOD で GET か POST なのかを判別します。

wsgi_input = env['wsgi.input']
length = int(env.get('CONTENT_LENGTH', 0))
query = wsgi_input.read(length).decode('utf-8')

POST での query の読み込みです。使い捨て変数だらけになるので一行でまとめても良いのですが、サンプルということでわざと分けています。

query = env.get('QUERY_STRING', '')

GET での query の読み込みです。第 2 引数は QUERY_STRING が存在しないときの戻り値です。

form = parse_qs(query)
for k, v in form.items():
	yield '<p>{} = {}</p>'.format(k, v).encode('utf-8')

urllib.parse.parse_qs() は name=value&foo=bar の形式になっている query 文字列を辞書にして返します。qs は Query String の略でしょうか。一文字違いで urllib.parse.parse_qsl() もありますが、こちらは辞書ではなくペアのリストを返します。parse_qsl() を使って上記を書き換えると次のようになります(import も修正してください)。

form = parse_qsl(query)
for k, v in form:
	yield '<p>{} = {}</p>'.format(k, v).encode('utf-8')

参考