本文通過(guò)分析webpy代碼學(xué)習(xí)python編程技巧涵亏、系統(tǒng)設(shè)計(jì)以及web編程的基本方法
我是邊看代碼代碼邊寫此文的镊屎,所以代碼中的有些問(wèn)題可能會(huì)放在后序的文章中詳細(xì)闡述惹挟。
webpy簡(jiǎn)單介紹
webpy是個(gè)使用python編寫的輕量級(jí)web服務(wù)器
相關(guān)資料
測(cè)試用代碼
# test_webpy.py
import web #指向源碼中的web目錄
urls = (
'/(.*)', 'hello'
)
app = web.application(urls, globals()) # 初始化
class hello:
def GET(self, name):
if not name:
name = 'World'
return 'Hello, ' + name + '!'
if __name__ == "__main__":
app.run() #啟動(dòng)
運(yùn)行結(jié)果
服務(wù)端:
瀏覽器:
application類基本功能分析
在繼續(xù)之前請(qǐng)了解wsgi相關(guān)概念,可以參考我的這篇文章缝驳。
1. 初始化步驟
即test_webpy.py第9行
app = web.application(urls, globals())
1. 參數(shù)urls用于存放用戶請(qǐng)求與處理邏輯的關(guān)系(此處是hello類)
對(duì)于是否是iter的判斷在py3helpers.py中有更完善的寫法连锯。
2. web.config
- 框架對(duì)web.config進(jìn)行了初始化。我在application初始化以前將其內(nèi)容print了出來(lái)用狱,并將其初始化的地方分別列了出來(lái)运怖。其中_is_dev_mode函數(shù)實(shí)現(xiàn)中有說(shuō)到一個(gè)sys不存在argv成員的問(wèn)題,有興趣的同學(xué)可以看下注釋中的連接夏伊。
- 其中debug配置項(xiàng)的具體功能有個(gè)描述摇展,也體現(xiàn)了框架的一些功能:“ when True, enables reloading, disabled template caching and sets internal error to debug error”。我對(duì)reloading和template caching的實(shí)現(xiàn)還是比較感興趣的溺忧,后續(xù)會(huì)詳細(xì)研究下實(shí)現(xiàn)方法咏连。
- Storage擴(kuò)展了dict,增加了通過(guò)"."訪問(wèn)鍵值的功能鲁森。
3. handle_with_processors()與self.processor[]
其作用為在響應(yīng)每個(gè)http請(qǐng)求前后做一些額外處理祟滴。此處先不關(guān)注processor具體是什么,只關(guān)注這些processor是如何注入的歌溉,你可以把這種方式作為一種設(shè)計(jì)模式來(lái)理解垄懂。(作者認(rèn)為所有設(shè)計(jì)模式的目的只有一個(gè):解耦)。
我在每個(gè)processor以及http請(qǐng)求處理函數(shù)中增加了一條日志,額外增加了一個(gè)unload test的processor埠偿,后面會(huì)提供代碼透罢。先來(lái)看看執(zhí)行結(jié)果,注意執(zhí)行順序:
再來(lái)看看代碼:
圖注:
- 初始化
- self.processors[]用于存放所有的processor冠蒋,其初始化與5個(gè)processor的添加都在__init__()中進(jìn)行羽圃,其中我新加的那個(gè)已經(jīng)在圖中指出。
- loadhook()和unloadhook()為兩個(gè)輔助函數(shù)抖剿,分別用于在處理Http前后增加processor朽寞。
- 注意代碼執(zhí)行順序與之前的日志輸出順序做比較,尤其是新加的那個(gè)unload test的執(zhí)行順序斩郎。
為其中一個(gè)load processor的內(nèi)容脑融,參考用
loadhook()和unloadhook()
這兩個(gè)輔助函數(shù)用于注冊(cè)processor用。
- 其中processor函數(shù)的參數(shù)handler請(qǐng)理解為“下一步處理”或“下一步要做的事情”
- processor函數(shù)中的h即為實(shí)際的processor缩宜,而此處的processor函數(shù)則是對(duì)實(shí)際函數(shù)的包裝
- unloadhook與loadhook主要的不同是只有unloadhook需要負(fù)責(zé)“傳遞”結(jié)果肘迎,因?yàn)橛闷渥?cè)的processor是在http請(qǐng)求完成處理以后執(zhí)行的
- 如果http請(qǐng)求處理返回的結(jié)果形式為generator,則需要對(duì)其進(jìn)行wrap(注意其中的wrap函數(shù))锻煌,以確保所有結(jié)果都遍歷完畢以后才執(zhí)行processor
4.實(shí)際執(zhí)行processor的地方
- 方塊里面是調(diào)用processor的代碼妓布。之前說(shuō)過(guò),每個(gè)processor函數(shù)接收的handler參數(shù)表示“下一步處理”宋梧,所以這里不能傳processors(表示“剩余任務(wù)”)或者process(processors)(表示“剩余任務(wù)執(zhí)行結(jié)果”)
- 倒數(shù)第二行的那個(gè)注釋所指的是unloadhook添加與執(zhí)行的順序是相反的匣沼,具體請(qǐng)看我添加的那個(gè)unload test的代碼執(zhí)行與日志輸出順序與self._unload的進(jìn)行比較
此設(shè)計(jì)模式總結(jié):
- 適用性
- 要向外部提供接口,此處為向下層提供的handle_with_processors回調(diào)接口
- 任務(wù)有明確的第三方處理者捂龄,此處已封裝為self.handle函數(shù)
- 要向兩者之間增加額外的處理邏輯释涛,即把自己作為第三方處理者的代理,對(duì)接口參數(shù)或處理結(jié)果透?jìng)骰蛘咴偌庸ぃū纠又胁⑽丛偌庸?shù)或結(jié)果倦沧,但是很容易實(shí)現(xiàn))
- 代理邏輯可以無(wú)限拓展
2.偽代碼
基礎(chǔ)代碼:
def 外部接口(請(qǐng)求參數(shù)):
return 第三方處理函數(shù)(請(qǐng)求參數(shù))
應(yīng)用該設(shè)計(jì)模式以后:
def 外部接口(請(qǐng)求參數(shù)):
預(yù)處理邏輯一(請(qǐng)求參數(shù))
預(yù)處理邏輯二(請(qǐng)求參數(shù))
...
結(jié)果 = 第三方處理函數(shù)(請(qǐng)求參數(shù))
結(jié)果 = 再處理邏輯一(請(qǐng)求參數(shù)唇撬,上一步結(jié)果)
結(jié)果 = 再處理邏輯二(請(qǐng)求參數(shù),上一步結(jié)果)
...
return 結(jié)果
3. mapping和fvars
此為__init()__的第二展融、第三個(gè)參數(shù)局荚,其與webpy框架的使用方法有很大的關(guān)系。
先來(lái)修改下測(cè)試代碼:
import web
class hello:
def __init__(self):
self.str = 'I am %s\n' % self.__class__.__name__
def GET(self, *args):
self.str += 'args len is %d(%s)' % (len(args), args)
return self.str
class hello2(hello):
pass
urls = (
'/hello', r'hello',
'/hello_cls', hello,
'/hello_arg/(\w*)', r'hello',
'/hello_arg/(\w*)/(\w*)', r'hello',
'/\w*', r'hello2',
'/1/(\w*)', r'\1',
'/2/(\w*)/(\w*)', r'\1',
'/3/(\w*)/(\w*)/(\w*)', r'\1\2',
'/4/hello_redi', r'redirect /2/hello/redi',
)
app = web.application(urls, globals())
if __name__ == "__main__":
app.run()
定義兩個(gè)類hello和hello2(繼承自hello)愈污,__init__的邏輯用來(lái)區(qū)分我是hello還是hello2耀态,GET函數(shù)用來(lái)打印傳進(jìn)來(lái)的參數(shù)。
在urls中定義了更多的條目用來(lái)進(jìn)行測(cè)試暂雹,其中條目是兩兩成對(duì)的首装,前者表示url請(qǐng)求,后者表示對(duì)應(yīng)的處理方式杭跪。webpy會(huì)按順序查找urls中的條目仙逻,如果找到則停止查找并進(jìn)行處理驰吓。
接下來(lái)我們對(duì)每一對(duì)條目進(jìn)行解釋:
-
'/hello', r'hello',
精確匹配,無(wú)任何參數(shù)傳遞
'/hello_cls', hello,
精確匹配系奉,可以直接填寫處理的類檬贰,即hello-
'/hello_arg/(\w*)', r'hello',
/hello_arg/用來(lái)對(duì)應(yīng)到hello類,而括號(hào)中的內(nèi)容將作為參數(shù)傳遞給處理邏輯
- '/hello_arg/(\w)/(\w)', r'hello',
多個(gè)參數(shù)的情況缺亮,注意querystring不會(huì)作為參數(shù)傳遞
-
'/\w*', r'hello2',
其他情況翁涤,由hello2處理
-
'/1/(\w*)', r'\1',
根據(jù)訪問(wèn)請(qǐng)求指定處理的類名,并且會(huì)作為參數(shù)傳遞
-
'/2/(\w)/(\w)', r'\1',
帶額外參數(shù)的情況 -
'/3/(\w)/(\w)/(\w*)', r'\1\2',
類名由多個(gè)參數(shù)組裝而來(lái) -
'/4/hello_redi', r'redirect /2/hello/redi',
重定向萌踱,相當(dāng)于訪問(wèn)了http://localhost/2/hello/redi葵礼,訪問(wèn)后url會(huì)有變化
接下來(lái)我們看看webpy代碼的實(shí)現(xiàn):
圖注:
- 在初始化時(shí)傳給application的第二個(gè)參數(shù)主要用于符號(hào)的查找。具體的使用看箭頭的指向并鸵,fvars儲(chǔ)存的就是globals()鸳粉,從中可以找到hello類等符號(hào)
- mapping存放的就是對(duì)照表,也就是urls中的內(nèi)容园担。在初始化的時(shí)候?qū)⑵溥M(jìn)行了兩兩分組
- 在之前講到processor的時(shí)候届谈,我們有提過(guò)handle(),它負(fù)責(zé)處理http請(qǐng)求弯汰。其中self._match()用于查找mapping疼约,獲得具體處理的對(duì)象(fn),以及URI相關(guān)信息(args)蝙泼;self._delegate()則負(fù)責(zé)執(zhí)行處理邏輯
- re_subm的實(shí)現(xiàn)可以看一下其__doc__中的舉例
- cls = fvars[f]這行代碼在實(shí)際運(yùn)行過(guò)程中可能會(huì)有問(wèn)題,當(dāng)cls不存在時(shí)后端會(huì)直接拋出異常在瀏覽器中看到500的錯(cuò)誤
4. reload mod功能
__init__()最后一個(gè)參數(shù)表示是否開啟reload的功能劝枣。reload功能指無(wú)需重啟后端服務(wù)就可以修改http處理邏輯并使之生效汤踏。包括對(duì)照表urls和對(duì)應(yīng)的處理邏輯(比如hello類)
圖注:
- 通過(guò)注冊(cè)兩個(gè)processor,在每次響應(yīng)http請(qǐng)求前進(jìn)行mod是否更新的檢查(Reloader)以及mapping與fvars的更新(reload_mapping)舔腾。注意, main模塊是不能reload的溪胶,所以在__init__中將其作為一個(gè)非main模塊重新import。相關(guān)的代碼有一些問(wèn)題稳诚,我做了一些調(diào)整哗脖,見圖
- Reloader()檢查所有mod的時(shí)間戳是否有變化,并reload有變化的mod扳还。但是已經(jīng)被引用的mod除非重新賦值引用其的變量才避,否則無(wú)法更新。個(gè)人認(rèn)為從性能考慮氨距,只用檢查application對(duì)應(yīng)的mod桑逝,其他的可以忽略。如果使用python3執(zhí)行代碼俏让,需要將765的代碼改成:
except Exception as e: