资讯专栏INFORMATION COLUMN

Bottle源码阅读(二)

zzbo / 1262人阅读

摘要:在源码阅读一中,我们了解了如何接收请求,处理请求以及如何检测模块变化重启。接下来我们看一下源码是怎么实现的经过封装后,最终获得的是具备有一些属性的装饰器当为时,将的属性传递给,使其具备相同的属性。

在《Bottle源码阅读(一)》中,我们了解了bottle如何接收请求,处理请求以及如何检测模块变化重启server。在ServerHandler类中的run函数中,application接受了两个参数,一个envrion, 和一个start_response的方法,接下来我们就来了解我们写的应用函数是如何被封装成适配的application。

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类

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/38447.html

相关文章

  • 初识Bottle

    摘要:而其他的引擎,例如能够帮我们进行验证登录自此,官网的我们已经大致有了了解后续我们可以选择运用该框架实现一些简单的应用,或者可以深入研究其源码,提升自身的编程水平 在初识Bottle(一)中,我们了解了Bottle的基本用法在Bottle源码阅读(一)和Bottle源码阅读(二)可以查看个人对bottle源码的相关阅读笔记 下面继续阅读Bottle的官方文档https://bottlep...

    stormjun 评论0 收藏0
  • Bottle框架中的装饰器类和描述符应用

    摘要:最近在阅读微型框架的源码,发现了中有一个既是装饰器类又是描述符的有趣实现。所以第三版的代码可以这样写第三版的代码没有使用装饰器,而是使用了描述符这个技巧。更大的问题来自如何将描述符与装饰器结合起来,因为是一个类而不是方法。 最近在阅读Python微型Web框架Bottle的源码,发现了Bottle中有一个既是装饰器类又是描述符的有趣实现。刚好这两个点是Python比较的难理解,又混合在...

    Panda 评论0 收藏0
  • Bottle源码阅读(一)

    摘要:在初识一中,我们了解了框架的基本用法。在本篇文章中,我们通过源码来探究一些基本原理。因此下一步就是研究我们写的应用函数是如何被封装成适配的 在初识bottle(一)中,我们了解了bottle框架的基本用法。在本篇文章中,我们通过源码来探究一些基本原理。 1. run的实现 所有的框架请求响应都基于一个原理http请求 --> wsgi服务器 --> wsgi接口(实际就是框架中自定义...

    whidy 评论0 收藏0
  • flask 源码解析:简介

    摘要:简介官网上对它的定位是一个微开发框架。另外一个必须理解的概念是,简单来说就是一套和框架应用之间的协议。功能比较丰富,支持解析自动防止攻击继承变量过滤器流程逻辑支持代码逻辑集成等等。那么,从下一篇文章,我们就正式开始源码之旅了 文章属于作者原创,原文发布在个人博客。 flask 简介 Flask 官网上对它的定位是一个微 python web 开发框架。 Flask is a micro...

    megatron 评论0 收藏0
  • 初识 Bottle (一)

    摘要:安装是一个轻量型的不依赖于任何第三方库的框架,整个框架只有一个文件。向打声招呼吧新建一个文件在浏览器或者,,得到结果当使用装饰器绑定路由时,实际是使用了的默认应用,即是的一个实例。 1. 安装 bottle是一个轻量型的不依赖于任何第三方库的web框架,整个框架只有bottle.py一个文件。 wget http://bottlepy.org/bottle.py 2. 向bottl...

    mengbo 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<