摘要:本文主要分析的是庫的這個模塊中的代碼。將結果轉換成一個迭代器。函數函數的定義如下位置位置位置該函數的參數中就是,是路由映射表則是,是本次請求路徑。位置,如果是其他情況,比如直接指定一個類對象作為處理對象。
本文主要分析的是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 web class 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 = session app.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.
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/37498.html
摘要:是應用性能管理監控解決方案提供商。目錄是列出的命令腳本所在目錄。包含文件如下的函數是命令執行的入口。而對于硬件信息的檢測則由進行。文檔地址源碼仔細看下去,太復雜了。下一篇再分析一個請求到結束探針工作的完整過程吧。 Newrelic 是APM(Application Performance Management)(應用性能管理/監控)解決方案提供商。項目中,通常用它來追蹤應用的性能。最近...
摘要:最大的特點就是其支持異步,所以它有著優異的性能。的代碼結構可以在其官網了解,本文著重分析的實現。事件驅動模型的大致思路的方法用于啟動事件循環。行文比較草率,如有錯誤和不足之處,敬請指正。 0. 簡介 tornado是一個用Python語言寫成的Web服務器兼Web應用框架,由FriendFeed公司在自己的網站FriendFeed中使用,被Facebook收購以后框架以開源軟件形式開放...
摘要:模板函數到底長什么樣下面我們就可以來看看模板函數到底長什么樣了。當然,首先得創建一個模板文件。總結通過打印中間結果和分析代碼,我們已經大概知道了的模板是如何轉化成內容的。下一篇文章會闡述模板的各種語法所對應的動態函數內容。 web.py模板的實現原理 web.py的模板實現利用了Python的可執行對象的動態特性:根據模板內容和渲染函數的參數創建一個函數,該函數執行的時候會返回一個Te...
摘要:上一篇文章源碼分析模板說明了的模板的大致工作原理。本文重點講述模板支持的語法是如何轉換生成函數的。模板的名稱統一是。模板代碼斷行模板內容函數內容從結果來看,模板中的斷行只是為了不再結果中插入一個多余的換行符而已。 上一篇文章web.py源碼分析: 模板(1)說明了web.py的模板的大致工作原理。本文重點講述web.py模板支持的語法是如何轉換生成__template__函數的。 we...
摘要:前兩篇文章主要說明了的模板系統將模板文件處理后得到的結果函數。生成函數的代碼這個是模板生成過程中最長最復雜的一段,會應用到的分析功能以及動態編譯功能。參數都是一個,表示還未解析的模板內容。 前兩篇文章主要說明了web.py的模板系統將模板文件處理后得到的結果:__template__()函數。本文主要講述模板文件是如何變成__template__()函數的。 Render和frende...
閱讀 2307·2023-04-25 14:17
閱讀 1515·2021-11-23 10:02
閱讀 2170·2021-11-23 09:51
閱讀 873·2021-10-14 09:49
閱讀 3384·2021-10-11 10:57
閱讀 2921·2021-09-24 09:47
閱讀 3046·2021-08-24 10:00
閱讀 2298·2019-08-29 18:46