剖析Flask上下文管理(源码)

知识储备(建议先看)

Flask上下文管理源码剖析(储备知识)疯狂点我>>>>>>>>>!!!!!!!!

一、源码开始

示例一

from  flask import  Flask


#1. 实例化Flask对象
app = Flask(__name__)


#2. 设置路由
"""
app.url_map = [ 
        ('/login', login)
        ('/home', home)
    ]
"""

@app.route('/index')
def index():
    return 'index'



if __name__ == '__main__':
    #3. 启动socket服务端
    app.run()

在以上示例中,app.run是请求的入口,而app是Flask实例化的对象,所以执行的是Flask类中的run方法,而在该改方法中又执行了run_simple方法,以下是run方法部分源码摘抄(其中self就是app对象):

from werkzeug.serving import run_simple

try:
    run_simple(host, port, self, **options)
finally:
    # reset the first request information if the development server
    # reset normally.  This makes it possible to restart the server
    # without reloader and that stuff from an interactive shell.
    self._got_first_request = False

在run_simple中会执行app(environ, start_response),参考werkzeug的源码,源码会执行app(environ, start_response)也就是执行app的__call__方法,以下是__call__方法源码摘抄:

1. 当用户请求来以后会执行__call__方法, __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."""
    return self.wsgi_app(environ, start_response)  #当用户请求进来后会执行wsgi_app将请求的数据和响应的数据传到wsgi_app中

2.执行wsgi_app

    def wsgi_app(self, environ, start_response):
        """The actual WSGI application. This is not implemented in
        :meth:`__call__` so that middlewares can be applied without
        losing a reference to the app object. Instead of doing this::

            app = MyMiddleware(app)

        It's a better idea to do this instead::

            app.wsgi_app = MyMiddleware(app.wsgi_app)

        Then you still have the original application object around and
        can continue to call methods on it.

        .. versionchanged:: 0.7
            Teardown events for the request and app contexts are called
            even if an unhandled error occurs. Other events may not be
            called depending on when an error occurs during dispatch.
            See :ref:`callbacks-and-errors`.

        :param environ: A WSGI environment.
        :param start_response: A callable accepting a status code,
            a list of headers, and an optional exception context to
            start the response.
        """

  
        #ctx.app 当前app名称
        #ctx.request request对象,由app.request_class(environ)生成
        #ctx.session session 相关信息
        ctx = self.request_context(environ)
        error = None
        try:
            try:
                #push数据到local,此时push的数据分请求上线文和应用上下文
                # 将ctx通过Localstack添加到local中
                # app_ctx是APPContext对象
                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 = self.request_context(environ),然后执行request_context函数,并且实例化RequestContext,(这里面也包含了app,执行了app_context函数,实例化了AppContext)

RequestContext封装了request和session

AppContext封装了app和g

以下是代码

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

此时的request和session为None,所以self.request=app.request_class(environ),而在Flask类中request_class = Request,此时执行的是Request(environ)对请求来的数据进行再次封装,也就是实例化Request类,用于封装请求数据,最后返回RequestContext对象,此时的ctx含有以下属性ctx.app(app对象)、ctx.request(请求封装的所有请求信息)、ctx.app(当前app对象)等

 

然后第二句执行ctx.push(),调用了RequestContext的push方法:

    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  #获取app应用上下文,此时为None
        if app_ctx is None or app_ctx.app != self.app:
            # 创建APPContext(self)对象,app_ctx=APPContext(self)
            #包含app_ctx.app  ,当前app对象
            # 包含app_ctx.g  , g可以看作是一个字典用来保存一个请求周期需要保存的值
            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 = ctx(request,session)
        #_request_ctx_stack帮助我们维护一个__storage__ = {}
        #self 是RequestContext对象,其中包含了请求相关的所有数据
        _request_ctx_stack.push(self)
        """
            __storage__= {
                1231:{
                stack:[ctx(request,session),]
                }
            }
        """

        if self.session is None:
            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)

到了这里可以看到,相关注解已经标注,flask内部将上下文分为了app_ctx(应用上下文)和_request_ctx(请求上下文),并分别用来两个LocalStack()来存放各自的数据(以下会用request_ctx说明,当然app_ctx也一样)。

 

然后分别执行app_ctx.push()方法和_request_ctx_stack.push(self)方法,将数据push到stack上,_request_ctx_stack.push(self),而_request_ctx_stack是一个LocalStack对象,是一个全局对象,具体路径在flask.globals,以下是其push方法: 

进入到LocalStack我们来看下push方法

                
                
def push(self, obj):
    """Pushes a new item to the stack"""
    #找_local对象中是否有stack,没有设置rv和_local.stack都为[]
    rv = getattr(self._local, 'stack', None)
    if rv is None:
        self._local.stack = rv = []
        # 执行Local对象的__setattr__方法,等价于a=[],rv=a, self._local.stack =a
        #创建字典,类似于storage={'唯一标识':{'stack':[]}}
        #例如storage={'12312':{'stack':[ctx,]}}
    rv.append(obj)
        #列表中追加请求相关所有数据也就是storage={'唯一标识':{'stack':[RequestContext对象,]}}
    return rv

通过LocalStack把数据提交给local并且存放,此时是用线程或者协程来标识数据隔离的。

 

我们来看下local:

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

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

    def __iter__(self):
        return iter(self.__storage__.items())

    def __call__(self, proxy):
        """Create a proxy for a name."""
        return LocalProxy(self, proxy)

    def __release_local__(self):
        self.__storage__.pop(self.__ident_func__(), None)

    def __getattr__(self, name):
        try:
            return self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        ident = self.__ident_func__()
        storage = self.__storage__
        try:
            storage[ident][name] = value
        except KeyError:
            storage[ident] = {name: value}

    def __delattr__(self, name):
        try:
            del self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)

 

这段代码就是LocalStack将数据传递,存放到local.

 

到这里我们知道了,当执行ctx.push()时,local对象中已经有数据了,接着开始执行self.full_dispatch_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
    """
    self.try_trigger_before_first_request_functions()
    try:
        request_started.send(self)
        rv = self.preprocess_request()
        if rv is None:
            rv = self.dispatch_request()
    except Exception as e:
        rv = self.handle_user_exception(e)
    return self.finalize_request(rv)
