在使用Flask时,当我们需要获取一些请求相关的信息时,会使用 from flask import request ,然后从request对象中就可以拿到请求的相关信息。今天就来一探request背后的实现原理。

先看下request相关的代码

#flask/globals.py
from functools import partial

from werkzeug.local import LocalProxy
from werkzeug.local import LocalStack

def _lookup_req_object(name):
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(_request_ctx_err_msg)
return getattr(top, name)


def _lookup_app_object(name):
top = _app_ctx_stack.top
if top is None:
raise RuntimeError(_app_ctx_err_msg)
return getattr(top, name)


def _find_app():
top = _app_ctx_stack.top
if top is None:
raise RuntimeError(_app_ctx_err_msg)
return top.app


# context locals
# 保存RequestContext对象的栈
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, "request"))
session = LocalProxy(partial(_lookup_req_object, "session"))
g = LocalProxy(partial(_lookup_app_object, "g"))

um 代码简短,但是涉及两个新的数据结构 LocalStack和LocalProxy,request 就是LocalProxy对象,所以我们先要研究下这两个类。LocalStack基于Local。Local类似于Java的ThreadLocal,是一种按照线程分开存储数据的结构,每个线程都独立操作独属于自己的数据。

try:
from greenlet import getcurrent as get_ident
except ImportError:
try:
from thread import get_ident
except ImportError:
from _thread import get_ident
# werkzeug/local.py L53
class Local(object):
# 限制了只有俩属性 属性用tuple存储
__slots__ = ("__storage__", "__ident_func__")

def __init__(self):
# 存储数据
# __storage__ 属性是个两层的dict {'thread_ident':{'property_name':'property_value'}}
object.__setattr__(self, "__storage__", {})
# 从导入来看 就是获取线程或者协程唯一标识的方法
object.__setattr__(self, "__ident_func__", get_ident)

def __release_local__(self):
# 清除线程本地数据 就是删除 线程id对应的key
self.__storage__.pop(self.__ident_func__(), None)

# 实现此方法用来动态获取对应线程dict中的属性
def __getattr__(self, name):
try:
# 两层dict取值
return self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)
# 设值方法
def __setattr__(self, name, value):
ident = self.__ident_func__()
storage = self.__storage__
try:
# 尝试直接设值
storage[ident][name] = value
except KeyError:
# 失败则表示之前还没存过数据 初始化为一个dict
storage[ident] = {name: value}

def __delattr__(self, name):
try:
# 删除对应的数据键
del self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)

总结起来,Local内部封装了一个两层的dict,第一层的键为线程标识,第二层的键为数据key。重写了 __getattr __ 、__setattr__方法,支持任意属性的动态存取。每个线程都是操作的专属于自己的数据,不存在竞态条件。

下面看LocalStack,LocalStack 基于Local 为每个线程封装了一个栈结构(用的list),并封装了一些栈操作 push pop top

# werkzeug/local.py L91
class LocalStack(object):

def __init__(self):
# 内部维护一个 Local实例
self._local = Local()

def push(self, obj):
# 获取 Local对象的stack属性 走Local对象的 __getattr__方法
rv = getattr(self._local, "stack", None)
if rv is None:
# 没有则赋值 走Local对象的__setattr__方法
self._local.stack = rv = []
# 将当前对象入到对应线程的对应栈中
rv.append(obj)
return rv

def pop(self):
stack = getattr(self._local, "stack", None)
if stack is None:
return None
elif len(stack) == 1:
release_local(self._local)
return stack[-1]
else:
return stack.pop()

@property
def top(self):
try:
return self._local.stack[-1]
except (AttributeError, IndexError):
return None

比较简单,再看下LocalProxy,顾名思义 是个代理类,持有实例然后转发操作。

# werkzeug/local.py L254
# LocalProxy覆盖了很多特殊方法 将对应的操作转发给代理的对象
# 下面只列举一点点
class LocalProxy(object):

__slots__ = ("__local", "__dict__", "__name__", "__wrapped__")

def __init__(self, local, name=None):
# __local 实际存储为 _LocalProxy__local 直接设值时需要使用全名
# 后面self.__local 存取时,python会自动做转换
object.__setattr__(self, "_LocalProxy__local", local)
object.__setattr__(self, "__name__", name)
if callable(local) and not hasattr(local, "__release_local__"):
# "local" is a callable that is not an instance of Local or
# LocalManager: mark it as a wrapped function.
object.__setattr__(self, "__wrapped__", local)

def _get_current_object(self):
# 如果__local 对象没有 __release_local__ 属性,说明其不是一个Local实例
if not hasattr(self.__local, "__release_local__"):
# 此时将其视为callable对象直接调用,并返回其结果
return self.__local()
try:
# 是Local对象 则尝试获取对应的属性,会走Local的 __getattr__ 方法动态获取
return getattr(self.__local, self.__name__)
except AttributeError:
raise RuntimeError("no object bound to %s" % self.__name__)

def __getattr__(self, name):
if name == "__members__":
return dir(self._get_current_object())
# 获取当前对象的 属性
return getattr(self._get_current_object(), name)

ok 上面三个数据结构了解完了,再来具体分析下 from flask import request 背后到底都发生了些什么。
之前,我们在 ​​​Flask路由机制​​一文中已经看过了 调用 app(environ, start_response)时的RequestContext对象的构建和push过程,如下

class Flask(_PackageBoundObject):
def __call__(self, environ, start_response):
return self.wsgi_app(environ, start_response)

