技術

Tornadoはリクエストハンドラにパラメータを渡す前にunquoteしてる

Tornadoを使ってたら、URLパラメータのエスケープ/アンエスケープ周りで不可解な挙動があったため、ソースを読んでみたらタイトルの通り、パラメータをリクエストハンドラに渡す前にunquoteする作りになってました(バージョン1.1 )。
ソースが短いから読みやすくてイイ。

不可解な挙動というのは、TornadoではテンプレートでURLパラメータを出力する際、url_escapeというテンプレート関数を使ってエスケープするのが普通だと思うのですが、url_escapeは内部でurllib.quote_plusを使ってるんですね。
つまり、リクエストハンドラ内では、元の文字列をquote_plusしたものをunquoteしたパラメータを受け取るわけで、パラメータに半角スペースと「+」記号が入ってるとおかしな事になる訳です。

例えば u”ai+u eo” という文字列をquote_plusすると u”ai%2Bu+eo” となります。それをunquoteすると%2Bは元に戻してくれますが、「+」記号は元に戻してくれないため u”ai+u+eo” となってしまいます。
ここまで来てしまうとリクエストハンドラ内だけではもう元の文字列を正確に再現する術がありません。
どーすんのよコレw

コレの解決方法は、
1. スペースを別の文字に置換してからquote_plus、unquoteされてきたモノをハンドラ内で復元する。
2. 1と同じような方法で、quote_plusしてさらにquoteする。unquoteされてきたモノをハンドラ内でunquote_plusする。
3. tornado/web.pyをいじって勝手にunquoteしないようにする。もしくはunquote_plusにする。
4. tornado.escape.url_escapeでquote_plusじゃなくてquoteを使うようにする。
という4パターンを思いつきました。
一番スマートそうな4をやってみました。

tornado/escape.py を直接いじるのはアレなので、tornado webサーバー起動時に必ず読み込まれるモジュール内に以下を記述しました。

import urllib
from tornado import escape

def url_escape(value, quote_plus = True):
	"""Returns a valid URL-encoded version of the given value."""
	if quote_plus:
		return urllib.quote_plus(escape.utf8(value))
	# quoteはデフォで'/'をクオートしないため第二引数に空文字を渡して全部クオートするようにする
	return urllib.quote(escape.utf8(value), '')

def url_unescape(value, unquote_plus = True):
	"""Decodes the given value from a URL."""
	if unquote_plus:
		return escape._unicode(urllib.unquote_plus(value))
	return escape._unicode(urllib.unquote(value))
escape.url_escape = url_escape
escape.url_unescape = url_unescape

関数のすり替えが簡単なので楽ですね。

これでテンプレートで url_escape(text, False) として使うと、いい感じにリクエストハンドラにパラメータが渡ってきます。
注意点は、web.pyでunquoteされる時はunicode化されないので必要ならハンドラ内でunicode化する必要があります。
またコメントで書いてますが、quoteはデフォルトで「/」をエスケープしないので第二引数を空文字にして、quote_plusのデフォルトに合わせてあります。
(quote_plus と quote は空白をどう置き換えるか以外にもどの文字を置き換えるかのデフォルト値が違います!)

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です



※画像をクリックして別の画像を表示

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください