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)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容