詳細解讀Python的web.py框架下的application.py模塊
本文主要分析的是web.py庫的application.py這個模塊中的代碼??偟膩碚f,這個模塊主要實現(xiàn)了WSGI兼容的接口,以便應用程序能夠被WSGI應用服務器調用。WSGI是Web Server Gateway Interface的縮寫,具體細節(jié)可以查看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接口,并啟動一個內(nèi)置的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服務器,而是實現(xiàn)一個WSGI兼容的接口供WSGI服務器調用。web.py框架為我們實現(xiàn)了這樣的接口,你只需要調用application = app.wsgifunc()就可以了,這里所得到的application變量就是WSGI接口(后面分析完代碼你就會知道了)。
WSGI接口的實現(xiàn)分析
分析主要圍繞著下面兩行代碼進行:
app = web.application(urls, globals()) application = app.wsgifunc()
web.application實例化
初始化這個實例需要傳遞兩個參數(shù):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))
這個函數(shù)還調用了一個工具函數(shù),效果是這樣的:
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,而且還對這兩個函數(shù)進行了裝飾。處理器的是用在HTTP請求處理前后的,它不是真正用來處理一個HTTP請求,但是可以用來作一些額外的工作,比如官方教程里面有提到的給子應用添加session的做法,就是使用了處理器:
def session_hook(): web.ctx.session = session app.add_processor(web.loadhook(session_hook))
處理器的定義和使用都是比較復雜的,后面專門講。
wsgifunc函數(shù)
wsgifunc的執(zhí)行結果是返回一個WSGI兼容的函數(shù),并且該函數(shù)內(nèi)部實現(xiàn)了URL路由等功能。
def wsgifunc(self, *middleware):
"""Returns a WSGI-compatible function for this application."""
...
for m in middleware:
wsgi = m(wsgi)
return wsgi
除開內(nèi)部函數(shù)的定義,wsgifunc的定義就是這么簡單,如果沒有實現(xiàn)任何中間件,那么就是直接返回其內(nèi)部定義的wsgi函數(shù)。
wsgi函數(shù)
該函數(shù)實現(xiàn)了WSGI兼容接口,同時也實現(xiàn)了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
下面來仔細分析一下這個函數(shù):
self._cleanup() self.load(env)
self._cleanup()內(nèi)部調用utils.ThreadedDict.clear_all(),清除所有的thread local數(shù)據(jù),避免內(nèi)存泄露(因為web.py框架的很多數(shù)據(jù)都會保存在thread local變量中)。
self.load(env)使用env中的參數(shù)初始化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(),這個函數(shù)會對請求的URL進行路由,找到合適的類或子應用來處理該請求,也會調用添加的處理器來做一些其他工作(關于處理器的部分,后面專門講)。對于處理的返回結果,可能有三種方式:
- 返回一個可迭代對象,則進行安全迭代處理。
- 返回其他值,則創(chuàng)建一個列表對象來存放。
- 如果拋出了一個HTTPError異常(比如我們使用raise web.OK("hello, world")這種方式來返回結果時),則將異常中的數(shù)據(jù)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部分。然后根據(jù)WSGI的規(guī)范作如下兩個事情:
- 調用start_resp函數(shù)。
- 將result結果轉換成一個迭代器。
現(xiàn)在你可以看到,之前我們提到的application = app.wsgifunc()就是將wsgi函數(shù)賦值給application變量,這樣應用服務器就可以采用WSGI標準和我們的應用對接了。
處理HTTP請求
前面分析的代碼已經(jīng)說明了web.py框架如何實現(xiàn)WSGI兼容接口的,即我們已經(jīng)知道了HTTP請求到達框架以及從框架返回給應用服務器的流程。那么框架內(nèi)部是如何調用我們的應用代碼來實現(xiàn)一個請求的處理的呢?這個就需要詳細分析剛才忽略掉的處理器的添加和調用過程。
loadhook和unloadhook裝飾器
這兩個函數(shù)是真實處理器的函數(shù)的裝飾器函數(shù)(雖然他的使用不是采用裝飾器的@操作符),裝飾后得到的處理器分別對應請求處理之前(loadhook)和請求處理之后(unloadhook)。
loadhook
def loadhook(h):
def processor(handler):
h()
return handler()
return processor
這個函數(shù)返回一個函數(shù)processor,它會確保先調用你提供的處理器函數(shù)h,然后再調用后續(xù)的操作函數(shù)handler。
unloadhook
def 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
這個函數(shù)也返回一個processor,它會先調用參數(shù)傳遞進來的handler,然后再調用你提供的處理器函數(shù)。
handle_with_processors函數(shù)
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)
這個函數(shù)挺復雜的,最核心的部分采用了遞歸實現(xiàn)(我感覺不遞歸應該也能實現(xiàn)同樣的功能)。為了說明清晰,采用實例說明。
前面有提到,初始化application實例的時候,會添加兩個處理器到self.processors:
self.add_processor(loadhook(self._load)) self.add_processor(unloadhook(self._unload))
所以,現(xiàn)在的self.processors是下面這個樣子的:
self.processors = [loadhook(self._load), unloadhook(self._unload)]
# 為了方便后續(xù)說明,我們縮寫一下:
self.processors = [load_processor, unload_processor]
當框架開始執(zhí)行handle_with_processors的時候,是逐個執(zhí)行這些處理器的。我們還是來看代碼分解,首先簡化一下handle_with_processors函數(shù):
- 函數(shù)執(zhí)行的起點是位置1,調用其內(nèi)部定義函數(shù)process(processors)。
- 如果位置2判斷處理器列表不為空,則進入if內(nèi)部。
- 在位置3調用本次需要執(zhí)行的處理器函數(shù),參數(shù)為一個lambda函數(shù),然后返回。
- 如果位置2判斷處理器列表為空,則執(zhí)行self.handle(),該函數(shù)真正的調用我們的應用代碼(下面會講到)。
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
以上面的例子來說,目前有兩個處理器:
self.processors = [load_processor, unload_processor]
從位置1進入代碼后,在位置2會判斷還有處理器要執(zhí)行,會走到位置3,此時要執(zhí)行代碼是這樣的:
return load_processor(lambda: process([unload_processor]))
load_processor函數(shù)是一個經(jīng)過loadhook裝飾的函數(shù),因此其定義在執(zhí)行時是這樣的:
def load_processor(lambda: process([unload_processor])): self._load() return process([unload_processor]) # 就是參數(shù)的lambda函數(shù)
會先執(zhí)行self._load(),然后再繼續(xù)執(zhí)行process函數(shù),依舊會走到位置3,此時要執(zhí)行的代碼是這樣的:
return unload_processor(lambda: process([]))
unload_processor函數(shù)是一個經(jīng)過unloadhook裝飾的函數(shù),因此其定義在執(zhí)行時是這樣的:
def unload_processor(lambda: process([])):
try:
result = process([]) # 參數(shù)傳遞進來的lambda函數(shù)
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
現(xiàn)在會先執(zhí)行process([])函數(shù),并且走到位置4(調用self.handle()的地方),從而得到應用的處理結果,然后再調用本處理器的處理函數(shù)self._unload()。
總結一下執(zhí)行的順序:
self._load()
self.handle()
self._unload()
如果還有更多的處理器,也是按照這種方法執(zhí)行下去,對于loadhook裝飾的處理器,先添加的先執(zhí)行,對于unloadhook裝飾的處理器,后添加的先執(zhí)行。
handle函數(shù)
講了這么多,才講到真正要調用我們寫的代碼的地方。在所有的load處理器執(zhí)行完之后,就會執(zhí)行self.handle()函數(shù),其內(nèi)部會調用我們寫的應用代碼。比如返回個hello, world之類的。self.handle的定義如下:
def handle(self): fn, args = self._match(self.mapping, web.ctx.path) return self._delegate(fn, self.fvars, args)
這個函數(shù)就很好理解了,第一行調用的self._match是進行路由功能,找到對應的類或者子應用,第二行的self._delegate就是調用這個類或者傳遞請求到子應用。
_match函數(shù)
_match函數(shù)的定義如下:
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
該函數(shù)的參數(shù)中mapping就是self.mapping,是URL路由映射表;value則是web.ctx.path,是本次請求路徑。該函數(shù)遍歷self.mapping,根據(jù)映射關系中處理對象的類型來處理:
- 位置1,處理對象是一個application實例,也就是一個子應用,則返回一個匿名函數(shù),該匿名函數(shù)會調用self._delegate_sub_application進行處理。
- 位置2,如果處理對象是一個字符串,則調用utils.re_subm進行處理,這里會把value(也就是web.ctx.path)中的和pat匹配的部分替換成what(也就是我們指定的一個URL模式的處理對象字符串),然后返回替換后的結果以及匹配的項(是一個re.MatchObject實例)。
- 位置3,如果是其他情況,比如直接指定一個類對象作為處理對象。
如果result非空,則返回處理對象和一個參數(shù)列表(這個參數(shù)列表就是傳遞給我們實現(xiàn)的GET等函數(shù)的參數(shù))。
_delegate函數(shù)
從_match函數(shù)返回的結果會作為參數(shù)傳遞給_delegate函數(shù):
fn, args = self._match(self.mapping, web.ctx.path) return self._delegate(fn, self.fvars, args)
其中:
- fn:是要處理當前請求的對象,一般是一個類名。
- args:是要傳遞給請求處理對象的參數(shù)。
- self.fvars:是實例化application時的全局名稱空間,會用于查找處理對象。
_delegate函數(shù)的實現(xiàn)如下:
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()
這個函數(shù)主要是根據(jù)參數(shù)f的類型來做出不同的處理:
- f為空,則返回302 Not Found.
- f是一個application實例,則調用子應用的handle_with_processors()進行處理。
- f是一個類對象,則調用內(nèi)部函數(shù)handle_class。
- f是一個字符串,則進行重定向處理,或者獲取要處理請求的類名后,調用handle_class進行處理(我們寫的代碼一般是在這個分支下被調用的)。
- f是一個可調用對象,直接調用。
- 其他情況返回302 Not Found.
- python?web.py啟動https端口的方式
- python3.x中安裝web.py步驟方法
- python web.py開發(fā)httpserver解決跨域問題實例解析
- 淺析Python的web.py框架中url的設定方法
- Linux系統(tǒng)上Nginx+Python的web.py與Django框架環(huán)境
- 使用Python的web.py框架實現(xiàn)類似Django的ORM查詢的教程
- 安裝Python的web.py框架并從hello world開始編程
- Python開發(fā)WebService系列教程之REST,web.py,eurasia,Django
- python web.py服務器與客戶端的實現(xiàn)示例
相關文章
Python使用Beets模塊實現(xiàn)自動整理音樂庫
Beets是一個功能強大的Python庫,用于處理音樂文件的元數(shù)據(jù),在本文中,我們將探討beets模塊的常見使用方法,感興趣的可以跟隨小編一起學習一下2024-03-03
Python使用Matplotlib繪制散點趨勢線的代碼詳解
Matplotlib是一個用于數(shù)據(jù)可視化的強大Python庫,其基本功能之一是創(chuàng)建帶有趨勢線的散點圖,散點圖對于可視化變量之間的關系非常有用,本文將指導您使用Matplotlib繪制散點趨勢線的過程,涵蓋線性和多項式趨勢線,需要的朋友可以參考下2025-01-01
python3 assert 斷言的使用詳解 (區(qū)別于python2)
今天小編就為大家分享一篇python3 assert 斷言的使用詳解 (區(qū)別于python2),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-11-11

