Flask源码解读(2) -- context

来源:互联网 发布:ubuntu ftp传输文件 编辑:程序博客网 时间:2024/06/08 00:59

Flask源码解读(2) -- context


上篇我们讲到底层代码将请求打包成environ并传给app, app是一个定义了__call__方法的对象.  __call__方法接收environ为参数并处理请求.

对于传入的__environ__, Flask内部将其转换并存储到context对象中. 视图函数可以通过context对象获取所有关于请求的信息, 另外context也提供了其他的功能

class Flask(_PackageBoundObject):        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)    def __call__(self, environ, start_response):        """Shortcut for :attr:`wsgi_app`."""        return self.wsgi_app(environ, start_response)
从__call__方法中看出, 调用了wsgi_app方法. wsgi_app中首先调用request_context方法生成ctx


request_context方法用environ生成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        # Request contexts can be pushed multiple times and interleaved with        # other request contexts.  Now only if the last level is popped we        # get rid of them.  Additionally if an application context is missing        # one is created implicitly so for each level we add this information        self._implicit_app_ctx_stack = []        # indicator if the context was preserved.  Next time another context        # is pushed the preserved context is popped.        self.preserved = False        # remembers the exception for pop if there is one in case the context        # preservation kicks in.        self._preserved_exc = None        # Functions that should be executed after the request on the response        # object.  These will be called before the regular "after_request"        # functions.        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 copy(self):        """Creates a copy of this request context with the same request object.        This can be used to move a request context to a different greenlet.        Because the actual request object is the same this cannot be used to        move a request context to a different thread unless access to the        request object is locked.        .. versionadded:: 0.10        """        return self.__class__(self.app,            environ=self.request.environ,            request=self.request        )    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,        )


上面就是RequestContext类的完整定义, 在Flask的wsgi_app方法中可以看到, 每个请求开始时, 新建request_context对象并调用request_context.push(), 在请求处理完后调用request_context.auto_pop()

在request_context的__init__方法中, 我们看到了经常使用的self.request, self.url_adapter, self.flashes, self.session属性. url_adapter和session后续讲解

另外通过property, 也可以访问self.g属性. self.g实际指向app_context的g属性.


重点看看request_context对象的push方法

push方法就是将request_context推入到_request_ctx_stack中. 当一个请求处理过程中出现异常或者用户指定保留request_context时, 在请求的结束时, request_context不会弹出栈

这样就有可能出现在push时发现栈中已经存在某个request_context. 因此在push时, 首先判断并删除栈中已经存在的request_context

紧接着通过app_ctx = self.app.app_context(), app_ctx.push()两条语句, 可以发现在request_context入栈时, 会自动新建一个app_context并将其入栈. 

然后就是关于self.session的初始化操作, 我们后文介绍


接着看看request_context对象的auto_pop方法

判定条件 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两条语句, 而不会执行pop操作. 执行的两条语句将当前request_context标记, 保证下次有request_context入栈时首先被弹出

如果正常情况下, auto_pop会调用pop. 在pop中, 首先通过_implicit_app_ctx_stack找到了建立request_context时, 一同建立的app_context, 将此app_context出栈, 再自己出栈.


总结:

每次处理一个请求前, 会建立context

context包括request_context, app_context, 一般request_context出入栈的时候, 会先将app_context出入栈







原创粉丝点击