Flask1.1.4 Werkzeug1.0.1 源码分析:上下文

在使用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 value

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

总结一下:

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

你可能感兴趣的:(flask,python,flask,python,后端)