preface

After the previous Flask source code analysis series, this time we will look at how Flask generates responses.

The basic use

Flask returns the value returned by the view function to the client as a Response. This step is transparent to Flask framework users, and the basic usage is as follows:

@app.route('/')
def hello(a):
    return 'Hello, two two! '.200, {'Content-Type': 'application/json'}
Copy the code

In hello, it returns HTTP status, body, header, etc. This method returns a tuple. What is it sending? To turn a tuple into a Response.

Flask Response

Flask Source Code Analysis (PART 1) : Flask startup process “mentioned the full_dispatch_request() method, which will find the method corresponding to the current request route, call this method, get the return (i.e., response), if the request route does not exist, error processing, return 500 errors.

In full_dispatch_request() method, finalize_request() method will be called to process the returned data. The construction of response object is realized in this method. The source code of this method is as follows.

# 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
Copy the code

The finalize_request() method calls the make_response() method to convert the tuple returned by the view function into a response object, which is then fixed by the process_response() method. Specifically, execute the methods stored in the ctx._after_request_functions variable.

Here’s a look at the make_response() method, whose source code is shown below.

    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
Copy the code

The make_response() method is written very intuitively, processing incoming content on a case-by-case basis and finally turning it into a response object via response_class().

If you take a closer look at the code, you’ll see that some of the jsonify() methods return the content directly, and actually use response_class() internally to turn the content into a response object.

Response_class is actually Response class, but it’s called response_class so that you can see at a glance the logic of the make_response() method, which actually has a lot of comments in it, but even if you remove all the comments, You can still see at a glance what the method is trying to do, and that’s good code.

Again, there is no need for excessive grammatical sugar, moderation, clarity and ease of understanding are the most important.

Then look at the Response class, which has the following code.

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
Copy the code

Inherited werkzeug. Wrappers: Response, does not realize what logic.

Response class in Werkzeug

It’s still a recipe for attributes

# 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 """
Copy the code

BaseResponse uses Mixin mechanisms to implement specific logic in BaseResponse. Take a look at BaseResponse logic

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, ):
        # building Headers
        if isinstance(headers, Headers):
            self.headers = headers
        elif not headers:
            self.headers = Headers()
        else:
            self.headers = Headers(headers)
        #... Omit the rest of the code
Copy the code

BaseResponse() defines the default property to return Response, and provides a number of methods that you can read for details.

Here’s a look at the implementation of the Headers class, which defines the details of the Headers in Response. The source code is as follows.

@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)
Copy the code

The Headers class uses a list to construct a dice-like object (dict, key-value). The purpose of this is to ensure the order of the Headers elements. In addition, the Headers class also runs the same key to store different values, again through a list. Here’s a look at its get() method.

Users can use it in the following ways

>>> d = Headers([('Content-Length'.The '42')])
>>> d.get('Content-Length', type=int)
42
Copy the code

Its concrete implementation is as follows.

# 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
Copy the code

Get () calls __getitem__() to get the value, and the main logic of __getitem__() is to traverse the _list. All the Headers attributes are stored in the _list as tuples, so there’s nothing hard to understand.

Custom Response

If you want to customize Response, inherit Response from Flask.

from flask import Flask, Response

class MyResponse(Response):
    pass

app = Flask(__name__)
app.response_class = MyResp
Copy the code

At the end

Flask responses are done. If this article has been helpful to you, click “Look at it” to support Flask responses.