zoukankan      html  css  js  c++  java
  • Flask系列10-- Flask请求上下文源码分析

    总览

    一.基础准备. 

    1. local类

      对于一个类,实例化得到它的对象后,如果开启多个线程对它的属性进行操作,会发现数据时不安全的

    import time
    from threading import Thread
    import threading
    
    class Foo(object):
        pass
    
    foo = Foo()
    
    def add(i):
        foo.num = i
        time.sleep(1)
        print(foo.num,i,threading.current_thread().ident,foo)
    
    for i in range(10):
        task = Thread(target=add,args=(i,))
        task.start()
    
    ##结果##
    9 9 5616 <__main__.Foo object at 0x0000018992A05400>
    9 8 9780 <__main__.Foo object at 0x0000018992A05400>
    9 6 4692 <__main__.Foo object at 0x0000018992A05400>
    9 3 2168 <__main__.Foo object at 0x0000018992A05400>
    9 1 4424 <__main__.Foo object at 0x0000018992A05400>
    9 0 10264 <__main__.Foo object at 0x0000018992A05400>
    9 7 11728 <__main__.Foo object at 0x0000018992A05400>
    9 5 6688 <__main__.Foo object at 0x0000018992A05400>
    9 2 808 <__main__.Foo object at 0x0000018992A05400>
    9 4 1160 <__main__.Foo object at 0x0000018992A05400>

    # 以上结论得知:
    # 线程操作公共对象 产生不安全现象

      为了保证对属性操作的安全,而且又不使用锁(使用锁会使异步线程变成同步操作), 可以使用继承local类的方式实现

    from threading import local
    
    class Foo(local):
        pass
    
    foo = Foo()
    
    def add(i):
        foo.num = i
        time.sleep(1)
        print(foo.num,i,threading.current_thread().ident,foo)
    
    for i in range(10):
        task = Thread(target=add,args=(i,))
        task.start()
    
    ##结果##
    8 8 10372 <__main__.Foo object at 0x000002157C037648>
    9 9 8604 <__main__.Foo object at 0x000002157C037648>
    6 6 9512 <__main__.Foo object at 0x000002157C037648>
    5 5 1240 <__main__.Foo object at 0x000002157C037648>
    3 3 5404 <__main__.Foo object at 0x000002157C037648>
    2 2 13548 <__main__.Foo object at 0x000002157C037648>
    0 0 10516 <__main__.Foo object at 0x000002157C037648>
    7 7 8644 <__main__.Foo object at 0x000002157C037648>
    4 4 8420 <__main__.Foo object at 0x000002157C037648>
    1 1 4372 <__main__.Foo object at 0x000002157C037648>

      多线程实现的栈(简易), 注意使用了local类. 使用local能保证每一个线程都能对类的属性进行操作,而且互不干扰

    from threading import local,Thread
    import threading
    
    class MyLocalStack(local):
        stack = {}
        pass
    
    mls = MyLocalStack()
    
    def ts(i):
        a = threading.current_thread().ident
        mls.stack[a] = [f"r{i+1}",f"s{i+1}"]
        print(mls.stack,a)
        time.sleep(1)
        print(mls.stack[a].pop(),mls.stack[a].pop(),a,'删除')
    
        # time.sleep(0.0089)
        mls.stack.pop(a)
        print(mls.stack , a, '删除')
    
    for i in range(5):
        task = Thread(target=ts,args=(i,))
        task.start()
    
    #堆栈
        # 程序员
    # 先进后出 后进先出
    # 先进先出 后进后出

      结果

     2. app()

    from flask import Flask
    app = Flask(__name__)
    app.run()

      这个app就是,flask启动时的那个对象, 一般函数加()的意思是执行这个函数,而这里对象加() 的意思就是执行app对象的__call__方法, 原因文档里面解释的非常清楚.看下图

      

    二. run_simple()源码分析

    函数执行层解

    app.run() -> run_simple() -> inner() -> make_server() -> BaseWSGIServer()>WSGIRequestHandler -> 
    handle() -> run_wsgi() -> execute() ->application_iter = app(environ, start_response)

    1.  run_simple() 源码详解,  如何调用了app.__call__()

      首先运行时调用了app.run()方法, 相当于调用了app.__call__(),而为什么调用了app.__call__()从哪看出来的呢, 百度的那些粘贴人总是说werkzeug的rum_simple方法,但从没有具体的解释,我就研究了一下

    from flask import Flask
    app = Flask(__name__)
    app.run()

    def run(self, host=None, port=None, debug=None,
        load_dotenv=True, **options): # self = app = Flask()
    
            from werkzeug.serving import run_simple  # 引入werkzeug相关 run_simple开始运行
        
            try:  # host 127.0.0.1 port=5000 self=app=Flask()
                run_simple(host, port, self, **options)  # self = app = Flask()    
    

    run_simple()中将执行inner()函数

    def run_simple(
        hostname,
        port,
        application,  # self = app = Flask() application: the WSGI application to execute
        use_reloader=False,
        use_debugger=False,
        use_evalex=True,
        extra_files=None,
        reloader_interval=1,
        reloader_type="auto",
        threaded=False,
        processes=1,
        request_handler=None,
        static_files=None,
        passthrough_errors=False,
        ssl_context=None,
    ):
    
            from ._reloader import run_with_reloader
            run_with_reloader(inner, extra_files, reloader_interval, reloader_type) # 这里开始 使用inner函数
        else:
            inner()  # 这里开始 使用inner函数   

    来看inner()函数,这个inner()函数是被包含在run_simple()函数中的

        def inner():
            try:
                fd = int(os.environ["WERKZEUG_SERVER_FD"])
            except (LookupError, ValueError):
                fd = None
            srv = make_server(
                hostname,
                port,
                application,   # self = app = Flask()  第三个位置参数
                threaded,
                processes,
                request_handler,
                passthrough_errors,
                ssl_context,
                fd=fd,
            )  # srv就是返回了一个 # BaseWSGIServer实例BaseWSGIServer(app) srv.app = app
            if fd is None:
                log_startup(srv.socket)
            srv.serve_forever() # srv(self = app = Flask() ) 

    inner()中注意make_server()

    def make_server(
        host=None,
        port=None,
        app=None,    # self = app = Flask()
        threaded=False,
        processes=1,
        request_handler=None,
        passthrough_errors=False,
        ssl_context=None,
        fd=None,
    ):
        """Create a new server instance that is either threaded, or forks
        or just processes one request after another.
    
        创建一个新的server实例
        """
        if threaded and processes > 1:
            raise ValueError("cannot have a multithreaded and multi process server.")
        elif threaded:
            return ThreadedWSGIServer(  # 多线程wsgiserver启动
                host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd
            )   # self = app = Flask()
        elif processes > 1:  # 多个进程时
            return ForkingWSGIServer(
                host,
                port,
                app,   # self = app = Flask()
                processes,
                request_handler,
                passthrough_errors,
                ssl_context,
                fd=fd,
            )
        else:
            return BaseWSGIServer(
                host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd
            ) # self = app = Flask()

    来看BaseWSGIServer

    class BaseWSGIServer(HTTPServer, object):
    
        """Simple single-threaded, single-process WSGI server."""
    
        multithread = False
        multiprocess = False
        request_queue_size = LISTEN_QUEUE
    
        def __init__(
            self,
            host,
            port,
            app,  # self = app = Flask()
            handler=None,
            passthrough_errors=False,
            ssl_context=None,
            fd=None,
        ):
            if handler is None:
                handler = WSGIRequestHandler
    
            self.address_family = select_address_family(host, port)
    
            if fd is not None:
                real_sock = socket.fromfd(fd, self.address_family, socket.SOCK_STREAM)
                port = 0
    
            server_address = get_sockaddr(host, int(port), self.address_family)
    
            # remove socket file if it already exists
            if self.address_family == af_unix and os.path.exists(server_address):
                os.unlink(server_address)
            HTTPServer.__init__(self, server_address, handler) #handler = WSGIRequestHandler
    
            self.app = app  # self = app = Flask()
            self.passthrough_errors = passthrough_errors
            self.shutdown_signal = False
            self.host = host
            self.port = self.socket.getsockname()[1]

    来看WSGIrequesthandler ,部分代码省略

        def run_wsgi(self):
            if self.headers.get("Expect", "").lower().strip() == "100-continue":
                self.wfile.write(b"HTTP/1.1 100 Continue
    
    ")
    
            self.environ = environ = self.make_environ()
            headers_set = []
            headers_sent = []
    
            def execute(app):
                application_iter = app(environ, start_response) # app()= self.wsgi_app(environ, start_response)
                try:
                    for data in application_iter:
                        write(data)
                    if not headers_sent:
                        write(b"")
                finally:
                    if hasattr(application_iter, "close"):
                        application_iter.close()
                    application_iter = None
    
            try:
                execute(self.server.app)  #
            except (_ConnectionError, socket.timeout) as e:
                self.connection_dropped(e, environ)
            except Exception:
                if self.server.passthrough_errors:
                    raise
                from .debug.tbtools import get_current_traceback
    
                traceback = get_current_traceback(ignore_system_exceptions=True)
                try:
                    # if we haven't yet sent the headers but they are set
                    # we roll back to be able to set them again.
                    if not headers_sent:
                        del headers_set[:]
                    execute(InternalServerError())
                except Exception:
                    pass
                self.server.log("error", "Error on request:
    %s", traceback.plaintext) 

     至此可以看出用到了app()进而就可以知道app.run()时实际就是执行了app.__call__()

     三.请求上下文源码分析

    1.请求上文

    函数执行层解

    由app.__all__()入口进入查看请求上文流程

    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

     def wsgi_app(self, environ, start_response): # environ = 请求的原始信息 self=app=Flask()
            # self = app = Flask()
            """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.
            """
            # self = app = Flask()
            # environ = 请求的原始信息 请求头 请求体 path method
    
            # environ = 请求的原始信息
            # self = app = Flask()
            ctx = self.request_context(environ)  # 请求上下文
            # ctx = request_context对象 -> RequestContext(app,environ)-> app,request,session
            # ctx = RequestContext(app,environ)
    
            error = None
            try:
                try:
                    ctx.push() # request_context对象 ctx = RequestContext(app,environ)
                    # 请求上文
                    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) 

      def request_context(self, environ):
            # self = app = Flask()
            # environ = 请求的原始信息
    """Create a :class:`~flask.ctx.RequestContext` representing a # 表示一个wsgi环境变量使用一个锁 WSGI environment. Use a ``with`` block to push the context, which will make :data:`request` point at this request. See :doc:`/reqcontext`. Typically you should not call this from your own code. A request context is automatically pushed by the :meth:`wsgi_app` when handling a request. Use :meth:`test_request_context` to create an environment and context instead of this method. :param environ: a WSGI environment """ # self = app = Flask() # environ = 请求的原始信息 return RequestContext(self, environ)

    实际上就是返回了一个RequestContext()对象,这个对象将request定义了出来

    class RequestContext(object):
    
        def __init__(self, app, environ, request=None):
            # app = Flask()
            # environ = 请求的原始信息
            
            # requestcontext.app = app = Flask()
            # requestcontext.request = request
            # requestcontext.session = None
    
            self.app = app
            if request is None:
                request = app.request_class(environ) # request 前身
            self.request = request
            self.url_adapter = app.create_url_adapter(self.request)  # 创建一个url适配器

    再回到wsgi_app()函数中,看到 ctx.push() 

        def push(self): # self= ctx = RequestContext(self, environ)
    
            top = _request_ctx_stack.top  # none
            # _request_ctx_stack._local = {_local:{__storage__:{}, __ident_func__:get_ident} }
            if top is not None and top.preserved:
                top.pop(top._preserved_exc)
    
            # Before we push the request context we have to ensure that there
            # is an application context.
            app_ctx = _app_ctx_stack.top # none
            #  _app_ctx_stack._local = {_local:{__storage__:{}, __ident_func__:get_ident} }
    
            if app_ctx is None or app_ctx.app != self.app:
                app_ctx = self.app.app_context()  # self= ctx = RequestContext(self, environ)
                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 = RequestContext(self, environ)
            # _request_ctx_stack._local = {_local:{__storage__:{}, __ident_func__:get_ident} }
            _request_ctx_stack.push(self) 
            #  结果: {_local:{__storage__:{9527:{stack: rv=[ctx = request_context(environ) }}, __ident_func__:get_ident} }
            
    
            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)

      看到其中 top = _request_ctx_stack.top  一个对象.top 要先看这个对象是什么__init__中执行了什么, .top的话要看__getattr__方法

    _request_ctx_stack = LocalStack() # _request_ctx_stack._local = {_local:{__storage__:{}, __ident_func__:get_ident} }
    

      LocalStack()

    class LocalStack(object):
        """This class works similar to a :class:`Local` but keeps a stack
        of objects instead.  This is best explained with an example::
    
            >>> ls = LocalStack()
            >>> ls.push(42)
            >>> ls.top
            42
            >>> ls.push(23)
            >>> ls.top
            23
            >>> ls.pop()
            23
            >>> ls.top
            42
    
        They can be force released by using a :class:`LocalManager` or with
        the :func:`release_local` function but the correct way is to pop the
        item from the stack after using.  When the stack is empty it will
        no longer be bound to the current context (and as such released).
    
        By calling the stack without arguments it returns a proxy that resolves to
        the topmost item on the stack.
    
        .. versionadded:: 0.6.1
        """
    
        def __init__(self):  # self = _request_ctx_stack
            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):
            object.__setattr__(self._local, "__ident_func__", value)
    
        __ident_func__ = property(_get__ident_func__, _set__ident_func__)
        del _get__ident_func__, _set__ident_func__
    
        def __call__(self):
            def _lookup():
                rv = self.top
                if rv is None:
                    raise RuntimeError("object unbound")
                return rv
    
            return LocalProxy(_lookup)
    
        def push(self, obj):
            # obj = ctx = RequestContext(self, environ)
            # self = _request_ctx_stack._local = {_local:{__storage__:{}, __ident_func__:get_ident} }
            
            """Pushes a new item to the stack"""
            rv = getattr(self._local, "stack", None)  # rv=none
            if rv is None:
                self._local.stack = rv = [] # {_local:{__storage__:{9527:{stack: rv=[ctx = request_context(environ) }}, __ident_func__:get_ident} }
            rv.append(obj)
            return rv
    
        def pop(self):
            """Removes the topmost item from the stack, will return the
            old value or `None` if the stack was already empty.
            """
            # _request_ctx_stack._local = {_local: {__storage__: {}, __ident_func__: get_ident}}
            
            stack = getattr(self._local, "stack", None)  # none
            if stack is None:
                return None
            elif len(stack) == 1:
                release_local(self._local)
                return stack[-1]
            else:
                return stack.pop()
    
        @property
        def top(self):
            """The topmost item on the stack.  If the stack is empty,
            `None` is returned.
            """
            try:
                return self._local.stack[-1]   #  rv=[ctx = request_context(environ)][-1]
            except (AttributeError, IndexError):
                return None
    

      然后发现了  top = _request_ctx_stack.top 的结果为none,接着看ctx.py中的push()  app_ctx = _app_ctx_stack.top # none 结果也一样

    执行到这里时,

        def push(self, obj):
            # obj = ctx = RequestContext(self, environ)
            # self = _request_ctx_stack._local = {_local:{__storage__:{}, __ident_func__:get_ident} }
            
            """Pushes a new item to the stack"""
            rv = getattr(self._local, "stack", None)  # rv=none
            if rv is None:
                self._local.stack = rv = [] # {_local:{__storage__:{9527:{stack: rv=[ctx = request_context(environ) }}, __ident_func__:get_ident} }
            rv.append(obj)
            return rv
    

      可以看到执行结束后返回的值,至此,请求上文分析完毕,下图是图解

    2.请求下文

    函数层级分析

    request.method -> LocalProxy(partial(_lookup_req_object, 'request')) ->_lookup_req_object() 
    -> LocalStack() -> Local() -> __init__() -> top() -> __getattr__()
    ->getattr(top, name)

    再来看请求下文,在视图函数中使用request.method当作入口查看请求下文

    request = LocalProxy(partial(_lookup_req_object, 'request'))

    _lookup_req_object:

    def _lookup_req_object(name):  # name = request
    # LocalStack = {_local: {"__storage__": {9527: {stack: [ctx(app, req, sess)]}}, "__ident_func__": get_ident}}
        top = _request_ctx_stack.top # ctx(app, req, sess)
        if top is None:
            raise RuntimeError(_request_ctx_err_msg)
        return getattr(top, name) # ctx(app, req, sess) name = request # request

     要看 _request_ctx_stack 

    _request_ctx_stack = LocalStack() # _request_ctx_stack._local = {_local:{__storage__:{}, __ident_func__:get_ident} }
    class LocalStack(object):
        """This class works similar to a :class:`Local` but keeps a stack
        of objects instead.  This is best explained with an example::
    
            >>> ls = LocalStack()
            >>> ls.push(42)
            >>> ls.top
            42
            >>> ls.push(23)
            >>> ls.top
            23
            >>> ls.pop()
            23
            >>> ls.top
            42
    
        They can be force released by using a :class:`LocalManager` or with
        the :func:`release_local` function but the correct way is to pop the
        item from the stack after using.  When the stack is empty it will
        no longer be bound to the current context (and as such released).
    
        By calling the stack without arguments it returns a proxy that resolves to
        the topmost item on the stack.
    
        .. versionadded:: 0.6.1
        """
    
        def __init__(self):  # self = _request_ctx_stack
            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):
            object.__setattr__(self._local, "__ident_func__", value)
    
        __ident_func__ = property(_get__ident_func__, _set__ident_func__)
        del _get__ident_func__, _set__ident_func__
    
        def __call__(self):
            def _lookup():
                rv = self.top
                if rv is None:
                    raise RuntimeError("object unbound")
                return rv
    
            return LocalProxy(_lookup)
    
        def push(self, obj):
            # obj = ctx = RequestContext(self, environ)
            # self = _request_ctx_stack._local = {_local:{__storage__:{}, __ident_func__:get_ident} }
            
            """Pushes a new item to the stack"""
            rv = getattr(self._local, "stack", None)  # rv=none
            if rv is None:
                self._local.stack = rv = [] # {_local:{__storage__:{9527:{stack: rv=[ctx = request_context(environ) }}, __ident_func__:get_ident} }
            rv.append(obj)
            return rv
    
        def pop(self):
            """Removes the topmost item from the stack, will return the
            old value or `None` if the stack was already empty.
            """
            # _request_ctx_stack._local = {_local: {__storage__: {}, __ident_func__: get_ident}}
            
            stack = getattr(self._local, "stack", None)  # none
            if stack is None:
                return None
            elif len(stack) == 1:
                release_local(self._local)
                return stack[-1]
            else:
                return stack.pop()
    
        @property
        def top(self):
            """The topmost item on the stack.  If the stack is empty,
            `None` is returned.
            """
            try: 
                return self._local.stack[-1]   #  rv=[ctx = request_context(environ)][-1] 这里要看__getattr__方法 实际上就是反悔了
            except (AttributeError, IndexError):
                return None

     再看Local类

    class Local(object):
        __slots__ = ("__storage__", "__ident_func__")
    
        def __init__(self):
            object.__setattr__(self, "__storage__", {})  
            object.__setattr__(self, "__ident_func__", get_ident)
            # self = _request_ctx_stack._local = {_local:{__storage__:{}, __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:#{_local:{__storage__:{9527:{stack: }}, __ident_func__:get_ident} }
                return self.__storage__[self.__ident_func__()][name]  # [ctx(app,req,sess)]
            except KeyError:
                raise AttributeError(name)
    
        def __setattr__(self, name, value):
            ident = self.__ident_func__()  # {_local:{__storage__:{9527:{stack:}}, __ident_func__:get_ident} }
            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)
    

      

     到这里请求下文查看完毕

  • 相关阅读:
    C# in Depth Third Edition 学习笔记-- Lambda表达式和表达式树
    几个比较实用的.Net 反编译工具
    使用Microsoft.Practices.EnterpriseLibrary.Data调用存数过程Output参数注意事项
    C# in Depth Third Edition 学习笔记-- C#2的一些特性
    C# in Depth Third Edition 学习笔记-- 可空类型
    C# in Depth Third Edition 学习笔记-- C#2.0: 解决C#1.0的问题 1 泛型
    C# in Depth Third Edition 学习笔记-- 值类型和引用
    .Net 程序员应该知道的工具和网站
    HTML 转 PDF
    C#、ASP.NET获取当前应用程序的绝对路径,获取程序工作路径 (转帖)
  • 原文地址:https://www.cnblogs.com/robertx/p/10698093.html
Copyright © 2011-2022 走看看