1. 程式人生 > >bottle(python的一個小的伺服器框架)的原始碼閱讀(一)

bottle(python的一個小的伺服器框架)的原始碼閱讀(一)

bottle學習的不是很多,用bottle實現了一個連結mongodb的server。
索性bottle的原始碼也不是很多(4000行,主要的程式碼部分)。
所以我就去讀了一下原始碼:

from bottle import Bottle, run
from bottle.ext.mongo import MongoPlugin

app = Bottle()
plugin = MongoPlugin(uri='127.0.0.1', db='collection', json_mongo=True)
app.install(plugin)

@app.route('/', method='GET')
def main(mongodb): return "hello, world" # 正式啟動伺服器,在8080埠 run(app, host=SERVER_HOST , post=SERVER_PORT)

先簡化一下,分析上面幾行的過程

補丁的安裝

bottle支援各種補丁,可以自己定製一個,然後安裝進去就可以了。

app = Bottle()
plugin = MongoPlugin(uri='127.0.0.1', db='collection', json_mongo=True)
app.install(plugin)

主要是上面這幾行。
我們重點看一下install方法裡面的情況。
各種牛b的程式碼,主要是
self.plugins.append(plugin)
這一行,把補丁加入到Bottle這個類的補丁plugins集合中。
具體什麼時候執行的等下面再講。

@app.route

這是個明顯的decorator
對應Bottle類裡面的def route:方法:

def decorator(callback):
    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)

其實裡面的東西並不是很多
由於我們只是執行
@app.route(‘/’, method=’GET’)
所以,其實就是執行下面程式碼一次。

route = Route(self, rule, verb, callback, name=name,
      plugins=plugins, skiplist=skiplist, **config) 
self.add_route(route)

裡面的verb就是‘GET’, rule就是‘/’, callback就是被修飾的main函式。建立好route之後就會被 呼叫 add_route(route)

def add_route(self, route):
    self.routes.append(route)
    self.router.add(route.rule, route.method, route, name=route.name)         

self.routes.append(route)這個沒什麼好說。
self.router.add東西有點多,當然因為我們就是一個靜態的路徑(非正則的路徑),所以其實就是

 if is_static and not self.strict_order:
     self.static.setdefault(method, {})
     self.static[method][self.build(rule)] = (target, None)
    return

如果是非靜態的話,會解析然後呼叫
def _compile(self, method)加入到
self.dyna_regexes[method]這個裡面。

run!!!

各種組裝好之後就開始run了。
run的話,額,大概就是指定個server(預設為裡面的WSGIRefServer),然後把app放在上面run,然後app會被呼叫call方法
然後呼叫wsgi方法
wsgi中有這麼一行
out = self._cast(self._handle(environ))
先呼叫_handle
_handle裡面
route, args = self.router.match(environ)
return route.call(**args)
呼叫 match裡面就會呼叫
靜態為例(裡面的路徑和方法我們在@app.route方法中已經填寫過了)

if method in self.static and path in self.static[method]:
    target, getargs = self.static[method][path]
    return target, getargs(path) if getargs else {}

動態的也類似,然後就是各種給引數,
然後route.call(**args)
執行一下。

這個call會呼叫_make_callback方法,此方法預先執行各個補丁的apply方法,比如我的那個MongoPlugin補丁,就會被執行,然後把預先為這個方法傳入引數比如傳入個mongodb的連線。
main(mongodb = concrete_mongodb)
通過@cached_property的封裝,保證每個方法包裝一次。

然後就可以愉快的呼叫call了得到結果了。

看不懂?直接上圖

Created with Raphaël 2.1.0run(監聽)新增app到具體的serverlistening有請求?app.__call__函式app.wsgihandle函式app.router.matchis app.static?in app.dyna_regexas?route.callall plugins apply(cached)real_method處理錯誤資訊(逐層抓取exception,error並作處理)返回responseyesno

下面是app.static 和app.dyna_regexas的來源

Created with Raphaël 2.1.0@route@init_routeapp.add_routerouter.addroute解析路徑route static? addroute dyna? add返回被包裝的方法

plugins來源:加入到app.plugins。然後在第一次被呼叫的時候會去看一下。然後安裝上。app.install是直接新增影響所有的函式,@route裡面如果寫了,那隻會影響單獨的函式。

Created with Raphaël 2.1.0install(plugin)callable or has 'apply'app.plugins.add(plugin)結束raise error!yesno