6.flask 源码解析:响应
Flask 源码分析完整教程目录:https://www.cnblogs.com/nickchen121/p/14763457.html
一、flask 源码解析:响应
1.1 response 简介
在 flask 应用中,我们只需要编写 view 函数,并不需要直接和响应(response)打交道,flask 会自动生成响应返回给客户端。
The return value from a view function is automatically converted into a response object for you.
—— Flask docs
我们知道 HTTP 响应分为三个部分:
状态栏(HTTP 版本、状态码和说明)、头部(以冒号隔开的字符对,用于各种控制和协商)、body(服务端返回的数据)。比如下面访问一个地址的响应:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Cache-Control: max-age=600
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Wed, 15 Feb 2017 07:50:41 GMT
Expires: Wed, 15 Feb 2017 08:00:41 GMT
Last-Modified: Wed, 15 Feb 2017 07:46:56 GMT
Server: GitHub.com
Transfer-Encoding: chunked
X-GitHub-Request-Id: D2A7:7B6B:33C0628:47C44B9:58A40851
<BODY>
flask 自然也会提供所有这些数据的操作,视图函数就支持返回三个值:第一个是返回的数据,第二个是状态码,第三个是头部字典。比如:
@app.route('/')
def hello_world():
return 'Hello, World!', 201, {'X-Foo': 'bar'}
这篇文章就讲讲这背后的魔法。
1.2 flask 响应(response)
在 flask 源码解析:应用启动流程 的最后,我们讲到 full_dispatch_request
在调用路由的视图函数之后,会调用 finalize_request
进行最后的处理,在这个方法里就包含了 response 对象的生成和处理逻辑。
finalize_request
的代码如下:
def finalize_request(self, rv, from_error_handler=False):
"""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.
"""
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
里面有两个方法调用:make_response
根据视图函数的返回值生成 response 对象,process_response
对 response 做一些后续的处理(比如执行 hooks 函数)。我们先来看看 make_response
:
def make_response(self, rv):
"""Converts the return value from a view function to a real
response object that is an instance of :attr:`response_class`.
"""
status_or_headers = headers = None
if isinstance(rv, tuple):
rv, status_or_headers, headers = rv + (None,) * (3 - len(rv))
if isinstance(status_or_headers, (dict, list)):
headers, status_or_headers = status_or_headers, None
if not isinstance(rv, self.response_class):
# When we create a response object directly, we let the constructor
# set the headers and status. We do this because there can be
# some extra logic involved when creating these objects with
# specific values (like default content type selection).
if isinstance(rv, (text_type, bytes, bytearray)):
rv = self.response_class(rv, headers=headers,
status=status_or_headers)
headers = status_or_headers = None
if status_or_headers is not None:
if isinstance(status_or_headers, string_types):
rv.status = status_or_headers
else:
rv.status_code = status_or_headers
if headers:
rv.headers.extend(headers)
return rv
make_response
是视图函数能返回多个不同数量和类型值的关键,因为它能处理这些情况,统一把它们转换成 response。
如果返回值本身就是 Response 实例,就直接使用它;如果返回值是字符串类型,就把它作为响应的 body,并自动设置状态码和头部信息;
如果返回值是 tuple,会尝试用 (response, status, headers) 或者 (response, headers) 去解析。
NOTE:因为视图函数可以返回 Response
对象,因此我们可以直接操作 Response
。
不管视图函数返回的是什么,最终都会变成 Response
对象,那么我们就来看看 Response
的定义:
from werkzeug.wrappers import Response as ResponseBase
class Response(ResponseBase):
"""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.
"""
default_mimetype = 'text/html'
Flask 的 Response
类非常简单,它只是继承了 werkzeug.wrappers:Response
,然后设置默认返回类型为 html。
不过从注释中,我们得到两条很有用的信息:
- 一般情况下不要直接操作
Response
对象,而是使用make_response
方法来生成它 - 如果需要使用自定义的响应对象,可以覆盖 flask app 对象的
response_class
属性。
继续,下面就要分析 werkzeug 对应的代码了。
1.3 werkzeug response
werkzeug 实现的 response 定义在 werkzeug/wrappers.py
文件中:
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
"""
和我们在 flask 请求分析的 Request 类一样,这里使用了 Mixin 机制。BaseResponse
精简后的大概框架如下:
class BaseResponse(object):
"""Base response class. The most important fact about a response object
is that it's a regular WSGI application. It's initialized with a couple
of response parameters (headers, body, status code etc.) and will start a
valid WSGI response when called with the environ and start response
callable.
"""
charset = 'utf-8'
default_status = 200
default_mimetype = 'text/plain'
automatically_set_content_length = True
def __init__(self, response=None, status=None, headers=None,
mimetype=None, content_type=None, direct_passthrough=False):
pass
BaseResponse
有一些类属性,定义了默认的值,比如默认字符编码是 utf-8,默认状态码是 200 等。实例化的时候接受的参数有:
- response: 字符串或者其他 iterable 对象,作为响应的 body
- status: 状态码,可以是整数,也可以是字符串
- headers: 响应的头部,可以是个列表,也可以是
werkzeug.datastructures.Headers
对象 - mimetype: mimetype 类型,告诉客户端响应 body 的格式,默认是文本格式
- content_type: 响应头部的
Content-Type
内容
所有这些参数都是可选的,默认情况下会生成一个状态码为 200,没有任何 body 的响应。status、status_code 作为 Response
的属性,可以直接读取和修改。body 数据在内部保存为 iterable 的类型,
但是对外也提供了直接读写的接口 self.data
:
def get_data(self, as_text=False):
"""The string representation of the request body. Whenever you call
this property the request iterable is encoded and flattened.
"""
self._ensure_sequence()
rv = b''.join(self.iter_encoded())
if as_text:
rv = rv.decode(self.charset)
return rv
def set_data(self, value):
"""Sets a new string as response. The value set must either by a
unicode or bytestring.
"""
if isinstance(value, text_type):
value = value.encode(self.charset)
else:
value = bytes(value)
self.response = [value]
if self.automatically_set_content_length:
self.headers['Content-Length'] = str(len(value))
data = property(get_data, set_data, doc='''
A descriptor that calls :meth:`get_data` and :meth:`set_data`. This
should not be used and will eventually get deprecated.
''')
body 字符的编码和长度都是自动设置的,用户不需要手动处理。
至于头部的存储,werkzeug 使用的是类似于字典的 werkzeug.datastructures:Headers
类。在flask 源码解析:请求这篇文章中,我们没有详细
解释头部的存储,那么这篇文章就具体分析一下吧。
Headers
这个类的提供了很多和字典相同的接口:keys、values、iterms,但是和字典的区别在于它保存的值是有序的,而且允许相同 key 的值存在。
为什么这么设计呢?因为着更符合 HTTP 头部的特性。先来看看有序,在 HTTP 传送的过程中,如果头部各个 key-value 键值对顺序发生变化,有些代理或者客户端等组件会认为请求被篡改而丢弃或者拒绝请求的处理,所以最好把头部设置为有序的,用户按照什么顺序设置的,就按照什么顺序存储;再说说相同 key 的问题,这是因为 HTTP 头部同一个 key 可能有多个 value(比如 Accept、SetCookie头部)。那么这个看起比较特殊的字典是怎么实现的呢?来看代码:
class Headers(object):
"""An object that stores some headers. It has a dict-like interface
but is ordered and can store the same keys multiple times.
"""
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)
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)
可以看到,头部信息在内部存储为二元组构成的列表,这样就能同时保证它的有序性和重复性。一个核心的方法是 __getitem__
,它定义了如何获取头部中的信息:
- 通过下标
header[3]
,直接返回对应未知存储的键值对元组 - 通过 key,返回 value
header['Accept']
,返回匹配的第一个 value 值 - 通过 slice
header[3:7]
,返回另外一个Headers
对象,保存了 slice 中所有的数据
然后实现 keys()
、items()
、pop()
、setdefault()
等方法让它表现出来字典的特性,除此之外还有 add()
、extend()
、add_header()
等和字典无关的方法方便操作。
1.4 自定义 response
如果需要扩展 flask Response
的功能,或者干脆把它替换掉,只要修改 flask app 的 response_class
属性就可以了,比如:
from flask import Flask, Response
class MyResponse(Response):
pass
app = Flask(__name__)
app.response_class = MyResponse
6.flask 源码解析:响应的更多相关文章
- flask源码解析之上下文为什么用栈
楔子 我在之前的文章<flask源码解析之上下文>中对flask上下文流程进行了详细的说明,但是在学习的过程中我一直在思考flask上下文中为什么要使用栈完成对请求上下文和应用上下文的入栈 ...
- flask源码解析之上下文
引入 对于flask而言,其请求过程与django有着截然不同的流程.在django中是将请求一步步封装最终传入视图函数的参数中,但是在flask中,视图函数中并没有请求参数,而是将请求通过上下文机制 ...
- Flask源码解析:Flask应用执行流程及原理
WSGI WSGI:全称是Web Server Gateway Interface,WSGI不是服务器,python模块,框架,API或者任何软件,只是一种规范,描述服务器端如何与web应用程序通信的 ...
- flask源码解析之session
内容回顾 cookie与session的区别: 1. session 是保存在服务端的键值对 2. cookie 只能保存4096个字节的数据,但是session不受限制 3. cookie保存在浏览 ...
- flask源码解析之DispatcherMiddleware
DispatcherMiddleware作用 实现多app的应用,完成路由分发的功能 如何使用 from werkzeug.wsgi import DispatcherMiddleware from ...
- flask 源码解析:上下文(一)
文章出处 https://www.cnblogs.com/jackchengcc/archive/2018/11/29/10025949.html 一:什么是上下文 每一段程序都有很多外部变量.只有 ...
- Flask源码解析:Flask上下文
一.上下文(Context) 什么是上下文: 每一段程序都有很多外部变量.只有像Add这种简单的函数才是没有外部变量的.一旦你的一段程序有了外部变量,这段程序就不完整,不能独立运行.你为了使他们运行, ...
- 用尽洪荒之力学习Flask源码
WSGIapp.run()werkzeug@app.route('/')ContextLocalLocalStackLocalProxyContext CreateStack pushStack po ...
- 【源码解析】BlockManager详解
1 Block管理模块的组件和功能 BlockManager:BlockManager源码解析 Driver和Executor都会创建 Block的put.get和remove等操作的实际执行者 Bl ...
- Flask之 请求,应用 上下文源码解析
什么是上下文? 每一段程序都有很多外部变量.只有像Add这种简单的函数才是没有外部变量的.一旦你的一段程序有了外部变量,这段程序就不完整,不能独立运行.你为了使他们运行,就要给所有的外部变量一个一个写 ...
随机推荐
- 【Excel】VBA编程 01 入门
视频地址: https://www.bilibili.com/video/BV1Q5411p71p 在Excel种需要打开[开发工具]和[启用所有宏]两点 打开开发工具选项 宏启用 菜单栏才会有开发工 ...
- 手把手使用 SVG + CSS 实现渐变进度环效果
效果 轨道 使用 svg 画个轨道 <svg viewBox="0 0 100 100"> <circle cx="50" cy=" ...
- 陆吾AI智能机械狗的通讯控制
陆吾AI智能机械狗现在是蛮有名的了,在YouTube上比较火的一个东西了,和波士顿机器狗不同,波士顿机器狗价格昂贵主要原因是其定位于工业领域的机械狗因此采用的是工业级的硬件,但是如果我们采用的家用环境 ...
- mybatis打印sql
转
我们在使用mybatis开发过程中,经常需要打印sql以及输入输出,下面说一下mybatis结合log4j打印sql的. 1.添加mybatis配置 mybatis的日志打印方式比较多,SLF4J | ...
- TSP 的遗传算法
省流:不如模拟退火 打 OI 的时候一直对乱搞很感兴趣,只是没时间学,现在算是弥补一下吧 旅行商问题(Traveling Salesman Problem, TSP):求无向图边权和最小的哈密顿回路 ...
- 【2】Kaggle 医学影像数据读取
赛题名称:RSNA 2024 Lumbar Spine Degenerative Classification 中文:腰椎退行性病变分类 kaggle官网赛题链接:https://www.kaggle ...
- 这应该是全网最详细的Vue3.5版本解读
前言 Vue3.5正式版在这两天发布了,网上已经有了不少关于Vue3.5版本的解读文章.但是欧阳发现这些文章对3.5中新增的功能介绍都不是很全,所以导致不少同学有个错觉,觉得Vue3.5版本不过如此, ...
- RuleLinKClient - 再也不担心表达引擎宕机了
原来有这么多时间 六月的那么一天,天气比以往时候都更凉爽,媳妇边收拾桌子,边漫不经心的对我说:你最近好像都没怎么阅读了. 正刷着新闻我,如同被一记响亮的晴空霹雳击中一般,不知所措.是了,最近几月诸事凑 ...
- 6.24Win&linux&分析后门 勒索病毒分析
操作系统应急响应 1.常见危害 暴力破解.漏洞利用.流量攻击(危害不确定) 木马控制(Webshell.PC木马等),病毒感染(挖矿.蠕虫.勒索等) 2.常见分析 计算机用户.端口.进程.启动项.计划 ...
- Identity – user login, forgot & reset password, 2fa, external login, logout 实战篇
前言 之前写过一篇 Identity – User Login, Forgot Password, Reset Password, Logout, 当时写的比较简陋, 今天有机会就写多一篇实战版. 建 ...