博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Flask上下文管理及源码刨析
阅读量:6205 次
发布时间:2019-06-21

本文共 9001 字,大约阅读时间需要 30 分钟。

 

 

基本流程概述

 

- 与django相比是两种不同的实现方式。    - django/tornado是通过传参数形式实现    - 而flask是通过上下文管理, 两种都可以实现,只不实现的方式不一样罢了。    - 上下文管理:        - 说上下文管理前要先提一下threadinglocal,它为每一个线程开辟一块独立的空间,但是Flask不是用它做得,它自己实现类一个local类        - 其中创建了一个字典来保存数据,这个字典的key是用线程的唯一标识,如果有协程用greelet拿的一个唯一标识,可以是线程的也可以支持协程,后面是存取的数据{greenlet做唯一标识:存数据} 这样可以保证数据隔离        - 当请求进来的时候:            - 将请求相关所有数据(request,session)封装到了RequestContext中。            - 再将RequestContext对象通过LocalStack类添加到Local中(调用push方法)        - 使用时,调用request            - 调用此类方法 request.method、print(request)、request+xxx 会执行LocalProxy中对应的魔法方法            - 魔法方法中调用_get_current_object函数            - 通过LocalStack中的top方法,去Local中获取值,取的是列表的最后一个值。        - 请求终止时            - 还是通过LocalStack的pop方法 将Local中将值在列表中pop掉。

 

内置session流程当请求刚进来的时候,会把请求相关的和session封装到RequestContext的对象中去,RequestCcontext对像再通过它里面对push方法把对象放到Flask特有的,类似与theadingLocal那么一个Local对象中去, push中会调用session里面open_session方法,通过这个方法帮助我们获取用户原有的session信息,有就获取,没有就返回一个空的类似字典的数据结构,赋值给对象中的session当使用的时候触发LocalProxy对像里对魔法方法,再调用get_current_obj,通过偏函数用Localstark中方法去Local获取到数据使用完后,调用session对像的save_session方法,将数据加密写到用户的cookie中,这个操作在after_request之后

 

 

 

 request 与 session

flask中要想调用当前请求的request对象,需要使用from flask import reuqest导入。当我们导入request全局对象的时候,本质上是从Local中本次请求线程对应的RequestContext内封装的request_context_obj.request对象。 除了request对象,session、g 以及current_app都是这个原理

 

 

LocalStack类与Local类

 

Local类

 

  是flask模仿threading.Local实现的一个本地线程,内部的self.__storage__封装了一个字典,用来存放每一个请求对应线程的私有数据数据,保证了每一个请求之间的数据隔离。

  他的结构是这样

  •  其中RequestContext对象中封装了当前请求的request对象和session对象
self.__storage__ = {    greenlet获取的唯一标识:{
"stack": [RequestContext]}, greenlet获取的唯一标识:{
"stack": [RequestContext]}, greenlet获取的唯一标识:{
"stack": [RequestContext]},}

 

__storage__是一个字典,但是在Local中还定义了__setattr__、__getattr__等方法,意味着我们可以向操作Local对象属性的方式操作__storage__字典
try:    from greenlet import getcurrent as get_ident        # 协程的唯一标识except ImportError:    try:        from thread import get_ident            # 线程的唯一标识    except ImportError:        from _thread import get_identclass Local(object):    __slots__ = ('__storage__', '__ident_func__')    def __init__(self):        object.__setattr__(self, '__storage__', {})            # 这里不能直接使用self.__storage__ = {},因为会触发setattr        object.__setattr__(self, '__ident_func__', get_ident)    def __release_local__(self):        self.__storage__.pop(self.__ident_func__(), None)    # 删除__storage__中存放的线程相关数据(requestcontext)    def __getattr__(self, name):        try:            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:            storage[ident] = {name: value}    def __delattr__(self, name):        try:            del self.__storage__[self.__ident_func__()][name]        except KeyError:            raise AttributeError(name)

 

 

LocalStack 类

 

Local类的作用只是用来存放数据,而LocalStack则提供了pop、top、push方法来操作Local中的数据。即Local中的数据是通过LocalStack来操作的。

  • push方法将一个RequestContext放到local.__storage__字典中stack对应的列表中
  • pop方法将RequestContext从列表中取出并从列表中删除
  • top方法用来获取RequestContext对象,并不会删除该对象

注意:每一次请求的生命周期中导入的request、session等其他flask全局对象,指向的都是同一个引用,也就是说在其他地方操作session都是操作的同一个session对象

class LocalStack(object):    def __init__(self):        self._local = Local()    def push(self, obj):        rv = getattr(self._local, 'stack', None)        if rv is None:            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):        """The topmost item on the stack.  If the stack is empty,        `None` is returned.        """        try:            return self._local.stack[-1]        except (AttributeError, IndexError):            return None

 

 

源码流程分析

 

1. 请求进来后会首先执行app的__call__方法,在该方法内部调用的其实就是app.wsgi_app()方法

2. ctx = self.request_context(environ)方法本质上就是实例化一个RequestContext对象,并将请求的所有相关信息(WSGI environment)封装到RequestContext对象中,在实例化RequestContext对象的时候,其__init__方法中会干以下几件事儿

  •  2.1 初始化RequestContext,初始化app, request, session等属性
  •  2.2 调用requestcontext_obj.match_request(),该函数内部会匹配url_rule
