taisablog.com

taisa's engineer blog

Python Bottleのソースを読む リクエスト・レスポンス編

Pythonの軽量WebフレームワークBottleのソースを読む リクエスト・レスポンス編  

Class Hierarchy

ここの部分 20160213010717

リクエスト受付からレスポンスまで

前回、サーバの立ち上げ時にルーティングが読み込まれるところまでを確認したので今回はリクエスト受付からレスポンスを返すまでを見てみる。
まずリクエストが来るとwsgiref/handlers.pyのrunが呼ばれ、Bottleの__call__が呼び出される wsgiref/handlers.py

self.setup_environ()
# リクエストを処理しレスポンスを取得する
self.result = application(self.environ, self.start_response)
# レスポンスを返す
self.finish_response()

bottle.py

def __call__(self, environ, start_response):
    """ Each instance of :class:'Bottle' is a WSGI application. """
    return self.wsgi(environ, start_response)


リクエストを処理する際に、LocalRequestのbindを呼び出しBaseRequestのコンストラクタにてenvironとLocalRequestをセットする。これでBaseRequestのラッパーからパラメータを取得可能になる。レスポンスも同様にLocalResponseのbindを呼び出す。

try:
    out = None
    environ['bottle.app'] = self
    request.bind(environ)
    response.bind()
    try:
        self.trigger_hook('before_request')
    except HTTPResponse:
        return  _e()
    # ここでリクエストの主処理を実行する
    out = _inner_handle()
    return out;
finally:
    if isinstance(out, HTTPResponse):
        out.apply(response)
    self.trigger_hook('after_request')


BaseRequestのコンストラク

self.environ = {} if environ is None else environ
self.environ['bottle.request'] = self


リクエスト情報は以下のように取得可能になる

def index():
    request.forms.get('test')
    return 'TOP'


LocalRequestは以下の通りマルチスレッドに対応している

#: A thread-safe instance of :class:`LocalRequest`. If accessed from within a #: request callback, this instance always refers to the *current* request #: (even on a multi-threaded server).


hookポイントで以下のようにbefore_requestとafter_requestが利用可能

@hook('before_request')
def before_request():
    request.session = 'session'
@hook('after_request')
def after_request():
    print 'test'

handleのinner_handle()にてrouteのactionを呼び出しresponseを取得する

def _inner_handle():
    # Maybe pass variables as locals for better performance?
    try:
        route, args = self.router.match(environ)
        environ['route.handle'] = route
        environ['bottle.route'] = route
        environ['route.url_args'] = args
        return route.call(**args)


リダイレクトは以下のメソッドが用意されている

def redirect(url, code=None):
    """ Aborts execution and causes a 303 or 302 redirect, depending on
        the HTTP protocol version. """
    if not code:
        code = 303 if request.get('SERVER_PROTOCOL') == "HTTP/1.1" else 302
    res = response.copy(cls=HTTPResponse)
    res.status = code
    res.body = ""
    res.set_header('Location', urljoin(request.url, url))
    raise res