1. 前言
本文將基于flask 0.1版本(git checkout 8605cc3)來(lái)分析flask的實(shí)現(xiàn),試圖理清flask中的一些概念漓库,加深讀者對(duì)flask的理解,提高對(duì)flask的認(rèn)識(shí)。從而,在使用flask過(guò)程中睡蟋,能夠減少困惑,胸有成竹枷颊,遇bug而不驚戳杀。
在試圖理解flask的設(shè)計(jì)之前,你知道應(yīng)該知道以下幾個(gè)概念:
flask(web框架)是什么
WSGI是什么
jinjia2是什么
Werkzeug是什么
本文將首先回答這些問(wèn)題夭苗,然后再分析flask源碼信卡。
2. 知識(shí)準(zhǔn)備
2.1 WSGI
下面這張圖來(lái)自這里,通過(guò)這張圖听诸,讀者對(duì)web框架所處的位置和WSGI協(xié)議能夠有一個(gè)感性的認(rèn)識(shí)坐求。
WSGI
wikipedia上對(duì)WSGI的解釋就比較通俗易懂。為了更好的理解WSGI晌梨,我們來(lái)看一個(gè)例子:
fromeventletimportwsgiimporteventletdefhello_world(environ,start_response):start_response('200 OK',[('Content-Type','text/plain')])return['Hello, World!\r\n']wsgi.server(eventlet.listen(('',8090)),hello_world)
我們定義了一個(gè)hello_world函數(shù)桥嗤,這個(gè)函數(shù)接受兩個(gè)參數(shù)须妻。分別是environ和start_response,我們將這個(gè)hello_world傳遞給eventlet.wsgi.server以后泛领,eventlet.wsgi.server在調(diào)用hello_world時(shí)荒吏,會(huì)自動(dòng)傳入environ和start_response這兩個(gè)參數(shù),并接受hello_world的返回值渊鞋。而這绰更,就是WSGI的作用。
也就是說(shuō)锡宋,在python的世界里儡湾,通過(guò)WSGI約定了web服務(wù)器怎么調(diào)用web應(yīng)用程序的代碼,web應(yīng)用程序需要符合什么樣的規(guī)范执俩,只要web應(yīng)用程序和web服務(wù)器都遵守WSGI
協(xié)議徐钠,那么,web應(yīng)用程序和web服務(wù)器就可以隨意的組合役首。這也就是WSGI存在的原因尝丐。
WSGI是一種協(xié)議,這里衡奥,需要注意兩個(gè)相近的概念:
uwsgi同WSGI一樣是一種協(xié)議
而uWSGI是實(shí)現(xiàn)了uwsgi和WSGI兩種協(xié)議的web服務(wù)器
2.2 jinjia2與Werkzeug
flask依賴jinjia2和Werkzeug爹袁,為了完全理解flask,我們還需要簡(jiǎn)單介紹一下這兩個(gè)依賴矮固。
jinjia2
Jinja2是一個(gè)功能齊全的模板引擎失息。它有完整的unicode支持,一個(gè)可選的集成沙箱執(zhí)行環(huán)境档址,被廣泛使用根时。
jinjia2的一個(gè)簡(jiǎn)單示例如下:
>>>fromjinja2importTemplate>>>template=Template('Hello !')>>>template.render(name='John Doe')u'Hello John Doe!'
Werkzeug
Werkzeug是一個(gè)WSGI工具包,它可以作為web框架的底層庫(kù)辰晕。
我發(fā)現(xiàn)Werkzeug的官方文檔介紹特別好,下面這一段摘錄自這里确虱。
Werkzeug是一個(gè)WSGI工具包含友。WSGI是一個(gè)web應(yīng)用和服務(wù)器通信的協(xié)議,web應(yīng)用可以通過(guò)WSGI一起工作校辩。一個(gè)基本的”Hello World”WSGI應(yīng)用看起來(lái)是這樣的:
defapplication(environ,start_response):start_response('200 OK',[('Content-Type','text/plain')])return['Hello World!']
上面這小段代碼就是WSGI協(xié)議的約定窘问,它有一個(gè)可調(diào)用的start_response 。environ包含了所有進(jìn)來(lái)的信息宜咒。 start_response用來(lái)表明已經(jīng)收到一個(gè)響應(yīng)惠赫。
通過(guò)Werkzeug,我們可以不必直接處理請(qǐng)求或者響應(yīng)這些底層的東西故黑,它已經(jīng)為我們封裝好了這些儿咱。
請(qǐng)求數(shù)據(jù)需要environ對(duì)象庭砍,Werkzeug允許我們以一個(gè)輕松的方式訪問(wèn)數(shù)據(jù)。響應(yīng)對(duì)象是一個(gè)WSGI應(yīng)用混埠,提供了更好的方法來(lái)創(chuàng)建響應(yīng)怠缸。如下所示:
fromwerkzeug.wrappersimportResponsedefapplication(environ,start_response):response=Response('Hello World!',mimetype='text/plain')returnresponse(environ,start_response)
2.3 如何理解wsgi, Werkzeug, flask之間的關(guān)系
Flask是一個(gè)基于Python開(kāi)發(fā)并且依賴jinja2模板和Werkzeug
WSGI服務(wù)的一個(gè)微型框架,對(duì)于Werkzeug钳宪,它只是工具包揭北,其用于接收http請(qǐng)求并對(duì)請(qǐng)求進(jìn)行預(yù)處理,然后觸發(fā)Flask框架吏颖,開(kāi)發(fā)人員基于Flask框架提供的功能對(duì)請(qǐng)求進(jìn)行相應(yīng)的處理搔体,并返回給用戶,如果要返回給用戶復(fù)雜的內(nèi)容時(shí)半醉,需要借助jinja2模板來(lái)實(shí)現(xiàn)對(duì)模板的處理疚俱。將模板和數(shù)據(jù)進(jìn)行渲染,將渲染后的字符串返回給用戶瀏覽器奉呛。
2.4 Flask是什么计螺,不是什么
Flask永遠(yuǎn)不會(huì)包含數(shù)據(jù)庫(kù)層,也不會(huì)有表單庫(kù)或是這個(gè)方面的其它東西瞧壮。Flask本身只是Werkzeug和Jinja2的之間的橋梁登馒,前者實(shí)現(xiàn)一個(gè)合適的WSGI應(yīng)用,后者處理模板咆槽。當(dāng)然陈轿,F(xiàn)lask也綁定了一些通用的標(biāo)準(zhǔn)庫(kù)包,比如logging秦忿。除此之外其它所有一切都交給擴(kuò)展來(lái)實(shí)現(xiàn)麦射。
為什么呢?因?yàn)槿藗冇胁煌钠煤托枨蟮埔ィ現(xiàn)lask不可能把所有的需求都囊括在核心里潜秋。大多數(shù)web應(yīng)用會(huì)需要一個(gè)模板引擎。然而不是每個(gè)應(yīng)用都需要一個(gè)SQL數(shù)據(jù)庫(kù)的胎许。
Flask 的理念是為所有應(yīng)用建立一個(gè)良好的基礎(chǔ)峻呛,其余的一切都取決于你自己或者 擴(kuò)展。
3. Flask源碼分析
Flask的使用非常簡(jiǎn)單辜窑,官網(wǎng)的例子如下:
fromflaskimportFlaskapp=Flask(__name__)@app.route("/")defhello():return"Hello World!"if__name__=="__main__":app.run()
每當(dāng)我們需要?jiǎng)?chuàng)建一個(gè)flask應(yīng)用時(shí)钩述,我們都會(huì)創(chuàng)建一個(gè)Flask對(duì)象:
app=Flask(__name__)
下面看一下Flask對(duì)象的__init__方法,如果不考慮jinjia2相關(guān)穆碎,核心成員就下面幾個(gè):
classFlask:def__init__(self,package_name):self.package_name=package_nameself.root_path=_get_package_path(self.package_name)self.view_functions={}self.error_handlers={}self.before_request_funcs=[]self.after_request_funcs=[]self.url_map=Map()
我們把目光聚集到后面幾個(gè)成員牙勘,view_functions中保存了視圖函數(shù)(處理用戶請(qǐng)求的函數(shù),如上面的hello())所禀,error_handlers中保存了錯(cuò)誤處理函數(shù)方面,before_request_funcs和after_request_funcs保存了請(qǐng)求的預(yù)處理函數(shù)和后處理函數(shù)放钦。
self.url_map用以保存URI到視圖函數(shù)的映射,即保存app.route()這個(gè)裝飾器的信息葡幸,如下所示:
defroute(...):defdecorator(f):self.add_url_rule(rule,f.__name__,**options)self.view_functions[f.__name__]=freturnfreturndecorator
上面說(shuō)到的是初始化部分最筒,下面看一下執(zhí)行部分,當(dāng)我們執(zhí)行app.run()時(shí)蔚叨,調(diào)用堆棧如下:
app.run()run_simple(host,port,self,**options)__call__(self,environ,start_response)wsgi_app(self,environ,start_response)
wsgi_app是flask核心:
defwsgi_app(self,environ,start_response):withself.request_context(environ):rv=self.preprocess_request()ifrvisNone:rv=self.dispatch_request()response=self.make_response(rv)response=self.process_response(response)returnresponse(environ,start_response)
可以看到床蜘,wsgi_app這個(gè)函數(shù)的作用就是先調(diào)用所有的預(yù)處理函數(shù),然后分發(fā)請(qǐng)求蔑水,再調(diào)用所有后處理函數(shù)邢锯,最后返回response。
看一下dispatch_request函數(shù)的實(shí)現(xiàn)搀别,因?yàn)榈で妫@里有flask的錯(cuò)誤處理邏輯:
defdispatch_request(self):try:endpoint,values=self.match_request()returnself.view_functions[endpoint](**values)exceptHTTPException,e:handler=self.error_handlers.get(e.code)ifhandlerisNone:returnereturnhandler(e)exceptException,e:handler=self.error_handlers.get(500)ifself.debugorhandlerisNone:raisereturnhandler(e)
如果出現(xiàn)錯(cuò)誤,則根據(jù)相應(yīng)的error code歇父,調(diào)用不同的錯(cuò)誤處理函數(shù)蒂培。
上面這段簡(jiǎn)單的源碼分析,就已經(jīng)將Flask幾個(gè)核心變量和核心函數(shù)串聯(lián)起來(lái)了榜苫。其實(shí)护戳,我們這里扣出來(lái)的幾段代碼,也就是Flask的核心代碼垂睬。畢竟媳荒,F(xiàn)lask的0.1版本包含大量注釋以后,也才六百行代碼驹饺。
4. flask的魔法
如果讀者打開(kāi)flask.py文件钳枕,將看到我前面的源碼分析幾乎已經(jīng)覆蓋了所有重要的代碼。但是赏壹,細(xì)心的讀者會(huì)看到鱼炒,在Flask.py文件的末尾處,有以下幾行代碼:
# context locals_request_ctx_stack=LocalStack()current_app=LocalProxy(lambda:_request_ctx_stack.top.app)request=LocalProxy(lambda:_request_ctx_stack.top.request)session=LocalProxy(lambda:_request_ctx_stack.top.session)g=LocalProxy(lambda:_request_ctx_stack.top.g)
這是我們得以方便的使用flask開(kāi)發(fā)的魔法蝌借,也是flask源碼中的難點(diǎn)田柔。在分析之前,我們先看一下它們的作用骨望。
在flask的開(kāi)發(fā)過(guò)程中,我們可以通過(guò)如下方式訪問(wèn)url中的參數(shù):
fromflaskimportrequest@app.route('/')defhello():name=request.args.get('name',None)
看起來(lái)request像是一個(gè)全局變量欣舵,那么擎鸠,一個(gè)全局變量為什么可以在一個(gè)多線程環(huán)境中隨意使用呢,下面就隨我來(lái)一探究竟吧缘圈!
先看一下全局變量_request_ctx_stack的定義:
_request_ctx_stack=LocalStack()
正如它LocalStack()的名字所暗示的那樣劣光,_request_ctx_stack是一個(gè)棧袜蚕。顯然,一個(gè)椌钗校肯定會(huì)有push牲剃、pop和top函數(shù),如下所示:
classLocalStack(object):def__init__(self):self._local=Local()defpush(self,obj):rv=getattr(self._local,'stack',None)ifrvisNone:self._local.stack=rv=[]rv.append(obj)returnrvdefpop(self):stack=getattr(self._local,'stack',None)ifstackisNone:returnNoneeliflen(stack)==1:release_local(self._local)returnstack[-1]else:returnstack.pop()
按照我們的理解雄可,要實(shí)現(xiàn)一個(gè)棧凿傅,那么LocalStack類應(yīng)該有一個(gè)成員變量,是一個(gè)list数苫,然后通過(guò)這個(gè)list來(lái)保存棧的元素聪舒。然而,LocalStack并沒(méi)有一個(gè)類型是list的成員變量虐急,LocalStack僅有一個(gè)成員變量self._local = Local()箱残。
順藤摸瓜,我們來(lái)到了Werkzeug的源碼中止吁,到達(dá)了Local類的定義處:
classLocal(object):def__init__(self):object.__setattr__(self,'__storage__',{})object.__setattr__(self,'__ident_func__',get_ident)def__getattr__(self,name):try:returnself.__storage__[self.__ident_func__()][name]exceptKeyError:raiseAttributeError(name)def__setattr__(self,name,value):ident=self.__ident_func__()storage=self.__storage__try:storage[ident][name]=valueexceptKeyError:storage[ident]={name:value}
需要注意的是被辑,Local類有兩個(gè)成員變量,分別是__storage__和__ident_func__敬惦,其中盼理,前者是一個(gè)字典,后者是一個(gè)函數(shù)仁热。這個(gè)函數(shù)的含義是榜揖,獲取當(dāng)前線程的id(或協(xié)程的id)。
此外抗蠢,我們注意到举哟,Local類自定義了__getattr__和__setattr__這兩個(gè)方法,也就是說(shuō)迅矛,我們?cè)诓僮鱯elf.local.stack時(shí)妨猩,會(huì)調(diào)用__setattr__和__getattr__方法。
_request_ctx_stack=LocalStack()_request_ctx_stack.push(item)# 注意秽褒,這里賦值的時(shí)候壶硅,會(huì)調(diào)用__setattr__方法self._local.stack=rv=[]==>__setattr__(self,name,value)
而__setattr的定義如下:
def__setattr__(self,name,value):ident=self.__ident_func__()storage=self.__storage__try:storage[ident][name]=valueexceptKeyError:storage[ident]={name:value}
在__setattr__中,通過(guò)__ident_func__獲取到了一個(gè)key销斟,然后進(jìn)行賦值庐椒。自此,我們可以知道蚂踊,LocalStack是一個(gè)全局字典约谈,或者說(shuō)是一個(gè)名字空間。這個(gè)名字空間是所有線程共享的。當(dāng)我們?cè)L問(wèn)字典中的某個(gè)元素的時(shí)候棱诱,會(huì)通過(guò)__getattr__進(jìn)行訪問(wèn)泼橘,__getattr__先通過(guò)線程id,找當(dāng)前這個(gè)線程的數(shù)據(jù)迈勋,然后進(jìn)行訪問(wèn)炬灭。
字段的內(nèi)容如下:
{'thread_id':{'stack':[]}}{'thread_id1':{'stack':[_RequestContext()]},'thread_id2':{'stack':[_RequestContext()]}}
最后,我們來(lái)看一下其他幾個(gè)全局變量:
current_app=LocalProxy(lambda:_request_ctx_stack.top.app)request=LocalProxy(lambda:_request_ctx_stack.top.request)session=LocalProxy(lambda:_request_ctx_stack.top.session)g=LocalProxy(lambda:_request_ctx_stack.top.g)
讀者可以自行看一下LocalProxy的源碼靡菇,LocalProxy僅僅是一個(gè)代理(可以想象設(shè)計(jì)模式中的代理模式)重归。
通過(guò)LocalStack和LocalProxy這樣的Python魔法,每個(gè)線程訪問(wèn)當(dāng)前請(qǐng)求中的數(shù)據(jù)(request, session)時(shí)镰官,
都好像都在訪問(wèn)一個(gè)全局變量提前,但是,互相之間又互不影響泳唠。這就是Flask為我們提供的便利狈网,也是我們
選擇Flask的理由!
5. 總結(jié)
在這篇文章中笨腥,我們簡(jiǎn)單地介紹了WSGI, jinjia2和Werkzeug拓哺,詳細(xì)介紹了Flask在web開(kāi)發(fā)中所處的位置和發(fā)揮的作用。最后脖母,深入Flask的源碼士鸥,了解了Flask的實(shí)現(xiàn)。