Python Web Flask原始碼解讀(一)——啟動流程
關於我
一個有思想的程式猿,終身學習實踐者,目前在一個創業團隊任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