Flask 源码分析完整教程目录:https://www.cnblogs.com/nickchen121/p/14763457.html

一、flask 源码解析:请求

1.1 简介

对于物理链路来说,请求只是不同电压信号,它根本不知道也不需要知道请求格式和内容到底是怎样的;

对于 TCP 层来说,请求就是传输的数据(二进制的数据流),它只要发送给对应的应用程序就行了;

对于 HTTP 层的服务器来说,请求必须是符合 HTTP 协议的内容;

对于 WSGI server 来说,请求又变成了文件流,它要读取其中的内容,把 HTTP 请求包含的各种信息保存到一个字典中,调用 WSGI app;

对于 flask app 来说,请求就是一个对象,当需要某些信息的时候,只需要读取该对象的属性或者方法就行了。

可以看到,虽然是同样的请求数据,在不同的阶段和不同组件看来,是完全不同的形式。因为每个组件都有它本身的目的和功能,这和生活中的事情一个道理:对于同样的事情,不同的人或者同一个人不同人生阶段的理解是不一样的。

这篇文章呢,我们只考虑最后一个内容,flask 怎么看待请求。

1.2 请求

我们知道要访问 flask 的请求对象非常简单,只需要 from flask import request

from flask import request

with app.request_context(environ):
assert request.method == 'POST'

前面一篇文章 已经介绍了这个神奇的变量是怎么工作的,它最后对应了 flask.wrappers:Request 类的对象。

这个类内部的实现虽然我们还不清楚,但是我们知道它接受 WSGI server 传递过来的 environ字典变量,并提供了很多常用的属性和方法可以使用,比如请求的 method、path、args 等。

请求还有一个不那么明显的特性——它不能被应用修改,应用只能读取请求的数据。

这个类的定义很简单,它继承了 werkzeug.wrappers:Request,然后添加了一些属性,这些属性和 flask 的逻辑有关,比如 view_args、blueprint、json 处理等。它的代码如下:

from werkzeug.wrappers import Request as RequestBase

class Request(RequestBase):
"""
The request object is a :class:`~werkzeug.wrappers.Request` subclass and
provides all of the attributes Werkzeug defines plus a few Flask
specific ones.
""" #: The internal URL rule that matched the request. This can be
#: useful to inspect which methods are allowed for the URL from
#: a before/after handler (``request.url_rule.methods``) etc.
url_rule = None #: A dict of view arguments that matched the request. If an exception
#: happened when matching, this will be ``None``.
view_args = None @property
def max_content_length(self):
"""Read-only view of the ``MAX_CONTENT_LENGTH`` config key."""
ctx = _request_ctx_stack.top
if ctx is not None:
return ctx.app.config['MAX_CONTENT_LENGTH'] @property
def endpoint(self):
"""The endpoint that matched the request. This in combination with
:attr:`view_args` can be used to reconstruct the same or a
modified URL. If an exception happened when matching, this will
be ``None``.
"""
if self.url_rule is not None:
return self.url_rule.endpoint @property
def blueprint(self):
"""The name of the current blueprint"""
if self.url_rule and '.' in self.url_rule.endpoint:
return self.url_rule.endpoint.rsplit('.', 1)[0] @property
def is_json(self):
mt = self.mimetype
if mt == 'application/json':
return True
if mt.startswith('application/') and mt.endswith('+json'):
return True
return False

这段代码没有什难理解的地方,唯一需要说明的就是 @property 装饰符能够把类的方法变成属性,这是 python 中经常见到的用法。

接着我们就要看 werkzeug.wrappers:Request

class Request(BaseRequest, AcceptMixin, ETagRequestMixin,
UserAgentMixin, AuthorizationMixin,
CommonRequestDescriptorsMixin): """Full featured request object implementing the following mixins: - :class:`AcceptMixin` for accept header parsing
- :class:`ETagRequestMixin` for etag and cache control handling
- :class:`UserAgentMixin` for user agent introspection
- :class:`AuthorizationMixin` for http auth handling
- :class:`CommonRequestDescriptorsMixin` for common headers
"""

这个方法有一点比较特殊,它没有任何的 body。但是有多个基类,第一个是 BaseRequest,其他的都是各种 Mixin

这里要讲一下 Mixin 机制,这是 python 多继承的一种方式,如果你希望某个类可以自行组合它的特性(比如这里的情况),或者希望某个特性用在多个类中,就可以使用 Mixin。

如果我们只需要能处理各种 Accept 头部的请求,可以这样做:

class Request(BaseRequest, AcceptMixin)
pass

但是不要滥用 Mixin,在大多数情况下子类继承了父类,然后实现需要的逻辑就能满足需求。

我们先来看看 BaseRequest:

