摘要:上次遺留了兩個(gè)問(wèn)題先說(shuō)一下自己的看法問(wèn)題明明一個(gè)線程只能處理一個(gè)請(qǐng)求那么棧里的元素永遠(yuǎn)是在棧頂那為什么需要用棧這個(gè)結(jié)構(gòu)用普通變量不行嗎和都是線程隔離的那么為什么要分開(kāi)我認(rèn)為在的情況下是可以不需要棧這個(gè)結(jié)構(gòu)的即使是單線程下也不需要原本我以為在
上次遺留了兩個(gè)問(wèn)題,先說(shuō)一下自己的看法
問(wèn)題:
1.明明一個(gè)線程只能處理一個(gè)請(qǐng)求,那么棧里的元素永遠(yuǎn)是在棧頂,那為什么需要用棧這個(gè)結(jié)構(gòu)?用普通變量不行嗎.
2._request_ctx_stack和_app_ctx_stack都是線程隔離的,那么為什么要分開(kāi)?
我認(rèn)為在web runtime的情況下是可以不需要棧這個(gè)結(jié)構(gòu)的,即使是單線程下也不需要,原本我以為在單線程下,當(dāng)前一個(gè)請(qǐng)求阻塞后,后一個(gè)請(qǐng)求還會(huì)被推入棧中,結(jié)果并不是這樣,這也就說(shuō)明了,棧的結(jié)構(gòu)和是不是單線程沒(méi)關(guān)系,為了驗(yàn)證這點(diǎn),我寫(xiě)了個(gè)簡(jiǎn)單的接口驗(yàn)證這點(diǎn):
from flask import Flask,_request_ctx_stack app = Flask(__name__) @app.route("/") def index(): print(_request_ctx_stack._local.__storage__) time.sleep(1) return "hello
" app.run(port=3000)
def wsgi_app(self, environ, start_response):
ctx = self.request_context(environ) ctx.push() print(_request_ctx_stack._local.__storage__)
我在Flask類中的wsgi_app()方法中加了這一句print(_request_ctx_stack._local.__storage__),wsgi_app()是后端接收應(yīng)用服務(wù)器發(fā)來(lái)的包裝好的WSGI請(qǐng)求的函數(shù),后面會(huì)講到,由于一個(gè)線程只能處理一個(gè)請(qǐng)求,所以結(jié)果應(yīng)該是棧中永遠(yuǎn)只有一個(gè)請(qǐng)求對(duì)象,在路由接口中我延時(shí)了1秒,假設(shè)成阻塞,看一下結(jié)果:
* Running on http://127.0.0.1:3000/ (Press CTRL+C to quit) {139851542578944: {"stack": []}} 127.0.0.1 - - [14/Apr/2018 14:31:17] "GET / HTTP/1.1" 200 - {139851542578944: {"stack": []}} 127.0.0.1 - - [14/Apr/2018 14:31:18] "GET / HTTP/1.1" 200 - {139851542578944: {"stack": []}} 127.0.0.1 - - [14/Apr/2018 14:31:19] "GET / HTTP/1.1" 200 -
每次棧中只有一個(gè)請(qǐng)求對(duì)象,這也就說(shuō)明了棧這個(gè)結(jié)構(gòu)和web runtime下的單線程無(wú)關(guān),那么就剩下非web runtime的情況了,最常見(jiàn)的是離線測(cè)試:
from flask import Flask,_request_ctx_stack,_app_ctx_stack app = Flask(__name__) app2 = Flask(__name__) def offline_test(): with app.app_context(): print(_app_ctx_stack._local.__storage__) with app2.app_context(): print(_app_ctx_stack._local.__storage__) with app.app_context(): with app.test_request_context(): print(_request_ctx_stack._local.__storage__) with app.test_request_context(): print(_request_ctx_stack._local.__storage__)
離線測(cè)試是單線程的,通過(guò)這個(gè)例子也能得到第二的問(wèn)題的答案,為什么要將請(qǐng)求和應(yīng)用分開(kāi),一個(gè)原因是flask支持多個(gè)app共存,這需要用到中間件,另一個(gè)原因是離線測(cè)試時(shí),有可能只需要用到應(yīng)用上下文,所以需要將兩者分開(kāi),在離線測(cè)試時(shí)如果進(jìn)行了嵌套則棧結(jié)構(gòu)的特點(diǎn)就發(fā)揮了出來(lái),看一下運(yùn)行的結(jié)果:
{140402410657536: {"stack": []}} {140402410657536: {"stack": [, ]}} {140402410657536: {"stack": []}} {140402410657536: {"stack": [, ]}}
結(jié)果顯而易見(jiàn)
總結(jié)一下:棧結(jié)構(gòu)和分離請(qǐng)求和應(yīng)用是為了離線測(cè)試更加靈活
web應(yīng)用服務(wù)器 WSGI 后端之間的關(guān)系
web應(yīng)用服務(wù)器的作用是監(jiān)聽(tīng)端口,當(dāng)接收到客戶端的請(qǐng)求后將請(qǐng)求轉(zhuǎn)化成WSGI格式(environ)然后傳給后端框架
應(yīng)用服務(wù)器<----WSGI協(xié)議---->后端框架
WSGI是應(yīng)用服務(wù)器和后端框架之間的橋梁,使得服務(wù)器和后端框架分離,各司其職,程序員也能專注于自己的邏輯
在WSGI中規(guī)定了每個(gè)python web應(yīng)用都需要是一個(gè)可調(diào)用的對(duì)象,即實(shí)現(xiàn)了__call__這個(gè)特殊方法,Flask就是一個(gè)可調(diào)用對(duì)象
web應(yīng)用服務(wù)器從哪里將包裝好的請(qǐng)求發(fā)送給后端
在flask中使用了werkzeug這個(gè)工具包,在werkzeug.serving中有一個(gè)類,class WSGIRequestHandler(BaseHTTPRequestHandler, object)
這個(gè)類提供了environ字典對(duì)象,定義了start_response()和run_wsgi()方法,在run_wsgi()中有一個(gè)execute(),看一下源碼:
def execute(app): application_iter = app(environ, start_response) #從這里發(fā)送到后端 try: for data in application_iter: write(data) if not headers_sent: write(b"") finally: if hasattr(application_iter, "close"): application_iter.close() application_iter = None
第一句application_iter = app(environ, start_response)就調(diào)用了Flask.__call__(),并將environ, start_response傳入,而Flask.__call__()就return了self.wsgi_app(),
這個(gè)wsgi_app(environ, start_response)是一個(gè)標(biāo)準(zhǔn)的請(qǐng)求處理函數(shù),所以它就是整個(gè)后端處理請(qǐng)求的入口函數(shù),environ是一個(gè)包含所有HTTP請(qǐng)求信息的字典對(duì)象,start_response是一個(gè)發(fā)送HTTP響應(yīng)的函數(shù),environ是從應(yīng)用服務(wù)器傳過(guò)來(lái)的,start_response是定義好的,這些都不需要后端開(kāi)發(fā)人員關(guān)心
總結(jié)一下:
1.WSGI規(guī)定了后端處理函數(shù)的格式,即需要接受environ,start_response這兩個(gè)參數(shù),這兩個(gè)參數(shù)從應(yīng)用服務(wù)器傳給后端框架
2.python web應(yīng)用對(duì)象需要是可調(diào)用的,即實(shí)現(xiàn)了__call__方法,返回WSGI規(guī)定格式的后端處理函數(shù)來(lái)處理請(qǐng)求及返回響應(yīng)
3.應(yīng)用服務(wù)器會(huì)使用werkzeug.serving中的WSGIRequestHandler類中的相應(yīng)方法,將http請(qǐng)求轉(zhuǎn)化成WSGI格式,所以說(shuō)werkzeug是一個(gè)遵循WSGI協(xié)議的工具包,提供給應(yīng)用服務(wù)器使用
后端處理請(qǐng)求返回響應(yīng)整個(gè)流程
之前說(shuō)到,后端處理請(qǐng)求的入口函數(shù)是wsgi_app(self,environ,start_response),先看下源碼:
def wsgi_app(self, environ, start_response): ctx = self.request_context(environ) #1 ctx.push() #2 error = None try: try: response = self.full_dispatch_request() #3 except Exception as e: error = e response = self.handle_exception(e) except: error = sys.exc_info()[1] raise return response(environ, start_response) finally: if self.should_ignore_error(error): error = None ctx.auto_pop(error)
其中有三句比較關(guān)鍵,我寫(xiě)了序號(hào)
第一句:self.request_context(environ),看下request_context這個(gè)方法:
def request_context(self, environ): return RequestContext(self, environ)
簡(jiǎn)而言之,傳入environ,初始化一個(gè)請(qǐng)求上下文對(duì)象并返回
第二句:ctx.push(),看下源碼:
def push(self): top = _request_ctx_stack.top if top is not None and top.preserved: top.pop(top._preserved_exc) app_ctx = _app_ctx_stack.top if app_ctx is None or app_ctx.app != self.app: app_ctx = self.app.app_context() app_ctx.push() self._implicit_app_ctx_stack.append(app_ctx) else: self._implicit_app_ctx_stack.append(None) if hasattr(sys, "exc_clear"): sys.exc_clear() _request_ctx_stack.push(self) self.session = self.app.open_session(self.request) if self.session is None: self.session = self.app.make_null_session()
簡(jiǎn)而言之,推入應(yīng)用上下文和請(qǐng)求上下文,如果設(shè)置了secret_key則開(kāi)啟一個(gè)session,關(guān)于flask的session放到后面說(shuō)
第三句:self.full_dispatch_request(),是處理請(qǐng)求的關(guān)鍵函數(shù),看下源碼:
def full_dispatch_request(self): """Dispatches the request and on top of that performs request pre and postprocessing as well as HTTP exception catching and error handling. .. versionadded:: 0.7 """ self.try_trigger_before_first_request_functions() try: request_started.send(self) rv = self.preprocess_request() #function1 if rv is None: rv = self.dispatch_request() #function2 except Exception as e: rv = self.handle_user_exception(e) return self.finalize_request(rv) #function3
這個(gè)函數(shù)中嵌套了另外三個(gè)函數(shù),預(yù)處理函數(shù)preprocess_request(),主處理函數(shù)dispatch_request()和最終處理函數(shù)finalize_request(rv)
1.preprocess_request()是處理被before_request裝飾器裝飾的函數(shù)
2.dispatch_request()匹配請(qǐng)求的URL,并返回視圖函數(shù)的返回值rv
3.finalize_request(rv)接受視圖函數(shù)的返回值,并生成響應(yīng),這里有make_response和process_response這兩個(gè)函數(shù),make_response生成響應(yīng)對(duì)象,process_response對(duì)響應(yīng)做一些處理,比如后面要講到的session
響應(yīng)生成后,在wsgi_app中return response,最后調(diào)用ctx.auto_pop()將請(qǐng)求和應(yīng)用上下文推出棧,return的response會(huì)通過(guò)start_response發(fā)送到應(yīng)用服務(wù)器,并由其發(fā)送到客戶端,這樣一次請(qǐng)求就結(jié)束了.
最后說(shuō)說(shuō)session
flask中的session是client side session,說(shuō)白了就是session會(huì)封裝在cookie中在最終響應(yīng)時(shí)會(huì)發(fā)送給客戶端,而在服務(wù)器本地不會(huì)存儲(chǔ),所以叫作client side session,要使用session需要設(shè)置secret_key這個(gè)配置,通過(guò)app.secret_key來(lái)設(shè)置,用來(lái)驗(yàn)證簽名,等到下次客戶端發(fā)來(lái)帶有cookie的請(qǐng)求時(shí),后端就能從生成對(duì)應(yīng)的session中解析出帶有的信息,寫(xiě)個(gè)簡(jiǎn)單的應(yīng)用來(lái)看下session怎么用:
from flask import Flask,session app = flask.Flask(__name__) app.secret_key = "gjx" @app.route("/") def index(): if "name" in session: print(session["name"]) else: print("stranger") return "/
" @app.route("/") def test(name): session["name"] = name print("session set successful") return "test
" app.run(port=3000)
跑起來(lái)后,在瀏覽器輸入127.0.0.1:3000/,會(huì)打印出stranger,
然后訪問(wèn)127.0.0.1:3000/jx后,后端打印出session set successful,并且瀏覽器會(huì)收到服務(wù)器發(fā)來(lái)的cookie,
Set-Cookie:session=eyJuYW1lIjoiangifQ.DbNHuQ.MPZLWzoLdga2SPMg0plMYmKlJMc; HttpOnly; Path=/ 這是我測(cè)試時(shí)收到的,有三個(gè)字段,第一個(gè)是session的內(nèi)容,第二個(gè)是時(shí)間戳,第三個(gè)是驗(yàn)證信息
這時(shí)已經(jīng)設(shè)置好了session,并且得到了cookie,再次訪問(wèn)127.0.0.1:3000/,后端打印出了jx,就是之前設(shè)置的值
如果對(duì)session內(nèi)的值更改,則返回的cookie也會(huì)更改,那么在那保存,在那創(chuàng)建session呢?
之前在分析后端請(qǐng)求流程是提到了,在RequestContext的push方法最后:
self.session = self.app.open_session(self.request) if self.session is None: self.session = self.app.make_null_session()
如果設(shè)置了secret_key則會(huì)執(zhí)行open_session開(kāi)啟一個(gè)session,那如果更改了在哪里保存呢?
在finalize_request執(zhí)行的self.process_response中:
def process_response(self, response): ctx = _request_ctx_stack.top bp = ctx.request.blueprint funcs = ctx._after_request_functions if bp is not None and bp in self.after_request_funcs: funcs = chain(funcs, reversed(self.after_request_funcs[bp])) if None in self.after_request_funcs: funcs = chain(funcs, reversed(self.after_request_funcs[None])) for handler in funcs: response = handler(response) if not self.session_interface.is_null_session(ctx.session): self.save_session(ctx.session, response) return response
在最后判斷如果session不是null session的話會(huì)執(zhí)行self.save_session來(lái)保存更新session,在self.save_session中會(huì)調(diào)用response.set_cookie,flask中的session大概就是這樣
總結(jié)一下:
1.分析了應(yīng)用服務(wù)器封裝好的environ從哪發(fā)送給后端
2.分析了應(yīng)用服務(wù)器 WSGI 后端之間的關(guān)系以及WSGI協(xié)議對(duì)接口的標(biāo)準(zhǔn)定義,使得后端人員只需要關(guān)心自己的邏輯
3.分析了后端接收到應(yīng)用服務(wù)器發(fā)來(lái)的WSGI請(qǐng)求之后的一系列處理流程,主要函數(shù)是wsgi_app(environ,start_response)
4.最后簡(jiǎn)單分析了flask中的session機(jī)制,它是client side session的.
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/44730.html
摘要:服務(wù)器通過(guò)協(xié)議與客戶端通信,因此也被稱為服務(wù)器。本文標(biāo)題為從零開(kāi)始搭建論壇一服務(wù)器與框架本文鏈接為更多閱讀自己動(dòng)手開(kāi)發(fā)網(wǎng)絡(luò)服務(wù)器一自己動(dòng)手開(kāi)發(fā)網(wǎng)絡(luò)服務(wù)器二自己動(dòng)手開(kāi)發(fā)網(wǎng)絡(luò)服務(wù)器三服務(wù)器網(wǎng)關(guān)接口實(shí)現(xiàn)原理分析最佳實(shí)踐指南應(yīng)用淺談框架編程簡(jiǎn)介 之前用 Django 做過(guò)一個(gè)小的站點(diǎn),感覺(jué)Django太過(guò)笨重,于是就準(zhǔn)備換一個(gè)比較輕量級(jí)的 Web 框架來(lái)玩玩。Web.py 作者已經(jīng)掛掉,項(xiàng)目好...
摘要:通過(guò)回調(diào)函數(shù)將響應(yīng)狀態(tài)和響應(yīng)頭返回給,同時(shí)返回響應(yīng)正文,響應(yīng)正文是可迭代的并包含了多個(gè)字符串。返回響應(yīng)正文負(fù)責(zé)獲取請(qǐng)求,將請(qǐng)求傳遞給,由處理請(qǐng)求后返回。 我想大部分Python開(kāi)發(fā)者最先接觸到的方向是WEB方向(因?yàn)榭偸怯虚_(kāi)發(fā)者希望馬上給自己做個(gè)博客出來(lái),例如我),既然是WEB,免不了接觸到一些WEB框架,例如Django,Flask,Torando等等,在開(kāi)發(fā)過(guò)程中,看過(guò)一些文檔總會(huì)...
摘要:通過(guò)查閱了些資料,總算把它們的關(guān)系理清了。在這個(gè)過(guò)程中,服務(wù)器的作用是接收請(qǐng)求處理請(qǐng)求返回響應(yīng)服務(wù)器是一類特殊的服務(wù)器,其作用是主要是接收請(qǐng)求并返回響應(yīng)。正是為了替代而出現(xiàn)的。三結(jié)語(yǔ)最后以,,之間的對(duì)話結(jié)束本文。 剛轉(zhuǎn)行互聯(lián)網(wǎng)行業(yè),聽(tīng)到了許多名詞:Flask、Django、WSGI、 Nginx、Apache等等,一直無(wú)法搞清楚這些開(kāi)源項(xiàng)目之間的關(guān)系,直至看到這篇文章后感覺(jué)醍醐灌頂,以...
摘要:在從零開(kāi)始搭建論壇一服務(wù)器與框架中我們弄清楚了服務(wù)器應(yīng)用程序框架的概念。框架應(yīng)用生成狀態(tài)碼以及響應(yīng)報(bào)頭,然后將二者傳遞至,等待服務(wù)器保存。添加響應(yīng)頭,狀態(tài)碼返回響應(yīng)信息創(chuàng)建一個(gè)服務(wù)器實(shí)例目前支持的成熟服務(wù)器有很多,是相當(dāng)不錯(cuò)的一個(gè)。 在 從零開(kāi)始搭建論壇(一):Web服務(wù)器與Web框架 中我們弄清楚了Web 服務(wù)器、Web 應(yīng)用程序、Web框架的概念。對(duì)于 Python 來(lái)說(shuō),越來(lái)越多...
閱讀 729·2021-11-24 10:19
閱讀 1106·2021-09-13 10:23
閱讀 3428·2021-09-06 15:15
閱讀 1777·2019-08-30 14:09
閱讀 1683·2019-08-30 11:15
閱讀 1837·2019-08-29 18:44
閱讀 934·2019-08-29 16:34
閱讀 2456·2019-08-29 12:46