flask源码之app.run方法的背后

引子:

  掐指一算从去年年底的公司分享开始接触到flask到现在已经有半年多了,3月份在网上查询资料,偶然发现了很多flask源码解析的博客,注意到一句话flask执行run方法后实际上调用的是flask实例的__call__方法,但是从网上找了很多的资料,发现大多数的博客内容雷同,且很模糊,并且好多都是断的,自己的瘾又犯了,搞了半个月,决定记录一下。

 

过程图示:

 

 

 

源码推导过程:

    1、程序入口 app.run()

from flask import Flask

app = Flask(__name__)

# app.route 通过 route内部定义的 url_map 匹配到视图函数
# 这时候已经拿到了对象 获取到请求 则执行对象的__call__方法
@app.route('/')
def hello_world():
    return 'Hello World!'


if __name__ == '__main__':
    """
    监听用户请求 如果有用户请求到来 则执行app__call__方法 也是flask 的请求入口
    
    """
    #app.__call__
    app.run()

    run方法内部

 def run(self, host=None, port=None, debug=None, load_dotenv=True, **options):
        
        # Change this into a no-op if the server is invoked from the
        # command line. Have a look at cli.py for more information.
        if os.environ.get("FLASK_RUN_FROM_CLI") == "true":
            from .debughelpers import explain_ignored_app_run

            explain_ignored_app_run()
            return

        if get_load_dotenv(load_dotenv):
            cli.load_dotenv()
            # if set, let env vars override previous values
            if "FLASK_ENV" in os.environ:
                self.env = get_env()
                self.debug = get_debug_flag()
            elif "FLASK_DEBUG" in os.environ:
                self.debug = get_debug_flag()

        # debug passed to method overrides all other sources
        if debug is not None:
            self.debug = bool(debug)

        _host = "127.0.0.1"
        _port = 5000
        server_name = self.config.get("SERVER_NAME")
        sn_host, sn_port = None, None

        if server_name:
            sn_host, _, sn_port = server_name.partition(":")

        host = host or sn_host or _host
        # pick the first value that's not None (0 is allowed)
        port = int(next((p for p in (port, sn_port) if p is not None), _port))

        options.setdefault("use_reloader", self.debug)
        options.setdefault("use_debugger", self.debug)
        options.setdefault("threaded", True)

        cli.show_server_banner(self.env, self.debug, self.name, False)
    
        以上代码明显是对flask实例做了一些默认配置的设置
       真正执行的是下边 try代码块,看到其实调用了werkzug的run_simple方法
       这里其实就和多实例调用的是一个方法
        from werkzeug.serving import run_simple
        try:
run_simple中的第三个参数self 就是 flask实例 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代码:

    run_simple接受的第三个参数 application就是 run方法中传进来的self 也就是falsk实例

def run_simple(
    hostname,
    port,
    application,
    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,
):
    if not isinstance(port, int):
        raise TypeError("port must be an integer")
    if use_debugger:
        from .debug import DebuggedApplication

        application = DebuggedApplication(application, use_evalex)
    if static_files:
        from .middleware.shared_data import SharedDataMiddleware

        application = SharedDataMiddleware(application, static_files)

    def log_startup(sock):
        display_hostname = hostname if hostname not in ("", "*") else "localhost"
        quit_msg = "(Press CTRL+C to quit)"
        if sock.family == af_unix:
            _log("info", " * Running on %s %s", display_hostname, quit_msg)
        else:
            if ":" in display_hostname:
                display_hostname = "[%s]" % display_hostname
            port = sock.getsockname()[1]
            _log(
                "info",
                " * Running on %s://%s:%d/ %s",
                "http" if ssl_context is None else "https",
                display_hostname,
                port,
                quit_msg,
            )

    def inner():
        try:
            fd = int(os.environ["WERKZEUG_SERVER_FD"])
        except (LookupError, ValueError):
            fd = None
    3、走这里 有调用了一个make_server的方法或者类 同时将 app传了进去 srv = make_server( hostname, port, application, threaded, processes, request_handler, passthrough_errors, ssl_context, fd=fd, ) if fd is None: log_startup(srv.socket) srv.serve_forever()    1、根据传入的条件 最总代码走了 内嵌函数 inner if use_reloader: # If we're not running already in the subprocess that is the # reloader we want to open up a socket early to make sure the # port is actually available. if not is_running_from_reloader(): if port == 0 and not can_open_by_fd: raise ValueError( "Cannot bind to a random port with enabled " "reloader if the Python interpreter does " "not support socket opening by fd." ) # Create and destroy a socket so that any exceptions are # raised before we spawn a separate Python interpreter and # lose this ability. address_family = select_address_family(hostname, port) server_address = get_sockaddr(hostname, port, address_family) s = socket.socket(address_family, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind(server_address) if hasattr(s, "set_inheritable"): s.set_inheritable(True) # If we can open the socket by file descriptor, then we can just # reuse this one and our socket will survive the restarts. if can_open_by_fd: os.environ["WERKZEUG_SERVER_FD"] = str(s.fileno()) s.listen(LISTEN_QUEUE) log_startup(s) else: s.close() if address_family == af_unix: _log("info", "Unlinking %s" % server_address) os.unlink(server_address) from ._reloader import run_with_reloader run_with_reloader(inner, extra_files, reloader_interval, reloader_type) else:
    2、执行inner inner()

  maker_server代码:

      最终返回的的是BaseWSGIServer的实例

