今日分享

扒源码 - 一个请求在flask中经历了什么_ide小闫同学pythonnote.cn查看:​1314520​回复:​225​The only person you are destined to become is the person you decide to be.

——  Ralph Waldo Emerson



客户端发起一个请求,​​Flask​​​ 都干了什么呢?​​url​​ 如何与视图进行绑定的?​更多精彩文章请关注公众号『Pythonnote』或者『全栈技术精选』

1.过程简述

1) 创建请求上下文

2) 创建应用上下文

3) 把上下文压入栈

4) 执行请求钩子 ​​before_first_request​​ 的相关操作

5) 执行请求钩子 ​​before_request​​ 的相关操作

6) 路由

7) 执行请求钩子 ​​after_request​​ 的相关操作

8) 执行请求钩子 ​​teardown_request​​ 的相关操作

9) 把上下文弹出栈

10) 返回响应结果

2.过程详述

2.1 wsgi 接口

总所周知,客户端每次发起请求,服务器都会调用框架实现的 ​​wsgi​​​ 接口进行通讯。在 ​​Flask​​​ 中,每个请求都会先调用 ​​Flask.__call__​​​ 方法,而此方法又调用了 ​​Flask.wsgi_app​​​ ,它便是 ​​Flask​​​ 中的 ​​wsgi​​ 接口了。接下来我们结合源码进行说明。更多精彩文章请关注公众号『Pythonnote』或者『全栈技术精选』

小闫使用的版本为 ​​flask1.1.2​​​,以下所有源码都源自于此版本,并以 ​​wsgi_app​​ 方法展开描述。

# flask/app.py


class Flask(_PackageBoundObject):


... 省略其他方法


def wsgi_app(self, environ, start_response):
"""The actual WSGI application.
... 此处省略一些说明
:param environ: A WSGI environment. 环境字典,包含全部 HTTP 请求信息
:param start_response: A callable accepting a status code,
a list of headers, and an optional exception context to
start the response. 回调函数
"""
# 创建请求上下文(过程中创建了应用上下文)
ctx = self.request_context(environ)
error = None
try:
try:
# 上下文入栈
ctx.push()
# 路由
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.handle_exception(e)
except: # noqa: B001
error = sys.exc_info()[1]
raise
# 返回响应结果
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
# 上下文出栈
ctx.auto_pop(error)


def __call__(self, environ, start_response):
"""The WSGI server calls the Flask application object as the
WSGI application. This calls :meth:`wsgi_app` which can be
wrapped to applying middleware."""
return self.wsgi_app(environ, start_response)

2.2 创建上下文

结合上面提供的 ​​wsgi_app​​ 源码,我们可以看到先执行了如下代码:

ctx = self.request_context(environ)

此处便是创建上下文操作。框架会先去创建请求上下文,并去判断是否有应用上下文,以及应用上下文与当前应用是否一致,然后决定是否去创建一个应用上下文。如下便是依次进行调用的方法:

# flask/app.py


class Flask(_PackageBoundObject):
def request_context(self, environ):
return RequestContext(self, environ)


# flask/ctx.py


class RequestContext(object):
def push(self):
top = _request_ctx_stack.top
if top is not None and top.preserved:
top.pop(top._preserved_exc)


# 从栈中弹出一个应用上下文
app_ctx = _app_ctx_stack.top
# 判断应用上下文是否存在并与当前应用一致
if app_ctx is None or app_ctx.app != self.app:
# 创建应用上下文
app_ctx = self.app.app_context()
# 入栈
app_ctx.push()
self._implicit_app_ctx_stack.append(app_ctx)
else:
self._implicit_app_ctx_stack.append(None)


if hasattr(sys, "exc_clear"):
sys.exc_clear()


_request_ctx_stack.push(self)


if self.session is None:
session_interface = self.app.session_interface
self.session = session_interface.open_session(self.app, self.request)


if self.session is None:
self.session = session_interface.make_null_session(self.app)


if self.url_adapter is not None:
self.match_request()

2.3 请求钩子

如果在代码中定义了四种请求钩子,那么它们会按照如下顺序执行。

扒源码 - 一个请求在flask中经历了什么_python_02

1.​​before_first_request​​:在处理第一个请求前执行

2.​​before_request​​:在每次请求前执行,在该装饰函数中,一旦return,视图函数不再执行

3.​​after_request​​:如果没有抛出错误,在每次请求后执行

a.接受一个参数:视图函数作出的响应
b.在此函数中可以对响应值,在返回之前做最后一步处理,再返回

4.teardown_request:在每次请求后执行更多精彩文章请关注公众号『Pythonnote』或者『全栈技术精选』

a.接受一个参数:用来接收错误信息

2.4 路由

在 ​​wsgi_app​​ 方法中如下代码便会进行请求分发:

response = self.full_dispatch_request()

下面将所涉及到的方法源码依次列出:

# flask/app.py


