Flask源码剖析(六):响应是怎么实现的

963 阅读4分钟

前言

接着此前Flask源码剖析系列,这次来看看Flask是怎么样生成响应的。

基本使用

Flask会将视图函数返回的值作为Response返回给客户端,这一步对Flask框架使用者而言是透明的,最基本的用法如下:

@app.route('/')
def hello():
    return 'Hello, 二两!', 200, {'Content-Type': 'application/json'}

在hello方法中,返回了了http状态、body以及header等,该方法返回的其实是一个tuple,这里究竟发送了什么?才让一个tuple变为一个Response。

Flask中的响应(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中的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

如果想自定义Response,直接继承Flask中的Response则可。

from flask import Flask, Response

class MyResponse(Response):
    pass

app = Flask(__name__)
app.response_class = MyResp

结尾

Flask响应相关的内容就介绍完了,如果本文对你有帮助,点击「在看」支持一下二两。