[Dd]enzow(ill)? with DB and Python

DBとか資格とかPythonとかの話をつらつらと

Pythonで一番小さいWEBフレームワークbottle.py その7(REQUEST DATA)

今回はREQUEST DATAのあたりを見ていきます。HTMLで作成したフォームの内容をサーバ側でどのように受取っていくかです。

REQUEST DATA

前回まとめたCookieや、HTTPヘッダー等はrequestというオブジェクトを通じて操作しました。以下の様なHTMLの<form>についても同じようにrequestを通じて扱うことができます。

<form>
    <input type='text' name='username' />
    <input type='password' name='password' />
</form>

bootleでは<form>経由のデータやCookieはFormsDictというPythonのdictライクなオブジェクトを通じて操作できるようにしています。FormsDictは以下の特徴があります。

  • Dictのようにdata['key']data.get('key')でのアクセスができる
  • data.keyの用に属性へのアクセスようにデータへアクセスができる

つまり、以下はいずれもcookie_keyというCookieへのアクセスになります。

# case1
print(type(request.cookies['cookie_key']), request.cookies['cookie_key'])
# case2
print(type(request.cookies.get('cookie_key')), request.cookies.get('cookie_key'))
# case3
print(type(request.get_cookie('cookie_key')), request.get_cookie('cookie_key'))
# case4
print(type(request.cookies.cookie_key), request.cookies.cookie_key)

いずれもstrを戻しますが、マルチバイトのデータを格納している場合は挙動が異なります。以下が実際の結果です。

<class 'str'> ããããã
<class 'str'> ããããã
<class 'str'> ããããã
<class 'str'> あいうえお

case4のみそのまま表示されています。case1-3は以下と同じ結果です。

>>> print('あいうえお'.encode('utf-8').decode('latin1'))
ããããã

HTTPはByteベースでやりとりがされていますが、Python3からはすべての文字列はunicodeになりました。 bottleではrequest.cookies.cookie_keyという形式のアクセスではutf-8でdecodeした結果を戻しますが、それ以外の方式ではlatin1でdecodeした結果が戻っています。

任意のエンコードで取得するにはgetunicodeを使用します。

request.cookies.getunicode('cookie_key', encoding='utf-8')

これは以下と同義です。

# str -> bytes -> str
request.cookies.get_cookie('cookie_key').encode('latin1').decode('utf-8')

cookieの場合は特にsigned_cookieがありますのでget_cookieを使用せざるを得ないケースもあるので、その場合は上述のように一旦デフォルトのlatin-1encodeしてbytesに戻したあと、utf-8等でdecodeすることが必要になるかもしれません。

なお、このあたりのbottle内のコードは以下です。

# bottle.py 2209行目あたり
    def getunicode(self, name, default=None, encoding=None):
        """ Return the value as a unicode string, or the default. """
        try:
            return self._fix(self[name], encoding)
        except (UnicodeError, KeyError):
            return default
:
# 2184行目あたり

    input_encoding = 'utf8'
    recode_unicode = True

    def _fix(self, s, encoding=None):
        if isinstance(s, unicode) and self.recode_unicode:  # Python 3 WSGI
            # 未指定の場合はinput_encoding(utf-8)でdecodeして戻す
            return s.encode('latin1').decode(encoding or self.input_encoding)
        elif isinstance(s, bytes):  # Python 2 WSGI
            return s.decode(encoding or self.input_encoding)
        else:
            return s

Fromからの取り出し

それでは簡単なHTMLからデータを受け取るサンプルを確認しておきます。ファイルは以下の構成にします。

├── form.py
└── views
    └── form.html

まず以下のHTMLをviews/form.htmlとして作成します。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>form test</title>
</head>
<body>
<h3>ログインフォーム</h3>
<form method="POST" action="form_test">
    <input type="text" name='login_name' placeholder="username" /><br/>
    <input type="password" name='login_pass' placeholder="password" /><br/>
    <input type="submit" value="ログイン" />
</form>

</body>
</html>

アプリケーション用のコードとしてform.pyとして以下を作成します。

# coding:utf-8
from bottle import (
    run,
    request,
    template,
    get,
    post
)

# URLを直接指定したり、リンクで飛んだ場合はこちら
@get('/form_test')
def get_form_test():
    return template('form.html')


# form等でPOSTでアクセスした場合はこちら
@post('/form_test')
def post_form_test():
    login_name = request.forms.getunicode('login_name')
    login_password = request.forms.getunicode('login_pass')

    return 'id:{} pass:{}'.format(login_name, login_password)

if __name__ == "__main__":
    # テスト用のサーバをlocalhost:8080で起動する
    # reloader=Trueにより、ソースを書き換えると自動的に再起動される
    run(host='localhost', port=8080, reloader=True, debug=True)

get_form_testpost_form_testhttp://host:port/form_testというURLに紐付いていますが、それぞれGETメソッド、POSTメソッドに対応しています。そのため、ブラウザでURLを直接指定した場合やリンクで移動した場合はまずget_form_testが実行されます。

return template('form.html')views/form.htmlの内容をクライアントに戻す動きをします。そのため、最初に作成したform.htmlがブラウザに戻され以下のような画面が表示されます。

f:id:denzow:20171215000931p:plain
ログイン画面

それぞれの入力欄に値を指定してログインを実行すると<form method="POST" action="form_test">に指定されているように、http://host:port/form_testPOSTアクセスが行われます。そのため、処理がpost_form_testに移ります。

post_form_testではlogin_name = request.forms.getunicode('login_name')といった形でフォームに入力したデータを取り出しています。指定しているlogin_nameというキーはHTMLのformタグ内の<input type="text" name='login_name' placeholder="username" />nameと一致しています。この様に入力用のinputタグに指定したnameで実際に入力された内容が取り出せます。

あとは受け取った内容を単にクライアントにreturn 'id:{} pass:{}'.format(login_name, login_password)と指定して戻していますので、実際に入力した内容がブラウザに表示されます。

f:id:denzow:20171215000944p:plain
ログインボタン押下後の結果

まとめ

最低限ではありますがformでの入力情報の取り出しと、bottleにおけるデータの取得についてまとめました。cookieと組み合わせればログイン機構の基本的なところはこれで実現できると思います。