zoukankan      html  css  js  c++  java
  • flask 源码专题(二):请求上下文与全文上下文

    源码解析

    0. 请求入口   

    if __name__ == '__main__':
        app.run()
    def run(self, host=None, port=None, debug=None,
            load_dotenv=True, **options):
        # Change this into a no-op if the server is invoked from the
        # command line. Have a look at cli.py for more information.
        ................ 
        from werkzeug.serving import run_simple
     
        try:
            run_simple(host, port, self, **options)
        finally:
            # reset the first request information if the development server
            # reset normally.  This makes it possible to restart the server
            # without reloader and that stuff from an interactive shell.
            self._got_first_request = False
    def __call__(self, environ, start_response):
        """The WSGI server calls the Flask application object as the
        WSGI application. This calls :meth:`wsgi_app` which can be
        wrapped to applying middleware."""
        return self.wsgi_app(environ, start_response)

    对于每次请求进来之后,都会执行Flask类实例的__call__方法,至于为什么执行的是__call__方法请看本博主的flask 源码专题(一):app.run()的背后     但是我们可以知道的是,只要是请求进来之后,__call__方法执行的 wsgi_app方法是对请求处理及响应的全部过程,对于每次请求上下文的创建和销毁也是在其内部完成,而上下文正是flask框架的核心,因此研究其内部的执行流程有着至关重要的作用。

    首先我们要明白,在上下文中,要完成的操作是:

      1.对原生请求进行封装,生成视图函数可操作的request对象

      2.获取请求中的cookie信息,生成Session对象

      3.执行预处理函数和视图函数

      4.返回响应结果

    以下为上下文源码,后续对各部分代码进行分别阐述

    def wsgi_app(self, environ, start_response):
        #  生成 ctx.request , request.session,请求上下文,即请求先关的数据都封装到了 ctx这个对象中去
        ctx = self.request_context(environ)#1.请求上下文对象的创建
        error = None
        try:
            try:
                # 将ctx入栈,但是内部也将应用上下文入栈
                ctx.push()#2. 将请求上下文和应用上下文入栈
                # 对请求的url进行视图函数的匹配,执行视图函数,返回响应信息(cookie)
                response = self.full_dispatch_request()#3
            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)#4 完成对请求上下文和应用上下文的出栈操作

    1.请求上下文对象的创建

    #  生成 ctx.request , request.session,请求上下文,即请求先关的数据都封装到了 ctx这个对象中去
    ctx = self.request_context(environ)
    生成RequestContext类实例,该实例包含了本次请求的request和Session信息
    def request_context(self, environ):
            return RequestContext(self, environ)

    对生成的类实例进行初始化,并且将传入的原生请求信息environ封装值Request类实例中。此时,request为封装之后的Request实例,Session为None

    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._after_request_functions = []
    
            self.match_request()
    request_class = Request

    2. 将请求上下文和应用上下文入栈

    # 将ctx入栈,但是内部也将应用上下文入栈
     ctx.push()
    def push(self):
        # 获取到的  top  == ctx
        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.
        """
            _request_ctx_stack 和 _app_ctx_stack 都是 Local 类的实例
        """
        # 获取 应用上下文的栈顶元素,得到 app_ctx
        app_ctx = _app_ctx_stack.top
        if app_ctx is None or app_ctx.app != self.app:
            # self.app == Fask()
            # 得到 一个 AppContext类的实例对象,得到一个 应用上下文对象 app_ctx,此时 app_ctx拥有以下属性: app_ctx.app = app, app_ctx.g = app.app_ctx_globals_class()
            app_ctx = self.app.app_context()
            # 将 app_ctx 入栈,应用上下文入栈
            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()
     
        # self 指的是 ctx,即将ctx入栈,即 _request_ctx_stack._local.stack = [ctx]。请求上下文入栈
        _request_ctx_stack.push(self)
        # 由于每次请求都会初始化创建你ctx,因此session都为None
        if self.session is None:
            # SecureCookieSessionInterface()
            # session_interface = SecureCookieSessionInterface(),即session_interface就是一个SecureCookieSessionInterface类的实例对象
            session_interface = self.app.session_interface
            # 第一次访问:生成一个 字典(容器) 返回至 self.session
            self.session = session_interface.open_session(
                self.app, self.request
            )
            if self.session is None:
                self.session = session_interface.make_null_session(self.app)

    上下文入栈与出栈其他详细的说明请参考flask 源码专题(三)

    将ctx入栈,主要完成有
    2.1.把app_ctx上下文对象添加到了_app_ctx_stack这个栈中
    2.2.把 ctx请求对象添加到Local类的列表中
    2.3.执行open_session方法,把session加载到内

    3返回 wsgi_app 函数,继续向下执行  response = self.full_dispatch_request() 函数:

    def full_dispatch_request(self):
        #  将 _got_first_request =  True,依次执行定义的 before_first_request 函数
        self.try_trigger_before_first_request_functions()
        try:
            # 触发 request_started 信号
            request_started.send(self)
            #  执行钩子函数:before_request,before_first_request
            rv = self.preprocess_request()
            # 如果执行的before_request,before_first_request函数没有返回值,则继续执行视图函数。若有返回值,则不执行视图函数
            if rv is None:
                # 执行此url对应的别名的视图函数并执行该函数,返回视图函数的返回值,得到相应信息
                rv = self.dispatch_request()
        except Exception as e:
            # 如果发生错误,则将异常信息作为返回值进行返回
            rv = self.handle_user_exception(e)
        # 封装返回信息并返回,包括 session
        return self.finalize_request(rv)

    在函数的内部首先执行预处理函数再执行视图函数,返回预处理函数或视图函数的返回值至浏览器。

    4 返回 wsgi_app 函数中,继续向下执行 ctx.auto_pop(error) 函数,完成对请求上下文和应用上下文的出栈操作:

    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 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 pop(self):
        """Removes the topmost item from the stack, will return the
        old value or `None` if the stack was already empty.
        """
        stack = getattr(self._local, 'stack', None)
        if stack is None:
            return None
        elif len(stack) == 1:
            release_local(self._local)
            return stack[-1]
        else:
            # 获取并删除列表中的第一个元素,同时返回该元素
            return stack.pop()

    stack获取到的是请求栈或应用栈的列表,栈的长度为1,则进入 elif 控制语句中,首先执行  release_local(self._local) :

    def release_local(local):
         
        local.__release_local__()

     local=self._local ,即执行 Local 类的 __release_local__ 方法,进入该方法:

    def __release_local__(self):
        # 将 self.__storage__ 所维护的字典中删除当前协程或线程id为key的元素
        self.__storage__.pop(self.__ident_func__(), None)
    从上面的语句中可以很明显看出,要执行的操作就是将以当前协程或线程id为key的元素从字典 self.__storage__ 中删除,
    返回至pop函数中的elif控制语句,最终将列表中的最后一个元素返回。
    注意,最终 _request_ctx_stack._local 的请求栈和应用栈列表中至少会存在一个元素。

                                                                                                                                                                                                                                                   

  • 相关阅读:
    揭秘!如何快速提高网站权重-关键词百度指数叠加
    dede编辑文章不更新时间的方法
    PHPCMS V9轻松完成WAP手机网站搭建全教程
    如何建立关键词词库
    3gcms-Flash幻灯片上传后图片模糊解决办法
    手机端wap站网页播放腾讯视频代码
    解决dede编辑器不能保存word文档样式问题
    vi查找替换命令详解 (转载)
    eclipse上安装 windowBuilder方法
    单播、多播(组播)和广播的区别
  • 原文地址:https://www.cnblogs.com/qiu-hua/p/12631008.html
Copyright © 2011-2022 走看看