Bottleチュートリアル
はじめに
初学者が勉強のため、pythonのwebフレームワークであるBottleのチュートリアル (https://bottlepy.org/docs/dev/tutorial.html) を適当に日本語訳してみた。
間違っているだろうし訳せていないところもあるが、せっかく書いたので公開してみる。
インストール
Bottoleは単一のpythonファイル。bottle.py
さえ持ってくればなんでもいい。
pip install bottle
でもいいしコード直でコピーしてきてもいい。
クイックスタート("Hello World")
from bottle import route, run
@route('/hello')
def hello():
return "Hello World!"
run(host='localhost', port=8080, debug=True, reloader=True)
これをapp.py
とかでbottle.py
と同じディレクトリに保存して、コンソールから実行。
run()
run()
はデフォルトの開発ウェブサーバ。開発時はdebugを真にしておく。
追記だけど、reloader=True
いれとかないと毎回サーバ再起動しないといけないからつけとこ。
route()
route()
はルータ。URLと関数等を結びつける。
今回は、
route(/hello)
に指定したURL(/hello
)と定義した関数hello()
を結びつけている。
デコレータ
route()の前についてる@が気になったのでメモ
@はデコレータと呼ばれる関数につける記号らしい。
デコレータは関数を装飾する関数(?)っぽい。
例えば@export get_name()
みたいな感じで関数の前につける。
デコレータ自身も関数で、def export(func):
という風に引数に関数をとって定義する。
これを使えば任意の関数に特定の処理を加えて装飾(デコレート)できるよってこと。
関数の処理の前後に何かしたり、返り値を操作したり・・。デコーレタはネストしてOKだよ。
本題、route()についてるのは、そのあとのdef hello():
を装飾してるわけ。
たぶん、これで装飾された処理が、今回だと/hello
っていうURLと結びつきますよ、ってことだと思う。
(本体のコード見たけどコメントでそれっぽいこと書いてた)
デフォルトアプリケーション
今回は簡単にするため@route()
をそのまま使ったけど、これはモジュールレベルのデコレータだよ。
内部的にはBottle
っていうデフォルトアプリケーションのインスタンスを自動で作って、そこに結びつけてるよ。
オブジェクト指向っぽく書きたいなら以下のようにもかけるよ。
from bottle import Bottle, run
app = Bottle()
@app.route('/hello')
def hello():
return "Hello World!"
run(app, host='localhost', port=8080)
は・・・? なんかよくわからないけど、「アプリケーション」っていう概念があって、あらゆる要素は基本的にその下に置かれるのかな。今回@route()
はモジュールレベル・・・なんだろ大元のコードの関数を直で使ってるって感じかな。
そうじゃなくて、明示的にBottle
っていうアプリケーションのrouteを使うよって書くことも可能、って感じ?
オブジェクト指向的アプローチは「デフォルトアプリケーション」の項目で詳しく教えてくれるらしい。
リクエストルーティング
route()
デコレータは、つけた関数への新たなルーティングをデフォルトアプリケーションに追加するよ。
別のルーティングも定義してみようぜ!!
(bottle
モジュールのtemplate
をインポートし忘れないで)
@route('/')
@route('/hello/<name>')
def greet(name='Stranger'):
return template('Hello {{name}}, how are you?', name=name)
このように、1つのコールバック関数に複数のルートを割り当てられるよ。
しかも、URLでワイルドカードをつかってキーワードでアクセスできるよ。
動的ルーティング
さっきみたいなワイルドカードを使ったルーティングを動的ルーティングっていう。<>
で囲むのが一番シンプルなやつだよ。
<>
の中に入れた文字列をキーワードにしてコールバック関数の引数に渡すよ。以下例。
@route('/wiki/<type>/<name>')
def show(type, name):
...
もっと詳しくワイルドカードを設定することもできるよ。
<name:filter>
とか<name:filter:config>
って感じにできる。
filterの例
-
:int
型を指定したり :float
-
:path
スラッシュ(/)を含むパスを取得したり? -
:re
<name:re:正規表現>
の形で正規表現入れたり
そのほかの設定については以下参照。
HTTPリクエストメソッド
HTTPのリクエストメソッドを指定できるよ。GET, POST, PUT, PATCH, DELETEね。
専用のデコレータ使ってもいいし、route()
メソッドの引数に指定してもいい。こんな感じ。
from bottle import get, post, request # or route
@get('/login') # or @route('/login')
def login():
return '''
<form action="/login" method="post">
Username: <input name="username" type="text" />
Password: <input name="password" type="password" />
<input value="Login" type="submit" />
</form>
'''
@post('/login') # or @route('/login', method='POST')
def do_login():
username = request.forms.get('username')
password = request.forms.get('password')
if check_login(username, password):
return "<p>Your login information was correct.</p>"
else:
return "<p>Login failed.</p>"
ちなみに例にでてきたrequest.forms
は後の章で詳しく説明するよ。
HEADとANYメソッドについて
理解不能、わからなくても使えるっぽい。
静的ファイルへのルーティング
CSSとか画像ファイルは適当に相対パス書けばいいと思ったら大間違い、ちゃんとルーティングしてあげる必要あります。
from bottle import static_file
@route('/static/<filename>')
def server_static(filename):
return static_file(filename, root='/path/to/your/static/files')
とはいえ、satic_file()
って便利なヘルパー関数があるよ。詳しくはStatic Filesを見てね。
今回の例では<filename>
のところと、/path/to/your/static/files
以下の静的ファイルを結びつけてるよ。もしサブフォルダも含めたい場合は、前に出てきた<:path>
フィルター使ってね。こんな感じに。
@route('/static/<filepath:path>')
def server_static(filepath):
return static_file(filepath, root='/path/to/your/static/files')
パスのところに相対パスを指定するのは注意してね。プロジェクトのディレクトリと作業ディレクトリは必しも同じとは限らないよ!
エラーページ
エラーが起きたときはデフォルトでエラーページ出すけど、めちゃくちゃ簡素やで。自分でエラーページ設定したいときはerror()
デコレータを使いなさい。
from bottle import error
@error(404)
def error404(error):
return 'Nothing here, sorry'
これで404エラーが出たときのページがオリジナルのやつになったよ。
使い方は普通のルーティングとほぼ一緒。渡される引数は決まってるけどね。
このエラーっていうのはまじもんのエラーだけで、後に出てくるabord()
なんかを使った場合はこのハンドラは呼び出されないからね。
決まってる値は以下参照。
コンテンツ生成
純粋なWSGIでは、アプリケーションが返す型は非常に限定的。ユニコードの文字列はすべて許可されておらず、これは実用的ではない。
Bottleはよりフレキシブルに幅広い型に対応している。ユニコードは自動的にエンコードされるのでユーザが気にすることはない。以下に、ユーザが返す可能性のあるデータ型とbottleでの扱いの概要を示す。
辞書型、false扱いの値(空、文字列、false、など?)、ユニコード文字列、バイト文字列、HTTPEror・HTTPResponseのインスタンス、ファイルオブジェクト、イテラブルとジェネレータ
デフォルトエンコーディング変更
なんか変更できるらしい?デフォルトはUTF-8
静的ファイル
直接ファイルを返せるよ、ただしstatic_file()
使うのを推奨してるよ。自動でmime-typeを推測したり、Last-odified
ヘッダをつけたり、セキュリティの観点からrootへのパスを制限してエラーを吐いたりしてくれるよ。
from bottle import static_file
@route('/images/<filename:re:.*\.png>')
def send_image(filename):
return static_file(filename, root='/path/to/image/files', mimetype='image/png')
@route('/static/<filename:path>')
def send_static(filename):
return static_file(filename, root='/path/to/static/files')
static_file()
の返り値に例外を挙げることも可能だよ。
強制ダウンロード
ダウンロードさせたかったら見て。
HTTPエラーとリダイレクト
abort()
関数はHTTPエラーページを生成するショートカットだよ。
from bottle import route, abort
@route('/restricted')
def restricted():
abort(401, "Sorry, access denied.")
違うURLにリダイレクトさせたい場合、新しいURLにLocationヘッダを設定し、303 See Otherを送信しますが、redirect()
関数がそれをやります。
from bottle import redirect
@route('/wrong/url')
def wrong():
redirect("/right/url")
その他の例外処理
HTTPErrorとHTTPResponse以外はスルー的な?設定で例外処理追加できるよ。
RESPONSEオブジェクトについて
よくわからんのでまたいずれ
ステータスコード
はてな
レスポンスヘッダ
はてな
クッキー
クッキーはブラウザに保存されている名前付きの値です。Reuest.get_cookie()
とResponse.set_cookie()
でクッキーを読み取ったり保存したりできます。
@route('/hello')
def hello_again():
if request.get_cookie("visited"):
return "Welcome back! Nice to see you again"
else:
response.set_cookie("visited", "yes")
return "Hello there! Nice to meet you"
Response.set_cookie()
関数は追加の引数を受け付けて、クッキーの寿命や振舞いを操作できます。一般的な設定はこちら。
- max_age: 最大時間(秒単位)
- expires: 期限(datetimeオブジェクトかUNIXタイムスタンプ)
- domain: 読み取り許可ドメイン
- path: パスによるクッキーの制限
- secure: HTTPS接続でクッキーの制限
- httponly: javascriptがクッキーを読めないようにする
- same_site: 外部サイトのクッキー利用を制限
max_age
もexpires
も設定されてないときは、クッキーはブラウザのセッション切れ時か、ウィンドウ閉じたときに期限切れになる。そのほかクッキー利用時に注意すべきことを記載する。
- 多くのブラウザでクッキーは4KBの制限がある
- 一部の利用者や検索エンジンはクッキーを完全に無効にしている。クッキーなしでもアプリケーションが作動するように作るべき。
- クッキーは全く暗号化されないので、利用者は保存したクッキーの値を見ることができる。悪意ある者はXSSの脆弱性を利用して、クッキーを盗むこともできる。ウィルスによってはブラウザのクッキーを読むものもある。なので、決して重要な情報をクッキーに保存してはいけない。
- クッキーは容易に偽造されるのでクッキーを信じてはいけない。
署名付きクッキー
上で述べたように、クッキーは容易に偽造される。Bottleはそれを防ぐためにクッキーに暗号化された署名をつけることができる。クッキーを読み書きする時に毎回、パスワード付きの署名のキーを提供するだけでいい(もちろんパスワードは非公開)。そうすれば、もし署名されていなかったりそれが間違っていた時にはRequest.get_cookie()
はNone
を返す。
@route('/login')
def do_login():
username = request.forms.get('username')
password = request.forms.get('password')
if check_login(username, password):
response.set_cookie("account", username, secret='some-secret-key')
return template("<p>Welcome {{name}}! You are now logged in.</p>", name=username)
else:
return "<p>Login failed.</p>"
@route('/restricted')
def restricted_area():
username = request.get_cookie("account", secret='some-secret-key')
if username:
return template("Hello {{name}}. Welcome back.", name=username)
else:
return "You are not logged in. Access denied."
ピクルスがどうとか言ってるけど理解不能。署名されたクッキーを自動的にピクルスにしたりアンピクルスにしたりするらしい(???)
注意:この署名はあくまでクッキーの偽装を防止するものであって、この操作をしたからと言って重要な情報をクッキーに保存していいわけではない。
リクエストデータ
クッキー、HTTPヘッダ、<form>
他リクエストデータは、グローバルなrequest
オブジェクトを通して利用できます。この特別なオブジェクトは、同時接続のあるマルチスレッド環境においても、常に現在のリクエスト(?)を参照します。
from bottle import request, route, template
@route('/hello')
def hello():
name = request.cookies.username or 'Guest'
return template('Hello {{name}}', name=name)
request
オブジェクトはBaseRequest
のサブクラスであり、データに対し豊富なAPIでアクセスできます。ここではもっとも一般的な利用例しか示しませんが、最初の一歩には十分です。
FORMSDICTの紹介
Bottleはフォームのデータとクッキーを保存するのに特別な辞書を使うよ。FormsDict
は普通の辞書みたいに振舞うけど、いくつかのユーザを補助する追加機能があるよ。
Attribute access: 辞書内のすべての値は属性値でアクセスできる。それらの仮想属性は、値がなかったりユニコードの復号に失敗しても、ユニコードの文字列を返す。その場合文字列は空になるが、存在はしている。
name = request.cookies.name
# is a shortcut for:
name = request.cookies.getunicode('name') # encoding='utf-8' (default)
# which basically does this:
try:
name = request.cookies.get('name', '').decode('utf-8')
except UnicodeError:
name = u''
Multiple values per key: FormsDict
はMultiDict
のサブクラスであり、1つのキーに1つ以上の値を保持できる。通常の辞書アクセス用の関数は1つの値を返すが、getall()
関数はすべての値を返す。
for choice in request.forms.getall('multiple_choice'):
do_something(choice)
WTForms supposrt: ライブラリによっては(WTFormsとか)すべてユニコードの辞書を入力する必要がある。FormsDict.decode()
がそれをやってくれるよ。すべての機能を保持しながら、デコードした自分自身のコピーを返してくれるよ。
Python2と3でなんか違いがあるっぽい・・2は辞書は全部バイト型、3は文字列はユニコードになってる。3の場合、HTTPはバイトベースのプロトコルだからサーバはどっかでデコードしないといけない。FormsDict.getunicode()
ならそれをやってくれるけど、普通のアクセス方法だとやってくれないよ。的なこと書いてるけどよくわからん。
クッキー
クッキーはブラウザ内の小さなテキスト保持領域であり、リクエスト毎にサーバに送られる。これは、複数のリクエストにまたがる状態を保持するのに便利だが(HTTPは状態を持たない)、セキュリティ関係には使用してはいけない。簡単に偽造されるからね。
すべてのクライアントから送られてくるクッキーはBaseRequest.cookies
(FormsDict)を通される。この例はシンプルなクッキーベースのビューカウンタだよ。
from bottle import route, request, response
@route('/counter')
def counter():
count = int( request.cookies.get('counter', '0') )
count += 1
response.set_cookie('counter', str(count))
return 'You visited this page %d times' % count
BaseRequest.get_cookie()
関数は別の方法でクッキーにアクセスする。署名付きクッキーのデコードをサポートする(前に書いてる)
HTTPヘッダ
クライアントから送られてきたHTTPヘッダはWSGIHeaderDict
に記録され、BaseRequest.headers
属性を通してアクセスできる。これは大文字小文字を区別しないキーを持つ標準の辞書だよ。
from bottle import route, request
@route('/is_ajax')
def is_ajax():
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return 'This is an AJAX request'
else:
return 'This is a normal request'
クエリ変数
フォーム周り
ファイルのアップロード
JSONコンテンツ
RAWのリクエストボディ
WSGI環境
テンプレート
BottleにはSimpleTemplate Engine
と呼ばれる高速でパワフルなテンプレートエンジンが付属します。@view()
デコレータかtemplate()
関数を使ってテンプレートをレンダリングできます。テンプレート名と、そこに渡すキーワード引数を指定すれば使えます。簡単な例を示しましょう。
@route('/hello')
@route('/hello/<name>')
def hello(name='World'):
return template('hello_template', name=name)
これでhello_template.tpl
というテンプレートファイルがロードされ、name
変数に値がセットされた状態でレンダリングされます。Bottleは./views/
ディレクトリか、bottle.TEMPLATE_PATH
で指定されたディレクトリをみてます。
view()
デコーダはtemplate()
を呼び出す代わりに、テンプレート変数を使って辞書を返すことができます。
@route('/hello')
@route('/hello/<name>')
@view('hello_template')
def hello(name='World'):
return dict(name=name)
文法
テンプレート構文はPython中心の極めて薄いレイヤです。その主目的はブロックの正しいインデントの保証であるため、インデントを気にすることなくテンプレートを構成できます。完全な解説は以下のリンクを参照してください。
%if name == 'World':
<h1>Hello {{name}}!</h1>
<p>This is a test.</p>
%else:
<h1>Hello {{name.title()}}!</h1>
<p>How are you?</p>
%end
キャッシング
テンプレートはコンパイル後メモリにキャッシュされます。テンプレートを変更しても、キャッシュをクリアするまで反映されません。bottle.TMPLATES.clear()
でクリアしましょう。デバックモードではキャッシュは無効です。
プラグイン
グローバルインストール
アンインストール
指定インストール
ブラックリスト
プラグインとサブアプリケーション
開発
これで基本は学べたので、自分のアプリを書いていきたいでしょう。ここでは開発の役に立つかもしれないTIPSを紹介しましょう。
デフォルトアプリケーション
BottleはBottle
というグローバルインスタンスを持ち続け、いくつかのモジュールレベルの関数やデコレータの最上位層になります。route()
デコレータなどは、デフォルトアプリケーション上のBottle.route()
を呼ぶショートカットになります。
@route('/')
def hello():
return 'Hello World'
run()
これはスモールアプリ開発に便利であり、タイピング量の節約につながりますが同時に、モジュールがインポートされるとすぐ、ルーターがグローバルなデフォルトアプリケーションにインストールされるということです(?)。このインポートの副作用を避けるために、Bottleはアプリケーションを明示的にビルドする方法を用意しています。
app = Bottle()
@app.route('/')
def hello():
return 'Hello World'
app.run()
アプリケーションオブジェクトの分離は再利用性を大きく向上させます。ほかの開発者があなたのモジュールから安全にapp
オブジェクトをインポートし、Bottle.mount()
を用いることでアプリケーションを統合できます。
bottle-0.13から、コンテキストマネージャとしてBottle
インスタンスが使えるようになりました。
app = Bottle()
with app:
# Our application object is now the default
# for all shortcut functions and decorators
assert my_app is default_app()
@route('/')
def hello():
return 'Hello World'
# Also useful to capture routes defined in other modules
import some_package.more_routes
デバッグモード
初期の開発段階では、デバッグモードは超便利です。
bottle.debug(True)
このモードでは、Bottleはエラーが出るたびに十分なデバッグ情報を提供します。また、開発の邪魔になるようないくつかの最適化を無効にしたり、起こりうる設定ミスをチェックするような項目を追加します。
デバッグモード中に変更になる項目を示します。
- エラーページにトレースバックが表示される
- テンプレートはキャッシュされない
- プラグインは直ちに適用される
プロダクションサーバではこのモードは使用しないでね。
オートリロード
開発中、変更を加える度に何度もサーバを再起動しなければいけません。オートリローダが代わりにやってくれるよ。ファイルを変更する度に、リローダはサーバのプロセスを再起動し、最新のコードに更新してくれるよ。
from bottle import run
run(reloader=True)
仕組み: メインプロセスはサーバを起動せず、メインプロセスを起動させるのに使われる要素で子プロセスを立ち上げます。すべてのモジュールレベルのコードは2度実行されますよ、気を付けて。
もっと詳しいこと書いてるけどまあ気にせんでいいやろ。
コマンドラインインターフェイス
バージョン0.10からコマンドラインツールが使えるようになりました。
$ python -m bottle
Usage: bottle.py [options] package.module:app
Options:
-h, --help show this help message and exit
--version show version number.
-b ADDRESS, --bind=ADDRESS
bind socket to ADDRESS.
-s SERVER, --server=SERVER
use SERVER as backend.
-p PLUGIN, --plugin=PLUGIN
install additional plugin/s.
-c FILE, --conf=FILE load config values from FILE.
-C NAME=VALUE, --param=NAME=VALUE
override config values.
--debug start server in debug mode.
--reload auto-reload on file changes.
ADDRESSはIPアドレスかIP:PORTのペアであり、デフォルトはlocalhost:8080だよ。その他のパラメータは見たらわかるでしょ。
なんかわからんけど例を示しとくね。
# Grab the 'app' object from the 'myapp.controller' module and
# start a paste server on port 80 on all interfaces.
python -m bottle -server paste -bind 0.0.0.0:80 myapp.controller:app
# Start a self-reloading development server and serve the global
# default application. The routes are defined in 'test.py'
python -m bottle --debug --reload test
# Install a custom debug plugin with some parameters
python -m bottle --debug --reload --plugin 'utils:DebugPlugin(exc=True)'' test
# Serve an application that is created with 'myapp.controller.make_app()'
# on demand.
python -m bottle 'myapp.controller:make_app()''
デプロイ
Bottleはデフォルトで組み込みサーバ上で動く。この非スレッティングなサーバは開発にはもってこいだけど、ロード数が増えるとパフォーマンスに限界があるよ。
パフォーマンス向上の簡単な方法は、マルチスレッド対応のサーバをインストールすること(pasteとかcherrypyみたいなね)、そしてデフォルトにサーバかわりに使うようにBottleに教えてあげること。
bottle.run(server='paste')
こういうのとか、いろいろデプロイのオプションについては別の記事を参考にしてね。
用語集
コールバック(callback)
何らかの外部アクションが発生した時に呼び出されるコード。Webフレームワークでは、各URLにコールバック関数を指定することで、URLとアプリケーションコード間のマッピングが実現されていることがよくある。
デコーレタ(decorator)
他の関数を返す関数のことで、通常関数の変換のために@decorator
の形で適用される。python documentation for function definitionを見ればより詳しく書いてるよ。
インバイロン?(environ)
なにこれ?
ハンドラ関数(handler function)
特定のイベントや状況を処理する関数のこと。Webフレームワークでは、各URLへのコールバックとしてハンドラ関数を充てることでアプリを開発していく。
ソースディレクトリ(source directory)
全部のソースファイルを含むディレクトリ(?)