Python + Bottleで、フォームやCookieに日本語を設定したら文字化けした

Python + Bottleで、フォームやCookieに日本語を使ったら文字化けしたため、メモを残します。

目次

 

環境

 

フォームやCookieに設定した値の取得について

フォームやCookieに設定した値は

  • フォームに入力した値:request.forms
  • Cookieに設定した値:request.cookies

という、FormsDict型のオブジェクトとして保存されています。

 
例えば、

<form action="/" method="POST">
    <div>
        <label for="input">入力</label>
        <input type="text" name="input" size="60">
    </div>
    <div>
        <input type="submit">
        <a href="/delete_cookie">Cookie削除</a>
    </div>
</form>

というフォームがあった場合、

from bottle import Bottle, request

app = Bottle()

@app.post('/')
def post_form():
    result = request.forms.get('input')

として値を取得します。

 
また、Cookieの場合は、

response.set_cookie('key', 'value')

Cookieに値を設定し、

cookie_by_get = request.get_cookie('key')

で値を取得します。

 

日本語の文字化けと対応について

ただ、上記のrequest.forms.get()request.get_cookie()では、日本語などのマルチバイト文字の場合に文字化けします。

result = request.forms.get('input')
print(result)
#=> ã

 
BottleのチュートリアルのNoteに、原因の記載があります。

In Python 3 all strings are unicode, but HTTP is a byte-based wire protocol. The server has to decode the byte strings somehow before they are passed to the application. To be on the safe side, WSGI suggests ISO-8859-1 (aka latin1), a reversible single-byte codec that can be re-encoded with a different encoding later. Bottle does that for FormsDict.getunicode() and attribute access, but not for the dict-access methods

request.forms.get(key)request.forms[key]では、latin1でデコードした値となるため、文字化けしているようです。

latin1でのデコードについては、PEP3333(日本語訳)のこのあたりで触れられています。
Unicode の問題 | PEP 3333: Python Web Server Gateway Interface v1.0.1 — knzm.readthedocs.org 2012-12-31 documentation

 
そのため、utf-8でデコードした値を取得するには、

などを使います。
INTRODUCING FORMSDICT | Tutorial — Bottle 0.13-dev documentation

 
フォームの値について、

# 値を取得
form_by_get = request.forms.get('input')
form_by_dict_key = request.forms['input']
form_by_getunicode = request.forms.getunicode('input')
form_by_attr = request.forms.input
form_by_decode = request.forms.decode().get('input')
form_by_getall = request.forms.getall('input')
form_by_getall_first = request.forms.getall('input')[0]
form_by_decode_getall = request.forms.decode().getall('input')
form_by_decode_getall_first = request.forms.decode().getall('input')[0]

# テンプレートへ反映
return jinja2_template(
    'form.html',
    form_by_get=form_by_get,
    form_by_dict_key=form_by_dict_key,
    form_by_getunicode=form_by_getunicode,
    form_by_attr=form_by_attr,
    form_by_decode=form_by_decode,
    form_by_getall=form_by_getall,
    form_by_getall_first=form_by_getall_first,
    form_by_decode_getall=form_by_decode_getall,
    form_by_decode_getall_first=form_by_decode_getall_first)

としてブラウザで確認したところ、

f:id:thinkAmi:20170409170633p:plain:w170

となりました。

 
また、Cookieの場合も、POSTで

response.set_cookie('input', request.forms.get('input'))

と値を設定してから、GETで

cookie_by_get = request.get_cookie('input', '')
cookie_by_dict_key = request.cookies['input'] if request.cookies else ''
cookie_by_getunicode = request.cookies.getunicode('input', default='')
cookie_by_attr = request.cookies.input if request.cookies else ''
cookie_by_decode = request.cookies.decode().get('input', '')

と値を取得してブラウザで表示したところ、

f:id:thinkAmi:20170409170718p:plain:w120

となりました。

 

その他

以前、WebTestのサンプルで、Bottleのフォームを使った時に文字化けしていました。
Pythonで、WebTestを使って、WSGIサーバを起動せずにWSGIアプリのテストをする - メモ的な思考的な

 
当時、文字化けの原因がつかめませんでしたが、POSTされたフォームの値をget()で取得していたのが原因でした。

そのため、上記のサンプルはget()ではなく、getunicode()を使うように書き換えました。

 

ソースコード

GitHubに上げました。e.g._FormsDict_using_multi_byte_stringディレクトリ以下が今回のサンプルです。
thinkAmi-sandbox/Bottle-sample: Bottle (python web framework) sample codes