flask 启动函数返回值的剖析
2.启动函数返回值的剖析
本次剖析根据 flask 内部的处理流程将返回值推导出。
2.1 源码剖析
from werkzeug import run_simple
def func(environ,start_response):
# 该函数必须要加上参数:environ,和start_response;否则报错参数异常,因为要满足包内部函数的调用。
print("请求来了")
return ????? # return 的返回值是什么呢
if __name__ == '__main__':
# app.run()
run_simple("127.0.0.1",5000,func)
>>> 点击请求地址(请求过来时)
>>> 请求来了
>>> 报错
# 函数被执行相当于 func()
flask 的启动函数执行的是__call__()
方法,该方法的源码为
def __call__(self, environ: dict, start_response: t.Callable) -> t.Any:
"""The WSGI server calls the Flask application object as the
WSGI application. This calls :meth:`wsgi_app`, which can be
wrapped to apply middleware.
"""
return self.wsgi_app(environ, start_response)
此处调用相关的 wsgi_app(...)
方法,该方法的源码如下
def wsgi_app(self, environ: dict, start_response: t.Callable) -> t.Any:
ctx = self.request_context(environ)
error: t.Optional[BaseException] = None
try:
try:
ctx.push()
response = self.full_dispatch_request() # 创建response返回值对象。
except Exception as e:
error = e
response = self.handle_exception(e)
except: # noqa: B001
error = sys.exc_info()[1]
raise
# 这里最终返回的是一个 response 对象,response 来源于上方的 full_dispatch_request函数。
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
ctx.auto_pop(error)
根据相关的注释信息去查看相关函数的full_dispatch_request
函数的源码。
def full_dispatch_request(self) -> Response: # 此函数的返回值类型是Response
"""Dispatches the request and on top of that performs request
pre and postprocessing as well as HTTP exception catching and
error handling.
.. versionadded:: 0.7
"""
self.try_trigger_before_first_request_functions()
try:
request_started.send(self)
rv = self.preprocess_request()
if rv is None:
rv = self.dispatch_request()
except Exception as e:
rv = self.handle_user_exception(e)
return self.finalize_request(rv) # 返回值执行的函数。rv 表示了返回值中的具体内容例如一个字符串
知识补充:->
常常出现在python函数定义的函数名后面,为函数添加元数据,描述函数的返回类型,也可以理解为给函数添加注解。
通俗理解:就是表明了本函数的参数类型和返回值的类型。
参考文献:https://blog.csdn.net/mahoon411/article/details/125363646
finalize_request
函数源码,如下所示:
def finalize_request(
self,
rv: t.Union[ft.ResponseReturnValue, HTTPException],
from_error_handler: bool = False,
) -> Response: # 描述了相关的返回值。
"""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) # 其中 rv 表示返回值的具体内容,此函数创建response对象
try:
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 #返回的是一个 response 对象,由上述的 make_response 进行返回。
make_response()
函数的返回值如下,本函数内部较长将删除一些源码注释,提高观察效率,如有需要可以在源码中进行观看:
def make_response(self, rv: ft.ResponseReturnValue) -> Response:
status = headers = None
# unpack tuple returns
if isinstance(rv, tuple): # 检查 rv 是否为相关的类型
len_rv = len(rv)
# a 3-tuple is unpacked directly
if len_rv == 3:
rv, status, headers = rv # type: ignore[misc]
# decide if a 2-tuple has status or headers
elif len_rv == 2:
if isinstance(rv[1], (Headers, dict, tuple, list)):
rv, headers = rv
else:
rv, status = rv # type: ignore[assignment,misc]
# other sized tuples are not allowed
else:
raise TypeError(
"The view function did not return a valid response tuple."
" The tuple must have the form (body, status, headers),"
" (body, status), or (body, headers)."
)
# the body must not be None
if rv is None:
raise TypeError(
f"The view function for {request.endpoint!r} did not"
" return a valid response. The function either returned"
" None or ended without a return statement."
)
# make sure the body is an instance of the response class
if not isinstance(rv, self.response_class):
if isinstance(rv, (str, bytes, bytearray)):
# let the response class set the status and headers instead of
# waiting to do it manually, so that the class can handle any
# special logic
rv = self.response_class(
rv,
status=status,
headers=headers, # type: ignore[arg-type]
)
status = headers = None
elif isinstance(rv, dict):
rv = jsonify(rv)
elif isinstance(rv, BaseResponse) or callable(rv):
# evaluate a WSGI callable, or coerce a different response
# class to the correct type
try:
rv = self.response_class.force_type(
rv, request.environ # type: ignore[arg-type]
)
except TypeError as e:
raise TypeError(
f"{e}\nThe view function did not return a valid"
" response. The return type must be a string,"
" dict, tuple, Response instance, or WSGI"
f" callable, but it was a {type(rv).__name__}."
).with_traceback(sys.exc_info()[2]) from None
else:
raise TypeError(
"The view function did not return a valid"
" response. The return type must be a string,"
" dict, tuple, Response instance, or WSGI"
f" callable, but it was a {type(rv).__name__}."
)
'''
重点:以上的结构为三个大的if 判断结构,判断rv是否为相关的类型。
'''
rv = t.cast(Response, rv) # 此处是将rv 转换为对象的 Response 的对象。
# prefer the status if it was provided
if status is not None:
if isinstance(status, (str, bytes, bytearray)):
rv.status = status
else:
rv.status_code = status
# extend existing headers with provided headers
if headers:
rv.headers.update(headers) # type: ignore[arg-type]
return rv
cast()
函数的源码如图所示
def cast(typ, val):
"""Cast a value to a type.
This returns the value unchanged. To the type checker this
signals that the return value has the designated type, but at
runtime we intentionally don't check anything (we want this
to be as fast as possible).
"""
# 通过注释可以发现,这个函数的功能就是转换数据到执行的类型
return val
rv = t.cast(Response, rv)
此处是将rv 转换为对象的 Response 的对象。通过源码可知,第一个参数为对象的类型。查看对象类型源码如下:
from werkzeug.wrappers import Response as ResponseBase # 源码内部的导包
class Response(ResponseBase): #该类继承于 ResponeBase 类。即Response
"""The response object that is used by default in Flask. Works like the
response object from Werkzeug but is set to have an HTML mimetype by
default. Quite often you don't have to create this object yourself because
:meth:`~flask.Flask.make_response` will take care of that for you.
If you want to replace the response object used you can subclass this and
set :attr:`~flask.Flask.response_class` to your subclass.
.. versionchanged:: 1.0
JSON support is added to the response, like the request. This is useful
when testing to get the test client response data as JSON.
.. versionchanged:: 1.0
Added :attr:`max_cookie_size`.
"""
default_mimetype = "text/html"
json_module = json
autocorrect_location_header = False
@property
def max_cookie_size(self) -> int: # type: ignore
"""Read-only view of the :data:`MAX_COOKIE_SIZE` config key.
See :attr:`~werkzeug.wrappers.Response.max_cookie_size` in
Werkzeug's docs.
"""
if current_app:
return current_app.config["MAX_COOKIE_SIZE"]
# return Werkzeug's default when not in an app context
return super().max_cookie_size
通过源码的注释,可以发现这是一个 flask 的内置类。可以直接使用,通过 pycharm
的路径显示(该类在wrappers.py
文件下,改文件在flask 文件夹下【源码文件】。)我们也可以得出他的导包路径为
from flask.wrappers import Response
而此类则是继承于Response
类(源码内部到包时修改为Response
),该类的源码如下图所示:
class Response(_SansIOResponse):
pass
# 该类的内容太长,不在此处粘贴。
该类是werkzug
下的类,源码文件位于werkzug
文件夹下的 warppers
文件夹下的response
文件中,因此导包路径为
from werkzeug.wrappers.response import Response
至此,可得 flask 中请求的核心功能大多都是有Werkzug
包内部完成的。
2.2 返回值的示例
2.2.1 依赖flask
根据 wsgi_app()
中的源码可知,response对象创建后需要将参数environ, start_response
再进行传入。
def wsgi_app(self, environ: dict, start_response: t.Callable) -> t.Any:
ctx = self.request_context(environ)
error: t.Optional[BaseException] = None
try:
try:
ctx.push()
response = self.full_dispatch_request() # 创建response返回值对象。
except Exception as e:
error = e
response = self.handle_exception(e)
except: # noqa: B001
error = sys.exc_info()[1]
raise
# 这里最终返回的是一个 response 对象,response 来源于上方的 full_dispatch_request函数。
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
ctx.auto_pop(error)
根据上述流程,将代码进行改造
from werkzeug import run_simple
from flask.wrappers import Response
def func(environ, start_response):
# 该函数必须要加上参数:environ,和start_response;否则报错参数异常,因为要满足包内部函数的调用。
print("请求来了")
response = Response("Hello World") # 创建相关的Response
return response(environ,start_response) # 封装参数进行返回
if __name__ == '__main__':
# app.run()
run_simple("127.0.0.1", 5000, func)
>>> # 正常在浏览器中响应,正常返回
>>> * Running on http://127.0.0.1:5000 (Press CTRL+C to quit)
>>> 请求来了
>>> 127.0.0.1 - - [21/Jul/2022 10:09:09] "GET / HTTP/1.1" 200 -
>>> 请求来了
>>> 127.0.0.1 - - [21/Jul/2022 10:09:09] "GET /favicon.ico HTTP/1.1" 200 -
- 此方法,依赖于flask中的值,而 flask 的请求核心都是依赖于
werkzug
,说明此处可以用相关的父类进行替代。
2.2.2 原生实现
from werkzeug import run_simple
from werkzeug.wrappers.response import Response # 导入的是werkzug 包
def func(environ, start_response):
# 该函数必须要加上参数:environ,和start_response;否则报错参数异常,因为要满足包内部函数的调用。
print("请求来了")
response = Response("Hello World")
return response(environ,start_response) # return 的返回值是什么呢
if __name__ == '__main__':
# app.run()
run_simple("127.0.0.1", 5000, func)
>>> # 正常在浏览器中响应,正常返回
>>> * Running on http://127.0.0.1:5000 (Press CTRL+C to quit)
>>> 127.0.0.1 - - [21/Jul/2022 10:16:40] "GET / HTTP/1.1" 200 -
>>> 请求来了
>>> 请求来了
>>> 127.0.0.1 - - [21/Jul/2022 10:16:40] "GET /favicon.ico HTTP/1.1" 200 -
- 总结:
flask
的网络请求核心全部都是由werkzug
进行的合成。