class Flask(_PackageBoundObject):
def full_dispatch_request(self):
"""Dispatches the request and on top of that performs request
pre and postprocessing as well as HTTP exception catching and
error handling.
"""
# 首次处理请求前的操作,通过 @before_first_request 定义,也就是我们的请求钩子
self.try_trigger_before_first_request_functions()
try:
request_started.send(self)
# 每次处理请求前进行的操作, 通过 @before_request 来定义
rv = self.preprocess_request()
if rv is None:
# 调用 dispatch_request 函数匹配 url,执行请求调度
rv = self.dispatch_request()
except Exception as e:
rv = self.handle_user_exception(e)
# 调用 finalize_request 方法将视图函数的返回值转换成一个真正的响应对象
return self.finalize_request(rv)


def dispatch_request(self):
"""Does the request dispatching. Matches the URL and returns the
return value of the view or error handler. This does not have to
be a response object. In order to convert the return value to a
proper response object, call :func:`make_response`.
"""
req = _request_ctx_stack.top.request
if req.routing_exception is not None:
self.raise_routing_exception(req)
rule = req.url_rule
# if we provide automatic options for this URL and the
# request came with the OPTIONS method, reply automatically
if (
getattr(rule, "provide_automatic_options", False)
and req.method == "OPTIONS"
):
return self.make_default_options_response()
# otherwise dispatch to the handler for that endpoint
return self.view_functions[rule.endpoint](**req.view_args)


def finalize_request(self, rv, from_error_handler=False):
"""Given the return value from a view function this finalizes
the request by converting it into a response and invoking the
postprocessing functions. This is invoked for both normal
request dispatching as well as error handlers.


Because this means that it might be called as a result of a
failure a special safe mode is available which can be enabled
with the `from_error_handler` flag. If enabled, failures in
response processing will be logged and otherwise ignored.


:internal:
"""
response = self.make_response(rv)
try:
# 每次正常处理请求后进行的操作, 通过 @after_request 来定义
response = self.process_response(response)
request_finished.send(self, response=response)
except Exception:
if not from_error_handler:
raise
self.logger.exception(
"Request finalizing failed with an error while handling an error"
)
return response

2.5 路由表的创建

下面是一个最简单的视图,其作用便是在访问根目录时,返回 ​​Hello World​​。

@app.route('/')
def hello_world():
return 'Hello World!'

你有没有想过视图函数与 ​​url​​​ 是如何绑定的呢?它是通过方法 ​​add_url_rule​​ 创建的路由规则,下面我们来看一下源码:

1) 先查看一下 ​​@app.route​​ 装饰器干了哪些工作:

# flask/app.py


class Flask(_PackageBoundObject):
def route(self, rule, **options):
def decorator(f):
# 获取 endpoint,可以看作为每个 view_func 的 ID
endpoint = options.pop("endpoint", None)
# 调用 add_url_rule 方法添加路由信息
self.add_url_rule(rule, endpoint, f, **options)
return f


return decorator

2) 然后就来看看 ​​add_url_rule​​ 为何方神圣。更多精彩文章请关注公众号『Pythonnote』或者『全栈技术精选』

# flask/app.py


class Flask(_PackageBoundObject):


# 定义view_functions
self.view_functions = {}
# 定义url_map
self.url_map = Map()


@setupmethod
def add_url_rule(
self,
rule,
endpoint=None,
view_func=None,
provide_automatic_options=None,
**options
):
if endpoint is None:
endpoint = _endpoint_from_view_func(view_func)
options["endpoint"] = endpoint
methods = options.pop("methods", None)


# if the methods are not given and the view_func object knows its
# methods we can use that instead. If neither exists, we go with
# a tuple of only ``GET`` as default.
if methods is None:
methods = getattr(view_func, "methods", None) or ("GET",)
if isinstance(methods, string_types):
raise TypeError(
"Allowed methods have to be iterables of strings, "
'for example: @app.route(..., methods=["POST"])'
)
methods = set(item.upper() for item in methods)


# Methods that should always be added
required_methods = set(getattr(view_func, "required_methods", ()))


# starting with Flask 0.8 the view_func object can disable and
# force-enable the automatic options handling.
if provide_automatic_options is None:
provide_automatic_options = getattr(
view_func, "provide_automatic_options", None
)


if provide_automatic_options is None:
if "OPTIONS" not in methods:
provide_automatic_options = True
required_methods.add("OPTIONS")
else:
provide_automatic_options = False


# Add the required methods now.
methods |= required_methods


# 创建 rule
rule = self.url_rule_class(rule, methods=methods, **options)
rule.provide_automatic_options = provide_automatic_options


# 把 rule 添加到 url_map
self.url_map.add(rule)
if view_func is not None:
old_func = self.view_functions.get(endpoint)
if old_func is not None and old_func != view_func:
raise AssertionError(
"View function mapping is overwriting an "
"existing endpoint function: %s" % endpoint
)
# 把 view_func 添加到 view_functions 字典
self.view_functions[endpoint] = view_func

扒源码 - 一个请求在flask中经历了什么_flask_03


排版:小闫

图片素材:小闫

文案:小闫

长按扫描下方二维码即刻关注小闫


扒源码 - 一个请求在flask中经历了什么_python_04扒源码 - 一个请求在flask中经历了什么_ide_05扒源码 - 一个请求在flask中经历了什么_python_06