• Flask的上下文源码剖析


    先写一段Flask程序

    from flask import Flask
    
    app = Flask(__name__)
    
    
    @app.route('/')
    def hello_world():
        return 'Hello World!'
    
    
    if __name__ == '__main__':
        app.__call__  # 不加括号就不会调用,看源码方便
        app.run()  # 请求一旦到来,就执行app.__call__()方法

    请求一旦到来,就会执行app.__call__()方法,我们先看__call__的源码。

        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)

    这段代码中的注释翻译过来是这样的:WSGI服务器调用Flask应用程序对象作为WSGI应用程序。这就叫: meth : ` wsgi _ app ',它可以打包应用中间件。"

    这里面有envirron和start_response参数,是WSGI传过来的,传过来就处理好。然后看下一步,点击wsgi_app看源码

    
    
    def wsgi_app(self, environ, start_response):
    ctx = self.request_context(environ) # 这里又处理一次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)

    wsgi_app方法里面有一个ctx变量,这里面的request_context里面封装了很多方法,里面一个是处理request,一个处理session的。并且又处理了一次environ。

    看request_context的源码。

        def request_context(self, environ):
    
            return RequestContext(self, environ)

    它里面返回了一个RequestContext(self, enveron)对象。

    看RequestContext源码

    class RequestContext(object):
    
        def __init__(self, app, environ, request=None):  # 这里面的envieon是不太彻底的请求
            self.app = app
            if request is None:  # 看着一句条件,看参数request=None,满足条件
                request = app.request_class(environ)  # 如果request=None就走这一行,把原生的请求给它,让他继续加工
            self.request = request  # 看到这就知道了前面的ctx.request就等于Request(environ)这个对象
            self.url_adapter = app.create_url_adapter(self.request)
            self.flashes = None
            self.session = None  # ctx.session = None
            self._implicit_app_ctx_stack = []
    self.preserved = False
    self._preserved_exc = None
    self._after_request_functions = []
    self.match_request()

            它里面有environ参数和request参数。看里面的if语句,如果参数request是空,就把原生的请求给request_class这个类继续加工。里面还有一个self.session,并且是空的。所以ctx.request就等于Request(environ)这个对象,ctx.session = None。并且ctx = self.request_context(environ)这一行代码不仅创建了一个对象,还实现了一个路由匹配,因为init里面还有一行代码self.match_request(),这个就是通过路由来找视图函数,这里就不多说了。综上所述request.context也就是ctx封装了处理request和session的方法。

     再看wsgi_app方法里面有ctx.push(),这里就是打包。点击puth看源码

        def push(self):
            top = _request_ctx_stack.top
            if top is not None and top.preserved:
                top.pop(top._preserved_exc)
    
            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)  # 这就是往里放
    
            if self.session is None:  # 打包之后,这时的session还是空的,满足条件,就给session重新赋值
                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)

    _request_ctx_stack.push(self)这一行代码就是往里放,放的时候如果遇到多线程肯定有隔离处理,所以它里面肯定有类似threading.local()方法,local对象它自动就帮你进行数据隔离了。

    点击_request_ctx_stack看源码。

    _request_ctx_stack = LocalStack()

    点击LocalStack看源码

    class LocalStack(object):

    def __init__(self):
    self._local = Local()

    def __release_local__(self):
    self._local.__release_local__()

    def _get__ident_func__(self):
    return self._local.__ident_func__

    def _set__ident_func__(self, value):...

    def __call__(self):...

    def push(self, obj):
    """Pushes a new item to the stack"""
    rv = getattr(self._local, 'stack', None)
    if rv is None:
    self._local.stack = rv = [] # 如果没有,就给stack创建一个空列表
    rv.append(obj)
    return rv

    def pop(self):...

    可以看到它里面又Local()对象,再看它的push方法,rv = getattr(self._local, 'stack', None)可以看到要执行这个push方法的时候要去它的self._local对象找它的stack。这就相当于,当线程进来的时候,会先拿到它的唯一标识,然后找它的stack,如果有就使用,没有就给它的stack创建一个空列表,把ctx放到列表里面,ctx里面有他自己的request和sesson,这就是打包放在这了。

    # 结构就是这个形式
    {
        线程一的唯一标识: {stack: [ctx(request, session), ]},
        线程二的唯一标识: {stack: [ctx(request, session), ]},
    }

    打包完之后,session还是空的,接下来就是给session重新赋值了。

            _request_ctx_stack.push(self)  # 这就是往里放
    
            if self.session is None:  # 打包之后,这时的session还是空的,满足条件,就给session重新赋值
                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)

     打包之后,session也赋值了,接下来继续看源码。

    def wsgi_app(self, environ, start_response):
        ctx = self.request_context(environ)  # 这里又处理一次environ
        error = None
        try:
            try:
                ctx.push()  # 这里就是打包起来
                response = self.full_dispatch_request()  # 打包之后,要通过路由去找视图函数

    在wsgi_app方法里面就该通过路由去找视图函数了,点击full_dispatch_request看它里面封装了什么方法。

    def full_dispatch_request(self):

    self.try_trigger_before_first_request_functions()
    try:
    request_started.send(self)
    rv = self.preprocess_request() # 这个就是来执行before_request1的
    if rv is None: # 如果执行之后,没有结果,就来执行我们的视图函数
    rv = self.dispatch_request()
    except Exception as e:
    rv = self.handle_user_exception(e)
    return self.finalize_request(rv)

    其他的先不看,先看它的preprocess_request,看它里面封装了什么。

        def preprocess_request(self):
    
            bp = _request_ctx_stack.top.request.blueprint
    
            funcs = self.url_value_preprocessors.get(None, ())
            if bp is not None and bp in self.url_value_preprocessors:
                funcs = chain(funcs, self.url_value_preprocessors[bp])
            for func in funcs:
                func(request.endpoint, request.view_args)
    
            funcs = self.before_request_funcs.get(None, ())  # 在Flask函数执行之前,会执行一些特殊的装饰器before_request
            if bp is not None and bp in self.before_request_funcs:
                funcs = chain(funcs, self.before_request_funcs[bp])
            for func in funcs:
                rv = func()
                if rv is not None:
                    return rv

    所以preprocess_request就是来执行before_request的。

    def full_dispatch_request(self):
    
        self.try_trigger_before_first_request_functions()
        try:
            request_started.send(self)
            rv = self.preprocess_request()  # 这个就是来执行before_request1的
            if rv is None:  # 如果执行之后,没有结果,就来执行我们的视图函数
                rv = self.dispatch_request()  # 执行视图函数
        except Exception as e:
            rv = self.handle_user_exception(e)
        return self.finalize_request(rv)  # 函数处理完,就该回去了,那么接下来做什么?看下面源码流程

    如果执行之后没什么结果,就去执行我们的视图函数。

    不管函数执行结果怎样,我们都会拿到一个rv。函数执行之后,会有一个返回值,那我们点击self.finalize_request来看返回什么。

        def finalize_request(self, rv, from_error_handler=False):
    
            response = self.make_response(rv)
            try:
                response = self.process_response(response)
                request_finished.send(self, response=response)
            except Exception:
                if not from_error_handler:
                    raise
                self.logger.exception('Request finalizing failed with an '
                                      'error while handling an error')
            return response

    里面有一个process_response,这里面返回的是一个实例。那我们来看看这个实例里面帮我们做了什么

        def process_response(self, response):
            ctx = _request_ctx_stack.top
            bp = ctx.request.blueprint
            funcs = ctx._after_request_functions
            if bp is not None and bp in self.after_request_funcs:
                funcs = chain(funcs, reversed(self.after_request_funcs[bp]))
            if None in self.after_request_funcs:
                funcs = chain(funcs, reversed(self.after_request_funcs[None]))
            for handler in funcs:
                response = handler(response)
            if not self.session_interface.is_null_session(ctx.session):
                self.session_interface.save_session(self, ctx.session, response)
            return response

    里面有after_request和save_session,原来我们已经存了用户的数据,这里是拿到数据返回给用户浏览器。

    def full_dispatch_request(self):
    
        self.try_trigger_before_first_request_functions()
        try:
            request_started.send(self)
            rv = self.preprocess_request()  # 这个就是来执行before_request1的
            if rv is None:  # 如果执行之后,没有结果,就来执行我们的视图函数
                rv = self.dispatch_request()  # 执行视图函数
        except Exception as e:
            rv = self.handle_user_exception(e)
        return self.finalize_request(rv)  # 函数处理完,就该回去了,这里面做的就是执行了after、_request和save_session,把手里现有的值返回就行了

    接下来我们需要往回看。

    def wsgi_app(self, environ, start_response):
    ctx = self.request_context(environ) # 这里又处理一次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.auto_pop

    给用户返回数据后有个finally,里面还有个ctx.auto_pop我们进去看看它做了什么

        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)

    里面有个self.pop,继续看里面的源码。

    class RequestContext(object):
       
        def __init__(self, app, environ, request=None):......
    
        def copy(self):......
    
        def match_request(self):......
    
        def push(self):......  # 刚开始传入数据就是通过push
    
        def pop(self, exc=_sentinel):......  # 这个pop就是删掉用户请求进来的信息

    就是把线程一的信息删掉,接下来执行线程二,这样用户请求进来的数据就不会混到一块了。

    # 结构就是这个形式
    {
        线程二的唯一标识: {stack: [ctx(request, session), ]},
    }

    综上,可以总结为三个阶段:

    第一阶段:将ctx(request/session)放到Local对象里面(Local()会给每一个线程开辟一个空间来存数据)

    第二阶段:视图函数导入,处理request和session

    第三阶段:请求处理完毕,获取session保存到cookie里面,然后删掉ctx

  • 相关阅读:
    js 2119. 反转两次的数字
    js 面试题 16.02. 单词频率
    javaScript 使数组中所有元素相等的最小操作数
    selfattention transformer
    RMSprop 优化器
    Variablepytorch
    华为云容器引擎 解决云下主机无法直接访问容器IP
    EXTJS7 实现点击拖拉选择文本
    远程git仓库密码修改后idea添加remote地址或推送时报错处理
    构建docker镜像部署rocketmq
  • 原文地址:https://www.cnblogs.com/aaronthon/p/9459897.html
Copyright © 2020-2023  润新知