Skip to main content

2、flask的启动过程

Y-aong...About 6 min源码分析flask源码分析flask

2、flask的启动过程

一、实例化对象的过程

我们需要首先明白一个对象实例化会做那些事情

def cls_instance():
    print('我是类属性')
    return 1


def init_instance():
    print('我是实例属性')
    return 2


class Demo:
    instance = cls_instance()

    def __init__(self):
        self.ini = init_instance()

    def __call__(self, *args, **kwargs):
        print('我是call方法')


app = Demo()
# 我是类属性
# 我是实例属性

我们可以看出来一个类进行初始化需要先执行类属性,接着执行实例属性,当请求过来的时候会执行__call__方法

那么Flask中的call方法是什么时候执行的呢?

我们可以先改造一下flask

    def __call__(
        self, environ: WSGIEnvironment, start_response: StartResponse
    ) -> cabc.Iterable[bytes]:
        """The WSGI server calls the Flask application object as the
        WSGI application. This calls :meth:`wsgi_app`, which can be
        wrapped to apply middleware.
        """
        print('call')
        return self.wsgi_app(environ, start_response)

当我们发送请求的时候会打印call,因此我们在分析flask的启动过程只需要分析以下代码就可以了,就是 Flask(name),路由添加,和app.run这三部分就可以了

from flask import Flask

app = Flask(__name__)


@app.route('/index')
def index():
    return '1231'


if __name__ == '__main__':
    app.run()

二、flask实例化过程

Flask类的构造

Scaffold

class Scaffold:
    name: str
    _static_folder: str | None = None
    _static_url_path: str | None = None

    def __init__(
        self,
        import_name: str,
        static_folder: str | os.PathLike[str] | None = None,
        static_url_path: str | None = None,
        template_folder: str | os.PathLike[str] | None = None,
        root_path: str | None = None,
    ):
        self.import_name = import_name
        self.static_folder = static_folder  # type: ignore
        self.static_url_path = static_url_path
        self.template_folder = template_folder
        if root_path is None:
            root_path = get_root_path(self.import_name)
        self.root_path = root_path
        self.view_functions= {}
        self.error_handler_spec = defaultdict(lambda: defaultdict(dict))
        self.before_request_func = defaultdict(list)
        self.after_request_funcs = defaultdict(list)
        self.teardown_request_funcs = defaultdict(list)
        self.template_context_processors= defaultdict(list, {None: [_default_template_ctx_processor]})
        self.url_value_preprocessors= defaultdict(list)
        self.url_default_functions = defaultdict(list)

APP

class App(Scaffold):
    aborter_class = Aborter
    jinja_environment = Environment
    app_ctx_globals_class = _AppCtxGlobals
    config_class = Config
    secret_key = ConfigAttribute[t.Union[str, bytes, None]]("SECRET_KEY")
    url_rule_class = Rule
    url_map_class = Map
    test_client_class: type[FlaskClient] | None = None
    test_cli_runner_class: type[FlaskCliRunner] | None = None
    default_config: dict[str, t.Any]
    response_class: type[Response]

    def __init__(
        self,
        import_name: str,
        static_url_path: str | None = None,
        static_folder: str | os.PathLike[str] | None = "static",
        static_host: str | None = None,
        host_matching: bool = False,
        subdomain_matching: bool = False,
        template_folder: str | os.PathLike[str] | None = "templates",
        instance_path: str | None = None,
        instance_relative_config: bool = False,
        root_path: str | None = None,
    ):
        super().__init__(
            import_name=import_name,
            static_folder=static_folder,
            static_url_path=static_url_path,
            template_folder=template_folder,
            root_path=root_path,
        )
        self.instance_path = instance_path
        self.config = self.make_config(instance_relative_config)
        self.aborter = self.make_aborter()
        self.json: JSONProvider = self.json_provider_class(self)
        self.url_build_error_handlers = []
        self.teardown_appcontext_funcs: list[ft.TeardownCallable] = []
        self.shell_context_processors: list[ft.ShellContextProcessorCallable] = []
        self.blueprints: dict[str, Blueprint] = {}
        self.extensions: dict[str, t.Any] = {}
        self.url_map = self.url_map_class(host_matching=host_matching)
        self.subdomain_matching = subdomain_matching
        self._got_first_request = False
        self.cli.name = self.name

Flask

