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

上文说到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)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容