在改方法中调用self.preprocess_request(),用于执行所有被before_request装饰器装饰的函数,从源码总可以看到如果该函数有返回,则不会执行self.dispatch_request()也就是视图函数,执行完毕之后调用self.dispatch_request()根据路由匹配执行视图函数,然后响应最后调用ctx.auto_pop(error)将stack中的数据删除,此时完成一次请求。
 
在我们了解完上下文管理后这个时候我们执行到了视图函数,来看下我们是如何对全局变量request,session,g进行使用的
代码
# context locals
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
g = LocalProxy(partial(_lookup_app_object, 'g'))

当我们视图使用request.method时候实际上是调用是其__getattr__方法即LocalProxy对象的__getattr__方法,我们先来看看LocalProxy对象实例化的参数:

def __init__(self, local, name=None):
    #local是传入的函数,该句等价于self.__local=local,_类名__字段强行设置私有字段值
    #如果是requst则函数就是partial(_lookup_req_object, 'request')
    object.__setattr__(self, '_LocalProxy__local', local)
    object.__setattr__(self, '__name__', name) #开始的时候设置__name__的值为None
    if callable(local) and not hasattr(local, '__release_local__'):
        # "local" is a callable that is not an instance of Local or
        # LocalManager: mark it as a wrapped function.
        object.__setattr__(self, '__wrapped__', local)

在源码中实例化时候传递的是partial(_lookup_req_object, 'request')函数作为参数,也就是self.__local=该函数,partial参数也就是我们之前提到的partial函数,作用是传递参数,此时为_lookup_req_object函数传递request参数,这个在看看其__getattr__方法:

def __getattr__(self, name):
    #以获取request.method 为例子,此时name=method
    if name == '__members__':
        return dir(self._get_current_object())
    #self._get_current_object()返回的是ctx.request,再从ctx.request获取method (ctx.request.method)
    return getattr(self._get_current_object(), name)

在以上方法中会调用self._get_current_object()方法,而_get_current_object()方法中会调用self.__local()也就是带参数request参数的_lookup_req_object方法从而返回ctx.request(请求上下文),最后通过然后反射获取name属性的值,这里我们name属性是path,如果是request.method name属性就是method,最后我们在看看_lookup_req_object怎么获取到的ctx.request,以下是源码摘抄:

def _lookup_req_object(name):
    #以name=request为列
    top = _request_ctx_stack.top
    # top是就是RequestContext(ctx)对象,里面含有request、session 等
    if top is None:
        raise RuntimeError(_request_ctx_err_msg)
    return getattr(top, name) #到RequestContext(ctx)中获取那么为request的值

在源码中很简单无非就是利用_request_ctx_stack(也就是LocalStack对象)的top属性返回stack中的ctx,在通过反射获取request,最后返回ctx.request。以上是整个flask的上下文核心机制,与其相似的全局对象有如下(session、g):

# context locals
_request_ctx_stack = LocalStack()  #LocalStack()包含pop、push方法以及Local对象,上下文通过该对象push和pop
_app_ctx_stack = LocalStack()
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, 'request’))  #reuqest是LocalProxy的对象,设置和获取request对象中的属性通过LocalProxy定义的各种双下划线实现
session = LocalProxy(partial(_lookup_req_object, 'session'))
g = LocalProxy(partial(_lookup_app_object, 'g')) 

 

 

 

posted @ 2018-12-21 22:00  Jacob先生  阅读(183)  评论(0编辑  收藏  举报