def make_server(
    host=None,
    port=None,
    app=None,
    threaded=False,
    processes=1,
    request_handler=None,
    passthrough_errors=False,
    ssl_context=None,
    fd=None,
):
  
    if threaded and processes > 1:
        raise ValueError("cannot have a multithreaded and multi process server.")
    elif threaded:
        return ThreadedWSGIServer(
            host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd
        )
    elif processes > 1:
        return ForkingWSGIServer(
            host,
            port,
            app,
            processes,
            request_handler,
            passthrough_errors,
            ssl_context,
            fd=fd,
        )
根据传入的参数 明显走else 此时的返回就是一个BaseWSGIServer的实例,同时在实例话的时候 传入了 app else: return BaseWSGIServer( host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd )

  BaseWSGIServer类的init方法:

class BaseWSGIServer(HTTPServer, object):
  BaseWSGIServer类又继承了HTTPServer类
    multithread = False
    multiprocess = False
    request_queue_size = LISTEN_QUEUE

    def __init__(
        self,
        host,
        port,
        app,
        handler=None,
        passthrough_errors=False,
        ssl_context=None,
        fd=None,
    ):
        if handler is None:
         handler = WSGIRequestHandler # 指定baseserver中的requestHandlerclass作为处理请求的handler 这个类后续将会用到 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)
     在BaseWSGIServer的__init__方法中 手动调用父类的 __init__ 根据继承.......
     这里需要特别注意 在调用HttpServer.__init__传入的参数 handler=WSGIRequstHandler
HTTPServer.__init__(self, server_address, handler) self.app = app self.passthrough_errors = passthrough_errors self.shutdown_signal = False self.host = host self.port = self.socket.getsockname()[1] # Patch in the original socket. if fd is not None: self.socket.close() self.socket = real_sock self.server_address = self.socket.getsockname() if ssl_context is not None: if isinstance(ssl_context, tuple): ssl_context = load_ssl_context(*ssl_context) if ssl_context == "adhoc": ssl_context = generate_adhoc_ssl_context() # If we are on Python 2 the return value from socket.fromfd # is an internal socket object but what we need for ssl wrap # is the wrapper around it :( sock = self.socket if PY2 and not isinstance(sock, socket.socket): sock = socket.socket(sock.family, sock.type, sock.proto, sock) self.socket = ssl_context.wrap_socket(sock, server_side=True) self.ssl_context = ssl_context else: self.ssl_context = None

  

  HTTPServer代码:
    此类没有实现__init__方法,但是继承了 TCPServer,子类手动调用父类的_init__方法,父类没有的情况下会向上调用
    这个类主要实现的是socket服务器绑定BaseWSGIServer实例,而这个实例属性又有flask实例,所以。。。。。。
class HTTPServer(socketserver.TCPServer):

    allow_reuse_address = 1    # Seems to make sense in testing environment

    def server_bind(self):
        """Override server_bind to store the server name."""
        socketserver.TCPServer.server_bind(self)
        host, port = self.server_address[:2]
        self.server_name = socket.getfqdn(host)
        self.server_port = port

  

  TCPServer.__init__代码:
class TCPServer(BaseServer):


    address_family = socket.AF_INET

    socket_type = socket.SOCK_STREAM

    request_queue_size = 5

    allow_reuse_address = False

    def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
        """Constructor.  May be extended, do not override."""

这里 是关键的一句 它又调用了 父类的 __init__方法
     同时注意传入的参数。RequestHandlerClass 就是BaseWSGISever 传入的 WSGIRequestHandler BaseServer.__init__(self, server_address, RequestHandlerClass)
这里明显调用了 python的内置socket模块去启动socket服务去了 self.socket = socket.socket(self.address_family, self.socket_type) if bind_and_activate: try: self.server_bind() self.server_activate() except: self.server_close() raise

  BaseServer.__init__代码:

      这里其实并没什么特别的,就是 初始化了几个属性,那么到这里 BaseWSGIServer类才完成实例化,才返回给make_server方法 返回值给了 run_simple中inner的srv变量,所以还要回到 run_simple的inner方法

class BaseServer:


    timeout = None

    def __init__(self, server_address, RequestHandlerClass):
        """Constructor.  May be extended, do not override."""
        self.server_address = server_address
       
       这里 给 属性赋值去了 
        self.RequestHandlerClass = RequestHandlerClass

       这里调用python内置模块threading。 线程事件方法 具体干什么的后续再研究 估计和服务器多线程有关系 
        self.__is_shut_down = threading.Event()
        self.__shutdown_request = False    

  run_simple.inner

    BaseWSGIServer完成实例化后,赋值给了srv变量,紧接着srv调用serve_forever方法,即调用了 BaseWSGIServer中的serve_forever方法

    def inner():
        try:
            fd = int(os.environ["WERKZEUG_SERVER_FD"])
        except (LookupError, ValueError):
            fd = None
        srv = make_server(
            hostname,
            port,
            application,
            threaded,
            processes,
            request_handler,
            passthrough_errors,
            ssl_context,
            fd=fd,
        )
        if fd is None:
            log_startup(srv.socket)
        srv.serve_forever()

  BaseWSGIServer.serve_forever代码:

    没什么特别的,又调用了父类HttpServer的serve_forever方法 但是在上述中明显发现 HttpServer没有该方法,根据继承明显调用了其长辈类的方法 在其祖父类 BaseServer找到了该方法

    def serve_forever(self):
        self.shutdown_signal = False
        try:
            HTTPServer.serve_forever(self)
        except KeyboardInterrupt:
            pass
        finally:
            self.server_close()

  BaseServer.serve_forever

    def serve_forever(self, poll_interval=0.5):
        """Handle one request at a time until shutdown.

        Polls for shutdown every poll_interval seconds. Ignores
        self.timeout. If you need to do periodic tasks, do them in
        another thread.
        """
        # 
        self.__is_shut_down.clear()
        try:
            # XXX: Consider using another file descriptor or connecting to the
            # socket to wake this up instead of polling. Polling reduces our
            # responsiveness to a shutdown request and wastes cpu at all other
            # times.
            # 选择模式 多路io复用
            with _ServerSelector() as selector:
                selector.register(self, selectors.EVENT_READ)

                while not self.__shutdown_request:
                    ready = selector.select(poll_interval)
                    # bpo-35017: shutdown() called during select(), exit immediately.
                    if self.__shutdown_request:
                        break
                    if ready:
                        # 调用 _handle_request_noblock 获取请求
                        self._handle_request_noblock()

                    self.service_actions()
        finally:
            self.__shutdown_request = False
            self.__is_shut_down.set()

  BaseServer._handle_request_noblock代码:

    其中 self.get_request方法查看源码,调用socket实例中的accept方法 等待接收请求,获取到请求后在self.process_request

    

 def _handle_request_noblock(self):
        """Handle one request, without blocking.

        I assume that selector.select() has returned that the socket is
        readable before this function was called, so there should be no risk of
        blocking in get_request().
        """
        try:
            request, client_address = self.get_request()
        except OSError:
            return
        if self.verify_request(request, client_address):
            try:
                # 处理请求
                self.process_request(request, client_address)
            except Exception:
                self.handle_error(request, client_address)
                self.shutdown_request(request)
            except:
                self.shutdown_request(request)
                raise
        else:
            self.shutdown_request(request)

  

  BaseServer.process_request代码:

    这里也没什么,就是到了2个内部的方法 并且没有返回值

    def process_request(self, request, client_address):
        """Call finish_request.

        Overridden by ForkingMixIn and ThreadingMixIn.

        """
        #  处理请求  finish_request 实际是实例化 RequestHandlerClass
        # 这里的 RequestHandlerClass 就是basewsgiserver 指定的 handler
        self.finish_request(request, client_address)
        self.shutdown_request(request)

  BaseServer.finish_request代码:

    根据上述 BaseWSGIServer实例化过程传入的参数 ,RequestHandlerClass = BaseRequestHandler,并且在该方法中实例化了该类,到这里没有看到一丝对请求进行解析的地方 request还是accept获取的原始请求

    def finish_request(self, request, client_address):
        """Finish one request by instantiating RequestHandlerClass."""
     这里是一个 比较 精妙(坑爹)的 地方 
        # 根据继承关系 在祖父类 BaseRequestHandler 找到的__init__方法中 实际调用了WSGIRequestHandler的 handle方法 
        self.RequestHandlerClass(request, client_address, self)

  紧接着  self.RequestHandlerClass(request, client_address, self) 其实就是 实例的 BaseRequestHandler 同时注意实例传入的第三个参数 self 就是BaseWSGIServer实例,返回BaseWSGIServer中看看 BaseRequestHandler 到底是什么玩意

  在 BaseRequestHandler类中没有实现 __init__方法,但是又没报错,所以在其长辈类中一定有 __init__方法进行接收,其父类是 BaseHTTPRequestHandler,点进去一看还是没有,继续向上找 StreamRequestHandler,还是没有,但似乎看到了曙光。

  BaseRequestHandler中含有 __init__方法 