class BaseRequest(object):
def __init__(self, environ, populate_request=True, shallow=False):
self.environ = environ
if populate_request and not shallow:
self.environ['werkzeug.request'] = self
self.shallow = shallow

能看到实例化需要的唯一变量是 environ,它只是简单地把变量保存下来,并没有做进一步的处理。Request 的内容很多,其中相当一部分是被 @cached_property 装饰的方法,比如下面这种:

    @cached_property
def args(self):
"""The parsed URL parameters."""
return url_decode(wsgi_get_bytes(self.environ.get('QUERY_STRING', '')),
self.url_charset, errors=self.encoding_errors,
cls=self.parameter_storage_class) @cached_property
def stream(self):
"""The stream to read incoming data from. Unlike :attr:`input_stream`
this stream is properly guarded that you can't accidentally read past
the length of the input. Werkzeug will internally always refer to
this stream to read data which makes it possible to wrap this
object with a stream that does filtering.
"""
_assert_not_shallow(self)
return get_input_stream(self.environ) @cached_property
def form(self):
"""The form parameters."""
self._load_form_data()
return self.form @cached_property
def cookies(self):
"""Read only access to the retrieved cookie values as dictionary."""
return parse_cookie(self.environ, self.charset,
self.encoding_errors,
cls=self.dict_storage_class) @cached_property
def headers(self):
"""The headers from the WSGI environ as immutable
:class:`~werkzeug.datastructures.EnvironHeaders`.
"""
return EnvironHeaders(self.environ)

@cached_property 从名字就能看出来,它是 @property 的升级版,添加了缓存功能。我们知道

@property 能把某个方法转换成属性,每次访问属性的时候,它都会执行底层的方法作为结果返回。

@cached_property 也一样,区别是只有第一次访问的时候才会调用底层的方法,后续的方法会直接使用之前返回的值。

那么它是如何实现的呢?我们能在 werkzeug.utils 找到它的定义:

class cached_property(property):

    """A decorator that converts a function into a lazy property.  The
function wrapped is called the first time to retrieve the result
and then that calculated result is used the next time you access
the value. The class has to have a `__dict__` in order for this property to
work.
""" # implementation detail: A subclass of python's builtin property
# decorator, we override __get__ to check for a cached value. If one
# choses to invoke __get__ by hand the property will still work as
# expected because the lookup logic is replicated in __get__ for
# manual invocation. def __init__(self, func, name=None, doc=None):
self.__name__ = name or func.__name__
self.__module__ = func.__module__
self.__doc__ = doc or func.__doc__
self.func = func def __set__(self, obj, value):
obj.__dict__[self.__name__] = value def __get__(self, obj, type=None):
if obj is None:
return self
value = obj.__dict__.get(self.__name__, _missing)
if value is _missing:
value = self.func(obj)
obj.__dict__[self.__name__] = value
return value

这个装饰器同时也是实现了 __set____get__ 方法的描述器

访问它装饰的属性,就会调用 __get__ 方法,这个方法先在 obj.__dict__ 中寻找是否已经存在对应的值。如果存在,就直接返回;如果不存在,调用底层的函数

self.func,并把得到的值保存起来,再返回。这也是它能实现缓存的原因:因为它会把函数的值作为属性保存到对象中。

关于 Request 内部各种属性的实现,就不分析了,因为它们每个具体的实现都不太一样,也不复杂,无外乎对 environ 字典中某些字段做一些处理和计算。

接下来回过头来看看 Mixin,这里只用 AcceptMixin 作为例子:

class AcceptMixin(object):

    @cached_property
def accept_mimetypes(self):
return parse_accept_header(self.environ.get('HTTP_ACCEPT'), MIMEAccept) @cached_property
def accept_charsets(self):
return parse_accept_header(self.environ.get('HTTP_ACCEPT_CHARSET'),
CharsetAccept) @cached_property
def accept_encodings(self):
return parse_accept_header(self.environ.get('HTTP_ACCEPT_ENCODING')) @cached_property
def accept_languages(self):
return parse_accept_header(self.environ.get('HTTP_ACCEPT_LANGUAGE'),
LanguageAccept)

AcceptMixin 实现了请求内容协商的部分,比如请求接受的语言、编码格式、相应内容等。

它也是定义了很多 @cached_property 方法,虽然自己没有 __init__ 方法,但是也直接使用了

self.environ,因此它并不能直接使用,只能和 BaseRequest 一起出现。

