博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
flask源码解读05: Context(AppContext, RequestContext)
阅读量:4324 次
发布时间:2019-06-06

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

Flask中的AppContext和RequestContext

前面分析app.run()背后产生的一系列调用时,说明底层代码会负责接收客户端连接,解析客户请求,并调用编写好的app(要求app是可调用对象)。调用app时, 实际调用的是app.wsgi_app方法,代码如下:

def wsgi_app(self, environ, start_response):                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:                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 = self.request_context(environ)根据传入的environ参数创建了rctx对象,这就是RequestContext对象。

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        self.url_adapter = app.create_url_adapter(self.request)        self.flashes = None        self.session = None         self._implicit_app_ctx_stack = []        self.preserved = False        self._preserved_exc = None        self._after_request_functions = []        self.match_request()    def _get_g(self):        return _app_ctx_stack.top.g    def _set_g(self, value):        _app_ctx_stack.top.g = value    g = property(_get_g, _set_g)    del _get_g, _set_g    def match_request(self):        """Can be overridden by a subclass to hook into the matching        of the request.        """        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    def push(self):        """Binds the request context to the current context."""        # If an exception occurs in debug mode or if context preservation is        # activated under exception situations exactly one context stays        # on the stack.  The rationale is that you want to access that        # information under debug situations.  However if someone forgets to        # pop that context again we want to make sure that on the next push        # it's invalidated, otherwise we run at risk that something leaks        # memory.  This is usually only a problem in test suite since this        # functionality is not active in production environments.        top = _request_ctx_stack.top        if top is not None and top.preserved:            top.pop(top._preserved_exc)        # Before we push the request context we have to ensure that there        # is an application context.        app_ctx = _app_ctx_stack.top        if app_ctx is None or app_ctx.app != self.app:            app_ctx = self.app.app_context()            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)        # Open the session at the moment that the request context is available.        # This allows a custom open_session method to use the request context.        # Only open a new session if this is the first time the request was        # pushed, otherwise stream_with_context loses the session.        if self.session is None:            session_interface = self.app.session_interface            self.session = session_interface.open_session(                self.app, self.request            )            if self.session is None:                self.session = session_interface.make_null_session(self.app)    def pop(self, exc=_sentinel):        """Pops the request context and unbinds it by doing that.  This will        also trigger the execution of functions registered by the        :meth:`~flask.Flask.teardown_request` decorator.        .. versionchanged:: 0.9           Added the `exc` argument.        """        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 this interpreter supports clearing the exception information                # we do that now.  This will only go into effect on Python 2.x,                # on 3.x it disappears automatically at the end of the exception                # stack.                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()            # 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)            assert rv is self, 'Popped wrong request context.  ' \                '(%r instead of %r)' % (rv, self)    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)    def __enter__(self):        self.push()        return self    def __exit__(self, exc_type, exc_value, tb):        # do not pop the request stack if we are in debug mode and an        # exception happened.  This will allow the debugger to still        # access the request object in the interactive shell.  Furthermore        # the context can be force kept alive for the test client.        # See flask.testing for how this works.        self.auto_pop(exc_value)        if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:            reraise(exc_type, exc_value, tb)    def __repr__(self):        return '<%s \'%s\' [%s] of %s>' % (            self.__class__.__name__,            self.request.url,            self.request.method,            self.app.name,        )

__init__初始化方法为实例添加各种属性,注意request,session,flash属性,这些属性在编写app时经常用到。request是根据传入的environ参数新建的app.request_class类实例,打包好了所有的请求信息。session后面会讲解

创建好RequestContext实例rctx后,会调用rctx.push方法将rctx入栈。push方法先检查_request_ctx_stack种是否有之前保留的RequestContext对象,如果有的话将其出栈。什么情况下处理一个请求时,栈中会保留上一个请求的RequestContext,后文解释。接着程序判断_app_ctx_stack是否为空,如果为空或者栈头的AppContext对象绑定的app和自己绑定的app不是同一个app,则需要新建一个AppContext对象。新建AppContext对象actx后,会将actx入栈,并且在self._implicit_app_ctx_stack.append队列中记录自己创建的AppContext对象。然后rctx将自己入栈。

由此我们知道,flask中有两个栈_request_ctx_stack和_app_ctx_stack,分别用来保存请求环境和app环境。在处理一个请求时要保证两个栈的栈顶元素是和该请求对应的。

 

在app.wsgi_app方法中,处理完请求后,会调用ctx.auto_pop(error)将rctx出栈。

在auto_pop中我们可以看到rctx被保留在栈中的原因:

  1. 最初用来创建请求的environ.flask._preserve_context属性被置位

  2. 处理请求时产生异常,并且app.preserve_context_on_exception被置位

我认为常见的原因是第二个,就是在发生异常时,为了调试程序方便,任然在栈中保留请求环境,调试人员可以看到发生异常时app和请求的各种信息。这种指定一般用在开发app的时候,在生产环境中不会用到。

 

如果上面的两种情况没有发生将会调用pop方法,在pop中,先查看_implicit_app_ctx_stack属性,这样可以知道rctx入栈的时候自动创建的actx。之后的finally语句保证了rctx和actx会被出栈。

 

转载于:https://www.cnblogs.com/lovelaker007/p/8573942.html

你可能感兴趣的文章
关于手码编写autolayout约束
查看>>
Java参考资料
查看>>
goto语句
查看>>
简单的车票管理系统
查看>>
2016年10月25 草堂随笔1 ModelState.IsValid
查看>>
Jenkins Pipelines Summary
查看>>
倾斜摄影 实景三维建模软件photoscan教程
查看>>
Actiion Func ;Donet framework 中已经定义好的委托
查看>>
Python 模块之 xlrd (Excel文件读写)
查看>>
再探@font-face及webIcon制作
查看>>
BZOJ.4212.神牛的养成计划(Trie 可持久化Trie)
查看>>
【unityd--初始学习四--3d世界中的物体移动及角度变换】
查看>>
电脑cmos是什么?和bois的区别?
查看>>
REST WCF 使用Stream进行Server与Client交互
查看>>
Python数据分析之Matplotlib绘制柱状图
查看>>
Django组件之contenttype
查看>>
刘江的博客
查看>>
SpringBoot入门教程(二)CentOS部署SpringBoot项目从0到1
查看>>
Unity 2D游戏开发教程之为游戏场景添加多个地面
查看>>
Java日期时间处理
查看>>