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.