• Flask1.1.4 Werkzeug1.0.1 源码分析:启动流程


    基于QuickStart中的一个demo来分析

    from flask import Flask
    
    app = Flask(__name__)
    
    
    @app.route("/")
    def hello_world():
        return "<p>Hello, World!</p>"
    
    
    if __name__ == '__main__':
        app.run()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    @app.route(“/”) 是一个接收参数的装饰器工厂函数,返回一个闭包了特定参数的装饰器

    # flask/app.py L1288
    def route(self, rule, **options):
    	# 典型的注册装饰器
        def decorator(f):
            endpoint = options.pop("endpoint", None)
            # 此方法将url信息和view_func 注册到flask实例中
            self.add_url_rule(rule, endpoint, f, **options)
            return f
    
        return decorator
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    关键在 self.add_url_rule(rule, endpoint, f, **options) 方法 下面代码做了一些精简

    # flask/app.py L1178
    @setupmethod
    def add_url_rule(
        self,
        rule,
        endpoint=None,
        view_func=None,
        provide_automatic_options=None,
        **options
    ):
    	# 此处endpoint是一个重要的概念 后续在路由部分会详细讲解 
    	# 在flask中是 url_map和view_functions 关联的重要纽带
    	# 每次请求来时 去url_map中搜索得到 endpoint,args 然后走 view_functions[endpoint](args) 拿到结果
    	# 若不传 默认为view_func.__name__
        if endpoint is None:
            endpoint = _endpoint_from_view_func(view_func)
        options["endpoint"] = endpoint
        methods = options.pop("methods", None)
    
    	# ...
    
    	# 此处的 url_rule_class 是一个Flask类属性 值为 werkzeug.routing.Rule
    	# 所以此处即为构建 werkzeug.routing.Rule 对象
        rule = self.url_rule_class(rule, methods=methods, **options)
        rule.provide_automatic_options = provide_automatic_options
    	
    	# 构建好的 rule对象保存到 url_map实例属性中 
    	# url_map是werkzeug.routing.Map 对象
    	# flask路由部分其实借助了werkzeug的能力
        self.url_map.add(rule)
        if view_func is not None:
            # ...
            # endpoint为key将对应的view_func保存在 view_functions属性中
            # view_functions 是个 dict
            self.view_functions[endpoint] = view_func
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    很简单,add_url_rule 主要作用就是 将rule和view_func 信息维护到 flask实例的 url_map和view_functions属性中

    ok,继续研究 app.run() 启动流程就在其中(app.run()会启动一个简易的web服务器用于开发和测试,生产环境会用其他高性能的web server,不过借助这个研究下启动流程还是可以的)

    # flask/app.py L889
    def run(self, host=None, port=None, debug=None, load_dotenv=True, **options):
      	# ...
      	
        from werkzeug.serving import run_simple
    
        try:
        	# 借助了 werkzeug.serving.run_simple
        	# 注意第三个参数 将flask实例传给了 run_simple方法
        	# 后续 web server会调用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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    接着看 run_simple代码,这边切换为研究 werkzeug 源码

    # werkzeug/serving.py L876
    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,
    ):
        
     	#...
     	# application 参数对应的是 flask实例
    
        def inner():
            try:
                fd = int(os.environ["WERKZEUG_SERVER_FD"])
            except (LookupError, ValueError):
                fd = None
            # 关键于此 创建一个server实例
            srv = make_server(
                hostname,
                port,
                application,
                threaded,
                processes,
                request_handler,
                passthrough_errors,
                ssl_context,
                fd=fd,
            )
            if fd is None:
                log_startup(srv.socket)
            # 调用web server实例的serve_forever方法
            srv.serve_forever()
    
        if use_reloader:
            #...
        else:
            inner()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    继续往下,先看 make_server方法

    # werkzeug/serving.py L830
    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:
            return BaseWSGIServer(
                host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd
            )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    看出来就是根据具体参数 构建一个server实例返回 挑一个看下 看 BaseWSGIServer

    # werkzeug/serving.py L708
    class BaseWSGIServer(HTTPServer, object):
        def __init__(
            self,
            host,
            port,
            app,
            handler=None,
            passthrough_errors=False,
            ssl_context=None,
            fd=None,
        ):
            if handler is None:
            	# 此处注意下 后续的请求具体处理会走 WSGIRequestHandler
                handler = WSGIRequestHandler
    
          	# 这边进行了 端口绑定和监听
          	# 并且传入了类 WSGIRequestHandler
            HTTPServer.__init__(self, server_address, handler)
    		# app即flaks实例
            self.app = app
           
        # 此方法在make_server 返回实例后立马调用
        def serve_forever(self):
            self.shutdown_signal = False
            try:
            	# 此处的 HTTPServer 位于python内置库http中 http.server.HTTPServer
            	# 将当前server实例作为参数传入
                HTTPServer.serve_forever(self)
            except KeyboardInterrupt:
                pass
            finally:
                self.server_close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    在往下看 HTTPServer.serve_forever() 可以发现 serve_forever 其实是其父类的父类 内置库 socketserver中 BaseServer 类实现的方法

    # socketserver.py L153
    class BaseServer:
        timeout = None
    
        def __init__(self, server_address, RequestHandlerClass):
            """Constructor.  May be extended, do not override."""
            self.server_address = server_address
            # 这个属性就是 WSGIRequestHandler
            self.RequestHandlerClass = RequestHandlerClass
            self.__is_shut_down = threading.Event()
            self.__shutdown_request = False
    
        def serve_forever(self, poll_interval=0.5):
            self.__is_shut_down.clear()
            try:
                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:
                        	# 接收到具体请求后 进行处理
                            self._handle_request_noblock()
    
                        self.service_actions()
            finally:
                self.__shutdown_request = False
                self.__is_shut_down.set()
                
    	def _handle_request_noblock(self):
            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)
                
        def process_request(self, request, client_address):
        	# 接收到具体请求后 进行处理
            self.finish_request(request, client_address)
            self.shutdown_request(request)
            
        def finish_request(self, request, client_address):
            """Finish one request by instantiating RequestHandlerClass."""
            # 每次都实例化了 WSGIRequestHandler 进行请求处理
            # 注意哦 第三个参数把当前的server实例传入
            self.RequestHandlerClass(request, client_address, self)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    好的 最终最终 请求的处理是在 WSGIRequestHandler 实例化过程中处理的
    那我们来看看 WSGIRequestHandler的代码吧
    WSGIRequestHandler 的初始化其实走父类的父类 BaseRequestHandler 的__init__方法

    # socketserver.py 696
    class BaseRequestHandler:
        def __init__(self, request, client_address, server):
            self.request = request
            self.client_address = client_address
            self.server = server
            self.setup()
            try:
            	# 具体的处理方法 其实是调用的子类的实现
                self.handle()
            finally:
                self.finish()
    
        def setup(self):
            pass
    
        def handle(self):
            pass
    
        def finish(self):
            pass
    
    # werkzeug/serving.py  L163
    class WSGIRequestHandler(BaseHTTPRequestHandler, object):
    	# 处理请求的核心方法
        def run_wsgi(self):
    		# 此处经典的 将http内容转化为符合wsgi格式的内容 因为后面是个wsgi的app啊
            self.environ = environ = self.make_environ()
            headers_set = []
            headers_sent = []
    		# 眼熟吧 调用wsgi app的参数之一
            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):
            	# 经典的wsgi app调用 返回值可迭代
            	# 此处的app是flask实例 实例可调用自然是因为Flask实现了__call__方法
                application_iter = app(environ, start_response)
                
            try:
            	# 执行此方法 参数为flask实例
                execute(self.server.app)
            except (_ConnectionError, socket.timeout) as e:
                self.connection_dropped(e, environ)
            except Exception:
                #...
    
    	# 上一个类调用的handle就是这个
        def handle(self):
            """Handles a request ignoring dropped connections."""
            try:
            	# 调用父类的handle
                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()
        
        # 没错 下面的类又调用了这个方法
        def handle_one_request(self):
            """Handle a single HTTP request."""
            self.raw_requestline = self.rfile.readline()
            if not self.raw_requestline:
                self.close_connection = 1
            elif self.parse_request():
            	# 最终最终 走了这个方法
                return self.run_wsgi()
    
    # http/server.py 147
    class BaseHTTPRequestHandler(socketserver.StreamRequestHandler):
    	# 上一个类调用的就是这个
        def handle(self):
            """Handle multiple requests if necessary."""
            self.close_connection = True
    		# 此处又调用回了 WSGIRequestHandler 的 handle_one_request
            self.handle_one_request()
            while not self.close_connection:
                self.handle_one_request()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90

    上面的调用路线看起来稍微有点绕,是因为涉及到了继承方法的覆盖,有些调用走的父类的,有些走的子类的方法。

    可以看到:其实就是监听端口,并开启一个无限的循环,每次接收到一个请求之后,就实例化WSGIRequestHandler进行处理,而WSGIRequestHandler主要做了HTTP格式数据到WSGI数据的转换,然后用WSGI的方式调用了flask实例进行实际的逻辑处理并返回数据。

    下面来看看Flask实例支持调用的代码吧。

    # flask/app.py L103
    class Flask(_PackageBoundObject):
    
        def __call__(self, environ, start_response):
            return self.wsgi_app(environ, start_response)
            
        def wsgi_app(self, environ, start_response):
        	# 重要 创建当前请求的上下文
        	# 此处已经对请求信息进行了处理(获得了 endpoint和view_func_args)
            ctx = self.request_context(environ)
            error = None
            try:
                try:
                	# 将当前请求的上下文入栈(LocalStack)
                	# 此处是Flask上下文的实现细节 通过共享的方式来传递上下文 给后面的view_func
                    ctx.push()
                    # 分发执行
                    response = self.full_dispatch_request()
                except Exception as e:
                    error = e
                    response = self.handle_exception(e)
                except:  # noqa: B001
                    error = sys.exc_info()[1]
                    raise
                return response(environ, start_response)
            finally:
                if self.should_ignore_error(error):
                    error = None
                # 请求处理完成后 清理上下文 出栈
                ctx.auto_pop(error)
                
        def full_dispatch_request(self):
        	# hook
            self.try_trigger_before_first_request_functions()
            try:
            	# hook
                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)
    
        def dispatch_request(self):
        	# 直接从上下文栈中获取当前的请求上下文
            req = _request_ctx_stack.top.request
            if req.routing_exception is not None:
                self.raise_routing_exception(req)
            rule = req.url_rule
            # ...
            # 根据endpoint获取对应的view_func 执行并返回
            return self.view_functions[rule.endpoint](**req.view_args)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    okk,到此处Flask启动流程基本就差不多了。
    总结一下步骤:

    1. 创建一个Flask实例 app
    2. 将url和view_func 通过 app.add_url_rule() 维护到app的url_map和view_functions 属性中。url_map包含了路由逻辑,view_functions存储了对应的逻辑函数,二者通过endpoint相关联。
    3. 步骤1和2 完成后,其实一个遵循WSGI协议的 web application已经准备好了,接下来我们要做的就是将其挂到一个同样支持WSGI协议的web server下面。web server接受HTTP协议的请求,并将其转化为WSGI格式的内容,然后调用 app(environ, start_response) 执行具体逻辑处理,并返回WSGI格式的结果。之后再把WSGI格式的结果转换为HTTP格式返回给客户端就可以啦。
    4. werkzeug 中的 BaseWSGIServer 继承了Python内置库的 http.server.HTTPServer,从中获得了HTTPServer监听端口并获取请求的能力,并整合了 app和 WSGIRequestHandler。每当一个请求就绪时,就交给一个WSGIRequestHandler实例处理。WSGIRequestHandler做了HTTP格式数据到WSGI格式的转换,并执行app(environ, start_response) ,返回响应。
    5. app(environ, start_response) 这步就又回到flask的请求处理逻辑,根据environ的信息配合事先已经绑定好的 url_map,得到具体的路由信息和参数(endpoint,view_func_args),然后从 view_functions 字典中取出对应的view_function运行并返回结果。
  • 相关阅读:
    JavaScript系列之内置对象set
    【深蓝学院】手写VIO第2章--IMU传感器--作业
    农业新闻查询易语言代码
    鸿蒙OS app开发环境搭建
    应约凯程约稿
    使用aop注解,实现日志增强,增强方式为前置,后置,环绕
    js制作动态表单
    全场景 MPP 数据库ERM StarRocks 源代码数据湖分析
    利用Fiddler初识HTTP协议(一)
    ZYNQ双核启动和固化步骤
  • 原文地址:https://blog.csdn.net/weixin_37882382/article/details/125622294