app如何打开了request url_从运行开始阅读flask werkzeug源码——app的初始化与启动...

整个的启动流程如下

f00f3bc6d99f5eeb6ceff4a69a14052e.png

接下来进行详细的分析

app的初始化(主要是werkzeug及底层实现)

from flask import Flask
app = Flask(__name__)

实例化Flask应用

Flask类继承_PackageBoundObject类,通过import_name参数获取运行路径,用于后续的基于路径的一些操作,如路径操作、模板文件的加载等

Flask类本身的__init__函数会对一些关键的数据结构做些初始化(代码都是经过精简的)

class Flask(_PackageBoundObject):
    def __init__(
        self,
        import_name,
        static_url_path=None,
        static_folder="static",
        static_host=None,
        host_matching=False,
        subdomain_matching=False,
        template_folder="templates",
        instance_path=None,
        instance_relative_config=False,
        root_path=None,
    ):
        _PackageBoundObject.__init__(
            self, import_name, template_folder=template_folder, root_path=root_path
        )

        self.static_url_path = static_url_path
        self.static_folder = static_folder

        if instance_path is None:
            instance_path = self.auto_find_instance_path()
        elif not os.path.isabs(instance_path):
            raise ValueError(
                "If an instance path is provided it must be absolute."
                " A relative path was given instead."
            )
        self.instance_path = instance_path
        self.config = self.make_config(instance_relative_config)
        # 大部分的设置可以通过函数修饰器的方式进行添加
        self.view_functions = {} # 视图函数的设置
        # 出错的时候的函数处理钩子
        self.error_handler_spec = {}
        self.url_build_error_handlers = [] 
        # 接受请求进入处理函数之前以及之后的的处理函数,first顾名思义
        self.before_request_funcs = {}
        self.before_first_request_funcs = []
        self.after_request_funcs = {}
        # 无论是否有异常,所有的请求结束后都会经过这个函数,key是blueprint里面的需要使用对应value钩子的函数名,None的话是所有的请求都要执行
        self.teardown_request_funcs = {}
        # 应用上下文销毁的时候会调用这个函数
        self.teardown_appcontext_funcs = []
        # 在before_request之前执行,同样有着none的规则
        self.url_value_preprocessors = {}
        # 可以修改url,在building url之前
        self.url_default_functions = {}
        self.template_context_processors = {None: [_default_template_ctx_processor]}
        self.shell_context_processors = []
        self.blueprints = {}
        self._blueprint_order = []
        # 将使用到的依赖拓展信息(感觉有点像config的使用方法
        self.extensions = {}
        self.url_map = self.url_map_class()

        self.url_map.host_matching = host_matching
        self.subdomain_matching = subdomain_matching
        self._got_first_request = False
        self._before_request_lock = Lock()
        if self.has_static_folder:
            assert (
                bool(static_host) == host_matching
            ), "Invalid static_host/host_matching combination"
            self.add_url_rule(
                self.static_url_path + "/<path:filename>",
                endpoint="static",
                host=static_host,
                view_func=self.send_static_file,
            )
        self.cli.name = self.name

这些钩子函数的具体运行顺序以及作用,会在后续启动app,接受请求的分析中进一步详细叙述

至于如何添加钩子,在后续专题中会有描述

app的启动

直接调用app.run()就会开始执行(本地),服务器端使用命令行的形式,实际上是使用werkzeug.run_simple(),在此之前只是增加host port的判断以及debug的判断

class Flask(_PackageBoundObject):
    def run(self, host=None, port=None, debug=None, load_dotenv=True, **options):
        if server_name:
            sn_host, _, sn_port = server_name.partition(":")
        host = host or sn_host or _host
        port = int(next((p for p in (port, sn_port) if p is not None), _port))
        # debug模式下的重新加载设置
        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)

        from werkzeug.serving import run_simple

        try:
            # 直接使用werkzeug下的run_simple函数构建服务器
            run_simple(host, port, self, **options)
        finally:
            self._got_first_request = False