class Flask(App):
    app_ctx_globals_class = _AppCtxGlobals
    config_class = Config
            self.url_map = self.url_map_class(host_matching=host_matching)

    url_map_class = Map
    default_config ={}
    request_class: type[Request] = Request
    response_class: type[Response] = Response
    session_interface: SessionInterface = SecureCookieSessionInterface()

    def __init__(
        self,
        import_name: str,
        static_url_path: str | None = None,
        static_folder: str | os.PathLike[str] | None = "static",
        static_host: str | None = None,
        host_matching: bool = False,
        subdomain_matching: bool = False,
        template_folder: str | os.PathLike[str] | None = "templates",
        instance_path: str | None = None,
        instance_relative_config: bool = False,
        root_path: str | None = None,
    ):
        super().__init__(
            import_name=import_name,
            static_url_path=static_url_path,
            static_folder=static_folder,
            static_host=static_host,
            host_matching=host_matching,
            subdomain_matching=subdomain_matching,
            template_folder=template_folder,
            instance_path=instance_path,
            instance_relative_config=instance_relative_config,
            root_path=root_path,
        )
        if self.has_static_folder:
            self_ref = weakref.ref(self)
            self.add_url_rule(
                f"{self.static_url_path}/<path:filename>",
                endpoint="static",
                host=static_host,
                view_func=lambda **kw: self_ref().send_static_file(**kw),  # type: ignore # noqa: B950
            )

当我们排除掉一些模版静态文件之后我们可以得到以下部分

class Flask:
	default_config ={}
    url_rule_class = Rule
    url_map_class = Map
    request_class: type[Request] = Request
    response_class: type[Response] = Response
    session_interface: SessionInterface = SecureCookieSessionInterface()
    
	def __init__():
        self.view_functions= {}
        self.error_handler_spec = defaultdict(lambda: defaultdict(dict))
        self.before_request_func = defaultdict(list)
        self.after_request_funcs = defaultdict(list)
        self.teardown_request_funcs = defaultdict(list)
        self.template_context_processors= defaultdict(list, {None: [_default_template_ctx_processor]})
        self.url_value_preprocessors= defaultdict(list)
        self.url_default_functions = defaultdict(list)
        self.url_map = self.url_map_class(host_matching=host_matching)
        if self.has_static_folder:
            self_ref = weakref.ref(self)
            self.add_url_rule(
                f"{self.static_url_path}/<path:filename>",
                endpoint="static",
                host=static_host,
                view_func=lambda **kw: self_ref().send_static_file(**kw),  # type: ignore # noqa: B950
            )
        

接下来我们一般的flask程序如下

from flask import Flask

config = {'SECRET_KEY': "woaijmy"}
# 实例化Flask
app = Flask(__name__)
# 加载配置参数
app.config.from_object(config)

# 添加前置处理参数
@app.before_request
def before():
    print('before')

# 添加后置处理参数
@app.after_request
def after():
    print('after')

# 定义视图函数
@app.route('/index')
def index():
    return '1231'


if __name__ == '__main__':
    print('url_map::', app.url_map)
    print('before_request::', app.before_request_funcs)
    print('after_request::', app.after_request_funcs)
    # 运行
    app.run()

01、初始化

我们可以知道flask初始化(Flask(__name__))基本实现了以下这些功能

  • 加载flask默认配置
  • 声明response、request、session对象
  • 定义函数列表
    • error_handler_spec:异常处理函数
    • before_request_func:请求前处理函数
    • after_request_funcs:请求后处理函数
    • teardown_request_funcs:请求关闭处理函数
    • view_functions:视图处理函数
    • url_default_functions:url默认处理函数
  • 定义url_map,添加静态文件路由

以上我们已经知道了初始化要做的一些事情,接下来配置信息

02、配置信息

class Config(dict):
    def from_object(self, obj: object | str) -> None:
        if isinstance(obj, str):
            obj = import_string(obj)
        for key in dir(obj):
            if key.isupper():
                self[key] = getattr(obj, key)

这个就是比较简单,直接对于config对象进行添加属性即可

03、定义处理函数

# 添加前置处理参数
@app.before_request
def before():
    print('before')

# 添加后置处理参数
@app.after_request
def after():
    print('after')

这个配置请求处理函数它的实现逻辑呢,实际就是将前置处理函数放到after_request_funcs,其他以func结尾的同样也是这样的逻辑

    @setupmethod
    def before_request(self, f: T_before_request) -> T_before_request:
        self.before_request_funcs.setdefault(None, []).append(f)
        return f

