源码系列:
Flask源码篇:Flask路由规则与请求匹配过程
Flask源码篇:2w字彻底吃透Flask是上下文原理
前面介绍Flask的时候提到过wsgi和Werkzeug,下面来从源码详细看下两个到底是什么,和Flask的工作流程有什么关系。
如果不想看过程分析,可以看最后的总结,还有流程图,也可以一看就懂!
wsgi
全称为Web Server Gateway Interface,是为Python语言定义的Web服务器和Web应用程序或框架之间的一种简单而通用的接口。类似于Java语言中的Servlet。
实际上wsgi是一种协议或规范,它规范了web服务器如何与Flask等python web服务器进行交互和通信。
通常,一个生产环境完整的web请求有如下流程:
实际上,在我们使用wsgi时,并不是一个单独的网关服务器,通常是继承在python后端服务器中。很多后端框架,比如Flask、Django等都自己实现了。其中Flask就是自己实现了Werkzeug(根据wsgi进一步抽象了)。
WSGI的主要作用是 Web Server 和 Python Application 之间的桥梁,或者翻译官,让两者能够无障碍沟通。可以归纳为以下几点:
WSGI 通常有以下协议:
每个 python web 应用都是一个可调用(callable)的对象,函数或者一个带有__call__
方法的类。__call__
方法有2个参数:第一个参数是WSGI的environ,第二个参数是一个start_response函数。environ
包含了请求的所有信息,都是一些键值对,要么是提供给server,要么提供给middleware。start_response
是 application 处理完之后需要调用的函数,参数是状态码、响应头部还有错误信息。
application 还有个非常重要的特点是:**它是可以嵌套的。**换句话说,我可以写个 application,它做的事情就是调用另外一个 application,然后再返回(类似一个 proxy)。一般来说,嵌套的最后一层是业务应用,中间就是 middleware。这样的好处是,可以解耦业务逻辑和其他功能,比如限流、认证、序列化等都实现成不同的中间层,不同的中间层和业务逻辑是不相关的,可以独立维护;而且用户也可以动态地组合不同的中间层来满足不同的需求。
基于以上特点,我们实现一个简单的python后端服务(功能上相当于Falsk),代码如下:
# 定义我们自己的python 应用
def application(environ, start_response):
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return [b"hello word"]
application 是一个函数,肯定是可调用对象,然后接收两个参数,两个参数分别是:environ和start_response
调用 start_response 函数负责将响应头、状态码传递给服务器, 响应体则由application函数返回给服务器, 一个完整的http response 就由这两个函数提供。
下面使用python自带的wsgi包实现一个wsgi网关服务器:
from wsgiref.simple_server import make_server
server = make_server('localhost', 8080, application)
server.serve_forever()
一个完整的python web后端服务器代码如下:
# wsgi使用
from wsgiref.simple_server import make_server
# 定义我们自己的python 应用
def application(environ, start_response):
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return [b"hello word"]
if __name__ == "__main__":
server = make_server('localhost', 8080, application)
server.serve_forever()
启动后,访问127.0.0.1:8080,即可得到响应hello word。
以上就是使用自己定义的python 可调用对象application和自带的wsgi包实现了一个非常简单web应用。
Werkzeug就是一个WSGI工具包,他可以作为一个Web框架的底层库。前面我们说过,Flask主要用到了Werkzeug和jinja2两个库。
其中Werkzeug提供了Flask很核心的功能:
Request
和Response
。Request
可以包装WSGI服务器传入的environ
参数,并对其进行进一步的解析,以使我们更容易的使用请求中的参数。Response
可以根据传入的参数,来发起一个特定的响应。Rule
、Map
类等。werkzeug.local
中定义了Local
、LocalStack
和LocalProxy
等类用于实现全局数据的隔离。Werkzeug还提供了很多工具,例如WSGI中间件、HTTP异常类、数据结构等。
后面还会详细介绍一些主要功能。
注:以下分析会着重通过源码分析Flask启动过程,Flask版本:2.0.2。
以一个最简单Flask应用来举例,代码如下:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run()
下面来分析下,Flask应用启动时到底发生了什么。
首先app = Flask(__name__)
代码创建了一个Flask对象。它实现了wsgi应用,并且是扮演了最核心的角色。
第一个参数__name__
是模块或包的名字,一般使用这个就好,也可以自己定义名字。
接着看最后一行app.run()
。run()
方法的定义,调用了werkzeug库中的一个 run_simple()
方法,最后启动了 BaseWSGIServer
服务器。
我们看下源码:
def run(
self,
host: t.Optional[str] = None,
port: t.Optional[int] = None,
debug: t.Optional[bool] = None,
load_dotenv: bool = True,
**options: t.Any,
) -> None:
# 如果从命令行启动
if os.environ.get("FLASK_RUN_FROM_CLI") == "true":
from .debughelpers import explain_ignored_app_run
explain_ignored_app_run()
return
# 加载配置
if get_load_dotenv(load_dotenv):
cli.load_dotenv()
# if set, let env vars override previous values
if "FLASK_ENV" in os.environ:
self.env = get_env()
self.debug = get_debug_flag()
elif "FLASK_DEBUG" in os.environ:
self.debug = get_debug_flag()
# debug passed to method overrides all other sources
if debug is not None:
self.debug = bool(debug)
server_name = self.config.get("SERVER_NAME")
sn_host = sn_port = None
if server_name:
sn_host, _, sn_port = server_name.partition(":")
# 下面结果if是设置host、port参数默认值
if not host:
if sn_host:
host = sn_host
else:
host = "127.0.0.1"
if port or port == 0:
port = int(port)
elif sn_port:
port = int(sn_port)
else:
port = 5000
options.setdefault("use_reloader", self.debug)
options.setdefault("use_debugger", self.debug)
options.setdefault("threaded", True)
cli.show_server_banner(self.env, self.debug, self.name, False)
from werkzeug.serving import run_simple
try:
# 这一行是核心,调用app.run方法,实际上是执行了werkzeug服务的run_simple方法
run_simple(t.cast(str, host), port, self, **options)
finally:
self._got_first_request = False
可以看到,run方法有host、post、debug、load_dotenv
等参数。load_dotenv主要用来指定配置文件,加载配置的。
启动falsk app的核心就是调用werkzeug库中的一个run_simple()
方法,同时传递了参数。
run_simple方法源码如下:
def run_simple(
hostname: str,
port: int,
application: "WSGIApplication",
use_reloader: bool = False,
use_debugger: bool = False,
use_evalex: bool = True,
extra_files: t.Optional[t.Iterable[str]] = None,
exclude_patterns: t.Optional[t.Iterable[str]] = None,
reloader_interval: int = 1,
reloader_type: str = "auto",
threaded: bool = False,
processes: int = 1,
request_handler: t.Optional[t.Type[WSGIRequestHandler]] = None,
static_files: t.Optional[t.Dict[str, t.Union[str, t.Tuple[str, str]]]] = None,
passthrough_errors: bool = False,
ssl_context: t.Optional[_TSSLContextArg] = None,
) -> None:
"""Start a WSGI application. Optional features include a reloader,
multithreading and fork support.
This function has a command-line interface too::
python -m werkzeug.serving --help
以下参数介绍省略
"""
if not isinstance(port, int):
raise TypeError("port must be an integer")
if use_debugger:
from .debug import DebuggedApplication
application = DebuggedApplication(application, use_evalex)
if static_files:
from .middleware.shared_data import SharedDataMiddleware
application = SharedDataMiddleware(application, static_files)
def log_startup(sock: socket.socket) -> None:
all_addresses_message = (
" * Running on all addresses.\n"
" WARNING: This is a development server. Do not use it in"
" a production deployment."
)
if sock.family == af_unix:
_log("info", " * Running on %s (Press CTRL+C to quit)", hostname)
else:
if hostname == "0.0.0.0":
_log("warning", all_addresses_message)
display_hostname = get_interface_ip(socket.AF_INET)
elif hostname == "::":
_log("warning", all_addresses_message)
display_hostname = get_interface_ip(socket.AF_INET6)
else:
display_hostname = hostname
if ":" in display_hostname:
display_hostname = f"[{display_hostname}]"
_log(
"info",
" * Running on %s://%s:%d/ (Press CTRL+C to quit)",
"http" if ssl_context is None else "https",
display_hostname,
sock.getsockname()[1],
)
def inner() -> None:
try:
fd: t.Optional[int] = int(os.environ["WERKZEUG_SERVER_FD"])
except (LookupError, ValueError):
fd = None
srv = make_server(
hostname,
port,
application,
threaded,
processes,
request_handler,
passthrough_errors,
ssl_context,
fd=fd,
)
if fd is None:
log_startup(srv.socket)
# 这里是inner函数的核心,调用了wsgi的make_server创建了一个wsgi服务器,并且执行serve_forever方法
srv.serve_forever()
if use_reloader:
if not is_running_from_reloader():
if port == 0 and not can_open_by_fd:
raise ValueError(
"Cannot bind to a random port with enabled "
"reloader if the Python interpreter does "
"not support socket opening by fd."
)
address_family = select_address_family(hostname, port)
server_address = get_sockaddr(hostname, port, address_family)
s = socket.socket(address_family, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(server_address)
s.set_inheritable(True)
if can_open_by_fd:
os.environ["WERKZEUG_SERVER_FD"] = str(s.fileno())
s.listen(LISTEN_QUEUE)
log_startup(s)
else:
s.close()
if address_family == af_unix:
server_address = t.cast(str, server_address)
_log("info", "Unlinking %s", server_address)
os.unlink(server_address)
from ._reloader import run_with_reloader as _rwr
# 这里是主要代码,把inner函数传递给run_with_reloader函数,开启一个线程执行inner
_rwr(
inner,
extra_files=extra_files,
exclude_patterns=exclude_patterns,
interval=reloader_interval,
reloader_type=reloader_type,
)
else:
inner()
我们只看最核心的代码部分即可。
可以看到方法里定义了一个非常重要的函数inner()
。use_reloader
这个参数也是从run方法传递过来的,默认为False。
如果use_reloader
为True,最后流程会走到执行run_with_reloader
方法,及run_with_reloader(inner, extra_files, reloader_interval, reloader_type)
。
run_with_reloader代码如下:
def run_with_reloader(
main_func: t.Callable[[], None],
extra_files: t.Optional[t.Iterable[str]] = None,
exclude_patterns: t.Optional[t.Iterable[str]] = None,
interval: t.Union[int, float] = 1,
reloader_type: str = "auto",
) -> None:
"""Run the given function in an independent Python interpreter."""
import signal
signal.signal(signal.SIGTERM, lambda *args: sys.exit(0))
reloader = reloader_loops[reloader_type](
extra_files=extra_files, exclude_patterns=exclude_patterns, interval=interval
)
try:
if os.environ.get("WERKZEUG_RUN_MAIN") == "true":
ensure_echo_on()
# 这里是主要代码:把我们上面传递过来的inner函数作为主函数,开始一个新线程(守护线程)
t = threading.Thread(target=main_func, args=())
t.daemon = True
with reloader:
t.start()
reloader.run()
else:
sys.exit(reloader.restart_with_reloader())
except KeyboardInterrupt:
pass
其主要功能就是把我们上面传递过来的inner函数作为主函数,开始一个新线程(守护线程),本质还是执行inner函数。
如果use_reloader
为False,则直接执行inner函数。
所以,inner()
函数是run_simple()
函数的核心。
下面看看inner函数主要做了什么。把inner函数代码单独拿出来,如下:
def inner() -> None:
try:
fd: t.Optional[int] = int(os.environ["WERKZEUG_SERVER_FD"])
except (LookupError, ValueError):
fd = None
srv = make_server(
hostname,
port,
application,
threaded,
processes,
request_handler,
passthrough_errors,
ssl_context,
fd=fd,
)
if fd is None:
log_startup(srv.socket)
# 这里是inner函数的核心,调用了wsgi的make_server创建了一个wsgi服务器,并且执行serve_forever方法
srv.serve_forever()
inner()
函数的核心就是:调用了make_server()
方法,并且执行了serve_forever方法。
下面来分析下make_server方法做了什么。
make_server()
方法是werkzeug包内的方法。其源码如下:
def make_server(
host: str,
port: int,
app: "WSGIApplication",
threaded: bool = False,
processes: int = 1,
request_handler: t.Optional[t.Type[WSGIRequestHandler]] = None,
passthrough_errors: bool = False,
ssl_context: t.Optional[_TSSLContextArg] = None,
fd: t.Optional[int] = None,
) -> BaseWSGIServer:
"""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, fd=fd
)
elif processes > 1:
return ForkingWSGIServer(
host,
port,
app,
processes,
request_handler,
passthrough_errors,
ssl_context,
fd=fd,
)
else:
# 返回了一个BaseWSGIServer对象
return BaseWSGIServer(
host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd
)
首先这个方法接受了run_simple
函数传递进来的参数:host、port、app(Flask对象)、thread(线程)、 processes = 1 (单进程 )。
最重要的是这个方法返回了一个BaseWSGIServer
对象(ThreadedWSGIServer和ForkingWSGIServer都继承了它),后面会启动BaseWSGIServer
,创造一个单线程,单进程的WSGI server
。
BaseWSGIServer源码如下:
class BaseWSGIServer(HTTPServer):
"""Simple single-threaded, single-process WSGI server."""
multithread = False
multiprocess = False
request_queue_size = LISTEN_QUEUE
def __init__(
self,
host: str,
port: int,
app: "WSGIApplication",
handler: t.Optional[t.Type[WSGIRequestHandler]] = None,
passthrough_errors: bool = False,
ssl_context: t.Optional[_TSSLContextArg] = None,
fd: t.Optional[int] = None,
) -> None:
if handler is None:
# wsgi的请求处理器
handler = WSGIRequestHandler
self.address_family = select_address_family(host, port)
if fd is not None:
real_sock = socket.fromfd(fd, self.address_family, socket.SOCK_STREAM)
port = 0
# 获取服务地址
server_address = get_sockaddr(host, int(port), self.address_family)
# remove socket file if it already exists
if self.address_family == af_unix:
server_address = t.cast(str, server_address)
if os.path.exists(server_address):
os.unlink(server_address)
# 调用父类HTTPServer的初始化方法,参数有地址和请求处理器
super().__init__(server_address, handler) # type: ignore
self.app = app
self.passthrough_errors = passthrough_errors
self.shutdown_signal = False
self.host = host
self.port = self.socket.getsockname()[1]
# Patch in the original socket.
if fd is not None:
self.socket.close()
self.socket = real_sock
self.server_address = self.socket.getsockname()
if ssl_context is not None:
if isinstance(ssl_context, tuple):
ssl_context = load_ssl_context(*ssl_context)
if ssl_context == "adhoc":
ssl_context = generate_adhoc_ssl_context()
self.socket = ssl_context.wrap_socket(self.socket, server_side=True)
self.ssl_context: t.Optional["ssl.SSLContext"] = ssl_context
else:
self.ssl_context = None
def log(self, type: str, message: str, *args: t.Any) -> None:
_log(type, message, *args)
def serve_forever(self, poll_interval: float = 0.5) -> None:
"""核心方法:调用了父类HTTPServer的serve_forever方法"""
self.shutdown_signal = False
try:
super().serve_forever(poll_interval=poll_interval)
except KeyboardInterrupt:
pass
finally:
self.server_close()
def handle_error(self, request: t.Any, client_address: t.Tuple[str, int]) -> None:
if self.passthrough_errors:
raise
return super().handle_error(request, client_address)
BaseWSGIServer
的核心就是继承了HTTPServer
,其中初始化方法里主要做了两件事,一个是使用get_sockaddr
方法找到服务地址,还有一个就是使用父类HTTPServer的初始化方法,参数有地址和请求处理器WSGIRequestHandler
。
其中最核心的方法就是前面inner方法里提到过的serve_forever()
方法。
这个方法其实也是调用了父类BaseServer
的serve_forever方法。BaseServer是socketserver模块下的一个类。
继承关系是:BaseWSGIServer继承HTTPServer,HTTPServer继承socketserver.TCPServer,socketserver.TCPServer继承BaseServer。
HTTPServer
在http.server.py 模块中。其源码如下:
class HTTPServer(socketserver.TCPServer):
allow_reuse_address = 1 # Seems to make sense in testing environment
def server_bind(self):
"""Override server_bind to store the server name."""
socketserver.TCPServer.server_bind(self)
host, port = self.server_address[:2]
self.server_name = socket.getfqdn(host)
self.server_port = port
可以看到
HTTPServer
继承了socketserver.TCPServer
类,socketserver.TCPServer
类属于socketserver
模块下下一个TCP服务类,不再做过多的解析了;server_bind
方法,用于绑定服务器地址和端口。所以HTTPServer主要作用是于绑定服务器地址和端口。
下面着重看下serve_forever方法。
上面说了,这个方法其实也是调用了父类BaseServer
的serve_forever
方法。
其代码如下:
def serve_forever(self, poll_interval=0.5):
"""Handle one request at a time until shutdown.
Polls for shutdown every poll_interval seconds. Ignores
self.timeout. If you need to do periodic tasks, do them in
another thread.
"""
self.__is_shut_down.clear()
try:
# XXX: Consider using another file descriptor or connecting to the
# socket to wake this up instead of polling. Polling reduces our
# responsiveness to a shutdown request and wastes cpu at all other
# times.
with _ServerSelector() as selector:
selector.register(self, selectors.EVENT_READ)
while not self.__shutdown_request:
ready = selector.select(poll_interval)
# bpo-35017: shutdown() called during select(), exit immediately.
if self.__shutdown_request:
break
if ready:
self._handle_request_noblock()
self.service_actions()
finally:
self.__shutdown_request = False
self.__is_shut_down.set()
可以看到主要作用就是写了一个死循环,然后不断处理接收到的请求。
WSGIRequestHandler
类的源码比较多,其中定义了很多方法用来处理请求。主要源码如下:
class WSGIRequestHandler(BaseHTTPRequestHandler):
"""A request handler that implements WSGI dispatching."""
server: "BaseWSGIServer"
def run_wsgi(self) -> None:
if self.headers.get("Expect", "").lower().strip() == "100-continue":
self.wfile.write(b"HTTP/1.1 100 Continue\r\n\r\n")
self.environ = environ = self.make_environ()
status_set: t.Optional[str] = None
headers_set: t.Optional[t.List[t.Tuple[str, str]]] = None
status_sent: t.Optional[str] = None
headers_sent: t.Optional[t.List[t.Tuple[str, str]]] = None
def write(data: bytes) -> None:
nonlocal status_sent, headers_sent
assert status_set is not None, "write() before start_response"
assert headers_set is not None, "write() before start_response"
if status_sent is None:
status_sent = status_set
headers_sent = headers_set
try:
code_str, msg = status_sent.split(None, 1)
except ValueError:
code_str, msg = status_sent, ""
code = int(code_str)
self.send_response(code, msg)
header_keys = set()
for key, value in headers_sent:
self.send_header(key, value)
key = key.lower()
header_keys.add(key)
if not (
"content-length" in header_keys
or environ["REQUEST_METHOD"] == "HEAD"
or code < 200
or code in (204, 304)
):
self.close_connection = True
self.send_header("Connection", "close")
if "server" not in header_keys:
self.send_header("Server", self.version_string())
if "date" not in header_keys:
self.send_header("Date", self.date_time_string())
self.end_headers()
assert isinstance(data, bytes), "applications must write bytes"
self.wfile.write(data)
self.wfile.flush()
def start_response(status, headers, exc_info=None): # type: ignore
nonlocal status_set, headers_set
if exc_info:
try:
if headers_sent:
raise exc_info[1].with_traceback(exc_info[2])
finally:
exc_info = None
elif headers_set:
raise AssertionError("Headers already set")
status_set = status
headers_set = headers
return write
def execute(app: "WSGIApplication") -> None:
# 这里的app注意就是我们上面run_simple方法传递进来的app级Flask app
# 执行execute方法会调用app(),这就是为什么要求我们的Flask对象要可被调用(实现__call__方法)
# 调用app时把environ环境参数和start_response传进去,与第二节里返示例完全一致了
application_iter = app(environ, start_response)
try:
for data in application_iter:
write(data)
if not headers_sent:
write(b"")
finally:
if hasattr(application_iter, "close"):
application_iter.close() # type: ignore
try:
# run_wsgi的核心是执行execute方法
execute(self.server.app)
except (ConnectionError, socket.timeout) as e:
self.connection_dropped(e, environ)
except Exception:
if self.server.passthrough_errors:
raise
from .debug.tbtools import get_current_traceback
traceback = get_current_traceback(ignore_system_exceptions=True)
try:
# if we haven't yet sent the headers but they are set
# we roll back to be able to set them again.
if status_sent is None:
status_set = None
headers_set = None
execute(InternalServerError())
except Exception:
pass
self.server.log("error", "Error on request:\n%s", traceback.plaintext)
def handle_one_request(self) -> None:
"""Handle a single HTTP request."""
self.raw_requestline = self.rfile.readline()
if not self.raw_requestline:
self.close_connection = True
elif self.parse_request():
self.run_wsgi()
非常重要:
首先handle_one_request(self)
方法,主要用来处理一个请求。当一个请求进来时就会调用这个方法。接着handle_one_request调用了核心方法run_wsgi()
方法。run_wsgi()
方法的核心就是execute
方法。其中最最重要的是execute方法里application_iter = app(environ, start_response)
这行代码。
这里的app注意就是我们上面run_simple方法传递进来的app及Flask app。执行execute方法会调用app(),这就是为什么要求我们的Flask对象要可被调用(实现__call__
方法)。调用app__call__
方法时把environ环境参数和start_response传进去,与第二节里返示例完全一致了。
经过以上的分析,可以看到,Flask的工作流程大致如下:
app.run()
方法;app.run()
方法主要调用了run_simple()
方法;run_simple()
方法主要执行了内部的inner()
方法;inner()
方法主要执行了make_server()
方法,返回了一个BaseWSGIServer
对象srv,srv对象有个WSGIRequestHandler
对象属性,用于处理请求;inner()
方法执行了BaseWSGIServer对象的serve_forever()
方法,这个方法主要是写了一个死循环,用于处理不断接收到的请求;handle_one_request()
方法;handle_one_request()
方法调用了核心方法run_wsgi()
方法;run_wsgi()
方法的核心就是execute()
方法;application_iter = app(environ, start_response)
,app就是上面run_simple方法传递进来的app,即Flask app,这样就会调用app的__call__
方法,调用app__call__
方法时把environ环境参数和start_response传进去。__call__
方法来处理请求了。整个流程图可以概括如下图:
可以看到整个Falsk启动过程中,最重要的是创建了BaseWSGIServer对象,并执行了对象的serve_forever()
方法。这样一个wsgi服务器就创建并启动好了,当请求进来时,就可以处理请求,并把请求header和参数等传递给Flask app的__call__
方法,接着就是根据请求做出响应了。
所以wsgi在其中的作用就是一个"翻译官"或"媒介",接受到客户端的请求,进行处理和包装,再传给Falsk应用服务器。
参考:
https://blog.csdn.net/sinat_36651044/article/details/77462831
https://www.cnblogs.com/skyflask/p/9193828.html
https://blog.csdn.net/bestallen/article/details/54342120
https://cizixs.com/2017/01/11/flask-insight-start-process/
https://blog.csdn.net/lantian_123/article/details/109396576?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-109396576-blog-122802770.pc_relevant_3mothn_strategy_recovery&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-109396576-blog-122802770.pc_relevant_3mothn_strategy_recovery&utm_relevant_index=1