整个的启动流程如下
![f00f3bc6d99f5eeb6ceff4a69a14052e.png](https://i-blog.csdnimg.cn/blog_migrate/084cb9510fb8072e0e3640fa23ac6b82.jpeg)
接下来进行详细的分析
app的初始化(主要是werkzeug及底层实现)
from flask import Flask
app = Flask(__name__)
实例化Flask应用
Flask类继承_PackageBoundObject类,通过import_name参数获取运行路径,用于后续的基于路径的一些操作,如路径操作、模板文件的加载等
Flask类本身的__init__函数会对一些关键的数据结构做些初始化(代码都是经过精简的)
class Flask(_PackageBoundObject):
def __init__(
self,
import_name,
static_url_path=None,
static_folder="static",
static_host=None,
host_matching=False,
subdomain_matching=False,
template_folder="templates",
instance_path=None,
instance_relative_config=False,
root_path=None,
):
_PackageBoundObject.__init__(
self, import_name, template_folder=template_folder, root_path=root_path
)
self.static_url_path = static_url_path
self.static_folder = static_folder
if instance_path is None:
instance_path = self.auto_find_instance_path()
elif not os.path.isabs(instance_path):
raise ValueError(
"If an instance path is provided it must be absolute."
" A relative path was given instead."
)
self.instance_path = instance_path
self.config = self.make_config(instance_relative_config)
# 大部分的设置可以通过函数修饰器的方式进行添加
self.view_functions = {} # 视图函数的设置
# 出错的时候的函数处理钩子
self.error_handler_spec = {}
self.url_build_error_handlers = []
# 接受请求进入处理函数之前以及之后的的处理函数,first顾名思义
self.before_request_funcs = {}
self.before_first_request_funcs = []
self.after_request_funcs = {}
# 无论是否有异常,所有的请求结束后都会经过这个函数,key是blueprint里面的需要使用对应value钩子的函数名,None的话是所有的请求都要执行
self.teardown_request_funcs = {}
# 应用上下文销毁的时候会调用这个函数
self.teardown_appcontext_funcs = []
# 在before_request之前执行,同样有着none的规则
self.url_value_preprocessors = {}
# 可以修改url,在building url之前
self.url_default_functions = {}
self.template_context_processors = {None: [_default_template_ctx_processor]}
self.shell_context_processors = []
self.blueprints = {}
self._blueprint_order = []
# 将使用到的依赖拓展信息(感觉有点像config的使用方法
self.extensions = {}
self.url_map = self.url_map_class()
self.url_map.host_matching = host_matching
self.subdomain_matching = subdomain_matching
self._got_first_request = False
self._before_request_lock = Lock()
if self.has_static_folder:
assert (
bool(static_host) == host_matching
), "Invalid static_host/host_matching combination"
self.add_url_rule(
self.static_url_path + "/<path:filename>",
endpoint="static",
host=static_host,
view_func=self.send_static_file,
)
self.cli.name = self.name
这些钩子函数的具体运行顺序以及作用,会在后续启动app,接受请求的分析中进一步详细叙述
至于如何添加钩子,在后续专题中会有描述
app的启动
直接调用app.run()就会开始执行(本地),服务器端使用命令行的形式,实际上是使用werkzeug.run_simple(),在此之前只是增加host port的判断以及debug的判断
class Flask(_PackageBoundObject):
def run(self, host=None, port=None, debug=None, load_dotenv=True, **options):
if server_name:
sn_host, _, sn_port = server_name.partition(":")
host = host or sn_host or _host
port = int(next((p for p in (port, sn_port) if p is not None), _port))
# debug模式下的重新加载设置
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:
# 直接使用werkzeug下的run_simple函数构建服务器
run_simple(host, port, self, **options)
finally:
self._got_first_request = False
那接下来详细地看一下werkzeug的run_simple(),这里去除了参数校验的部分
def run_simple(
hostname,
port,
application,
use_reloader=False,
use_debugger=False,
use_evalex=True,
extra_files=None,
reloader_interval=1,
reloader_type="auto",
threaded=False,
processes=1,
request_handler=None,
static_files=None,
passthrough_errors=False,
ssl_context=None,
):
# 可以使用参数的形式得到执行命令行的效果,只有四种指令
# 可以打印出设置的路径下的请求访问信息
# 默认直接调用传进来的app
if use_debugger:
from .debug import DebuggedApplication
application = DebuggedApplication(application, use_evalex)
# 创建对应文件路径的url,可以访问工程地址(export)下的对应模块文件,本质上也是一个WGSI app,有点像app的修饰器,可以根据条件选择执行哪个app,如果当前的访问路径不匹配就调用由原来的app
if static_files:
from .middleware.shared_data import SharedDataMiddleware
application = SharedDataMiddleware(application, static_files)
# 重启的时候打印log
def log_startup(sock):
display_hostname = hostname if hostname not in ("", "*") else "localhost"
quit_msg = "(Press CTRL+C to quit)"
if sock.family == af_unix:
_log("info", " * Running on %s %s", display_hostname, quit_msg)
else:
if ":" in display_hostname:
display_hostname = "[%s]" % display_hostname
port = sock.getsockname()[1]
_log(
"info",
" * Running on %s://%s:%d/ %s",
"http" if ssl_context is None else "https",
display_hostname,
port,
quit_msg,
)
def inner():
try:
# 已经指定了网络IO用的文件描述符
fd = int(os.environ["WERKZEUG_SERVER_FD"])
except (LookupError, ValueError):
fd = None
# 创建server,注意这里传进去的request handler默认为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)
srv.serve_forever()
# 当使用的模块发生改变的时候,重新启动,在debug模式下会走这边
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)
if hasattr(s, "set_inheritable"):
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:
_log("info", "Unlinking %s" % server_address)
os.unlink(server_address)
from ._reloader import run_with_reloader
# 设置重启的主函数、监视的文件以及重启时间间隔
run_with_reloader(inner, extra_files, reloader_interval, reloader_type)
else:
inner()
这个run_simple函数有点东西的,包括了:
- debug模式下,在Flask app外面再包一层WSGI app,接入一些参数调试功能,以及特殊路径请求的打印功能
- 支持符合路径的文件系统的共享访问,实现方式与第一条类似
- 通过run_with_reloader函数,支持对全部或者指定文件的监视,进行修改后重启
具体的代码分析留到下次,这里进行最简单的访问流程的分析
实际启动的代码(inner函数)十分简单,只是文件描述符判断,以及新建一个server,再进行启动,那我们来看一下make_server函数的骚操作,注意这里传进去的request handler默认为None
def make_server(
host=None,
port=None,
app=None,
threaded=False,
processes=1,
request_handler=None,
passthrough_errors=False,
ssl_context=None,
fd=None,
):
# make_server()可以是多线程、多进程以及单进程,默认单进程
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:
return BaseWSGIServer(
host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd
)
这里直接返回的是BaseWSGIServer,是一个简单的单线程单进程服务器
BaseWSGIServer——>HTTPServer(get_request())——>TCPServer(创建socket, get_request())——>BaseServer(serve_forever())
- BaseServer更加接近于底层,负责底层网络数据的收发,不关心里面的协议,因此没有关于数据部分的处理实现
- TCPServer处于TCP层面,涉及到Socket等信息,都在这一层处理,由于已经涉及到具体的传输层协议,可以进行一定的数据处理,分理出基本的socket传输信息
- HTTPServer基于TCP协议,其函数都是基于TCP实现,或者是重写,抽象程度更高,分理出更加详细的request信息
- BaseWSGIServer更高层了,是在HTTP协议基础上进行WSGI规范的更高一层抽象
class BaseWSGIServer(HTTPServer, object):
def __init__(
self,
host,
port,
app,
handler=None,
passthrough_errors=False,
ssl_context=None,
fd=None,
):
# 默认是没有handler的
if handler is None:
handler = WSGIRequestHandler
self.address_family = select_address_family(host, port)
# 原来就已经有了fd,找到对应的socket
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)
if self.address_family == af_unix and os.path.exists(server_address):
os.unlink(server_address)
# 对父类的初始化socket绑定服务的地址,这里会初始化self.socket
HTTPServer.__init__(self, server_address, handler)
self.app = app
self.passthrough_errors = passthrough_errors
self.shutdown_signal = False
self.host = host
self.port = self.socket.getsockname()[1]
# 文件描述符的存在使用原来的描述符对应的socket
if fd is not None:
# 一定要记得关闭不用的socket,会占用描述符资源等
self.socket.close()
self.socket = real_sock
self.server_address = self.socket.getsockname()
# https的安全性要求
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()
sock = self.socket
if PY2 and not isinstance(sock, socket.socket):
sock = socket.socket(sock.family, sock.type, sock.proto, sock)
self.socket = ssl_context.wrap_socket(sock, server_side=True)
self.ssl_context = ssl_context
else:
self.ssl_context = None
class HTTPServer(socketserver.TCPServer):
def server_bind(self):
socketserver.TCPServer.server_bind(self)
host, port = self.server_address[:2]
self.server_name = socket.getfqdn(host)
self.server_port = port
这里实际上分的是两种情况,使用ssl以及不使用ssl,至于是否使用原来的描述符,这个只是使用不同的socket连接罢了,使用ssl的情况留到下次再说。
这个函数完成了server的初始化,包括socket的建立,ip port的设置等。
我们回到make_server()的上一层函数,在完成了服务器的初始化之后,调用服务器的serve_forever()启动服务器,我们接下来去看一下serve_forever()的实现
class BaseWSGIServer(HTTPServer, object):
def serve_forever(self):
self.shutdown_signal = False
try:
HTTPServer.serve_forever(self)
except KeyboardInterrupt:
pass
finally:
self.server_close()
调用的是父类的serve_forever(),父类是TCPServer,没有serve_forever()方法,其父类是BaseServer,有对应的方法
class BaseServer:
def serve_forever(self, poll_interval=0.5):
self.__is_shut_down.clear()
try:
with _ServerSelector() as selector:
selector.register(self, selectors.EVENT_READ)
while not self.__shutdown_request:
ready = selector.select(poll_interval)
if ready:
self._handle_request_noblock()
self.service_actions()
finally:
self.__shutdown_request = False
self.__is_shut_down.set()
我们可以看到:
- 使用的是select来实现IO多路复用,监听的方法是read方法
- 当数据到达之后,调用_handle_request_noblock()方法,获得request数据
- 唤醒后调用service_actions()进行服务
下一篇:
从运行开始阅读flask werkzeug源码——请求的接收
其他文章:
从运行开始阅读flask werkzeug源码——flask请求处理
从运行开始阅读flask、werkzeug源码——debug模式
从运行开始阅读flask werkzeug源码——钩子函数