class WSGIRequestHandler(BaseHTTPRequestHandler, object):

  

class BaseHTTPRequestHandler(socketserver.StreamRequestHandler):

  

class StreamRequestHandler(BaseRequestHandler):

  BaseRequestHandler.__init__代码:

    到这里 要注意几点

      1 、self.server就是finish_request传进来的第三个参数 self 也就是 BaseWSGIServer 

      2、这里的self指向了 WSGIRequestHandler,__init__方式只是类的初始化方法,在此之前执行了__new__方法,这个方法返回值就是self  也就是类的实例,若看到这忘记了,自己到python总结中看看自己是怎么写的

      3、那么根据 2 ,可以推算出 self.handle 调的是 WSGIRequestHandler.handle() 方法,BaseRequestHandler中实现的handle方法均为空方法,这里为什么会这么写呢,希望一年后的你能给出答案---这里实际上子类在实例化的时候执行的,所以这里的self是子类实例子,父类无法感知子类有那些方法,不写会报错,估计作者就是在这里顶一下,别报错

    泥马兜兜转转这么大一圈一夜回到解放前,还要回到 WSGIRequestHandler去看 handle()方法 

class BaseRequestHandler:

    def __init__(self, request, client_address, server):
        #print("WSGIRequestHandler.self====>",self)
     谁调的__init__方法 self 就是谁 self.request = request self.client_address = client_address self.server = server self.setup() try: self.handle() finally: self.finish()

  

  WSGIRequestHandler.handle代码:

 def handle(self):
        """Handles a request ignoring dropped connections."""
        rv = None
        try:
            # 1 在 WSGIRequestHandler 的祖父类中 的构造方法中调用了此 handle方法 
            # 2 本方法中 又调用了父类的 hanle方法  
            # 3 极其坑爹 rv = BaseHTTPRequestHandler.handle(self)  这里的self 是 当前 WSGIRequestHandler 实例 
            # 4 所以在父类中 BaseHTTPRequestHandler.handle 执行的 handle_one_request 就是 WSGIRequestHandler 的 handle_one_request  同样的套路 
        这里也是比较 精妙(kd)的一部分 rv = BaseHTTPRequestHandler.handle(self) except (_ConnectionError, socket.timeout) as e: self.connection_dropped(e) except Exception as e: if self.server.ssl_context is None or not is_ssl_error(e): raise if self.server.shutdown_signal: self.initiate_shutdown() return rv

  BaseHTTPRequestHandler.handle中的代码

    但是不知道为什么 在请求过来之后 这个方法会被调用2次,不知道是不是可debug模式有关,在回到WSGIRequestHandler.handle_one_request

    def handle(self):
        """Handle multiple requests if necessary."""
        # 本方法在 子类WSGIRequestHandler 中的handle 被调用
        # 并且这里的self 并不是指 BaseHTTPRequestHandler的实例  而是子类 WSGIRequestHandler 的实例 因为子类在调用的时候 将self传入
        self.close_connection = True
        # print("in BaseHTTPRequestHandler", self)
        # 调用 WSGIRequestHandler 的 handle_one_request 方法
        # 因为 WSGIRequestHandler 重写了 该方法

        #print("in BaseHTTPRequestHandler handle start ===>")
        self.handle_one_request()
        #print("in BaseHTTPRequestHandler handle end===>")
        while not self.close_connection:
            #print(55555)
            self.handle_one_request()

  WSGIRequestHandler.handle_one_request代码:

    def handle_one_request(self):
        """Handle a single HTTP request."""
        # print("WSGIRequestHandler IN handle_one_request")
        # import traceback
        # s=traceback.extract_stack()
        # print("WSGIRequestHandler==>%s"%s)
        # print(222222)
        self.raw_requestline = self.rfile.readline()
        if not self.raw_requestline:
            self.close_connection = 1
        # 解析request
        elif self.parse_request():
            # 最终执行 run_wsgi 方法
            return self.run_wsgi()

  这里貌似看到了一丝曙光

  WSGIRequestHandler.run_wsgi代码:

    这里 实现了几个 嵌套函数 其中    execute(self.server.app)  中self.server 属性 就是 上述 BaseRequestHandler.__init__代码中的 self.server 也就是 BaseWSGIServer的实例 然而在实例该类的时候 将app当做也传了进去 所以 self.server.app正是傻逼似的 flask实例

     并且在 execute 方法中  application_iter = app(environ, start_response)  这样调用实例,那么类加括号是实例化,方法加括号是调用,那么实例括号调用实例的__call__  最后一步看看 flask的 __call__长得是不是像佩奇,nnd的。

 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")
        # 生成被解析过的request对象
        self.environ = environ = self.make_environ()
        headers_set = []
        headers_sent = []
        #  解析 environ 处理request
        def write(data):
            assert headers_set, "write() before start_response"
            if not headers_sent:
                status, response_headers = headers_sent[:] = headers_set
                try:
                    code, msg = status.split(None, 1)
                except ValueError:
                    code, msg = status, ""
                code = int(code)
                self.send_response(code, msg)
                header_keys = set()
                for key, value in response_headers:
                    self.send_header(key, value)
                    key = key.lower()
                    header_keys.add(key)
                if not (
                    "content-length" in header_keys
                    or environ["REQUEST_METHOD"] == "HEAD"
                    or code < 200
                    or code in (204, 304)
                ):
                    self.close_connection = True
                    self.send_header("Connection", "close")
                if "server" not in header_keys:
                    self.send_header("Server", self.version_string())
                if "date" not in header_keys:
                    self.send_header("Date", self.date_time_string())
                self.end_headers()

            assert isinstance(data, bytes), "applications must write bytes"
            self.wfile.write(data)
            self.wfile.flush()

        def start_response(status, response_headers, exc_info=None):
            if exc_info:
                try:
                    if headers_sent:
                        reraise(*exc_info)
                finally:
                    exc_info = None
            elif headers_set:
                raise AssertionError("Headers already set")
            headers_set[:] = [status, response_headers]
            return write

        def execute(app):
            # 在这里实际调用了 flask实例也就是app的__call__方法
            application_iter = 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 执行flask实例 __call__
            # 此 self.server.app 来源于 BaseWSGIServer 的祖父类
            #  BaseServer 的 self.finish()=======>self.RequestHandlerClass(request, client_address, self)
            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)

    flask实例的__call__代码:

        就一行代码,凌乱

    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."""
        # import traceback
        # s=traceback.extract_stack()
        # print("s===>%s"%s)
        # print("调用链 ======>")
        return self.wsgi_app(environ, start_response)

  

总结:

  1、需要熟悉 python中__new__ 和 __init__方法

  2、组合类

  3、其实看到这,仔细想想是不是符合wsgi的概念呢

 

posted @ 2021-05-28 01:24  Yuan_x  阅读(431)  评论(0编辑  收藏  举报