Flask源码分析之上下文请求流程(请求上下文&应用上下文)

from flask import Flask

app = Flask(__name__)


@app.route("/")
def hello():
    return "Hello World"


if __name__ == "__main__":
    app.run()

当程序运行时,执行app.run(),会先执行Flask.__call__方法

    def __call__(self, environ, start_response):
        """Shortcut for :attr:`wsgi_app`."""
        return self.wsgi_app(environ, start_response)

call方法中调用Flask.wsgi_app方法

    def wsgi_app(self, environ, start_response):
        ctx = self.request_context(environ)  # 获取environ中的信息,封装在request和session中
        ctx.push()  # 放在Local中的__storage__字典中,字典格式为{"stack":[ctx, ]}  此处stack中存放的为列表形式==栈,详细信息查看文章《Flask多app应用之“栈”详解》
        error = None
        try:
            try:
                response = self.full_dispatch_request()  # 请求
            except Exception as e:
                error = e
                response = self.handle_exception(e)  # 报错
            except:
                error = sys.exc_info()[1]
                raise
            return response(environ, start_response)  # response
        finally:
            if self.should_ignore_error(error):
                error = None
            ctx.auto_pop(error)  # 请求结束,删除__storage__中信息

深入ctx = request_context(environ)

    def request_context(self, environ):
        return RequestContext(self, environ)  # self为Flask对象=app,environ为请求信息

所以此处ctx为RequestContext对象!

接着我们来看RequestContext中都封装了些什么:

深入Request Context(self, environ)

源码可见,RequestContext中封装了我们用到的reqeust,session,so:request = ctx.request , session = ctx.reqeust

class RequestContext(object):
    def __init__(self, app, environ, request=None):
        self.app = app
        if request is None:
            request = app.request_class(environ)
        self.request = request   #  request字段
        self.url_adapter = app.create_url_adapter(self.request)
        self.flashes = None
        self.session = None  # session字段
        self._implicit_app_ctx_stack = []
        self.preserved = False 
        self._preserved_exc = None
        self._after_request_functions = []
        self.match_request()

1.1深入ctx.push()

    def push(self):  # push方法为RuquestContext类的方法
        
        top = _request_ctx_stack.top
        if top is not None and top.preserved:
            top.pop(top._preserved_exc)
 
        app_ctx = _app_ctx_stack.top
        if app_ctx is None or app_ctx.app != self.app:
            app_ctx = self.app.app_context()  #  AppContext对象,应用上下文
            app_ctx.push()  
            self._implicit_app_ctx_stack.append(app_ctx)
        else:
            self._implicit_app_ctx_stack.append(None)

        if hasattr(sys, 'exc_clear'):
            sys.exc_clear()

        _request_ctx_stack.push(self)   # 请求上下文,
        self.session = self.app.open_session(self.request)
        if self.session is None:
            self.session = self.app.make_null_session()
应用上下文开始>>>>>>>>>>>>>
深入app_ctx = self.app.app_context(),源码可见app_ctx为AppContext的对象
    def app_context(self):
        return AppContext(self)

继续深入AppContext(),里面封装了一些参数

    def __init__(self, app):
        self.app = app  # app对象
        self.url_adapter = app.create_url_adapter(None)
        self.g = app.app_ctx_globals_class()  # g,记住里面有个一个g字段,后期我们会用到
        self._refcnt = 0

下面继续我们的流程:

