接着此前Flask源码剖析系列,这次来看看Flask是怎么样生成响应的。
Flask会将视图函数返回的值作为Response返回给客户端,这一步对Flask框架使用者而言是透明的,最基本的用法如下:
@app.route('/')
def hello():
return 'Hello, 二两!', 200, {'Content-Type': 'application/json'}
在hello方法中,返回了了http状态、body以及header等,该方法返回的其实是一个tuple,这里究竟发送了什么?才让一个tuple变为一个Response。
在本系列第一篇文章「Flask源码剖析(一):Flask启动流程」中提到了full_dispatch_request()方法,该方法会找到当前请求路由对应的方法,调用该方法,获得返回(即response),如果请求路由不存在,则进行错误处理,返回500错误。
在full_dispatch_request()方法中会调用finalize_request()方法对返回数据进行处理,response对象的构建就在该方法中实现,该方法源码如下。
# flask/app.py/Flask
def finalize_request(self, rv, from_error_handler=False):
response = self.make_response(rv)
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
finalize_request()方法调用make_response()方法将视图函数返回的tuple转为了response对象,随后再通过process_response()方法进行了相关的hooks处理,具体而言就是执行ctx._after_request_functions
变量中存放的方法。
这里重点看一下make_response()方法,其源码如下。
def make_response(self, rv):
status = headers = None
if isinstance(rv, tuple):
len_rv = len(rv)
if len_rv == 3:
rv, status, headers = rv
elif len_rv == 2:
if isinstance(rv[1], (Headers, dict, tuple, list)):
rv, headers = rv
else:
rv, status = rv
else:
raise TypeError(...)
if rv is None:
raise TypeError(...)
if not isinstance(rv, self.response_class):
if isinstance(rv, (text_type, bytes, bytearray)):
rv = self.response_class(rv, status=status, headers=headers)
status = headers = None
elif isinstance(rv, dict):
rv = jsonify(rv)
elif isinstance(rv, BaseResponse) or callable(rv):
try:
rv = self.response_class.force_type(rv, request.environ)
except TypeError as e:
new_error = TypeError(...)
reraise(TypeError, new_error, sys.exc_info()[2])
else:
raise TypeError(...)
if status is not None:
if isinstance(status, (text_type, bytes, bytearray)):
rv.status = status
else:
rv.status_code = status
if headers:
rv.headers.extend(headers)
return rv
make_response()方法写的非常直观,将传入的内容按不同的情况进行处理,最终通过response_class()将其其转为response对象。
如果你仔细看代码,会发现有些直接通过jsonify()方法就将内容返回了,其实jsonify()方法内部也使用了response_class()将内容转为response对象。
response_class其实就是Response类,只是换了个名字,目的是为了让人可以一眼就看明白make_response()方法的逻辑,make_response()方法中其实含有很多注释,但就算将注释全部删除,还是可以一眼看明白这个方法大致想做什么,这才是优秀的代码。
还是那个观点,没有必要过度语法糖,适度使用,清晰易理解最重要。
接着看一Response类,其代码如下。
class Response(ResponseBase, JSONMixin):
default_mimetype = "text/html"
def _get_data_for_json(self, cache):
return self.get_data()
@property
def max_cookie_size(self):
"""Read-only view of the :data:`MAX_COOKIE_SIZE` config key.
See :attr:`~werkzeug.wrappers.BaseResponse.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(Response, self).max_cookie_size
继承了werkzeug.wrappers:Response,本身没有实现什么逻辑。
依旧是属性的配方
# werkzeug/wrappers
class Response(
BaseResponse,
ETagResponseMixin,
ResponseStreamMixin,
CommonResponseDescriptorsMixin,
WWWAuthenticateMixin,
):
"""Full featured response object implementing the following mixins:
- :class:`ETagResponseMixin` for etag and cache control handling
- :class:`ResponseStreamMixin` to add support for the `stream` property
- :class:`CommonResponseDescriptorsMixin` for various HTTP descriptors
- :class:`WWWAuthenticateMixin` for HTTP authentication support
"""
通过Mixin机制,让具体逻辑都在BaseResponse中实现,简单看一下BaseResponse类的逻辑
class BaseResponse(object):
#: the charset of the response.
charset = "utf-8"
#: the default status if none is provided.
default_status = 200
#: the default mimetype if none is provided.
default_mimetype = "text/plain"
max_cookie_size = 4093
def __init__(
self,
response=None,
status=None,
headers=None,
mimetype=None,
content_type=None,
direct_passthrough=False,
):
# 构建Headers
if isinstance(headers, Headers):
self.headers = headers
elif not headers:
self.headers = Headers()
else:
self.headers = Headers(headers)
# ... 省略其余代码
在BaseResponse()中,定义了Response返回的默认属性,此外还提供了很多方法,具体细节,各位有兴致自行去查阅。
这里看一下Headers类的实现,该类用于定义Response中header的细节,其源码如下。
@native_itermethods(["keys", "values", "items"])
class Headers(object):
def __init__(self, defaults=None):
self._list = []
if defaults is not None:
if isinstance(defaults, (list, Headers)):
self._list.extend(defaults)
else:
self.extend(defaults)
Headers类通过list的形式构建出一个类似与dict的对象(操作方面像dict,key-value),这要做的目的是为了保证headers中元素的顺序,通过list来保证顺序,此外Headers类还运行使用相同的key存储不同的values,同样通过list来实现,这里看一下它的get()方法。
使用者可以通过如下方式使用
>>> d = Headers([('Content-Length', '42')])
>>> d.get('Content-Length', type=int)
42
其具体实现如下。
# werkzeug/datastructures.py/Headers
def __getitem__(self, key, _get_mode=False):
if not _get_mode:
if isinstance(key, integer_types):
return self._list[key]
elif isinstance(key, slice):
return self.__class__(self._list[key])
if not isinstance(key, string_types):
raise exceptions.BadRequestKeyError(key)
ikey = key.lower()
for k, v in self._list:
if k.lower() == ikey:
return v
if _get_mode:
raise KeyError()
raise exceptions.BadRequestKeyError(key)
def get(self, key, default=None, type=None, as_bytes=False):
try:
rv = self.__getitem__(key, _get_mode=True)
except KeyError:
return default
if as_bytes:
rv = rv.encode("latin1")
if type is None:
return rv
try:
return type(rv)
except ValueError:
return default
get()会调用__getitem__()
方法去获取具体的值,而__getitem__()
方法的主要逻辑就是遍历_list
,Headers所有的属性都以元组的形式存放在_list
中,没什么难理解的。
如果想自定义Response,直接继承Flask中的Response则可。
from flask import Flask, Response
class MyResponse(Response):
pass
app = Flask(__name__)
app.response_class = MyResp
Flask响应相关的内容就介绍完了,如果本文对你有帮助,点击「在看」支持一下二两。