首先,先向大家介紹一下什麼是 werkzeug,Werkzeug是一個WSGI工具包,他可以作為一個Web框架的底層庫。這裡稍微說一下, werkzeug 不是一個web伺服器,也不是一個web框架,而是一個工具包,官方的介紹說是一個 WSGI 工具包,它可以作為一個 Web 框架的底層庫,因為它封裝好了很多 Web 框架的東西,例如 Request,Response 等等。
例如我最常用的 Flask 框架就是一 Werkzeug 為基礎開發的,這也是我要解析一下 Werkzeug 底層的原因,因為我想知道 Flask 的實現邏輯以及底層控制。這篇文章沒有涉及到 Flask 的相關內容,只是以 Werkzeug 建立一個簡單的 Web 應用,然後以這個 Web 應用為例剖析請求的處理以及響應的產生過程。
下面我們以一個簡短的例子開始,先看看怎麼使用 werkzeug,然後再逐步刨析 werkzeug 的實現原理。
安裝 werkzeug
我希望讀者是在 virtualenv 環境中跟著我的步伐走得,如果你還不知道什麼是 virtualenv,那麼你可以在我的部落格中搜尋一下 virtualenv,然後先弄好,再繼續,因為很可能因為一些庫的衝突等問題導致你看不到本文中介紹的東西。
ok,下面開始安裝 werkzeug,
1 |
pip install Werkzeug |
這條命令下去,幾秒鐘之後你就可以使用 werkzeug 了。
一個簡單地 web 伺服器
接下來,我們就開始使用 werkzeug 來建立一個簡單的 web 伺服器,這個伺服器就僅僅返回 “Hello Werkzeug”,沒有其他內容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
#!/usr/bin/env python # encoding: utf-8 import os from werkzeug.serving import run_simple from werkzeug.wrappers import Request, Response from werkzeug.wsgi import SharedDataMiddleware class Shortly(object): def dispatch_request(self, request): return Response('Hello Werkzeug!') def wsgi_app(self, environ, start_response): request = Request(environ) response = self.dispatch_request(request) return response(environ, start_response) def __call__(self, environ, start_response): return self.wsgi_app(environ, start_response) def create_app(with_static=True): app = Shortly() if with_static: app.wsgi_app = SharedDataMiddleware(app.wsgi_app, { '/static': os.path.join(os.path.dirname(__file__), 'static') }) return app if __name__ == '__main__': app = create_app() run_simple('127.0.0.1', 6666, app, use_debugger=True, use_reloader=True) |
這段程式碼就實現了我說的功能,那麼我們就來看看這段程式碼是怎麼運作的?
首先,一切都回到最開始的地方開始,從 main 開始看起,可以發現 main 是非常簡單地,只有一個初始化函式,然後就呼叫了 werkzeug 的 run_simple 函式。okay,我們可以發現這個 app 其實是一個 Shortly 物件,這個類就只實現了 3 個方法,一個是 dispatch_request, wsig_app, call ,就這麼簡單了,那我們就知道了,關鍵的程式碼都不是這些,應該是 run_simple.
run_simple 解析
okay,我們這個系列部落格的目的就是解析 werkzeug 原始碼,所以拿到 werkzeug 原始碼肯定是我們必須要做的。所以第一步我們就需要從 github 上將 werkzeug clone 下來:
1 |
git clone https://github.com/mitsuhiko/werkzeug.git |
然後,我們就找 run_simple 的程式碼咯
1 |
vim werkzeug/serving.py |
goto line 559
我們可以看到這個函式的定義,秉著關注重點的原則,我們就忽略條件判斷,以一條最簡單地路線來看程式碼,那麼這裡就假設:
1 2 3 |
use_debugger = False static_files = False use_reloader = False |
OK, 那到這裡其實 run_simple 呼叫的就是 inner 了,那麼就來看看 inner 的程式碼:
1 2 3 4 5 6 7 8 9 10 11 |
646: try: 647: fd = int(os.environ['WERKZEUG_SERVER_FD']) 648: except (LookupError, ValueError): 649: fd = None 650: srv = make_server(hostname, port, application, threaded, 651: processes, request_handler, 652: passthrough_errors, ssl_context, 653: fd=fd) 654: if fd is None: 655: log_startup(srv.socket) 656: srv.serve_forever() |
忽略 fd,那麼剩下一點點了:
1 2 3 4 5 6 |
650: srv = make_server(hostname, port, application, threaded, 651: processes, request_handler, 652: passthrough_errors, ssl_context, 653: fd=fd) 656: srv.serve_forever() |
好,你應該和我一樣有興致得想知道這個 make_server 裡面是什麼內容了,我也很期待,那就跟上去看看。
make_server 的程式碼我就不貼了,還是最簡原則,忽略各種條件,那麼這裡就假設:
1 2 |
threaded = False processes = 1 |
那麼程式碼也很簡單了,就剩下:
1 2 |
546: return BaseWSGIServer(host, port, app, request_handler, 547: passthrough_errors, ssl_context, fd=fd) |
很好,好不容易跟蹤到這,終於上關鍵了,那就是這個 BaseWSGIServer 了,我們就來看看這個類實現了什麼功能。
先看這個類的定義:
1 |
443: class BaseWSGIServer(HTTPServer, object): |
這個類是繼承自 HTTPServer 的,那麼我們就有點底了,這差不多到頭了,已經和 Python 的 API 碰上了。好,既然是繼承自 HTTPServer,那麼就把他當做 HTTPServer,然後繼續看 run_simple 的程式碼,我們一路跟蹤下來,我們發現了 656 行有一個 srv.serve_forever()
,那麼這不就是 HTTPServer 的用法嗎? server.serve_forever() 。
okay,到這那麼事情已經暫告一段落了,雖然很多事情都還沒搞清楚,例如請求是怎麼被封裝的,響應又在哪裡被處理了,例如URL路由之類的怎麼操作的。但是,我們已經對 Werkzeug 有一個大概的印象了,知道他低層還是 HTTPServer 實現的,沒有太多特殊的自定義協議。在下一章我們會逐步得進行進行更深層次的解密。歡迎繼續關注。