flask源码之上下文

引言:

  上一篇说的是flask实例调用run方法的背后,其实刚开个头,flask的上下文才是整个flask的精华,所有的请求解析,视图函数的执行,以及响应处理等核心代码都聚集在这短短30行代码里。另外flask上下文分为请求上下文和程序上下文,实现的方法基本一致

 

代码:

   上下文的入口,就是flask的__call__,至于为什么在上文中已经详细阐述,忘了就回去自己看看

   1.0====   flask.__call__代码:

    其实这个方法没什么,更像是一个统一的入口就调用了wsgi_app

    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."""
        # import traceback
        # s=traceback.extract_stack()
        # print("s===>%s"%s)
        # print("调用链 ======>")
        return self.wsgi_app(environ, start_response)

  1.0.1====   wsgi_app代码:

    看似平常,里边封装了n多的东西

    def wsgi_app(self, environ, start_response):

            
            flask 核心源码
            对于每次请求上下文的创建以及销毁也是在其内部完成的 所以上下文是flask框架的核心
                所以上下文的主要功能和流程
                1 对原生的请求进行封装生成视图函数可以操作的request对象
                2 获取请求的cookie信息 生成session对象
                3 执行预处理函数和视图函数
                4 返回相应

        """
        # 生成ctx.request  request.session请求上下文,即请求相关数据都封装到ctx对象中
        # ctx.app    当前  app  名称  其实就是 flask 实例
        # ctx.reqeust  请求相关  request对象
        # ctx.session 为空
        # ctx = self.request_context(environ) 在调该方法的时候实际上是 RequestContext 对象
        # 因为 RequestContext 在实例化的时候 将self也就是flask对象当作参数传进去了所以 RequestContext
        # 的初始化方法中的app就是flask对象所以能ctx.app等
        # print("wsgi_app===>environ--",environ)
        ctx = self.request_context(environ)
        error = None
        try:
            try:
                # ctx入栈 因为ctx是封装了请求相关 内部也将于应用上下文入栈
                # 将localstack 添加到local中 并且这时候ctx 中的session不为None request_context中的request_class 给了个{}
                # 这个时候 app实例中已经包含了session的空字典 那么就可以在视图函数中 操作
                ctx.push()
                # 对请求的url进行视图函数的匹配 执行视图函数 返回相应
                response = self.full_dispatch_request()
            except Exception as e:
                error = e
                # 若发生错误 将错误信息返回
                response = self.handle_exception(e)
            except:  # noqa: B001
                error = sys.exc_info()[1]
                raise
            # 封装响应信息
            return response(environ, start_response)
        finally:

            if self.should_ignore_error(error):
                error = None
            # 出栈 删除本次请求的相关信息 也就是说 将自己请求的所有数据在local中删除调
            ctx.auto_pop(error)

  首先第一步调用了 self.request_context(environ)

  1.0.2====  request_context代码:

    也没什么就是 实例化了 RequestContext的类

    def request_context(self, environ):

        # print("====self",self)
        # self 就是flask对象也就是app,RequestContext实例化的时候的app就是这个self
        return RequestContext(self, environ)

  

        1.0.3  ==== RequestContext.__init__代码:

    这里request刚进来的时候,肯定是个None,但是紧接着  request = app.request_class(environ) 这行代码就比较有意思了

    def __init__(self, app, environ, request=None, session=None):
        #print("in RequestContext ====>",app)
        self.app = app
        if request is None:
            # 这里实例化的实际上是 Request 实例 该类又继承 BaseRequest
            # 所以 这个requst可以在视图函数中
            # 直接导入使用并且使用 request.forms 或request.args 这样的方法
            request = app.request_class(environ)
        self.request = request
        self.url_adapter = None
        try:
            self.url_adapter = app.create_url_adapter(self.request)
        except HTTPException as e:
            self.request.routing_exception = e
        self.flashes = None
        self.session = session
        self._after_request_functions = []

    1.0.3.1====app.request_class代码:

      这里app明显指的就是flask实例,那么在flask中找到    request_class = Request ,那么根据这句 就知道  request = app.request_class(environ) 就知道 这里其实是实例的Request,那么下边要去看 Request的源码,但明显没有实现__init__方法 同样的套路去找长辈类,但是这里有意思的是pycharm直接跳到了另外一个叫Request的类

      翻翻上边的引入 

          from werkzeug.wrappers import Request as RequestBase  所以这里其实是werkzeug.warpers里边的Requst类 

class Request(RequestBase, JSONMixin):

        1.0.3.2====Request as RequestBase 代码:

      进去依然平淡无奇,就是继承了好多类 一个一个点进去看 其中在  BaseRequest 类中发现了很多熟悉的方法 

class Request(
    BaseRequest,
    AcceptMixin,
    ETagRequestMixin,
    UserAgentMixin,
    AuthorizationMixin,
    CommonRequestDescriptorsMixin,
):

   1.0.3.3=====BaseRequest.__init__代码:

       平淡无奇就是 赋了值   self.environ["werkzeug.request"] = self  这个属性貌似要在后边用到