5.flask 源码解析:请求的更多相关文章

  1. flask源码解析之上下文为什么用栈

    楔子 我在之前的文章<flask源码解析之上下文>中对flask上下文流程进行了详细的说明,但是在学习的过程中我一直在思考flask上下文中为什么要使用栈完成对请求上下文和应用上下文的入栈 ...

  2. 浅谈flask源码之请求过程

    更新时间:2018年07月26日 09:51:36   作者:Dear.   我要评论   这篇文章主要介绍了浅谈flask源码之请求过程,小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随 ...

  3. flask源码解析之上下文

    引入 对于flask而言,其请求过程与django有着截然不同的流程.在django中是将请求一步步封装最终传入视图函数的参数中,但是在flask中,视图函数中并没有请求参数,而是将请求通过上下文机制 ...

  4. flask源码解析之DispatcherMiddleware

    DispatcherMiddleware作用 实现多app的应用,完成路由分发的功能 如何使用 from werkzeug.wsgi import DispatcherMiddleware from ...

  5. Flask源码解析:Flask应用执行流程及原理

    WSGI WSGI:全称是Web Server Gateway Interface,WSGI不是服务器,python模块,框架,API或者任何软件,只是一种规范,描述服务器端如何与web应用程序通信的 ...

  6. flask源码解析之session

    内容回顾 cookie与session的区别: 1. session 是保存在服务端的键值对 2. cookie 只能保存4096个字节的数据,但是session不受限制 3. cookie保存在浏览 ...

  7. flask源码剖析--请求流程

    想了解这篇里面的内容,请先去了解我另外一篇博客Flask上下文 在了解flask之前,我们需要了解两个小知识点 偏函数 import functools def func(a1,a2): print( ...

  8. flask 源码解析:上下文(一)

    文章出处  https://www.cnblogs.com/jackchengcc/archive/2018/11/29/10025949.html 一:什么是上下文 每一段程序都有很多外部变量.只有 ...

  9. 【Flask源码分析——请求上下文与应用上下文】

    Flask中有两种上下文,请求上下文和应用上下文.两者的作用域都处于一个请求的局部中. 查看源代码,上下文类在flask.ctx模块中定义 AppContext类定义应用上下文,app是当前应用Web ...

  10. axios源码解析 - 请求拦截器

    axios请求拦截器,也就是在请求发送之前执行自定义的函数. axios源码版本 - ^0.27.2 (源码是精简版) 平时在业务中会这样去写请求拦截器,代码如下: // 创建一个新的实例 var s ...

随机推荐

  1. 【Vue2】Axios、Async+Await、解构赋值

    Axios入门使用,Async和Await用法,解构赋值语法 <!DOCTYPE html> <html lang="en"> <head> & ...

  2. 【Mybatis + Spring】 Mybatis - Spring 结合

    环境搭建 EvBuild 软件环境准备 - MySQL 5.0 + - IDEA 2018 + - JDK1.8 + 依赖包相关 - Junit单元测试 - JDBC驱动 - Mybatis 组件 - ...

  3. 【转载】大模型时代的PDF解析工具

    本文来自博客园,作者:叶伟民,转载请注明原文链接:https://www.cnblogs.com/adalovelacer/p/18092208/pdf-tools-for-large-languag ...

  4. 腾达Tenda电力猫PA3的无线名称和密码

    趁着2023年的双11,买了一对腾达电力猫,毕竟在家里长距离使用这东西还是蛮方便的. =============================== 配置其实蛮简单的,配对嘛,就是两个都插上电,然后在 ...

  5. 【转载】 Sun RPC 编程简介

    原文地址: http://blog.chinaunix.net/uid-1724205-id-2813082.html ======================================== ...

  6. vue前端自适应布局,一步到位所有自适应

    vue前端自适应布局,一步到位所有自适应 页面展示 实现内容 1,左右布局 左侧固定宽带,右侧自适应剩余的宽度. 中间一条分割线,可以拖拉,自适应调整左右侧的宽度. 左侧的高度超长自动出现横向滚动条, ...

  7. Win32_GDI_五星红旗绘制

    五星红旗画法 设置矩形长与高的比为3:2 把矩形分为四个相等的长方形 把左上角宽分为15份,高分为10份 定位大五角星圆心位置,x为宽的5份,y为高的5份 计算五角星五个点的坐标 void SetFi ...

  8. PySide6/PyQt开发经验总结(2) - 设置快捷键

    Qt设置快捷键 本文仅供本人知识总结使用,所以内容会比较浅显,不喜勿喷. 目录 Qt设置快捷键 一.需要的类 QShortcut 函数: 二.设置快捷键 官方文档原文翻译: 我的理解: 一.需要的类 ...

  9. .net framework创建Nuget包简要教程

    前言 nuget包生成在.net framework和.net core/.net standard下的是不同的. .net framework中稍微复杂些,下面记录了我自己在.net framewo ...

  10. 学习真DDD的最佳路径

    本文书接上回<DDD是软件工程的第一性原理?>,关注公众号(老肖想当外语大佬)获取信息: 最新文章更新: DDD框架源码(.NET.Java双平台): 加群畅聊,建模分析.技术实现交流: ...