Bottleソース読解(二)
「Bottleソースリード(一)」では、bottleがリクエストを受信する方法、リクエストを処理する方法、モジュールの変化を検出してserverを再起動する方法について説明します.ServerHandlerクラスのrun関数では、アプリケーションは2つのパラメータ、1つのenvrion、1つのstart_を受け入れます.responseの方法は,次に,我々が書いた応用関数がどのように適切なアプリケーションにカプセル化されるかを理解する.
1.上位レベル呼び出し
まず簡単な例を見てみましょう
getアクセラレータでrequestline'/hello'をhello関数にルーティングし、関数が返された結果をWSGIRequestHandlerのwfileで返します.
次にgetソースコードがどのように実現されているかを見てみましょう
ついでにwrapsとupdateを見てみましょうwrapperのソースコード、wrapsは実際にpartial関数(Cが書いた)、update_を呼び出しましたwrapperは、被装飾関数の元の属性('_module_','_name_','_doc_')を保持する.
2.Bottleクラスの応用関数装飾器
以上より,応用関数装飾器@get(’/hello’),実際にはBottleであることが分かる.get(a, *ka) .getは実際にroute、hello関数をcallback、'/hello'をpathとして呼び出し、Routeインスタンスをselfに追加する.routeは、要求を受信とselfによって再送信.matchはselfを呼び出す.router.match(environ)は対応するRouteインスタンスを返し、callback実行結果応答を返します.
3.ブレークポイント表示要求応答プロセス
4.キャッシュ応答結果
上記のコードでは、call関数を呼び出すときにcached_が使用されていることに気づきました.propertyの装飾器は、最初にリクエストを開始すると、アプリケーション関数が実行され、結果も保存されます.再度リクエストすると、_からdict__の各見出しページがあります.
ここで,発起要求から解析要求,ルーティング配布,アプリケーション関数の結果を返す大まかな流れが明らかになった.urlとcallbackのマッピング関係の確立とmatchはRouterクラスに重点を置く必要があり,callbackの結果解析と関連するmetadataはRouteクラスに引き続き注目する必要がある
1.上位レベル呼び出し
まず簡単な例を見てみましょう
from bottle import get, run
@get('/hello')
def hello():
return "hello"
run(host='localhost', port=8080)
getアクセラレータでrequestline'/hello'をhello関数にルーティングし、関数が返された結果をWSGIRequestHandlerのwfileで返します.
次にgetソースコードがどのように実現されているかを見てみましょう
route = make_default_app_wrapper('route')
# ,get Bottle.get
get = make_default_app_wrapper('get')
post = make_default_app_wrapper('post')
put = make_default_app_wrapper('put')
delete = make_default_app_wrapper('delete')
error = make_default_app_wrapper('error')
mount = make_default_app_wrapper('mount')
hook = make_default_app_wrapper('hook')
install = make_default_app_wrapper('install')
uninstall = make_default_app_wrapper('uninstall')
url = make_default_app_wrapper('get_url')
def make_default_app_wrapper(name):
''' Return a callable that relays calls to the current default app. '''
# name 'get' , Bottle.get wrapper, 。
@functools.wraps(getattr(Bottle, name))
def wrapper(*a, **ka):
# app, , Bottle.get(*a, **ka)
return getattr(app(), name)(*a, **ka)
return wrapper
app = default_app = AppStack()
app.push()
class AppStack(list):
""" A stack-like list. Calling it returns the head of the stack. """
def __call__(self):
""" Return the current default application. """
return self[-1]
def push(self, value=None):
""" Add a new :class:`Bottle` instance to the stack """
if not isinstance(value, Bottle):
value = Bottle()
self.append(value)
return value
ついでにwrapsとupdateを見てみましょうwrapperのソースコード、wrapsは実際にpartial関数(Cが書いた)、update_を呼び出しましたwrapperは、被装飾関数の元の属性('_module_','_name_','_doc_')を保持する.
def update_wrapper(wrapper,
wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
for attr in assigned:
setattr(wrapper, attr, getattr(wrapped, attr))
for attr in updated:
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
# Return the wrapper so this can be used as a decorator via partial()
return wrapper
def wraps(wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
return partial(update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)
2.Bottleクラスの応用関数装飾器
以上より,応用関数装飾器@get(’/hello’),実際にはBottleであることが分かる.get(a, *ka) .getは実際にroute、hello関数をcallback、'/hello'をpathとして呼び出し、Routeインスタンスをselfに追加する.routeは、要求を受信とselfによって再送信.matchはselfを呼び出す.router.match(environ)は対応するRouteインスタンスを返し、callback実行結果応答を返します.
def get(self, path=None, method='GET', **options):
""" Equals :meth:`route`. """
return self.route(path, method, **options)
def route(self, path=None, method='GET', callback=None, name=None,
apply=None, skip=None, **config):
if callable(path): path, callback = None, path
plugins = makelist(apply)
skiplist = makelist(skip)
def decorator(callback):
# TODO: Documentation and tests
if isinstance(callback, basestring): callback = load(callback)
for rule in makelist(path) or yieldroutes(callback):
for verb in makelist(method):
verb = verb.upper()
route = Route(self, rule, verb, callback, name=name,
plugins=plugins, skiplist=skiplist, **config)
self.add_route(route)
return callback
return decorator(callback) if callback else decorator
3.ブレークポイント表示要求応答プロセス
1. ServerHandler run , application Bottle
self.result = application(self.environ, self.start_response)
2. bottle.Bottle
def __init__(self, catchall=True, autojson=True):
self.router = Router()
# Router url,method , match
def add_route(self, route):
''' Add a route object, but do not change the :data:`Route.app`
attribute.'''
self.routes.append(route)
self.router.add(route.rule, route.method, route, name=route.name)
if DEBUG: route.prepare()
def __call__(self, environ, start_response):
''' Each instance of :class:'Bottle' is a WSGI application. '''
return self.wsgi(environ, start_response)
def wsgi(self, environ, start_response):
...
out = self._cast(self._handle(environ))
...
return out
def _handle(self, environ):
...
# , Route , router , match , route
route, args = self.router.match(environ)
...
return route.call(**args)
...
3.bottle.Route , metadata
@cached_property
def call(self):
''' The route callback with all plugins applied. This property is
created on demand and then cached to speed up subsequent requests.'''
return self._make_callback()
def _make_callback(self):
callback = self.callback
for plugin in self.all_plugins():
try:
if hasattr(plugin, 'apply'):
api = getattr(plugin, 'api', 1)
context = self if api > 1 else self._context
callback = plugin.apply(callback, context)
...
return callback
4. plugin JSONPlugin,
class JSONPlugin(object):
name = 'json'
api = 2
def __init__(self, json_dumps=json_dumps):
self.json_dumps = json_dumps
def apply(self, callback, route):
dumps = self.json_dumps
if not dumps: return callback
def wrapper(*a, **ka):
try:
rv = callback(*a, **ka)
except HTTPError:
rv = _e()
if isinstance(rv, dict):
#Attempt to serialize, raises exception on failure
json_response = dumps(rv)
#Set content type only if serialization succesful
response.content_type = 'application/json'
return json_response
elif isinstance(rv, HTTPResponse) and isinstance(rv.body, dict):
rv.body = dumps(rv.body)
rv.content_type = 'application/json'
return rv
return wrapper
4.キャッシュ応答結果
上記のコードでは、call関数を呼び出すときにcached_が使用されていることに気づきました.propertyの装飾器は、最初にリクエストを開始すると、アプリケーション関数が実行され、結果も保存されます.再度リクエストすると、_からdict__の各見出しページがあります.
class cached_property(object):
''' A property that is only computed once per instance and then replaces
itself with an ordinary attribute. Deleting the attribute resets the
property. '''
def __init__(self, func):
self.__doc__ = getattr(func, '__doc__')
self.func = func
def __get__(self, obj, cls):
if obj is None: return self
# , __dict__ , func
value = obj.__dict__[self.func.__name__] = self.func(obj)
return value
ここで,発起要求から解析要求,ルーティング配布,アプリケーション関数の結果を返す大まかな流れが明らかになった.urlとcallbackのマッピング関係の確立とmatchはRouterクラスに重点を置く必要があり,callbackの結果解析と関連するmetadataはRouteクラスに引き続き注目する必要がある