本文发自 http://www.binss.me/blog/Source-code-reading-notes-for-bottle-framework/,转载请注明出处。

?bottle是最轻量级的Python Web框架,代码含注释一共只有4000行,存放在单个文件(bottle.py)中。它实现了Web应用常用的功能,如路由、模版渲染、cookie设置等,无需依赖于其他库,可独立运行。

组成

Router

路由的有序集合,URL映射器。

对WSGI的请求(environ)进行解析,返回第一个匹配的target。主要函数有:

  • def add(self, rule, method, target, name=None)

    向Router中添加/替换rule,rule维护了匹配信息(method, url)到target的映射

  • def match(self, environ)

    对WSGI的请求(environ)进行解析,返回第一个匹配的target

    先匹配匹配静态rule(如 /contact ),再匹配动态rule(带有通配符,如 /wiki/ )

    如果没有匹配项,判断是否是由于HTTP Method不匹配造成的(可以匹配URL),如是,返回405 Method Not Allowed,如否,返回404 Not Found。

Route

保存一条route信息,对应于Router里的一条rule。

Request

对WSGI environ的封装。以属性的方式提供了许多常用函数,如get_header、get_cookie、files、headers等。

一般属性使用@property来定义。部分属性使用了DictProperty来定义(一般为只读属性)。使用attr和key两级索引。

以ThreadLocal方式存储,每一个线程都有独立的副本。

Response

封装了HTTP Response,包括body、status code、header、cookie等信息。

Template

对模版进行渲染,将模版中的参数替换为用户设置的值。

实现了SimpleTemplate类,利用StplParser来进行模版渲染,也支持使用第三方渲染器如mako、Cheetah、Jinja2

通过调用template函数,传入参数,即可得到渲染后的内容。主要函数如下:

  • def search(cls, name, lookup=None)

    在指定目录下查找模版文件,返回模版文件的完整路径。

    由基类BaseTemplate实现。

  • def prepare(self, **options)

    进行准备工作,如引入第三方渲染器模块,设置编码类型、转义函数,初始化缓存等。

    子类必须实现。

  • def render(self, args, *kwargs)

    通过传入的参数渲染模版,返回渲染后的字符串。

    对于SimpleTemplate来说,将用户传入的参数和辅助函数加入到字典中,并通过eval执行编译后的模版代码。在此过程中,通过将SimpleTemplate.co、SimpleTemplate.code函数设置为cached_property使其只执行一次,实现模版代码的复用。

    子类必须实现。

ServerAdapter

bottle实现的是一个Web App,可以通过WSGI协议将其挂到Web Server上使用。

默认对许多Server提供了支持,列表如下:

server_names = {
    'cgi': CGIServer,
    'flup': FlupFCGIServer,
    'wsgiref': WSGIRefServer,
    'waitress': WaitressServer,
    'cherrypy': CherryPyServer,
    'paste': PasteServer,
    'fapws3': FapwsServer,
    'tornado': TornadoServer,
    'gae': AppEngineServer,
    'twisted': TwistedServer,
    'diesel': DieselServer,
    'meinheld': MeinheldServer,
    'gunicorn': GunicornServer,
    'eventlet': EventletServer,
    'gevent': GeventServer,
    'geventSocketIO': GeventSocketIOServer,
    'rocket': RocketServer,
    'bjoern': BjoernServer,
    'aiohttp': AiohttpServer,
    'auto': AutoServer,     # 从几个默认的WSGI Server中尝试进行启动,直到第一个成功的为止
}

如果列表中没有,用户也可以对支持WSGI的Web Server进行自定义。只需继承自ServerAdapter,然后实现def run(self, handler),在里面启动Web Server即可。

Bottle

主类,一个实例为一个app。

  • def route(self, path=None, method='GET', callback=None, name=None, apply=None, kip=None, **config)

    将传入的参数包装成Route对象,加入到bottle.routes列表中。同时在Router中新增rule。

  • def match(self, environ):

    通过Router进行匹配,获取对应的处理函数。

  • def wsgi(self, environ, start_response)

    标准wsgi接口。处理请求environ,调用start_response返回响应。

  • def mount(self, prefix, app, **options)

    可将其他bottle实例或WSGI应用挂载到当前bottle实例上。

    app.mount('/photo/', photo_app)

    这样当用户访问 /photo/ 时,请求就会交给photo_app来处理,实现了逻辑上的解耦。

  • def add_hook(self, name, func)

    添加钩子。允许用户在特定时机执行自定义函数。

    name参数为钩子类型,指定了钩子func运行的时机。

    before_request: 收到请求,route匹配前执行

    after_request: handler处理请求后执行

    app_reset: Bottle.reset调用时执行

  • def install(self, plugin)

    安装插件以扩展bottle的功能。常用的有:

    1. Bottle-Beaker 实现session
    2. Bottle-Flash 支持flash
    3. Bottle-Sqlite sqlite集成
    4. Bottle-Mongo MongoDB集成
    5. Bottle-Redis Redis集成

    更多请查看文档 http://www.bottlepy.org/docs/dev/plugins/index.html

语法糖

bottle将很多常用函数包装成装饰器,以语法糖的形式调用可以使代码更加简洁。

  • @view 在执行函数后,将结果作为参数调用template函数进行模版渲染。

    原来需要在返回时调用template:

    def hello(name='World'):
        return template('hello_template', name=name)

    如今配合语法糖使用view,代码更美观:

    @view('hello_template')
    def hello(name='World'):
        return {'name': name}
  • @route

    设置路由路径。

    @route('/')
    def hello():
        return 'Hello World'
  • @error

    返回特定的HTTP error。

    @error(404)
    def error404(error):
        return 'Nothing here, sorry'
  • @hook

    调用add_hook函数添加hook。

    @hook('before_request')
    def callback():
        print 'callback'

其他

麻雀虽小,五脏俱全。bottle还实现了以下贴心的功能。

  • 自动重启

    设置run的reloader参数为True可以开启自动重启。

  • 默认app

    通过make_default_app_wrapper,当前bottle app内的接口将暴露到全局命名空间。如@app.get可以写成@get。

流程

下面展示bottle处理的流程。

  1. 用户调用全局run启动Web Server,默认为wsgiref(Python内置WSGI Server)

  2. Web Server收到请求,以WSGI标准的形式调用bottle(__call__),然后wsgi函数被调用。根据WSGI标准,environ为请求内容,start_response为回调函数

  3. (bottle._handle)调用before_hook

  4. 通过router.match获取匹配的route,调用处理函数,返回处理结果

  5. 调用after_request

  6. (bottle._cast)将4得到的处理结果转换为符合WSGI标准的结果

  7. 调用start_response设置Header,然后返回6得到的结果

  8. Web app执行完毕,等待下一个请求

扩展性

bottle令人眼前一亮之处不仅在于小巧,还在于强大的可扩展性。

对于模版渲染,除了使用bottle自带的SimpleTemplate外,还能使用mako、Cheetah、Jinja2。

对于Web Server,除了python自带的wsgiref外,还能使用tornado、twisted等等。还可选用eventlet、gevent进行Monkey patch。

相比其他主流Web应用框架,bottle的功能还是比较简单,没有实现如session、数据库集成等功能。而这些都可以通过插件解决。在安装插件后,通过install添加到bottle中即可使用。

总结

短短4000行(其中还包含了注释),实现了一个小而美的Web框架,同时不失可扩展性,这就是bottle几年来倍受欢迎的原因。通过读4000行代码,来理解一个Web框架的实现方式,还是非常值得的。