flask源码解读05: Context(AppContext, RequestContext)

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会被出栈。

猜你喜欢

转载自www.cnblogs.com/lovelaker007/p/8573942.html