作者:劉利強
今天看到光頭哥更新的最新一篇博文:Running Your Flask Application Over HTTPS,雖然他講的是如何改進配置使用更安全的 Https 服務,然而我更感興趣的是其中的一段:
以前印象中看過 Flask 是原生支持 ssl 的,但是當時沒怎么在意,今天突然來了興趣,在想 Flask 是如何實現(xiàn)的,所以就扒了一下源碼,發(fā)現(xiàn)關鍵不在于 Flask,還是它的底層包:Werkzeug,所以就繼續(xù)看看 Werkzeug 的源碼,看下具體是如何實現(xiàn)的,為了簡化一下源碼解析,直接從 Werkzeug 如何提供 Https 服務建起。
這里其實有個前提沒有介紹,那就是讀者可能需要有一些關于 Https 的基礎知識,如果沒有的話,可以想看一下 wiki 中關于 https 的內容了解一下背景,然后再繼續(xù)。
在創(chuàng)建一個 https 服務器之前,我們需要先生成一份證書,對于生產環(huán)節(jié)的證書都是需要驗證過的,但是對于我們實驗的話,那就隨便了,這里我有一個簡單的代碼可以生成證書,可以直接拿來使用:
執(zhí)行之后,我們就可以在指定的目錄下看到證書和公鑰了,默認的路徑是: /tmp/flask/key/
看到這些我們就可以確認我們的證書和公鑰是生成好了,可以進行下一步了。
關于 Werkzeug ,在我的博客之前寫過一篇介紹的文章,感興趣的同學可以先看一下:werkzeug初級指導;這里就不多做累贅了,直接給出一份我寫的簡單服務器代碼:
出于我的習慣,我平時都是編寫一份啟動腳本的,這里也一并給出來: run.sh
執(zhí)行 ./run.sh
之后我們應該可以看到一些 Log,然后嘗試訪問一下,我這里的配置都在 run.sh 里面了,所以訪問的時候就直接訪問: https://localhost:9527
,但是,可以在 chrome 中發(fā)現(xiàn)有警報:
當然,這個警告是正常的,因為之前說了,這個證書是未授權的,所以會警報,這里也不延伸如何解決這種警告(根本性解決),我們可以選擇高級選項選擇信任并且打開,你講可以成功訪問這個服務器:
這個例子只是想演示一下如何使用 Werkzeug 創(chuàng)建一個 https 服務器,并且是能夠訪問成功的,但是咱們的正事是探究 Werkzeug 是如何實現(xiàn)的。接下來,將以這個例子進行深入,看看 Werkzeug 的實現(xiàn)原理。
截止至寫這篇文章的時候,Werkzeug 最新的穩(wěn)定版本是 0.12.1,所以源碼就以 0.12.1
這個版本為例進行閱讀?;趧偛诺?Demo 代碼,我們忽略前面創(chuàng)建 WSGI 引用的過程,直接看關鍵的一行,其實也就這行是真正和 https 相關的,所以我們就以這行為出發(fā)點,進行深入:
在 werkzeug 的源代碼: werkzeug/serving.py
中我們可以看到 run_simple
的源代碼:
這里忽略了很多代碼,同時因為工具限制,代碼行數(shù)后續(xù)有點混亂,但是,無妨,不影響閱讀,最后有兩行是標紅的,其實這兩行也就是 run_simple
的核心代碼了,為了簡單,我們就以 use_reloader = False
看,那么,其實 run_simple
的代碼就可以簡約成這樣:
寥寥幾行代碼,所以我們一下子就看出來了,核心還得看 make_server
這個函數(shù)的實現(xiàn),所以毫不猶豫得就跟進去了,位置在 werkzeug/serving.py
line: 534 處,代碼也是異常簡單,為了方便閱讀,我也做了一個簡化,簡化之后就成了:
很好,這里又多了一個新東西,那就是 BaseWSGIServer
,這個到底是啥呢,讓我們跟進去看看,位置還是在 werkzeug/serving.py
中,line 446,簡化之后可以看成:
一目了然,我們可以發(fā)現(xiàn)其實這就是一個 Python 自帶的 HTTPServer 的子類,然后再細看一下,其實這就是 Python 原生實現(xiàn) https 服務器的簡單實現(xiàn)了,到此可以終止了。
從這一條線我們可以看到,其實 Flask 或者說 Werkzeug 實現(xiàn) https 還是在原生 HTTPServer 的基礎上簡單做了一些封裝實現(xiàn)的,并沒有做關于 ssl 的特殊處理,所以用的時候我們需要記住這個實現(xiàn)原理就可以定位很多問題了。
在第二部分的實現(xiàn)中,我們簡化了不少代碼,現(xiàn)在,我們理清了代碼的思路之后,就回過頭來看看被我們忽略掉的有價值的東西。
DEBUG 模式是如何實現(xiàn)的
在使用 Flask 的時候,有個很有趣的功能就是當我們在調用 run
的時候加上個參數(shù) run(debug=True)
就是 DEBUG 模式了,那么它是如何實現(xiàn)的呢?
首先,我想了想,DEBUG 模式和非 DEBUG 模式的區(qū)別是啥,好像很少,但是細細數(shù)一下好像又很多,例如:
還有很多的差別,那么這些差別都是如何實現(xiàn)的捏?看一下代碼,可以在 werkzeug/serving.py
line 632 中看到 WSGI app 對象被包裝了一層,包裝類是: DebuggedApplication
,源碼位置在 werkzeug/debug/__init__.py
line 228 處,其中微妙之處在于 line 437,這里其實就是處理各種 DEBUG 特性的地方:
DEBUG 模式的時候異常是跳到異常的 html 頁面,而非 DEBUG 模式直接 500 了
DEBUG 模式的時候 log 信息比非 DEBUG 模式多了很多,應該是打印級別降低了
DEBUG 模式的時候還支持在前端頁面上調試代碼
靜態(tài)文件處理
在我們的 DEMO 代碼里面,有一段關于靜態(tài)文件的代碼,可以簡單明了得看到靜態(tài)文件是通過中間件的方式實現(xiàn)的,而內部的具體實現(xiàn)則是通過匹配 url 的前綴是否是靜態(tài)文件的前綴實現(xiàn)的,簡化只則為:
重用描述符?
在 werkzeug/serving.py 的 inner
函數(shù)中,有一段被我忽略了,原來是這樣的:
之后跟進 make_server
之后可以發(fā)現(xiàn) fd
是這樣被使用的:
臥槽,居然還可以這樣,跟了一下 HTTPServer
的源代碼,可以發(fā)現(xiàn) HTTPServer ->TCPServer -> BaseServer
,然后 srv.serve_forever()
中的 serve_forever
是繼承自 BaseServer
的,在 BaseServer
中是這么用的
查看一下 select.select
的文檔可以發(fā)現(xiàn):
select.select(rlist, wlist, xlist[, timeout]) This is a straightforward interface to the Unix select() system call. The first three arguments are sequences of ‘waitable objects’: either integers representing file descriptors or objects with a parameterless method named fileno() returning such an integer:
所以繼續(xù)往下看可以發(fā)現(xiàn) TCPServer
的代碼中有這么一段:
所以,可以看到,剛才那段替換居然是可以的?。。?/p>
更多的點
這篇文章已經寫很多了,留個坑吧,下次接著寫,可以發(fā)現(xiàn)這里還是有很多知識是平時不常用的,但是很有意思。
本文中涉及的 DEMO 代碼可以在 Github 中查看:Werkzeug https Demo
HTTPS Wiki
Running Your Flask Application Over HTTPS
How to serve HTTPS directly from Flask
werkzeug 初探
Creating an HTTPS server in Python
Python select funciton docs
題圖:pexels,CC0 授權。