def wsgi_app(self, environ, start_response):
# 根据environ构建 flask.ctx.RequestContext 对象
ctx = self.request_context(environ)
error = None
try:
try:
# 入栈
ctx.push()
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.handle_exception(e)
except: # noqa: B001
error = sys.exc_info()[1]
raise
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
# 请求处理完成后清理当前的上下文 即出栈
ctx.auto_pop(error)

重点看下 ctx.push() 的代码

class RequestContext(object):

def __init__(self, app, environ, request=None, session=None):
self.app = app
if request is None:
# 构建 flask.Request 对象
request = app.request_class(environ)
# 将flask.Request 设置为RequestContext实例的 属性
# 后面我们通过 flask.request 进行属性读取实际读取的就是这个对象的属性
self.request = request
self.session = session

def push(self):
# 熟悉吧 将RequestContext对象 入栈全局的LocalStack对象 _request_ctx_stack 中
_request_ctx_stack.push(self)

再重温下 flask.request的代码

from functools import partial

def _lookup_req_object(name):
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(_request_ctx_err_msg)
return getattr(top, name)

# 保存RequestContext上下文对象的 LocalStack实例
_request_ctx_stack = LocalStack()

# request 是个LocalProxy对象
# 传入的不是Local实例,而是一个functools.partial对象 可调用 并且固定了传入函数的部分参数
request = LocalProxy(partial(_lookup_req_object, "request"))

那么,下面来分析下我们 from flask import request 之后,获取当前请求头 request.headers 这个操作背后都发生了什么

# 我们在view_functions 中想要获取当前的请求头
from flask import request
req_headers = request.headers

# request 是个LocalProxy对象 属性获取会走其 __getattr__方法
class LocalProxy(object):

def __init__(self, local, name=None):
# request变量初始化时 传入的local变量为 functools.partial(_lookup_req_object, "request")
object.__setattr__(self, "_LocalProxy__local", local)
object.__setattr__(self, "__name__", name)

def _get_current_object(self):
if not hasattr(self.__local, "__release_local__"):
# 因为request变量 传入的__local 不是Local对象 这边会直接调用返回
# 即 执行了 _lookup_req_object("request")
return self.__local()
# 处理headers属性的获取
def __getattr__(self, name):
# 其实最终执行了 getattr(_lookup_req_object("request"), "headers")
return getattr(self._get_current_object(), name)

# 那么 _lookup_req_object("request") 做了什么 返回什么呢
def _lookup_req_object(name):
# 当前线程 对应栈的栈顶元素
# top 即当前线程当前请求的RequestContext对象
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(_request_ctx_err_msg)
# name值为 "request"
# 即获取 RequestContext对象的 request属性
# 还记得吧 上面有说 RequestContext初始化时 会根据environ创建flask.Request对象并设为request属性的值
return getattr(top, name)

# 搞清楚啦 每次对request 变量进行属性的读取,其实都是动态的获取当前请求的RequestContext对象,然后对其request属性(flask.Request对象)进行属性的读取

读到这里,上下文的概念基本就差不多啦。但是,其实上面还没有讲完,request.headers 最终其实转换为 flask.Request.headers ,应该都会好奇这边又是怎么处理headers属性的读取呢。
反正我好奇,下面就来研究下 flask.Request对象吧。

# 其实Request本身内容很少 大多数方法都是继承来的
class Request(RequestBase, JSONMixin):
url_rule = None
view_args = None
routing_exception = None

# 下面这个就是RequestBase类 导入时被重命名了
# 就是个组合类 组合了一系列的功能
class Request(
BaseRequest,
AcceptMixin,
ETagRequestMixin,
UserAgentMixin,
AuthorizationMixin,
CORSRequestMixin,
CommonRequestDescriptorsMixin,
):
pass

class BaseRequest(object):
def __init__(self, environ, populate_request=True, shallow=False):
# 保存了environ 请求相关的信息都可以从environ中分析得到
self.environ = environ
# property的缓存版本
@cached_property
def headers(self):
# 我们这次获取的headers 就是用 environ初始化了EnvironHeaders对象并返回
return EnvironHeaders(self.environ)

class EnvironHeaders(ImmutableHeadersMixin, Headers):

def __init__(self, environ):
# 保存environ
self.environ = environ

# request.headers[''] 或者 request.headers.get() 方法会走这个特殊方法
# 其实就是去environ里面获取对应的信息
def __getitem__(self, key, _get_mode=False):
if not isinstance(key, string_types):
raise KeyError(key)
key = key.upper().replace("-", "_")
if key in ("CONTENT_TYPE", "CONTENT_LENGTH"):
return _unicodify_header_value(self.environ[key])
return _unicodify_header_value(self.environ["HTTP_" + key])

总结下,flask.Request 基于environ,封装了很多从environ中获取请求信息的特性和方法。这样,我们不用直接操作environ。
好的,最后再来看下 @cached_property

# 继承自 内置的property类
class cached_property(property):

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

# get方法引入了缓存
# 描述符协议
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

ok,至此上下文搞清楚了,请求信息的获取也顺便搞清楚了,完美~

总结一下:

  1. 每当一个请求到来时,都会构建对应的RequestContext对象,并将其入栈 。处理完成后,会再出栈。
  2. 维护当前请求上下文信息的是 全局变量 _request_ctx_stack,其指向 LocalStack对象。
  3. request 是一个LocalProxy对象,每当我们需要在view_function中获取当前请求信息时,如request.headers时,LocalProxy对象会动态的去获取当前的RequestContext,并从其 request属性中获取对应的值。
  4. 其实所有的信息都在 environ里面,只不过我们不用直接操作environ,flask替我们封装了 Request类,其中包含了http相关的属性,我们直接获取即可。