Python Web Flask源碼解讀(一)——啟動流程
- 2019 年 10 月 3 日
- 筆記
關於我
一個有思想的程式猿,終身學習實踐者,目前在一個創業團隊任team lead,技術棧涉及Android、Python、Java和Go,這個也是我們團隊的主要技術棧。
Github:https://github.com/hylinux1024
微信公眾號:終身開發者(angrycode)
0x00 什麼是WSGI
Web Server Gateway Interface
它由Python
標準定義的一套Web Server
與Web Application
的介面交互規範。
WSGI
不是一個應用、框架、模組或者庫,而是規範。
那什麼是Web Server
(Web
伺服器)和什麼是Web Application
(Web
應用)呢?
舉例子來說明容易理解,例如常見的Web
應用框架有Django
、Flask
等,而Web
伺服器有uWSGI
、Gunicorn
等。WSGI
就是定義了這兩端介面交互的規範。
0x01 什麼是Werkzeug
Werkzeug is a comprehensive WSGI web application library.
Werkzeug
是一套實現WSGI
規範的函數庫。我們可以使用它來創建一個Web Application
(Web
應用)。例如本文介紹的Flask
應用框架就是基於Werkzeug
來開發的。
這裡我們使用Werkzeug
啟動一個簡單的伺服器應用
from werkzeug.wrappers import Request, Response @Request.application def application(request): return Response('Hello, World!') if __name__ == '__main__': from werkzeug.serving import run_simple run_simple('localhost', 4000, application)
運行之後可以在控制台上將看到如下資訊
* Running on http://localhost:4000/ (Press CTRL+C to quit)
使用瀏覽器打開 http://localhost:4000/ 看到以下資訊,說明
Hello, World!
0x02 什麼是Flask
Flask is a lightweight WSGI web application framework.
Flask
是一個輕量級的web
應用框架,它是跑在web
伺服器中的一個應用。Flask
底層就是封裝的Werkzeug
。
使用Flask
開發一個web
應用非常簡單
from flask import Flask app = Flask(__name__) @app.route('/') def hello(): return f'Hello, World!' if __name__ == '__main__': app.run()
很簡單吧。
接下來我們看看Flask
應用的啟動流程。
0x03 啟動流程
從項目地址 https://github.com/pallets/flask 中把源碼clone
下來,然後切換到0.1版本的tag
。為何要使用0.1版本呢?因為這個是作者最開始寫的版本,程式碼量應該是最少的,而且可以很容易看到作者整體編碼思路。
下面就從最簡單的Demo
開始看看Flask
是如何啟動的。我們知道程式啟動是執行了以下方法
if __name__ == '__main__': app.run()
而
app = Flask(__name__)
打開Flask
源碼中的__init__
方法
Flask.__init__()
def __init__(self, package_name): #: 是否打開debug模式 self.debug = False #: 包名或模組名 self.package_name = package_name #: 獲取app所在目錄 self.root_path = _get_package_path(self.package_name) #: 存儲視圖函數的字典,鍵為函數名稱,值為函數對象,使用@route裝飾器進行註冊 self.view_functions = {} #: 存儲錯誤處理的字典. 鍵為error code, 值為處理錯誤的函數,使用errorhandler裝飾器進行註冊 self.error_handlers = {} #: 處理請求前執行的函數列表,使用before_request裝飾器進行註冊 self.before_request_funcs = [] #: 處理請求前執行的函數列表,使用after_request裝飾器進行註冊 self.after_request_funcs = [] #: 模版上下文 self.template_context_processors = [_default_template_ctx_processor] #: url 映射 self.url_map = Map() #: 靜態文件 if self.static_path is not None: self.url_map.add(Rule(self.static_path + '/<filename>', build_only=True, endpoint='static')) if pkg_resources is not None: target = (self.package_name, 'static') else: target = os.path.join(self.root_path, 'static') self.wsgi_app = SharedDataMiddleware(self.wsgi_app, { self.static_path: target }) #: 初始化 Jinja2 模版環境. self.jinja_env = Environment(loader=self.create_jinja_loader(), **self.jinja_options) self.jinja_env.globals.update( url_for=url_for, get_flashed_messages=get_flashed_messages )
在Flask
的構造函數中進行了各種初始化操作。
然後就是執行app.run()
方法
app.run()
def run(self, host='localhost', port=5000, **options): """啟動本地開發伺服器. 如果debug設置為True,那麼會自動檢查程式碼是否改動,有改動則會自動執行部署 :param host: 監聽的IP地址. 如果設置為 ``'0.0.0.0'``就可以進行外部訪問 :param port: 埠,默認5000 :param options: 這個參數主要是對應run_simple中需要的參數 """ from werkzeug.serving import run_simple if 'debug' in options: self.debug = options.pop('debug') options.setdefault('use_reloader', self.debug) options.setdefault('use_debugger', self.debug) return run_simple(host, port, self, **options)
run
很簡潔,主要是調用了werkzeug.serving
中的run_simple
方法。
再打開run_simple
的源碼
rum_simple()
def run_simple(hostname, port, application, use_reloader=False, use_debugger=False, use_evalex=True, extra_files=None, reloader_interval=1, threaded=False, processes=1, request_handler=None, static_files=None, passthrough_errors=False, ssl_context=None): # 這方法還是比較短的,但是注釋寫得很詳細,由於篇幅問題,就把源碼中的注釋省略了 if use_debugger: from werkzeug.debug import DebuggedApplication application = DebuggedApplication(application, use_evalex) if static_files: from werkzeug.wsgi import SharedDataMiddleware application = SharedDataMiddleware(application, static_files) def inner(): make_server(hostname, port, application, threaded, processes, request_handler, passthrough_errors, ssl_context).serve_forever() if os.environ.get('WERKZEUG_RUN_MAIN') != 'true': display_hostname = hostname != '*' and hostname or 'localhost' if ':' in display_hostname: display_hostname = '[%s]' % display_hostname _log('info', ' * Running on %s://%s:%d/', ssl_context is None and 'http' or 'https', display_hostname, port) if use_reloader: # Create and destroy a socket so that any exceptions are raised before # we spawn a separate Python interpreter and lose this ability. test_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) test_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) test_socket.bind((hostname, port)) test_socket.close() run_with_reloader(inner, extra_files, reloader_interval) else: inner()
在rum_simple
方法中還定義一個嵌套方法inner()
,這個是方法的核心部分。
inner()
def inner(): make_server(hostname, port, application, threaded, processes, request_handler, passthrough_errors, ssl_context).serve_forever()
在inner()
方法裡面,調用make_server(...).serve_forever()
啟動了服務。
make_server()
def make_server(host, port, app=None, threaded=False, processes=1, request_handler=None, passthrough_errors=False, ssl_context=None): """Create a new server instance that is either threaded, or forks or just processes one request after another. """ if threaded and processes > 1: raise ValueError("cannot have a multithreaded and " "multi process server.") elif threaded: return ThreadedWSGIServer(host, port, app, request_handler, passthrough_errors, ssl_context) elif processes > 1: return ForkingWSGIServer(host, port, app, processes, request_handler, passthrough_errors, ssl_context) else: return BaseWSGIServer(host, port, app, request_handler, passthrough_errors, ssl_context)
在make_server()
中會根據執行緒或者進程的數量創建對應的WSGI
伺服器。Flask
在默認情況下是創建BaseWSGIServer
伺服器。
BaseWSGIServer、ThreadedWSGIServer、ForkingWSGIServer
class BaseWSGIServer(HTTPServer, object): ... class ThreadedWSGIServer(ThreadingMixIn, BaseWSGIServer): """A WSGI server that does threading.""" ... class ForkingWSGIServer(ForkingMixIn, BaseWSGIServer): """A WSGI server that does forking.""" ...
可以看出他們之前的繼承關係如下
打開BaseWSGIServer
的start_server()
方法
start_server()
def serve_forever(self): try: HTTPServer.serve_forever(self) except KeyboardInterrupt: pass
可以看到最終是使用HTTPServer
中的啟動服務的方法。而HTTPServer
是Python
標準類庫中的介面。
HTTPServer
是socketserver.TCPServer
的子類
socketserver.TCPServer
如果要使用Python
中類庫啟動一個http server
,則類似程式碼應該是這樣的
import http.server import socketserver PORT = 8000 Handler = http.server.SimpleHTTPRequestHandler with socketserver.TCPServer(("", PORT), Handler) as httpd: print("serving at port", PORT) httpd.serve_forever()
至此,整個服務的啟動就到這裡就啟動起來了。
這個過程的調用流程為
graph TD A[Flask]-->B[app.run] B[app.run]-->C[werkzeug.run_simple] C[werkzeug.run_simple]-->D[BaseWSGIServer] D[BaseWSGIServer]-->E[HTTPServer.serve_forever] E[HTTPServer.serve_forever]-->F[TCPServer.serve_forever]
0x04 總結一下
WSGI
是WEB
伺服器與WEB
應用之間交互的介面規範。werkzeug
是實現了這一個規範的函數庫,而Flask
框架是基於werkzeug
來實現的。
我們從Flask.run()
方法啟動服務開始,追蹤了整個服務啟動的流程。
0x05 學習資料
- https://werkzeug.palletsprojects.com/en/0.15.x/
- https://palletsprojects.com/p/flask/
- https://docs.python.org/3/library/http.server.html#module-http.server