摘要:上次遺留了兩個問題先說一下自己的看法問題明明一個線程只能處理一個請求那么棧里的元素永遠是在棧頂那為什么需要用棧這個結構用普通變量不行嗎和都是線程隔離的那么為什么要分開我認為在的情況下是可以不需要棧這個結構的即使是單線程下也不需要原本我以為在
上次遺留了兩個問題,先說一下自己的看法
問題:
1.明明一個線程只能處理一個請求,那么棧里的元素永遠是在棧頂,那為什么需要用棧這個結構?用普通變量不行嗎.
2._request_ctx_stack和_app_ctx_stack都是線程隔離的,那么為什么要分開?
我認為在web runtime的情況下是可以不需要棧這個結構的,即使是單線程下也不需要,原本我以為在單線程下,當前一個請求阻塞后,后一個請求還會被推入棧中,結果并不是這樣,這也就說明了,棧的結構和是不是單線程沒關系,為了驗證這點,我寫了個簡單的接口驗證這點:
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()是后端接收應用服務器發來的包裝好的WSGI請求的函數,后面會講到,由于一個線程只能處理一個請求,所以結果應該是棧中永遠只有一個請求對象,在路由接口中我延時了1秒,假設成阻塞,看一下結果:
* 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 -
每次棧中只有一個請求對象,這也就說明了棧這個結構和web runtime下的單線程無關,那么就剩下非web runtime的情況了,最常見的是離線測試:
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__)
離線測試是單線程的,通過這個例子也能得到第二的問題的答案,為什么要將請求和應用分開,一個原因是flask支持多個app共存,這需要用到中間件,另一個原因是離線測試時,有可能只需要用到應用上下文,所以需要將兩者分開,在離線測試時如果進行了嵌套則棧結構的特點就發揮了出來,看一下運行的結果:
{140402410657536: {"stack": []}} {140402410657536: {"stack": [, ]}} {140402410657536: {"stack": []}} {140402410657536: {"stack": [, ]}}
結果顯而易見
總結一下:棧結構和分離請求和應用是為了離線測試更加靈活
web應用服務器 WSGI 后端之間的關系
web應用服務器的作用是監聽端口,當接收到客戶端的請求后將請求轉化成WSGI格式(environ)然后傳給后端框架
應用服務器<----WSGI協議---->后端框架
WSGI是應用服務器和后端框架之間的橋梁,使得服務器和后端框架分離,各司其職,程序員也能專注于自己的邏輯
在WSGI中規定了每個python web應用都需要是一個可調用的對象,即實現了__call__這個特殊方法,Flask就是一個可調用對象
web應用服務器從哪里將包裝好的請求發送給后端
在flask中使用了werkzeug這個工具包,在werkzeug.serving中有一個類,class WSGIRequestHandler(BaseHTTPRequestHandler, object)
這個類提供了environ字典對象,定義了start_response()和run_wsgi()方法,在run_wsgi()中有一個execute(),看一下源碼:
def execute(app): application_iter = app(environ, start_response) #從這里發送到后端 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)就調用了Flask.__call__(),并將environ, start_response傳入,而Flask.__call__()就return了self.wsgi_app(),
這個wsgi_app(environ, start_response)是一個標準的請求處理函數,所以它就是整個后端處理請求的入口函數,environ是一個包含所有HTTP請求信息的字典對象,start_response是一個發送HTTP響應的函數,environ是從應用服務器傳過來的,start_response是定義好的,這些都不需要后端開發人員關心
總結一下:
1.WSGI規定了后端處理函數的格式,即需要接受environ,start_response這兩個參數,這兩個參數從應用服務器傳給后端框架
2.python web應用對象需要是可調用的,即實現了__call__方法,返回WSGI規定格式的后端處理函數來處理請求及返回響應
3.應用服務器會使用werkzeug.serving中的WSGIRequestHandler類中的相應方法,將http請求轉化成WSGI格式,所以說werkzeug是一個遵循WSGI協議的工具包,提供給應用服務器使用
后端處理請求返回響應整個流程
之前說到,后端處理請求的入口函數是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)
其中有三句比較關鍵,我寫了序號
第一句:self.request_context(environ),看下request_context這個方法:
def request_context(self, environ): return RequestContext(self, environ)
簡而言之,傳入environ,初始化一個請求上下文對象并返回
第二句: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()
簡而言之,推入應用上下文和請求上下文,如果設置了secret_key則開啟一個session,關于flask的session放到后面說
第三句:self.full_dispatch_request(),是處理請求的關鍵函數,看下源碼:
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
這個函數中嵌套了另外三個函數,預處理函數preprocess_request(),主處理函數dispatch_request()和最終處理函數finalize_request(rv)
1.preprocess_request()是處理被before_request裝飾器裝飾的函數
2.dispatch_request()匹配請求的URL,并返回視圖函數的返回值rv
3.finalize_request(rv)接受視圖函數的返回值,并生成響應,這里有make_response和process_response這兩個函數,make_response生成響應對象,process_response對響應做一些處理,比如后面要講到的session
響應生成后,在wsgi_app中return response,最后調用ctx.auto_pop()將請求和應用上下文推出棧,return的response會通過start_response發送到應用服務器,并由其發送到客戶端,這樣一次請求就結束了.
最后說說session
flask中的session是client side session,說白了就是session會封裝在cookie中在最終響應時會發送給客戶端,而在服務器本地不會存儲,所以叫作client side session,要使用session需要設置secret_key這個配置,通過app.secret_key來設置,用來驗證簽名,等到下次客戶端發來帶有cookie的請求時,后端就能從生成對應的session中解析出帶有的信息,寫個簡單的應用來看下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)
跑起來后,在瀏覽器輸入127.0.0.1:3000/,會打印出stranger,
然后訪問127.0.0.1:3000/jx后,后端打印出session set successful,并且瀏覽器會收到服務器發來的cookie,
Set-Cookie:session=eyJuYW1lIjoiangifQ.DbNHuQ.MPZLWzoLdga2SPMg0plMYmKlJMc; HttpOnly; Path=/ 這是我測試時收到的,有三個字段,第一個是session的內容,第二個是時間戳,第三個是驗證信息
這時已經設置好了session,并且得到了cookie,再次訪問127.0.0.1:3000/,后端打印出了jx,就是之前設置的值
如果對session內的值更改,則返回的cookie也會更改,那么在那保存,在那創建session呢?
之前在分析后端請求流程是提到了,在RequestContext的push方法最后:
self.session = self.app.open_session(self.request) if self.session is None: self.session = self.app.make_null_session()
如果設置了secret_key則會執行open_session開啟一個session,那如果更改了在哪里保存呢?
在finalize_request執行的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的話會執行self.save_session來保存更新session,在self.save_session中會調用response.set_cookie,flask中的session大概就是這樣
總結一下:
1.分析了應用服務器封裝好的environ從哪發送給后端
2.分析了應用服務器 WSGI 后端之間的關系以及WSGI協議對接口的標準定義,使得后端人員只需要關心自己的邏輯
3.分析了后端接收到應用服務器發來的WSGI請求之后的一系列處理流程,主要函數是wsgi_app(environ,start_response)
4.最后簡單分析了flask中的session機制,它是client side session的.
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/44730.html
摘要:服務器通過協議與客戶端通信,因此也被稱為服務器。本文標題為從零開始搭建論壇一服務器與框架本文鏈接為更多閱讀自己動手開發網絡服務器一自己動手開發網絡服務器二自己動手開發網絡服務器三服務器網關接口實現原理分析最佳實踐指南應用淺談框架編程簡介 之前用 Django 做過一個小的站點,感覺Django太過笨重,于是就準備換一個比較輕量級的 Web 框架來玩玩。Web.py 作者已經掛掉,項目好...
摘要:通過回調函數將響應狀態和響應頭返回給,同時返回響應正文,響應正文是可迭代的并包含了多個字符串。返回響應正文負責獲取請求,將請求傳遞給,由處理請求后返回。 我想大部分Python開發者最先接觸到的方向是WEB方向(因為總是有開發者希望馬上給自己做個博客出來,例如我),既然是WEB,免不了接觸到一些WEB框架,例如Django,Flask,Torando等等,在開發過程中,看過一些文檔總會...
摘要:通過查閱了些資料,總算把它們的關系理清了。在這個過程中,服務器的作用是接收請求處理請求返回響應服務器是一類特殊的服務器,其作用是主要是接收請求并返回響應。正是為了替代而出現的。三結語最后以,,之間的對話結束本文。 剛轉行互聯網行業,聽到了許多名詞:Flask、Django、WSGI、 Nginx、Apache等等,一直無法搞清楚這些開源項目之間的關系,直至看到這篇文章后感覺醍醐灌頂,以...
摘要:在從零開始搭建論壇一服務器與框架中我們弄清楚了服務器應用程序框架的概念。框架應用生成狀態碼以及響應報頭,然后將二者傳遞至,等待服務器保存。添加響應頭,狀態碼返回響應信息創建一個服務器實例目前支持的成熟服務器有很多,是相當不錯的一個。 在 從零開始搭建論壇(一):Web服務器與Web框架 中我們弄清楚了Web 服務器、Web 應用程序、Web框架的概念。對于 Python 來說,越來越多...
閱讀 736·2021-11-24 10:19
閱讀 1119·2021-09-13 10:23
閱讀 3439·2021-09-06 15:15
閱讀 1786·2019-08-30 14:09
閱讀 1698·2019-08-30 11:15
閱讀 1849·2019-08-29 18:44
閱讀 945·2019-08-29 16:34
閱讀 2468·2019-08-29 12:46