美文网首页
Flask源码剖析三之上下文管理

Flask源码剖析三之上下文管理

作者: 羽煊 | 来源:发表于2019-06-16 00:56 被阅读0次

    上文说到Flask的本地WSGI Server建立,WSGI Server 监听本地端口,当有请求进入时会触发WSGIRequestHandler,WSGIRequestHandler会触发Flask app实例,这样Server,Request,APP实例就建立起了关系,本文从app实例被触发说起,谈谈Flask的上下文管理。

    一 请求到来前,入栈
    从上文的分析中可以知道,WSGIRequestHandler会触发app实例,其实就是Flask 类中的__call__方法,我们从这个call开始。

    def __call__(self, environ, start_response):
            return self.wsgi_app(environ, start_response)
    

    call 调用了wsgi_app

    def wsgi_app(self, environ, start_response):
         ctx = self.request_context(environ)
            try:
                try:
                    ctx.push()
    

    ctx = self.request_context(environ)
    def request_context(self, environ):
    return RequestContext(self, environ)

    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
    

    ctx其实就是一个RequestContext对象,里面包含request,session,app
    再看这一句request = app.request_class(environ),app.request_class来自flask.app.Flask中的request_class = Request
    class Request(RequestBase, JSONMixin):
    到这里基本可以知道,其实ctx就是一步一步封装起来的Request对象。
    再来看wsgi_app这个方法,封装了ctx对象后,紧接着就是ctx.push()。我们重点来分析下这个push方法。
    flask.ctx

        def push(self):
            top = _request_ctx_stack.top
            _request_ctx_stack.push(self)
    

    top = _request_ctx_stack.top这个操作是从_request_ctx_stack栈里取一个对象。从
    Flask源码剖析二之本地栈的建立可知,此时_request_ctx_stack是一个空字典,top = None。_request_ctx_stack.push(self) 是将ctx也就是将requestContext对象入栈。

    入栈过程:
    _request_ctx_stack.push(self)就是执行的LocalStack类中的push方法
    werkzeug.local.LocalStack

    class LocalStack(object):
        def __init__(self):
            self._local = Local()
    
        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 = []
            rv.append(obj)
            return rv
    

    rv = getattr(self._local, "stack", None)当请求第一次进来时栈为空,返回一个None
    self._local.stack = rv = [] 。self._local 是Local()的一个对象。
    werkzeug.local. Local

    class Local(object):
        __slots__ = ("__storage__", "__ident_func__")
    
        def __init__(self):
            object.__setattr__(self, "__storage__", {})
            object.__setattr__(self, "__ident_func__", get_ident)
    
        def __setattr__(self, name, value):
            ident = self.__ident_func__()
            storage = self.__storage__
            try:
                storage[ident][name] = value
            except KeyError:
                storage[ident] = {name: value}
    

    self._local.stack = [] 触发执行了Local()对象的setattr方法。 ident = self.ident_func()拿到的是当前线程或协程的ID,这个一个以为表示,每个请求的request对象都保存自己的ID里,这样就做到了线程隔离。
    storage[ident][name] = value 其实就是向本地栈的空字典_request_ctx_stack里写值
    self._local.stack = []执行后,_request_ctx_stack = {唯一ID:{'stack': []}}。

            if rv is None:
                self._local.stack = rv = []
            rv.append(obj)
    

    这就执行后_request_ctx_stack = {唯一ID:{'stack': [RequestContext(ctx)]}}
    _request_ctx_stack 中现在已经有值了,值是请求上下文对象ctx 。所以总结起来ctx.push就是把请求上下文对象写入_request_ctx_stack中。
    清楚了请求上下文,应用上下文和请求上下文是一个套路。
    _app_ctx_stack = {唯一ID:{'stack': [AppContext(app_ctx)]}}
    简单看一下应用上下文的入栈流程
    flask.ctx

    class RequestContext(object):
        def push(self):
            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()
    

    app_ctx = _app_ctx_stack.top同样的请求第一次进来,_app_ctx_stack为空。app_ctx = None
    app_ctx = self.app.app_context() 这句获取app上下文对象。

    def app_context(self):
            return AppContext(self)
    class AppContext(object):
        def __init__(self, app):
            self.app = app
            self.url_adapter = app.create_url_adapter(None)
            self.g = app.app_ctx_globals_class()
    

    这里app_ctx就是一个AppContext对象。里面包含app和g
    app_ctx.push() 这句其实和请求上下文中的ctx.push()是一样的逻辑。只不过app_ctx用的是_app_ctx_stack栈,_app_ctx_stack = LocalStack() 这个栈的结构和_request_ctx_stack栈是一样的。 后面的push操作可以参考_request_ctx_stack.push()。

    二 请求到来,执行视图函数
    在视图函数中对上下文操作
    @app.route('/')
    def index():
    print(request.args)
    对这个函数只做 print(request.args),在flask.globals中
    request = LocalProxy(partial(_lookup_req_object, 'request')),request是个LocalProxy对象,同时将偏函数partial(_lookup_req_object, 'request') 传给LocalProxy。

    class LocalProxy(object):
        def __init__(self, local, name=None):
            object.__setattr__(self, "_LocalProxy__local", local)
    

    在LocalProxy中self.__LocalProxy__local = partial(_lookup_req_object, 'request'),
    self.__LocalProxy__local 其实就是self.__local。所以self.__local = partial(_lookup_req_object, 'request')其实也就是 self.__local = _lookup_req_object('request')。
    当执行request.args是执行了LocalProxy的getattr,向LocalProxy传了2个参数,local= _lookup_req_object('request'), name = args

        def __getattr__(self, name):
            if name == "__members__":
                return dir(self._get_current_object())
            return getattr(self._get_current_object(), name)
    

    self._get_current_object()执行了

        def _get_current_object(self):
            """Return the current object.  This is useful if you want the real
            object behind the proxy at a time for performance reasons or because
            you want to pass the object into a different context.
            """
            if not hasattr(self.__local, "__release_local__"):
                return self.__local()
            try:
                return getattr(self.__local, self.__name__)
            except AttributeError:
                raise RuntimeError("no object bound to %s" % self.__name__)
    

    从上面的分析可以知道self.__local = _lookup_req_object('request'),所以getattr(self.__local, self.name) 就是getattr(_lookup_req_object('request'),args) ,再看_lookup_req_object这函数

    def _lookup_req_object(name):
        top = _request_ctx_stack.top
        if top is None:
            raise RuntimeError(_request_ctx_err_msg)
        return getattr(top, name)
    

    这是一个全局函数,是从_request_ctx_stack栈顶的请求上下文对象ctx中取一个叫request的对象。拿到request对象后再执行

    def __getattr__(self, name): 
        return getattr(self._get_current_object(), name)
    

    返回的结果是getattr(request,args) 从request对象里面取args。

    三 请求结束

    def wsgi_app(self, environ, start_response):
            finally:
                if self.should_ignore_error(error):
                    error = None
                ctx.auto_pop(error)
    

    在wsgi_app方法里可以看到请求最后都会调用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)
    

    auto_pop最终调用pop方法,将对象移除。

      def pop(self, exc=_sentinel):
         finally:
                rv = _request_ctx_stack.pop()
                if app_ctx is not None:
                    app_ctx.pop(exc)
    

    相关文章

      网友评论

          本文标题:Flask源码剖析三之上下文管理

          本文链接:https://www.haomeiwen.com/subject/xycwfctx.html