class Flask:    def wsgi_app(self, environ, start_response):        ctx = self.request_context(environ)        ctx.push()        # 中间省略部分内容    def request_context(self, environ):        return RequestContext(self, environ)class RequestContext:    def __init__(self, app, environ, request=None):        self.app = app        if request is None:            request = app.request_class(environ)        self.request = request        self.url_adapter = app.create_url_adapter(self.request)        self.flashes = None        self.session = None                    # 在self.push()中被赋值        self.match_request()    def match_request(self):        try:            url_rule, self.request.view_args = \                self.url_adapter.match(return_rule=True)            self.request.url_rule = url_rule        except HTTPException as e:            self.request.routing_exception = e

 

 

2.3  request = app.request_class(environ)会将请求的所有相关信息分装成一个Request对象,并绑定到RequestContext对象的request属性上

class Flask:    request_class = Request            # app.request_class其实就是Request

 

 

3.  ctx.push(),这个方法会将RequestContext对象通过LocalStack类push到Local.__storage__字典的列表中,

  获取当前请求对应的session数据并绑定RequestContext对象的session属性上

# globals.py_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'
class RequestContext:    def push(self):        # 省略部分        _request_ctx_stack.push(self)        # 把requestcontext保存到列表中        self.session = self.app.open_session(self.request)    # 获取session数据并绑定到requestcontext对象的session属性中        if self.session is None:            self.session = self.app.make_null_session()

 

 

4.  当我们使用from flask import request, sesion的时候,就会触发LocalProxy类中相应对魔法方法,调用 _get_current_object() 函数,函数中调用 _lookup_req_object,通过LocalStack到Local到__storage__字典中

  对应的列表中的 RequestContext对象的request属性和session属性 对应的Request对象和SecureCookieSession对象取出

request = LocalProxy(partial(_lookup_req_object, 'request'))    # 获取requestcontext_obj.requestsession = LocalProxy(partial(_lookup_req_object, 'session'))    # 获取requestcontext_obj.sessiondef _lookup_req_object(name):    top = _request_ctx_stack.top    if top is None:        raise RuntimeError(_request_ctx_err_msg)    return getattr(top, name)        # 利用反射获取RequestContext对象的属性值

 

 

5. 请求结束时,调用RequestContext中调pop方法,通过Localstack将Local中__storage__字典内的数据pop掉

class LocalStack():      """"省略"""    def pop(self):        """Removes the topmost item from the stack, will return the        old value or `None` if the stack was already empty.        """        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()
class Local(object):    __slots__ = ('__storage__', '__ident_func__')    def __init__(self):        object.__setattr__(self, '__storage__', {})        object.__setattr__(self, '__ident_func__', get_ident)        def __delattr__(self, name):        try:            del self.__storage__[self.__ident_func__()][name]        except KeyError:            raise AttributeError(name)

 

应用上下文

 

应用上下文的原理和流程和管理上下文基本一致,他们分别创建了自己的一个Local类,在自己的Local类中为每一个线程创建类独立的存储数据的空间

上下文全局变量current_app, g, request, session等都是对象,我们可以在这些对象上通过属性绑定一些数据,在需要的时候再取出这些数据进行操作

from flask import Flask,request,g,current_app,app = Flask(__name__)@app.before_requestdef before():    g.permission_code_list = ['list','add']    current_app.x = 123    request.name = "zhou"@app.route('/',methods=['GET',"POST"])def index():    print(g.permission_code_list)    print(current_app.x)    print(request.name)    # ['list', 'add']    # 123    # zhou    return "index"if __name__ == '__main__':    app.run()

 

源码

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_request_ctx_stack = LocalStack() #请求上下文_app_ctx_stack = LocalStack()  #应用上下文current_app = LocalProxy(_find_app)g = LocalProxy(partial(_lookup_app_object, 'g'))request = LocalProxy(partial(_lookup_req_object, 'request'))session = LocalProxy(partial(_lookup_req_object, 'session'))

 

 

 

 

 

 

转载于:https://www.cnblogs.com/zhoujunhao/p/8659780.html

你可能感兴趣的文章
二叉树的基本操作及应用(三)
查看>>
A SimpleDataStore
查看>>
朱晔和你聊Spring系列S1E3:Spring咖啡罐里的豆子
查看>>
IOS CALayer的属性和使用
查看>>
温故而知新:柯里化 与 bind() 的认知
查看>>
查看修改swap空间大小
查看>>
Django REST framework
查看>>
C链表反转(时间复杂度O(n))
查看>>
CSS 如何让Table的里面TD全有边框 而Table的右左边框没有
查看>>
如何让帝国CMS7.2搜索模板支持动态标签调用
查看>>
被吐嘈的NodeJS的异常处理
查看>>
apache 虚拟主机详细配置:http.conf配置详解
查看>>
ON DUPLICATE KEY UPDATE
查看>>
BABOK - 开篇:业务分析知识体系介绍
查看>>
Java入门系列-22-IO流
查看>>
Template、ItemsPanel、ItemContainerStyle、ItemTemplate
查看>>
MySQL:Innodb page clean 线程 (二) :解析
查看>>
图嵌入综述 (arxiv 1709.07604) 译文五、六、七
查看>>
垃圾回收算法优缺点对比
查看>>
正则表达式 匹配常用手机号 (13、15\17\18开头的十一位手机号)
查看>>