class BaseRequest(object):

    charset = "utf-8"
    encoding_errors = "replace"
    max_content_length = None
    parameter_storage_class = ImmutableMultiDict
    list_storage_class = ImmutableList
    dict_storage_class = ImmutableTypeConversionDict
    trusted_hosts = None
    disable_data_descriptor = False


    
    def __init__(self, environ, populate_request=True, shallow=False):
        self.environ = environ
        if populate_request and not shallow:
            self.environ["werkzeug.request"] = self
        self.shallow = shallow

  发现的新大陆:

      这不就是在视图函数中 request.form,request.values,request.args嘛,其他的常用方法都在这里封装,然后打印看看 wsgi_app中的environ,哎我去,尼玛

   @cached_property
    def args(self):

        return url_decode(
            wsgi_get_bytes(self.environ.get("QUERY_STRING", "")),
            self.url_charset,
            errors=self.encoding_errors,
            cls=self.parameter_storage_class,
        )

   @cached_property
    def form(self):

        self._load_form_data()
        return self.form

    @cached_property
    def values(self):

        args = []
        for d in self.args, self.form:
            if not isinstance(d, MultiDict):
                d = MultiDict(d)
            args.append(d)
        return CombinedMultiDict(args)

    返回wsgi_app:

      代码紧接着 执行ctx.push  其实执行的还是 RequestContext 的push方法 

    def wsgi_app(self, environ, start_response):

        # ctx = self.request_context(environ) 在调该方法的时候实际上是 RequestContext 对象
        # 因为 RequestContext 在实例化的时候 将self也就是flask对象当作参数传进去了所以 RequestContext
        # 的初始化方法中的app就是flask对象所以能ctx.app等
        # print("wsgi_app===>environ--",environ)
        ctx = self.request_context(environ)
        error = None
        try:
            try:
                # ctx入栈 因为ctx是封装了请求相关 内部也将于应用上下文入栈
                # 将localstack 添加到local中 并且这时候ctx 中的session不为None request_context中的request_class 给了个{}
                # 这个时候 app实例中已经包含了session的空字典 那么就可以在视图函数中 操作
                ctx.push()
                # 对请求的url进行视图函数的匹配 执行视图函数 返回相应
                response = self.full_dispatch_request()
            except Exception as e:
                error = e
                # 若发生错误 将错误信息返回
                response = self.handle_exception(e)
            except:  # noqa: B001
                error = sys.exc_info()[1]
                raise
            # 封装响应信息
            return response(environ, start_response)
        finally:

            if self.should_ignore_error(error):
                error = None
            # 出栈 删除本次请求的相关信息 也就是说 将自己请求的所有数据在local中删除调
            ctx.auto_pop(error)

  1.1.0=====RequestContext.push

      这里最核心的是这一句    _request_ctx_stack.push(self)  那么   _request_ctx_stack又是个什么玩意  查看导入   from .globals import _request_ctx_stack  来源flask 全局变量 类似thread.local的东西 具体原理去复习另外一篇 flask源码之全局变量

    注意传值,将self传了进去,这个self就是RequestContext的实例,同时包含了 app ,session ........等

    def push(self):
        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 这里其实就是 AppContext 的实例对象
            # app_ctx.app  当前app对象
            # app_ctx.g   只对当前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()
        # self 是 requestContext 对象 那个 其中包含了请求相关的数据
        # _request_ctx_stack 就是globals中的 LocalStack() 对象
        # 所以 _request_ctx_stack.push(self) 将请求相关的数据 全部添加到 Local 中
        _request_ctx_stack.push(self)


        # 获取session 所以就算请求刚进来 没有session 那么在这里赋值之后session 就不为空 
        if self.session is None:
            # self.app.session_interface 这应该是个独立的类 专门处理session的接口
            # session_interface=SecureCookieSessionInterface
            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)

        if self.url_adapter is not None:
            self.match_request()

  

    1.1.1=== _request_ctx_stack.push

      _request_ctx_stack = LocalStack()   所以_request_ctx_stack.push 调用的是 LocalStack.push

_request_ctx_stack = LocalStack()

    1.1.2===  LocalStack.push

      LocalStack是 伴随着 服务启动开始就被实例化的类,这里实例化就实例化了 Local() ,这里Local 什么都没做 就是实例化了2个属性,但是在赋值的时候并没有使用 self[key]=value这种方式,这是因为这个家伙实现了一堆双下方法,同时实现了__setitem__方法,使用这种方法赋值避免 死递归

    同时要注意下方标红的代码

     

class LocalStack(object):

    def __init__(self):
        self._local = Local()
    
     def push(self, obj):
        # 将 loaclstack
        rv = getattr(self._local, "stack", None)
        # print("_local====>", self._local)
        # print("rv====>",rv)
        # 这里的rv是 None
        # 将 loaclstack 添加到local中去 self._local = Local()
        if rv is None:
            # 这里会执行 Local() 的__setattr__方法 以后在用的时候小心死递归
            # 这里就等于 local() 里的这个 storage[携程号][stack]=[]
            self._local.stack = rv = []
            # 这里的obj 就是 外边传进来的self 也就是 requestContext 对象
        rv.append(obj)
        return rv

  Local代码:

class Local(object):
    __slots__ = ("__storage__", "__ident_func__")

    def __init__(self):
        object.__setattr__(self, "__storage__", {})
        object.__setattr__(self, "__ident_func__", get_ident)

  接着返回  wsgi_app 开始匹配执行视图函数

    def wsgi_app(self, environ, start_response):

        # ctx = self.request_context(environ) 在调该方法的时候实际上是 RequestContext 对象
        # 因为 RequestContext 在实例化的时候 将self也就是flask对象当作参数传进去了所以 RequestContext
        # 的初始化方法中的app就是flask对象所以能ctx.app等
        # print("wsgi_app===>environ--",environ)
        ctx = self.request_context(environ)
        error = None
        try:
            try:
                # ctx入栈 因为ctx是封装了请求相关 内部也将于应用上下文入栈
                # 将localstack 添加到local中 并且这时候ctx 中的session不为None request_context中的request_class 给了个{}
                # 这个时候 app实例中已经包含了session的空字典 那么就可以在视图函数中 操作
                ctx.push()
                # 对请求的url进行视图函数的匹配 执行视图函数 返回相应
                response = self.full_dispatch_request()
            except Exception as e:
                error = e
                # 若发生错误 将错误信息返回
                response = self.handle_exception(e)
            except:  # noqa: B001
                error = sys.exc_info()[1]
                raise
            # 封装响应信息
            return response(environ, start_response)
        finally:

            if self.should_ignore_error(error):
                error = None
            # 出栈 删除本次请求的相关信息 也就是说 将自己请求的所有数据在local中删除调
            ctx.auto_pop(error)

  

  1.2.0=== full_dispatch_request 代码:

      真正执行的是  dispatch_request 方法

    def full_dispatch_request(self):

        # 执行before_first_request 只执行一次 中间件
        self.try_trigger_before_first_request_functions()
        try:
            # 触发信号 request_startd信号
            request_started.send(self)
            # 预处理  找到其中间件 看源码 貌似 调的app的蓝图
            rv = self.preprocess_request()
            if rv is None:
                # 执行views
                rv = self.dispatch_request()
        except Exception as e:
            rv = self.handle_user_exception(e)

        # 处理  视图 函数 完成 之后
        return self.finalize_request(rv)

    1.2.1===dispatch_request 代码:

      看到这如果记不清楚,去复习下 flask的路由,flask源码之路由

    def dispatch_request(self):

        req = _request_ctx_stack.top.request
        if req.routing_exception is not None:
            self.raise_routing_exception(req)
        rule = req.url_rule
        # if we provide automatic options for this URL and the
        # request came with the OPTIONS method, reply automatically
        if (
            getattr(rule, "provide_automatic_options", False)
            and req.method == "OPTIONS"
        ):
            return self.make_default_options_response()
        # otherwise dispatch to the handler for that endpoint
        # 还是 从self.view_functions 通过 endpoint 调的
        return self.view_functions[rule.endpoint](**req.view_args)

  这里还要返回 full_dispatch_request 中    finalize_request 

    def full_dispatch_request(self):
        """Dispatches the request and on top of that performs request
        pre and postprocessing as well as HTTP exception catching and
        error handling.

        .. versionadded:: 0.7
        """

        # 执行before_first_request 只执行一次 中间件
        self.try_trigger_before_first_request_functions()
        try:
            # 触发信号 request_startd信号
            request_started.send(self)
            # 预处理  找到其中间件 看源码 貌似 调的app的蓝图
            rv = self.preprocess_request()
            if rv is None:
                # 执行views
                rv = self.dispatch_request()
        except Exception as e:
            rv = self.handle_user_exception(e)

        # 处理  视图 函数 完成 之后
        return self.finalize_request(rv)

  finalize_request就不在写 用处不大,看源码,其实就是类似request_class ,里边同样实现了 response_class 并且调用了make_response 变成了响应对象

  响应完成之后还要 删除 本次请求的相关信息   将自己请求的所有数据在local中删除调

  

  1.2.2===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)

  1.2.3==== pop

    def pop(self, exc=_sentinel):

        # 先删除 程序上下文
        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 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()
            if clear_request:
                rv.request.environ["werkzeug.request"] = None
            if app_ctx is not None:
                app_ctx.pop(exc)

            assert rv is self, "Popped wrong request context. (%r instead of %r)" % (
                rv,
                self,
            )

  1.2.4=====_request_ctx_stack.pop就等于LocalStack.pop

      这里 stack 本质上是个 字典的 对象

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

    至此 flask整个 请求相应周期完成

 

总结:

  1、需要 了解 threadlocal的原理

  2、python的引用语义

  3、python丰富的双下方法以及它们的使用 例如 __setitem__ 引发的死循环

  

 

posted @ 2021-05-28 23:03  Yuan_x  阅读(28)  评论(0编辑  收藏  举报