最近在學習Flask這個Web框架, 相比於Django, Flask算的上是微型的Web框架了,他只有路由和模板渲染兩個功能, 想幹別的事都須要使用插件. 好在目前的插件數量也很多, 也不乏一些十分好用的插件, 讓Flask在企業Web應用開發中仍是有一席之地的(我據說知乎就是用的Flask+tornado).html
Flask插件網站
這一路學下來, 基本上就會寫一些視圖函數, 完成簡單的業務邏輯, 對於框架執行流程其實知道得不多, 僅僅是對路由機制有一點點的瞭解.python
Flask的路由機制主要依賴Werkzeug.routing模塊, 主要是Map
類,Rule
類,MapAdapter
類等提供的功能
因而一直想看看Flask的源碼, 對這個框架進行進一步的研究, 可是無奈實在看不懂, 不知道從何看起是最大的問題.web
因而我本身一直嘗試尋找切入點, 好讓我理清看源碼的思路, 接下來我就主要記錄一下本身的琢磨過程, 這個思考琢磨的過程對我一個新手開發者來講很重要.django
我想:flask
實際上這個思考過程並無這麼瓜熟蒂落, 我在學習Flask以前就看過相關WSGI的介紹, 無奈也不是特別能理解.
可是當先去了解了某些概念後, 之後再別的地方再次碰到這個問題, 首先就知道去哪裏查相關資料了, 這個時候再問題爲導向去學習新的知識的時候就會理解的快得多, 而且當理解以後就會融會貫通.
那第一步就是研究一下究竟什麼是WSGI.後端
WSGI全稱Web Server Gateway Interface, 不要被名字唬住, 先看這麼一句話一塊兒體會一下WSGI是什麼:瀏覽器
This document specifies a proposed standard interface between web servers and Python web applications or frameworks, to promote web application portability across a variety of web servers.
這句話來自PEP3333的摘要部分, 簡單翻譯一下就是服務器
這份文檔(指的就是PEP3333)詳細說明了Web服務器和Web應用之間的標準接口, 旨在提高Web應用的可移植性
這裏面的關鍵字就是:app
若是知道了WSGI的目的, 理解後續的內容就輕鬆很多, 那咱們就先講一下WSGI的目的, 理解一下可移植性的含義吧負載均衡
當咱們訪問某個網站的時候:
基本上就是這麼一回事:
這裏面一共出現了三個角色, 分別是:
因爲服務器硬件我們也沒什麼好說的, 所以接下來提到的服務器(包括HTTP服務器)指的都是HTTP服務器軟件.
對於初學者來講服務器好像就是硬件嘛, 和軟件沒有關係, 但實際上, 服務器既能夠指硬件, 也能夠指軟件. 維基百科.
那WSGI協議的做用在這裏就是鏈接服務器軟件和Web應用的橋樑, 這兩方規定一系列協議, 要求二者之間傳輸的數據對方都能看得懂.
那這麼作有什麼好處呢?其實就是上面說的可移植性了, 若是對可移植性仍是不太理解也沒有關係, 下面讓咱們來看看這裏面一個問題.
既然服務器軟件和Web應用均可接受請求, 返回請求, 爲何搞這麼複雜?非要搞個協議, 接受請求,處理請求和返回請求的都是一我的不能夠麼?我和我交流還須要協議麼?
沒錯, 上一我的也是這麼想的, 並且事實上, 咱們確實能夠這麼作.接下來咱們來看一下三種模型, 來更好的理解一下WSGI協議和可移植性.
tornado是由F打頭的404網站收購而且開源出來的Web框架, 他的第一個特色奏是將HTTP服務器和Web應用整合到了一塊兒.
因此能夠用tornado搭建出來的服務器模型以下
這就是咱們剛纔說的接受請求, 處理請求, 返回請求都是一我的, 感受也不錯.
可是請注意了, 雖然表面上看起來這好像是一我的處理的, 可是咱們在編寫處理業務邏輯的代碼時, 確定不會去碰服務器相關的代碼, 只須要寫好視圖和路由就能夠了, 所以對於tornado來講, 他的服務器部分和邏輯處理部分仍是分開的. 這就至關於雖然是一我的, 可是他的手和大腦是兩個部分.
實際上長這樣
這裏有兩個問題.
誰叫他們是一我的呢!
這就是所謂的沒有可移植性
請注意:
實際上, tornado的服務器和Web框架是兼容WSGI協議的, 有興趣的話能夠本身搜索一下相關內容.舉這個例子是由於在Python Web框架中, tornado實現了高性能的HTTP服務器僅此而已.
若是百度過Tornado的也許知道他有一個很大的特色就是, 異步, 非阻塞, 高性能之類的. 其實, 這個特色說是他的HTTP服務器部分, tornado服務器擅長處理多個長鏈接, 能夠用於在線聊天等業務場景.
終於輪到WSGI出場了.
對於一個遵照WSGI協議的服務器和Web應用來講, 它並不在乎究竟是誰傳過來的數據, 只須要知道傳過來的數據符合某種格式, 兩邊都能處理對方傳入的數據.
打個比方, 你特別特別想吃水餃, 因而請了倆人, A專門擀麪皮, B專門包餃子, 因爲擀麪皮的人動做比較快, A還要負責把包好的餃子拿過來下鍋, 這樣你才能吃到水餃.
A擀麪皮的時候須要遵照餃子協議, 必定要把麪皮擀成圓的(沒錯, 餛飩皮就是方的!), 而且皮還不能太大, 太厚, 這樣B才能保證包出餃子來.
一樣的, B也要遵照餃子協議, 再拿到餃子皮後使用靈巧的手法捏出造型各異的水餃, 可是他包的必定是餃子, 不能是一坨A看不懂的東西!
B把包好水餃拿給A去下餃子, 好在這二人都遵照了餃子協議, A一看, 不錯, 這傢伙包出的的確是餃子, 那我也保證我可以最後煮熟的東西是水餃了.
最後, 你吃到了水餃, 可是A個B始終也不認識對方, 若是在這個過程當中你吧B換了另外一個遵循餃子協議的C, 他與A仍是可以緊密合做.
A只在意本身擀出的是麪皮, 拿到的是餃子
B只在意拿到的麪皮, 包出的是餃子
因而咱們能夠搭建像這樣的模型:
目前被普遍應用的WSGI服務器(又稱爲WSGI容器), 主要有gunicorn
和uWSGI
.完整列表
而遵循WSGI協議的python web框架有Django
, Flask
, Pyramid
, web2py
, Bottle
等等等.完整列表
最終, 咱們有:
隨便怎麼組合均可以, 怎麼樣, 是否是可移植性更強了?
然而在真正的生產環境中, 以咱們上面的模型是徹底扛不住不少人一塊兒訪問的, 因而就有了服務器集羣的概念, 使用一個性能更好的服務器打頭陣, 而後它所作的事情就是把接收到的請求再分發給其餘計算機去處理.
這就好像後來你開了個餃子館, 爲了可以接待更多的人, 你必須再僱對幾個包餃子的組合. 固然, 還必須有一個收銀人員.
這裏拿NGINX來舉例
在這個模型中, 咱們的WSGI服務器起到了承上啓下的做用, 它只處理NGINX丟給他的請求.
固然, 這也不意味着咱們對WSGI服務器性能要求不高了, 由於真正去調用Web應用的仍是WSGI服務器, 咱們只不過使用NGINX去實現了負載均衡.
其實第三種形態遠沒有這麼簡單. 我瞭解的也不是不少, 可是到了這裏, 咱們應該已經徹底理解了WSGI協議的目的了. 那接下來就來看一下WSGI協議到底指定了哪些規則吧!
瞭解過部署的朋友可能知道還有另外一個高性能的服務器-Apache, 可是經過阿帕奇與WSGI應用的交互彷佛是經過阿帕奇自帶的模塊去進行的.
How to use Django with Apache and mod_wsgi
WSGI協議這麼厲害!那必定很難實現吧!
其實WSGI協議內容沒有這麼神祕, 也特別容易實現, 它只是一系列簡單的規則, 這個規則有多簡單呢, 一下子咱們用3行代碼就能夠寫完一個簡單的WSGI應用, 並且不須要導入任何模塊和包.
因爲涉及服務器與Web應用交互, WSGI的規則分爲兩個部分, 分別對WSGI服務器端和WSGI應用端作了要求. 做爲Web後端開發人員, 咱們和框架(應用)打交道, 只須要了解WSGI協議對應用的要求.(我纔不會說我也沒有看服務器端的協議內容)
假設如今已經有了一個WSGI服務器, 如今須要編寫一個WSGI應用, 那麼咱們的應用該如何和服務器進行交互呢?
換句話說, 咱們的應用須要接受什麼樣的數據, 須要返回什麼樣的數據呢?
OK, 讓咱們把WSGI應用端規則羅列一下:
environ
(字典類型), 另外一個參數是start_response
(函數)start_response
什麼?你問我什麼是可調用對象, 什麼是可迭代對象?不如去問問 他吧.
這兩個參數的具體內容爲:
environ
本次請求的全部內容, 我挑了幾個看着眼熟的.{ 'REQUEST_METHOD': 'GET', 'RAW_URI': '/', 'SERVER_PROTOCOL': 'HTTP/1.1', 'HTTP_HOST': '127.0.0.1:5000', 'HTTP_CONNECTION': 'keep-alive', 'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'HTTP_ACCEPT_LANGUAGE': 'zh-CN,zh;q=0.9,en;q=0.8', 'REMOTE_ADDR': '127.0.0.1', 'REMOTE_PORT': '62445', 'PATH_INFO': '/', ... }
start_response
函數, 返回數據以前須要調用, 用於設置狀態碼和響應頭start_response
函數時, 第一個參數用於設置狀態碼, 是一個字符串根據這些個規則, 咱們很容易寫出一個遵循WSGI協議的Web應用:
# 1. 必須是一個**可調用對象**, 它必須接受**兩個參數** def demo_app(environ,start_response): # 2. 其中第一個參數是`environ`(字典類型), 另外一個參數是`start_response`(函數) # print(environ) # 3. 返回以前調用一次start_response 返回狀態碼和響應頭, 注意參數格式, 不一樣的請求頭信息用元組隔開 start_response("200 OK", [('Content-Type','text/html')]) # 4. 返回一個可迭代對象, 這裏就是最終的響應體 return [b"<h1>Hello WSGI!</h1>"]
是否是很簡單? 排除註釋一共就只有3行代碼.
至此, 咱們就看完了WSGI協議應用端的部分. 那麼如今, 來看一下Flask中的WSGI是怎麼體現的吧
上面的函數是一個徹底符合WSGI協議的函數, 所以他就能夠做爲一個WSGI應用, 你甚至能夠將它跑起來!
若是你已經安裝了Gunicorn, 將上述代碼保存, 並命名爲my_app.py
, 你只須要將終端切換到該文件所在的路徑下, 而後輸入
gunicorn -b 127.0.0.1:5000 my_app:demo_app
就能夠正常運行起來, 此時用瀏覽器訪問127.0.0.1:5000, 就能看到返回的結果!
你也能夠將print所在行註釋掉, 看一下environ裏面包含的完整信息
注意: Gunicorn只支持類Unix系統! 不支持Windows!
咱們知道了, 遵循WSGI協議的應用必定是一個可調用對象, 因此它既能夠是一個函數, 也能夠是一個實現了__call__()
方法的類.
話很少說, 上源碼
class Flask(_PackageBoundObject): ..... def __call__(self, environ, start_response): return self.wsgi_app(environ, start_response) ...
咱們輕鬆找到了Flask中處理請求的入口, 可是, 它又調用了另外一個方法, 趁熱打鐵, 讓咱們來看一下這個方法.
class Flask(_PackageBoundObject): ... def wsgi_app(self, environ, start_response): ctx = self.request_context(environ) error = None try: try: ctx.push() response = self.full_dispatch_request() 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) ...
這個就是Flask中真正請求的入口了, 在兜裏一個圈子後, 最終將兩個參數傳給了wsgi_app()
這個方法, 並交由它來處理.
全部的請求就將會在這幾行代碼中處理完成, 而且最終返回. 只要搞懂了這幾步, 就能知道Flask是怎麼處理請求的啦!
看上去很輕鬆, 但實際上的步驟仍是至關複雜的, 我會慢慢嘗試去理解, 一旦有所收穫也會第一時間整理而且更新.
若是有什麼問題能夠私信我, 內容有錯誤也歡迎你們幫我糾正!
servlet
協議.https://zhuanlan.zhihu.com/p/46983059
參考文章
Python Web開發最難懂的WSGI協議,到底包含哪些內容
WSGI接口 - 廖雪峯的官方網站
PEP 3333 吐槽一下: 這個PEP 3333是WSGI協議的第二個版本(1.0.1)了, 第一個版本(1.0.0)記錄在PEP 333中. 這個pep命名方式也是很厲害的