04、定义视图函数

	@setupmethod
    def route(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
        def decorator(f: T_route) -> T_route:
            endpoint = options.pop("endpoint", None)
            self.add_url_rule(rule, endpoint, f, **options)
            return f

        return decorator
	@setupmethod
    def add_url_rule(
        self,
        rule: str,
        endpoint: str | None = None,
        view_func: ft.RouteCallable | None = None,
        provide_automatic_options: bool | None = None,
        **options: t.Any,
    ) -> None:
         if endpoint is None:
            endpoint = _endpoint_from_view_func(view_func)  # type: ignore
        options["endpoint"] = endpoint
        methods = options.pop("methods", None)

        if methods is None:
            methods = getattr(view_func, "methods", None) or ("GET",)
        if isinstance(methods, str):
            raise TypeError(
                "Allowed methods must be a list of strings, for"
                ' example: @app.route(..., methods=["POST"])'
            )
        methods = {item.upper() for item in methods}
        required_methods = set(getattr(view_func, "required_methods", ()))
        if provide_automatic_options is None:
            provide_automatic_options = getattr(view_func, "provide_automatic_options", None )

        if provide_automatic_options is None:
            if "OPTIONS" not in methods:
                provide_automatic_options = True
                required_methods.add("OPTIONS")
            else:
                provide_automatic_options = False

        # Add the required methods now.
        methods |= required_methods
        rule_obj = self.url_rule_class(rule, methods=methods, **options)
        rule_obj.provide_automatic_options = provide_automatic_options  # type: ignore[attr-defined]
        self.url_map.add(rule_obj)
        if view_func is not None:
            old_func = self.view_functions.get(endpoint)
            if old_func is not None and old_func != view_func:
                raise AssertionError(
                    "View function mapping is overwriting an existing"
                    f" endpoint function: {endpoint}"
                )
            self.view_functions[endpoint] = view_func
            
     @setupmethod
    def endpoint(self, endpoint: str) -> t.Callable[[F], F]:
        def decorator(f: F) -> F:
            self.view_functions[endpoint] = f
            return f

        return decorator

这里是稍微复杂点,我们可以理出来以下事情

  • 获取endpoint

  • 获取methods

  • 创建rule_obj并放入到url_map

  • 获取view_func,以endpoint为key,以view_func为值放入到view_functions

    所以这里就是定义视图函数,定义url_rule并放到url_map中,将视图函数放入到view_functions

    • url_map:<Rule '/index' (HEAD, GET, OPTIONS) -> index>, url,请求方法,endpoint的集合

    • view_funcs:'index': <function index at 0x000001B61CC30AF0>endpoint为key,视图函数为值的字典列表

这里的endpiont就是视图函数和url的中间值,我们可以利用endpoint找到url,也可以使用endpoint找到视图函数

05、app.run

 def run(
        self,
        host: str | None = None,
        port: int | None = None,
        debug: bool | None = None,
        load_dotenv: bool = True,
        **options: t.Any,
    ) -> None:
        if os.environ.get("FLASK_RUN_FROM_CLI") == "true":
            if not is_running_from_reloader():
                click.secho('')
            return
        if get_load_dotenv(load_dotenv):
            cli.load_dotenv()
            if "FLASK_DEBUG" in os.environ:
                self.debug = get_debug_flag()
        if debug is not None:
            self.debug = bool(debug)
        server_name = self.config.get("SERVER_NAME")
        sn_host = sn_port = None
        if server_name:
            sn_host, _, sn_port = server_name.partition(":")
        if not host:
            if sn_host:
                host = sn_host
            else:
                host = "127.0.0.1"
        if port or port == 0:
            port = int(port)
        elif sn_port:
            port = int(sn_port)
        else:
            port = 5000
        options.setdefault("use_reloader", self.debug)
        options.setdefault("use_debugger", self.debug)
        options.setdefault("threaded", True)
        cli.show_server_banner(self.debug, self.name)
        from werkzeug.serving import run_simple
        try:
            run_simple(t.cast(str, host), port, self, **options)
        finally:
            self._got_first_request = False

这里我们其他的都可以进行忽略只要看以下这个部分就可以了

from werkzeug.serving import run_simple

try:
    run_simple(t.cast(str, host), port, self, **options)
finally:
    self._got_first_request = False

这个代码表示我们可以利用werkzeug实现一个socket服务

同样我们可以自己利用werkzeug实现一个简单的web服务

from werkzeug.serving import run_simple
from werkzeug.wrappers import Response


def func(environ, start_response):
    print('请求来了')
    response = Response('你好')
    return response(environ, start_response)


if __name__ == '__main__':
    run_simple('127.0.0.1', 5000, func)

三、总结

flask的启动过程大致可以分为以下四步

  • 初始化Flask对象
  • 创建resquest处理函数
  • 定义视图函数并创建路由规则
  • 利用werkzeug运行服务接受请求
Comments
  • Latest
  • Oldest
  • Hottest
Powered by Waline v2.15.8