深入app_ctx.push()

    def push(self):  # push为AppContext()类的方法
        self._refcnt += 1
        if hasattr(sys, 'exc_clear'):
            sys.exc_clear()
        _app_ctx_stack.push(self)  #我们需要深入的方法,self为app_ctx
        appcontext_pushed.send(self.app

深入_app_ctx_stack.push(self)

_app_ctx_stack = LocalStack()

_app_ctx_stack为:LocalStack的对象,这里为全局变量,下面我们还会用到,push为LocalStack的方法

我们先来看一下LocalStack的构造方法

    def __init__(self):
        self._local = Local() # 这个Local为Flask上下文管理的核心技术,详细请看《Flask上下文源码分析之thredading.local拓展》

我么来看下LocalStack.push做了什么

    def push(self, obj):
        rv = getattr(self._local, 'stack', None)
        if rv is None:
            self._local.stack = rv = []  # 相当于 rv = [] self._local.stack = rv,给rv赋值的同时,会给self._local.stack赋值
        rv.append(obj)  # self._local.stack = [obj,]
        return rv

self._local.stack 相当于执行了Local中的__setattr__方法

    def __getattr__(self, name):
        try:
            return self.__storage__[self.__ident_func__()][name]  # __storage__ = {"唯一标识":{"stack": [app_ctx]}}
        except KeyError:
            raise AttributeError(name)

现在我们开始使用以上提到的g

from flask import Flask,request,g

app = Flask(__name__)

@app.before_request
def before():
    g.permission_code_list = ['list','add']  # 设置参数


@app.route('/',methods=['GET',"POST"])
def index():
    print(g.permission_code_list)  # 获取参数
    return "index"


if __name__ == '__main__':
    app.run()

上面是一个小例子,我们先来看看这个g是什么,为什么就可以这样取到值

g = LocalProxy(partial(_lookup_app_object, 'g'))

g是LocalProxy类的对象,partial为偏函数,示例

from functools import partial

def foo(name):
    print(name)

partial(foo, 'i am pyton')  # 执行foo,并把参数传进去

so:这个偏函数执行了_lokkup_app_object,参数为g,我们现在深入_locakup_app_object

def _lookup_app_object(name):
    top = _app_ctx_stack.top   #  这个_app_ctx_stack又是LocalStack对象,
    if top is None:
        raise RuntimeError(_app_ctx_err_msg)
    return getattr(top, name)  #  getattr(app_ctx, "g")

深入top = _app_ctx_stack.top

    def top(self):
        try:
            return self._local.stack[-1]  # 这里相当于执行了Local中的__getattr__方法, 拿到__storage__["唯一id"]["stack"] = [app_ctx, ],[-1]取列表最后一个值
        except (AttributeError, IndexError):
            return None
    def __getattr__(self, name):
        try:
            return self.__storage__[self.__ident_func__()][name]  #__storage__["唯一id"]["stack"] = [app_ctx, ]
        except KeyError:
            raise AttributeError(name)

通过源码分析到,top,就是app_ctx对象,app_ctx为AppContext对象,里面封装的参数我们上面有讲解

return getattr(top, name)  #  getattr(app_ctx, "g")
所以全局变量g其实返回的就是app_ctx.g ,所以我们执行,g.permission_code_list = [xxx] 就相当于执行了LocalProxy中的__setattr__方法,获取值执行的是LocalProxy中的__getattr__方法
应用上下文结束>>>>>>>

======================华丽的分割线======================


请求上下文开始>>>>>>>

接着1.1深入ctx.push()开始

深入_request_ctx_stack.push(self)

_request_ctx_stack同样为LocalStack对象,push为LocalStack类的方法,self为ctx,ctx中封装了reqeust,session等

push方法上面已经讲解过,最终的操作为,把ctx放在了Local中的字典__staorage__中,示例:

__staorage__ = {
	"唯一标识":{
		”stask“:[ctx]
	}
}

request, session应用示例

from flask import g, Flask, request, session


app = Flask(__name__)


@app.route("/")
def hello():
    print(request.method)
    return "Hello Word"

request源码


request, session 同g一样,同样是LocalProxy对象,偏函数同样是获取ctx对象中的request,session

赋值执行LocalProxy的__setattr__,取值执行__getattr__

请求上下文结束>>>>>>>>>>>>>>>>

请求上下文流程之结尾

ctx.auto_pop(error)

深入auto_pop,auto_pop最终又调用了自己的pop方法

    def auto_pop(self, exc):
        if self.request.environ.get('flask._preserve_context') or \
           (exc is not None and self.app.preserve_context_on_exception):
            self.preserved = True
            self._preserved_exc = exc
        else:
            self.pop(exc)  # exec
    def pop(self, exc=_sentinel):
       
        app_ctx = self._implicit_app_ctx_stack.pop()

        try:
            clear_request = False
            if not self._implicit_app_ctx_stack:
                self.preserved = False
                self._preserved_exc = None
                if exc is _sentinel:
                    exc = sys.exc_info()[1]
                self.app.do_teardown_request(exc)

                
                if hasattr(sys, 'exc_clear'):
                    sys.exc_clear()

                request_close = getattr(self.request, 'close', None)
                if request_close is not None:
                    request_close()
                clear_request = True
        finally:
            rv = _request_ctx_stack.pop()   # 清空应用上下文  {"唯一id":{"task":[ctx]}},由于是字典类型,所以pop

            # get rid of circular dependencies at the end of the request
            # so that we don't require the GC to be active.
            if clear_request:
                rv.request.environ['werkzeug.request'] = None

            # Get rid of the app as well if necessary.
            if app_ctx is not None:
                app_ctx.pop(exc)  # 清空应用上下文  {"唯一id":{"task":[app_ctx]}},由于是字典类型,所以pop

            assert rv is self, 'Popped wrong request context.  ' \
                '(%r instead of %r)' % (rv, self)

_request_ctx_stack 为LocalStack对象,LocalStack方法pop

 def pop(self):
        stack = getattr(self._local, 'stack', None)
        if stack is None:
            return None
        elif len(stack) == 1:  # 一次请求只有一个,所以一般情况下这里面只有一个
            release_local(self._local)  # 深入release_local
            return stack[-1]  # 返回最后一个
        else:
            return stack.pop()  # 清空全部,下次stack变成None

release_local(self._local)最终执行了Local类中的方法

    def __release_local__(self):
        self.__storage__.pop(self.__ident_func__(), None) # 清空当前请求相关的信息

over~



猜你喜欢

转载自blog.csdn.net/weixin_39352048/article/details/80049072