Flask source code interpretation 05: Context (AppContext, RequestContext)

AppContext and RequestContext in Flask

When analyzing the series of calls behind app.run(), it shows that the underlying code will be responsible for receiving client connections, parsing client requests, and calling the written app (app is required to be a callable object). When calling the app, the app.wsgi_app method is actually called. The code is as follows:

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)

It can be found that before processing the user request, the statement ctx = self.request_context(environ) creates the rctx object according to the incoming environ parameter, which is the RequestContext object.

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, )

The __init__ initialization method adds various attributes to the instance, pay attention to the request, session, and flash attributes, which are often used when writing apps. request is a newly created app.request_class class instance based on the incoming environ parameter, and all the request information is packaged. It will be explained later in the session

After the RequestContext instance rctx is created, the rctx.push method will be called to push the rctx onto the stack. The push method first checks whether there is a previously reserved RequestContext object in _request_ctx_stack, and pops it out of the stack if there is one. When processing a request, the RequestContext of the previous request will be retained in the stack, which will be explained later. Then the program determines whether _app_ctx_stack is empty. If it is empty or the app bound to the AppContext object in the stack header and the app bound to itself are not the same app, you need to create a new AppContext object. After the new AppContext object actx is created, actx will be pushed onto the stack, and the AppContext object created by itself will be recorded in the self._implicit_app_ctx_stack.append queue. Then rctx pushes itself onto the stack.

From this we know that there are two stacks _request_ctx_stack and _app_ctx_stack in flask, which are used to save the request environment and the app environment respectively. When processing a request, ensure that the top elements of the two stacks correspond to the request.

 

In the app.wsgi_app method, after processing the request, ctx.auto_pop(error) will be called to pop the rctx from the stack.

In auto_pop we can see why rctx is kept on the stack:

  1. The environ.flask._preserve_context property originally used to create the request is set

  2. An exception is generated while processing the request, and app.preserve_context_on_exception is set

I think the common reason is the second one, that is, when an exception occurs, for the convenience of debugging the program, the request environment is still retained in the stack, and the debugger can see various information about the app and the request when the exception occurs. This designation is generally used when developing an app, but not in a production environment.

 

If the above two situations do not occur, the pop method will be called. In pop, check the _implicit_app_ctx_stack attribute first, so that you can know the actx that is automatically created when rctx is pushed into the stack. The finally statement after that ensures that rctx and actx will be popped.

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324889909&siteId=291194637