1. 程式人生 > >web.py原始碼分析: application

web.py原始碼分析: application

本文主要分析的是web.py庫的 application.py 這個模組中的程式碼。總的來說, 這個模組主要實現了WSGI相容的介面,以便應用程式能夠被WSGI應用伺服器呼叫 。WSGI是 Web Server Gateway Interface 的縮寫,具體細節可以檢視 WSGI的WIKI頁面介面的使用使用web.py自帶的HTTP Server下面這個例子來自官方文件的 Hello World ,這個程式碼一般是應用入口的程式碼:import web urls = ("/.*", "hello")app = web.application(urls, globals())class hello: def GET(self): return 'Hello, world!'if __name__ == "__main__": app.run()上面的例子描述了一個web.py應用最基本的組成元素:URL路由表一個 web.application 例項 app呼叫 app.run()其中, app.run() 的呼叫是初始化各種WCGI介面,並啟動一個內建的HTTP伺服器和這些介面對接,程式碼如下:def run(self, *middleware): return wsgi.runwsgi(self.wsgifunc(*middleware))與WSGI應用伺服器對接如果你的應用要與WSGI應用伺服器對接,比如uWSGI,gunicorn等,那麼應用入口的程式碼就要換一種寫法了:import webclass hello: def GET(self): return 'Hello, world!'urls = ("/.*", "hello")app = web.application(urls, globals())application = app.wsgifunc()在這種場景下,應用的程式碼不需要啟動HTTP伺服器,而是實現一個WSGI相容的介面供WSGI伺服器呼叫。web.py框架為我們實現了這樣的介面,你只需要呼叫 application = app.wsgifunc() 就可以了,這裡所得到的 application 變數就是WSGI介面(後面分析完程式碼你就會知道了)。WSGI介面的實現分析分析主要圍繞著下面兩行程式碼進行:app = web.application(urls, globals())application = app.wsgifunc()web.application例項化初始化這個例項需要傳遞兩個引數:URL路由元組和 globals() 的結果。另外,還可以傳遞第三個變數: autoreload ,用來指定是否需要自動重新匯入Python模組,這在除錯的時候很有用,不過我們分析主要過程的時候可以忽略。application 類的初始化程式碼如下:class application: def __init__(self, mapping=(), fvars={}, autoreload=None): if autoreload is None: autoreload = web.config.get('debug', False) self.init_mapping(mapping) self.fvars = fvars self.processors = [] self.add_processor(loadhook(self._load)) self.add_processor(unloadhook(self._unload)) if autoreload: ...其中,autoreload相關功能的程式碼略去了。其他的程式碼主要作了如下幾個事情:self.init_mapping(mapping) :初始化URL路由對映關係。self.add_processor() :添加了兩個處理器。初始化URL路由對映關係def init_mapping(self, mapping): self.mapping = list(utils.group(mapping, 2))這個函式還呼叫了一個工具函式,效果是這樣的:urls = ("/", "Index", "/hello/(.*)", "Hello", "/world", "World")如果使用者初始化時傳遞的元組是這樣的,那麼呼叫 init_mapping 之後:self.mapping = [["/", "Index"], ["/hello/(.*)", "Hello"], ["/world", "World"]]後面框架在進行URL路由時,就會遍歷這個列表。新增處理器 self.add_processor(loadhook(self._load)) self.add_processor(unloadhook(self._unload))這兩行程式碼添加了兩個處理器: self._load 和 self._unload ,而且還對這兩個函式進行了裝飾。處理器的是用在HTTP請求處理前後的,它不是真正用來處理一個HTTP請求,但是可以用來作一些額外的工作,比如官方教程裡面有提到的給子應用新增session的做法,就是使用了處理器:def session_hook(): web.ctx.session = sessionapp.add_processor(web.loadhook(session_hook))處理器的定義和使用都是比較複雜的,後面專門講。wsgifunc函式wsgifunc的執行結果是返回一個WSGI相容的函式,並且該函式內部實現了URL路由等功能。def wsgifunc(self, *middleware): """Returns a WSGI-compatible function for this application.""" ... for m in middleware: wsgi = m(wsgi) return wsgi除開內部函式的定義,wsgifunc的定義就是這麼簡單,如果沒有實現任何中介軟體,那麼就是直接返回其內部定義的 wsgi 函式。wsgi函式該函式實現了WSGI相容介面,同時也實現了URL路由等功能。def wsgi(env, start_resp): # clear threadlocal to avoid inteference of previous requests self._cleanup() self.load(env) try: # allow uppercase methods only if web.ctx.method.upper() != web.ctx.method: raise web.nomethod() result = self.handle_with_processors() if is_generator(result): result = peep(result) else: result = [result] except web.HTTPError, e: result = [e.data] result = web.safestr(iter(result)) status, headers = web.ctx.status, web.ctx.headers start_resp(status, headers) def cleanup(): self._cleanup() yield '' # force this function to be a generator return itertools.chain(result, cleanup())for m in middleware: wsgi = m(wsgi)return wsgi下面來仔細分析一下這個函式: self._cleanup() self.load(env)self._cleanup() 內部呼叫 utils.ThreadedDict.clear_all() ,清除所有的thread local資料,避免記憶體洩露(因為web.py框架的很多資料都會儲存在thread local變數中)。self.load(env) 使用 env 中的引數初始化 web.ctx 變數,這些變數涵蓋了當前請求的資訊,我們在應用中有可能會使用到,比如 web.ctx.fullpath 。 try: # allow uppercase methods only if web.ctx.method.upper() != web.ctx.method: raise web.nomethod() result = self.handle_with_processors() if is_generator(result): result = peep(result) else: result = [result] except web.HTTPError, e: result = [e.data]這一段主要是呼叫 self.handle_with_processors() ,這個函式會對請求的URL進行路由,找到合適的類或子應用來處理該請求,也會呼叫新增的處理器來做一些其他工作(關於處理器的部分,後面專門講)。對於處理的返回結果,可能有三種方式:返回一個可迭代物件,則進行安全迭代處理。返回其他值,則建立一個列表物件來存放。如果丟擲了一個HTTPError異常(比如我們使用raise web.OK("hello, world")這種方式來返回結果時),則將異常中的資料 e.data 封裝成一個列表。- result = web.safestr(iter(result)) status, headers = web.ctx.status, web.ctx.headers start_resp(status, headers) def cleanup(): self._cleanup() yield '' # force this function to be a generator return itertools.chain(result, cleanup())接下來的這段程式碼,會對前面返回的列表 result 進行字串化處理,得到HTTP Response的body部分。然後根據WSGI的規範作如下兩個事情:呼叫 start_resp 函式。將 result 結果轉換成一個迭代器。現在你可以看到,之前我們提到的 application = app.wsgifunc() 就是將 wsgi 函式賦值給 application 變數,這樣應用伺服器就可以採用WSGI標準和我們的應用對接了。處理HTTP請求前面分析的程式碼已經說明了web.py框架如何實現WSGI相容介面的,即我們已經知道了HTTP請求到達框架以及從框架返回給應用伺服器的流程。那麼框架內部是如何呼叫我們的應用程式碼來實現一個請求的處理的呢?這個就需要詳細分析剛才忽略掉的處理器的新增和呼叫過程。loadhook和unloadhook裝飾器這兩個函式是真實處理器的函式的裝飾器函式(雖然他的使用不是採用裝飾器的@操作符),裝飾後得到的處理器分別對應請求處理之前( loadhook )和請求處理之後( unloadhook )。loadhookdef loadhook(h): def processor(handler): h() return handler() return processor這個函式返回一個函式 processor ,它會確保先呼叫你提供的處理器函式 h ,然後再呼叫後續的操作函式 handler 。unloadhookdef unloadhook(h): def processor(handler): try: result = handler() is_generator = result and hasattr(result, 'next') except: # run the hook even when handler raises some exception h() raise if is_generator: return wrap(result) else: h() return result def wrap(result): def next(): try: return result.next() except: # call the hook at the and of iterator h() raise result = iter(result) while True: yield next() return processor這個函式也返回一個 processor ,它會先呼叫引數傳遞進來的 handler ,然後再呼叫你提供的處理器函式。handle_with_processors函式def handle_with_processors(self): def process(processors): try: if processors: p, processors = processors[0], processors[1:] return p(lambda: process(processors)) else: return self.handle() except web.HTTPError: raise except (KeyboardInterrupt, SystemExit): raise except: print >> web.debug, traceback.format_exc() raise self.internalerror() # processors must be applied in the resvere order. (??) return process(self.processors)這個函式挺複雜的,最核心的部分採用了遞迴實現(我感覺不遞迴應該也能實現同樣的功能)。為了說明清晰,採用例項說明。前面有提到,初始化 application 例項的時候,會新增兩個處理器到 self.processors : self.add_processor(loadhook(self._load)) self.add_processor(unloadhook(self._unload))所以,現在的 self.processors 是下面這個樣子的:self.processors = [loadhook(self._load), unloadhook(self._unload)]# 為了方便後續說明,我們縮寫一下:self.processors = [load_processor, unload_processor]當框架開始執行 handle_with_processors 的時候,是逐個執行這些處理器的。我們還是來看程式碼分解,首先簡化一下 handle_with_processors 函式:def handle_with_processors(self): def process(processors): try: if processors: # 位置2 p, processors = processors[0], processors[1:] return p(lambda: process(processors)) # 位置3 else: return self.handle() # 位置4 except web.HTTPError: raise ... # processors must be applied in the resvere order. (??) return process(self.processors) # 位置1函式執行的起點是 位置1 ,呼叫其內部定義函式 process(processors) 。如果 位置2 判斷處理器列表不為空,則進入 if 內部。在 位置3 呼叫本次需要執行的處理器函式,引數為一個lambda函式,然後返回。如果 位置2 判斷處理器列表為空,則執行 self.handle() ,該函式真正的呼叫我們的應用程式碼(下面會講到)。以上面的例子來說,目前有兩個處理器:self.processors = [load_processor, unload_processor]從 位置1 進入程式碼後,在 位置2 會判斷還有處理器要執行,會走到 位置3 ,此時要執行程式碼是這樣的:return load_processor(lambda: process([unload_processor]))load_processor 函式是一個經過 loadhook 裝飾的函式,因此其定義在執行時是這樣的:def load_processor(lambda: process([unload_processor])): self._load() return process([unload_processor]) # 就是引數的lambda函式會先執行 self._load() ,然後再繼續執行 process 函式,依舊會走到 位置3 ,此時要執行的程式碼是這樣的:return unload_processor(lambda: process([]))unload_processor 函式是一個經過 unloadhook 裝飾的函式,因此其定義在執行時是這樣的:def unload_processor(lambda: process([])): try: result = process([]) # 引數傳遞進來的lambda函式 is_generator = result and hasattr(result, 'next') except: # run the hook even when handler raises some exception self._unload() raise if is_generator: return wrap(result) else: self._unload() return result現在會先執行 process([]) 函式,並且走到 位置4 (呼叫 self.handle() 的地方),從而得到應用的處理結果,然後再呼叫本處理器的處理函式 self._unload() 。總結一下執行的順序:self._load()self.handle()self._unload()如果還有更多的處理器,也是按照這種方法執行下去, 對於 loadhook 裝飾的處理器,先新增的先執行,對於 unloadhook 裝飾的處理器,後新增的先執行 。handle函式講了這麼多,才講到真正要呼叫我們寫的程式碼的地方。在所有的load處理器執行完之後,就會執行 self.handle() 函式,其內部會呼叫我們寫的應用程式碼。比如返回個 hello, world 之類的。 self.handle 的定義如下:def handle(self): fn, args = self._match(self.mapping, web.ctx.path) return self._delegate(fn, self.fvars, args)這個函式就很好理解了,第一行呼叫的 self._match 是進行路由功能,找到對應的類或者子應用,第二行的 self._delegate 就是呼叫這個類或者傳遞請求到子應用。_match函式_match 函式的定義如下:def _match(self, mapping, value): for pat, what in mapping: if isinstance(what, application): # 位置1 if value.startswith(pat): f = lambda: self._delegate_sub_application(pat, what) return f, None else: continue elif isinstance(what, basestring): # 位置2 what, result = utils.re_subm('^' + pat + '$', what, value) else: # 位置3 result = utils.re_compile('^' + pat + '$').match(value) if result: # it's a match return what, [x for x in result.groups()] return None, None該函式的引數中 mapping 就是 self.mapping ,是URL路由對映表; value 則是 web.ctx.path ,是本次請求路徑。該函式遍歷 self.mapping ,根據對映關係中處理物件的型別來處理:位置1 ,處理物件是一個 application 例項,也就是一個子應用,則返回一個匿名函式,該匿名函式會呼叫 self._delegate_sub_application 進行處理。位置2 ,如果處理物件是一個字串,則呼叫 utils.re_subm 進行處理,這裡會把 value (也就是 web.ctx.path )中的和 pat 匹配的部分替換成 what (也就是我們指定的一個URL模式的處理物件字串),然後返回替換後的結果以及匹配的項(是一個re.MatchObject例項)。位置3 ,如果是其他情況,比如直接指定一個類物件作為處理物件。如果 result 非空,則返回處理物件和一個引數列表(這個引數列表就是傳遞給我們實現的GET等函式的引數)。_delegate函式從 _match 函式返回的結果會作為引數傳遞給 _delegate 函式:fn, args = self._match(self.mapping, web.ctx.path)return self._delegate(fn, self.fvars, args)其中:fn :是要處理當前請求的物件,一般是一個類名。args :是要傳遞給請求處理物件的引數。self.fvars :是例項化 application 時的全域性名稱空間,會用於查詢處理物件。_delegate 函式的實現如下:def _delegate(self, f, fvars, args=[]): def handle_class(cls): meth = web.ctx.method if meth == 'HEAD' and not hasattr(cls, meth): meth = 'GET' if not hasattr(cls, meth): raise web.nomethod(cls) tocall = getattr(cls(), meth) return tocall(*args) def is_class(o): return isinstance(o, (types.ClassType, type)) if f is None: raise web.notfound() elif isinstance(f, application): return f.handle_with_processors() elif is_class(f): return handle_class(f) elif isinstance(f, basestring): if f.startswith('redirect '): url = f.split(' ', 1)[1] if web.ctx.method == "GET": x = web.ctx.env.get('QUERY_STRING', '') if x: url += '?' + x raise web.redirect(url) elif '.' in f: mod, cls = f.rsplit('.', 1) mod = __import__(mod, None, None, ['']) cls = getattr(mod, cls) else: cls = fvars[f] return handle_class(cls) elif hasattr(f, '__call__'): return f() else: return web.notfound()這個函式主要是根據引數 f 的型別來做出不同的處理:f 為空,則返回 302 Not Found .f 是一個 application 例項,則呼叫子應用的 handle_with_processors() 進行處理。f 是一個類物件,則呼叫內部函式 handle_class 。f 是一個字串,則進行重定向處理,或者獲取要處理請求的類名後,呼叫 handle_class 進行處理(我們寫的程式碼一般是在這個分支下被呼叫的)。f 是一個可呼叫物件,直接呼叫。其他情況返回 302 Not Found .本文來自:Linux教程網