那接下来详细地看一下werkzeug的run_simple(),这里去除了参数校验的部分

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,
):
    # 可以使用参数的形式得到执行命令行的效果,只有四种指令
    # 可以打印出设置的路径下的请求访问信息
    # 默认直接调用传进来的app
    if use_debugger:
        from .debug import DebuggedApplication

        application = DebuggedApplication(application, use_evalex)
    # 创建对应文件路径的url,可以访问工程地址(export)下的对应模块文件,本质上也是一个WGSI app,有点像app的修饰器,可以根据条件选择执行哪个app,如果当前的访问路径不匹配就调用由原来的app
    if static_files:
        from .middleware.shared_data import SharedDataMiddleware

        application = SharedDataMiddleware(application, static_files)

    # 重启的时候打印log
    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:
            # 已经指定了网络IO用的文件描述符
            fd = int(os.environ["WERKZEUG_SERVER_FD"])
        except (LookupError, ValueError):
            fd = None
        # 创建server,注意这里传进去的request handler默认为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()

    # 当使用的模块发生改变的时候,重新启动,在debug模式下会走这边
    if use_reloader:
        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."
                )
            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 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:
        inner()

这个run_simple函数有点东西的,包括了:

  • debug模式下,在Flask app外面再包一层WSGI app,接入一些参数调试功能,以及特殊路径请求的打印功能
  • 支持符合路径的文件系统的共享访问,实现方式与第一条类似
  • 通过run_with_reloader函数,支持对全部或者指定文件的监视,进行修改后重启

具体的代码分析留到下次,这里进行最简单的访问流程的分析

实际启动的代码(inner函数)十分简单,只是文件描述符判断,以及新建一个server,再进行启动,那我们来看一下make_server函数的骚操作,注意这里传进去的request handler默认为None

def make_server(
    host=None,
    port=None,
    app=None,
    threaded=False,
    processes=1,
    request_handler=None,
    passthrough_errors=False,
    ssl_context=None,
    fd=None,
):
    # make_server()可以是多线程、多进程以及单进程,默认单进程
    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
        )

这里直接返回的是BaseWSGIServer,是一个简单的单线程单进程服务器

BaseWSGIServer——>HTTPServer(get_request())——>TCPServer(创建socket, get_request())——>BaseServer(serve_forever())

  • BaseServer更加接近于底层,负责底层网络数据的收发,不关心里面的协议,因此没有关于数据部分的处理实现
  • TCPServer处于TCP层面,涉及到Socket等信息,都在这一层处理,由于已经涉及到具体的传输层协议,可以进行一定的数据处理,分理出基本的socket传输信息
  • HTTPServer基于TCP协议,其函数都是基于TCP实现,或者是重写,抽象程度更高,分理出更加详细的request信息
  • BaseWSGIServer更高层了,是在HTTP协议基础上进行WSGI规范的更高一层抽象
class BaseWSGIServer(HTTPServer, object):
    def __init__(
        self,
        host,
        port,
        app,
        handler=None,
        passthrough_errors=False,
        ssl_context=None,
        fd=None,
    ):
        # 默认是没有handler的
        if handler is None:
            handler = WSGIRequestHandler

        self.address_family = select_address_family(host, port)
        # 原来就已经有了fd,找到对应的socket
        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)

        if self.address_family == af_unix and os.path.exists(server_address):
            os.unlink(server_address)
        # 对父类的初始化socket绑定服务的地址,这里会初始化self.socket
        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]

        # 文件描述符的存在使用原来的描述符对应的socket
        if fd is not None:
            # 一定要记得关闭不用的socket,会占用描述符资源等
            self.socket.close()
            self.socket = real_sock
            self.server_address = self.socket.getsockname()
        # https的安全性要求
        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()
            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

class HTTPServer(socketserver.TCPServer):
    def server_bind(self):
        socketserver.TCPServer.server_bind(self)
        host, port = self.server_address[:2]
        self.server_name = socket.getfqdn(host)
        self.server_port = port

这里实际上分的是两种情况,使用ssl以及不使用ssl,至于是否使用原来的描述符,这个只是使用不同的socket连接罢了,使用ssl的情况留到下次再说。

这个函数完成了server的初始化,包括socket的建立,ip port的设置等。

我们回到make_server()的上一层函数,在完成了服务器的初始化之后,调用服务器的serve_forever()启动服务器,我们接下来去看一下serve_forever()的实现

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

调用的是父类的serve_forever(),父类是TCPServer,没有serve_forever()方法,其父类是BaseServer,有对应的方法

class BaseServer:
    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)
                    if ready:
                        self._handle_request_noblock()

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

我们可以看到:

  • 使用的是select来实现IO多路复用,监听的方法是read方法
  • 当数据到达之后,调用_handle_request_noblock()方法,获得request数据
  • 唤醒后调用service_actions()进行服务

下一篇:

从运行开始阅读flask werkzeug源码——请求的接收

其他文章:

从运行开始阅读flask werkzeug源码——flask请求处理

从运行开始阅读flask、werkzeug源码——debug模式

从运行开始阅读flask werkzeug源码——钩子函数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值