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\r\n\r\n")

        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:\n%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)

  

 到这里请求下文查看完毕

 

posted @ 2019-04-12 19:35  robertx  阅读(458)  评论(0编辑  收藏  举报