Flask ソース コードの記事: Flask ルーティング ルールとリクエスト マッチング プロセス

ソースシリーズ:

Flask ソース コードの記事: wsgi、Werkzeug、および Flask 起動ワークフロー

Flask ソースコードの記事: 2 単語で Flask がコンテキスト原則であることを完全に理解

具体的な分析プロセスを読みたくない場合は、概要を直接読むと理解できます。

1 起動時のルーティング関連操作

いわゆるルーティング原理は、Flask が独自のルーティング システムをどのように作成し、リクエストが来たときに、ルーティング システムに従って処理機能を正確に見つけてリクエストに応答するかを示します。

このセクションでは、次のように、最も単純な Flask アプリケーションの例を使用してルーティングの原理を説明します。

from flask import Flask

app = Flask(__name__)


@app.route('/')
def hello_world():
    return 'Hello World!'


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

(1) app.route()を解析する

まず、ルートの登録はscaffold (Flask が scaffold を継承) パッケージ配下のroute()デコレータを通じて実現されており、そのソースコードは次のとおりです。

def route(self, rule: str, **options: t.Any) -> t.Callable:
    def decorator(f: t.Callable) -> t.Callable:
      	# 获取endpoint
        endpoint = options.pop("endpoint", None)
        # 添加路由,rule就是app.route()传来的路由字符串,及'/'
        self.add_url_rule(rule, endpoint, f, **options)
        return f

    return decorator

このデコレーターは主に 2 つのことを行うことがわかります: 1. の取得endpoint、2. ルーティングの追加。

これらの中で最も重要なのは、ルート マッピングを追加するために使用される関数ですadd_url_rule()

補充:

  1. エンドポイントは、後で Flask がルーティングと関数名のマッピングを保存するときに使用されます。指定しない場合、デフォルトは装飾された関数名です。使い方は後で分析します。
  2. app.route() の本質はやはり add_url_rule() 関数であるため、この関数を直接使用することもできます。使用方法については、記事Flask ルーティングを参照してください。

(2) add_url_rule()を解析する

add_url_rule の動作を見てみましょう。そのコア ソース コードは次のとおりです。

class Flask(Scaffold):
    # 这里只有要讨论的主要代码,其他代码省略了
    
    url_rule_class = Rule
    url_map_class = Map
    
    def __init__(
        self,
        import_name: str,
        static_url_path: t.Optional[str] = None,
        static_folder: t.Optional[t.Union[str, os.PathLike]] = "static",
        static_host: t.Optional[str] = None,
        host_matching: bool = False,
        subdomain_matching: bool = False,
        template_folder: t.Optional[str] = "templates",
        instance_path: t.Optional[str] = None,
        instance_relative_config: bool = False,
        root_path: t.Optional[str] = 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.url_map = self.url_map_class()
        self.url_map.host_matching = host_matching
        self.subdomain_matching = subdomain_matching
  
    def add_url_rule(
        self,
        rule: str,
        endpoint: t.Optional[str] = None,
        view_func: t.Optional[t.Callable] = None,
        provide_automatic_options: t.Optional[bool] = None,
        **options: t.Any,
     ) -> None:
         # 1.如果没提供endpoint,获取默认的endpoint
         if endpoint is None:
             endpoint = _endpoint_from_view_func(view_func)  # type: ignore
         options["endpoint"] = endpoint

          # 2.获取请求方法,在装饰器@app.route('/index', methods=['POST','GET'])的method参数
          # 如果没有指定,则给个默认的元组("GET",)
          # 关于provide_automatic_options处理一些暂不看
          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}

          # Methods that should always be added
          required_methods = set(getattr(view_func, "required_methods", ()))

          # starting with Flask 0.8 the view_func object can disable and
          # force-enable the automatic options handling.
          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

          # 3.重要的一步:url_rule_class方法实例化Rule对象
          rule = self.url_rule_class(rule, methods=methods, **options)
          rule.provide_automatic_options = provide_automatic_options  # type: ignore
					
          # 4.重要的一步:url_map(Map对象)的add方法
          self.url_map.add(rule)
          
          # 5.判断endpoint和view_func的映射存不存在,如果已经有其他view_func用了这个endpoint,则报错,否则新的映射加到self.view_functions里
          # self.view_functions继承自Scaffold,是一个字典对象
          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

上記のソース コードを分析すると、このメソッドは主に次のことを行います。

  1. 指定されていない場合はendpoint、デフォルトを取得しますendpoint
  2. デコレータ@app.route('/index', methods=['POST','GET'])のパラメータでリクエスト メソッドを取得しますmethod。指定されていない場合は、デフォルトのタプル ("GET",) を指定します。
  3. self.url_rule_class():Ruleオブジェクトをインスタンス化します。これについては、Rule クラスで後述します。
  4. self.url_map.add()メソッドを呼び出します。self.url_map はMapオブジェクトです。これについては後で説明します。
  5. self.view_functions[endpoint] = view_funcendpointとview_funcのマッピングを追加します。

このうち、Ruleオブジェクトとサムのインスタンス化self.url_map.add()は Falsk ルーティングの核心であり、以下ではRuleクラスとMapクラスを分析します。

(3) Ruleクラスを解析する

Ruleクラスはwerkzeug.routingモジュールの下にあり、そのソース コードはさらに続きます。ここでは、次のように、使用するメイン コードのみを抽出します。

class Rule(RuleFactory):
  	def __init__(
        self,
        string: str,
        methods: t.Optional[t.Iterable[str]] = None,
        endpoint: t.Optional[str] = None,
        # 此处省略了其他参数的代码 ...
    ) -> None:
        if not string.startswith("/"):
            raise ValueError("urls must start with a leading slash")
        self.rule = string
        self.is_leaf = not string.endswith("/")
        self.map: "Map" = None  # type: ignore
        self.methods = methods
        self.endpoint: str = endpoint 
				# 省略了其他初始化的代码
        
    def get_rules(self, map: "Map") -> t.Iterator["Rule"]:
        """获取map对象的rule迭代器"""
        yield self
    
    def bind(self, map: "Map", rebind: bool = False) -> None:
        """把map对象绑定到Rule对象上,并且根据rule和map信息创建一个path正则表达式,存储在rule对象的self._regex属性里,路由匹配的时候用"""
        if self.map is not None and not rebind:
             raise RuntimeError(f"url rule {
      
      self!r} already bound to map {
      
      self.map!r}")
        # 把map对象绑定到Rule对象上
        self.map = map
        if self.strict_slashes is None:
            self.strict_slashes = map.strict_slashes
        if self.merge_slashes is None:
            self.merge_slashes = map.merge_slashes
        if self.subdomain is None:
            self.subdomain = map.default_subdomain
        # 调用compile方法创建一个正则表达式
        self.compile()
     
    def compile(self) -> None:
        """编写正则表达式并存储到属性self._regex中"""
				# 此处省略了正则的解析过程代码
        regex = f"^{
      
      ''.join(regex_parts)}{
      
      tail}$"
        self._regex = re.compile(regex)
        
    def match(
        self, path: str, method: t.Optional[str] = None
    		) -> t.Optional[t.MutableMapping[str, t.Any]]:
        """这个函数用于校验传进来path参数(路由)是否能够匹配,匹配不上返回None"""
        # 省去了部分代码,只摘录了主要代码,看一下大致逻辑即可
        if not self.build_only:
            require_redirect = False
					  # 1.根据bind后的正则结果(self._regex正则)去找path的结果集
            m = self._regex.search(path)
            if m is not None:
                groups = m.groupdict()
            
            # 2.编辑匹配到的结果集,加到一个result字典里并返回
            result = {
    
    }
            for name, value in groups.items():
                try:
                  	value = self._converters[name].to_python(value)
                except ValidationError:
                  	return None
                result[str(name)] = value
                if self.defaults:
                  	result.update(self.defaults)
            return result
        return None    

RuleクラスはRuleFactoryクラスを継承し、主なパラメータは次のとおりです。

  • string: ルート文字列
  • methods: ルートメソッド
  • endpoint:エンドポイントパラメータ

Rule インスタンスは URL パターンを表し、WSGI アプリケーションはさまざまな URL パターンを処理し、同時に多くの Rule インスタンスを生成し、これらのインスタンスはパラメータとして Map クラスに渡されます。

(4) Mapクラスを解析する

Mapこのクラスも werkzeug.routing モジュールの下にあり、多くのソース コードがあります。ここでは使用する主なコードのみを抜粋します。主なソース コードは次のとおりです。

class Map:
  	def __init__(
        self,
        rules: t.Optional[t.Iterable[RuleFactory]] = None
        # 此处省略了其他参数
    ) -> None:
      	# 根据传进来的rules参数维护了一个私有变量self._rules列表
        self._rules: t.List[Rule] = []
        # endpoint和rule的映射
        self._rules_by_endpoint: t.Dict[str, t.List[Rule]] = {
    
    }
        # 此处省略了其他初始化操作
        
    def add(self, rulefactory: RuleFactory) -> None:
        """
        把Rule对象或一个RuleFactory对象添加到map并且绑定到map,要求rule没被绑定过
        """
        for rule in rulefactory.get_rules(self):
            # 调用rule对象的bind方法
            rule.bind(self)
            # 把rule对象添加到self._rules列表里
            self._rules.append(rule)
            # 把endpoint和rule的映射加到属性self._rules_by_endpoint里
            self._rules_by_endpoint.setdefault(rule.endpoint, []).append(rule)
        self._remap = True
        
    def bind(
        self,
        server_name: str,
        script_name: t.Optional[str] = None,
        subdomain: t.Optional[str] = None,
        url_scheme: str = "http",
        default_method: str = "GET",
        path_info: t.Optional[str] = None,
        query_args: t.Optional[t.Union[t.Mapping[str, t.Any], str]] = None,
    ) -> "MapAdapter":
        """
        返回一个新的类MapAdapter
        """
        server_name = server_name.lower()
        if self.host_matching:
            if subdomain is not None:
                raise RuntimeError("host matching enabled and a subdomain was provided")
        elif subdomain is None:
            subdomain = self.default_subdomain
        if script_name is None:
            script_name = "/"
        if path_info is None:
            path_info = "/"

        try:
            server_name = _encode_idna(server_name)  # type: ignore
        except UnicodeError as e:
            raise BadHost() from e

        return MapAdapter(
            self,
            server_name,
            script_name,
            subdomain,
            url_scheme,
            path_info,
            default_method,
            query_args,
        )

Mapクラスには 2 つの非常に重要な属性があります。

  1. self._rules、属性は一連の Rule オブジェクトを格納するリストです。
  2. self._rules_by_endpoint

コアメソッドがありますadd()。ここでの分析app.add_url_rule()メソッドはステップ4で呼び出されるメソッドです。詳しくは後述する。

(5) MapAdapterクラスを解析する

Mapクラスでは、MapAdapterクラスが使用されます。このクラスについて理解しましょう。

class MapAdapter:

    """`Map.bind`或`Map.bind_to_environ` 会返回这个类
    主要用来做匹配
    """

    def match(
        self,
        path_info: t.Optional[str] = None,
        method: t.Optional[str] = None,
        return_rule: bool = False,
        query_args: t.Optional[t.Union[t.Mapping[str, t.Any], str]] = None,
        websocket: t.Optional[bool] = None,
    ) -> t.Tuple[t.Union[str, Rule], t.Mapping[str, t.Any]]:
        """匹配请求的路由和Rule对象"""
				# 只摘摘录了主要代码,省略了大量代码...
				
        # 这里是主要步骤:遍历map对象的rule列表,依次和path进行匹配
        for rule in self.map._rules:
            try:
                # 调用rule对象的match方法返回匹配结果
                rv = rule.match(path, method)
            except RequestPath as e:
						 # 下面省略了大量代码...
         
         # 返回rule对象(或endpoint)和匹配的路由结果
         if return_rule:
            return rule, rv
         else:
             return rule.endpoint, rv

Map.bindまたはMap.bind_to_environメソッドがMapAdapterオブジェクトを返します。

MapAdapterオブジェクトのコア メソッドは、matchなステップがマップ オブジェクトのルール リストを走査し、順にパスと照合することです。もちろん、ルール オブジェクトの match メソッドが呼び出され、照合結果が返されます。

Map次に、クラスとオブジェクトがどのように結合され、少し独立して使用できるかを見てみましょうRlue。次の例を参照してください。

from werkzeug.routing import Map, Rule

m = Map([
    Rule('/', endpoint='index'),
    Rule('/blog', endpoint='blog/index'),
    Rule('/blog/<int:id>', endpoint='blog/detail')
])

# 返回一个MapAdapter对象
map_adapter = m.bind("example.com", "/")

# MapAdapter对象的 match方法会返回匹配的结果
print(map_adapter.match("/", "GET"))
# ('index', {})

print(map_adapter.match("/blog/42"))
# ('blog/detail', {'id': 42})

print(map_adapter.match("/blog"))
# ('blog/index', {})

Mapオブジェクトがオブジェクトbindを返しMapAdapterMapAdapterそのオブジェクトのメソッドがmatchルートの一致する結果を見つけることができることがわかります。

(6) url_rule_class()を解析する

add_url_rule最初の主要なステップは、オブジェクトrule = self.url_rule_class(rule, methods=methods, **options)を作成することですRule

Rlueクラスを分析すると、Rule オブジェクトには主にstring(ルーティング文字列)、methods、およびendpoint3 つの属性があることがわかりました。以下の具体的な例を使用して、インスタンス化されたRuleオブジェクトがどのように見えるかを確認してください。

引き続き冒頭の最上位の例ですが、@app.route('/')次のように、コードを渡した後に Rule オブジェクトがインスタンス化されるときのオブジェクトの特定のプロパティをデバッグを通じて見てみましょう。

ここに画像の説明を挿入

ruleルール オブジェクトの属性は渡されたルートであり、endpoint属性は関数名を通じて取得され、method属性はサポートされているリクエスト メソッドであり、OPTIONSデフォルトで「HEAD」が追加されていることがわかります。

(7) 解析map.add(rule)

add_url_rule2 番目の主要な手順は、オブジェクトの add メソッドself.url_map.add(rule)を呼び出すことです。Map

ステップ 4 で Map オブジェクトを分析するときに言及しましたが、戻ってこのメソッドの動作を詳しく見てみましょう。

def add(self, rulefactory: RuleFactory) -> None:
        """
        把Rule对象或一个RuleFactory对象添加到map并且绑定到map,要求rule没被绑定过
        """
        for rule in rulefactory.get_rules(self):
            # 调用rule对象的bind方法
            rule.bind(self)
            # 把rule对象添加到self._rules列表里
            self._rules.append(rule)
            # 把endpoint和rule的映射加到属性self._rules_by_endpoint里
            self._rules_by_endpoint.setdefault(rule.endpoint, []).append(rule)
        self._remap = True

実際、主なことは、前の手順でインスタンス化した Rule オブジェクトまたは RuleFactory オブジェクトを Map オブジェクトに追加し、それをマップにバインドすることです。

主に次の 2 つのことを行いました。

  1. ルール オブジェクトのバインド メソッドを呼び出すrule.bind(self): Rule クラスを分析するときに、このメソッドについて説明しました。その主な機能は、マップ オブジェクトを Rule オブジェクトにバインドし、ルール オブジェクトに基づいて正規表現 (Rule オブジェクトのプロパティ) を作成することです。ルールとマップ情報self._regex
  2. ルール オブジェクトをMapオブジェクトのリストに追加しますself._rules
  3. endpointと のruleマッピングをMapオブジェクトのプロパティself._rules_by_endpoint(辞書)に追加します。

例を使用して、追加後に Map オブジェクトがどのようになるかを確認できます。デバッグを通じて、結果は次のようになります。

ここに画像の説明を挿入

と のMap両方の属性に、新しく追加された '/' ルート (ルートはデフォルトで追加された静的ファイルのロケーション ルート)に対応するデータが含まれていることがわかります。self._rulesself._rules_by_endpoint/static/<path:filename>

以上でルーティングマップの追加方法の解析は終わりました。

2 リクエスト受信時のルートマッチング処理

(1) wsgi_appを解析する

前のリクエストが来たときに、前のリクエストMapとオブジェクトに従ってRlueルートを一致させる方法を分析してみましょう。

前の章では、__call__リクエストが届いたときに、wsgi サーバーを介してリクエストを処理した後、Flask アプリを呼び出す方法を分析しました。コードは次のとおりです。

def __call__(self, environ: dict, start_response: t.Callable) -> t.Any:
    """The WSGI server calls the Flask application object as the
    WSGI application. This calls :meth:`wsgi_app`, which can be
    wrapped to apply middleware.
    """
    return self.wsgi_app(environ, start_response)

アプリのメソッドが実際に呼び出されていることがわかりますwsgi_app()そのコードは次のとおりです。

def wsgi_app(self, environ: dict, start_response: t.Callable) -> t.Any:
  	# 1.获取请求上下文
    ctx = self.request_context(environ)
    error: t.Optional[BaseException] = None
    try:
        try:
          	# 2.调用请求上下文的push方法
            ctx.push()
            # 3.调用full_dispatch_request()分发请求,获取响应结果
            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)

それには 3 つの主なステップがあります。

  1. リクエストコンテキストを取得します。ctx = self.request_context(environ)
  2. リクエストコンテキストのプッシュメソッドを呼び出します。ctx.push()
  3. full_dispatch_request() を呼び出してリクエストを分散し、応答結果を取得します。response = self.full_dispatch_request()

各ステップの機能を 1 つずつ分析してみましょう。

(2) request_contextの解析

wsgi_appこのメソッドの最初の主要なステップ。

このメソッドは主にコンテキスト オブジェクトを取得するためのものです。

environ(環境変数など) をメソッドに渡す必要があります。Flask ではコンテキストも重要な概念です。もちろん、次の章ではコンテキストの分析に焦点を当てます。この章では、必要なことだけに焦点を当てます。

def request_context(self, environ: dict) -> RequestContext:
    return RequestContext(self, environ)

このメソッドはRequestContextオブジェクトを作成することです。RequestContextクラス部分のソースコードは以下の通りです。

class RequestContext:
    def __init__(
        self,
        app: "Flask",
        environ: dict,
        request: t.Optional["Request"] = None,
        session: t.Optional["SessionMixin"] = None,
    ) -> None:
        self.app = app
        if request is None:
            request = app.request_class(environ)
        self.request = request
        self.url_adapter = None
        try:
          	# 此处是重点,调用了Falsk对象的create_url_adapter方法获取了MapAdapter对象
            self.url_adapter = app.create_url_adapter(self.request)
        except HTTPException as e:
            self.request.routing_exception = e
        self.flashes = None
        self.session = session
				# 其他代码省略...
    # 其他方法的源码省略...

RequestContextオブジェクトを作成する初期化メソッドにおいて、非常に重要なステップは、MapAdapterオブジェクトを取得することです。

上記のセクション 4 でその機能を分析しましたが、主にルートを照合するために使用されます。

create_url_adapterソースコードを見てみましょう:

def create_url_adapter(
    self, request: t.Optional[Request]
) -> t.Optional[MapAdapter]:
    if request is not None:
        if not self.subdomain_matching:
            subdomain = self.url_map.default_subdomain or None
        else:
            subdomain = None
				# 此处是重点,调用了Map对象的bind_to_environ方法
        return self.url_map.bind_to_environ(
            request.environ,
            server_name=self.config["SERVER_NAME"],
            subdomain=subdomain,
        )
    if self.config["SERVER_NAME"] is not None:
      	# 此处是重点,调用了Map对象的bind方法
        return self.url_map.bind(
            self.config["SERVER_NAME"],
            script_name=self.config["APPLICATION_ROOT"],
            url_scheme=self.config["PREFERRED_URL_SCHEME"],
        )

    return None

ご覧のとおり、このメソッドの主な機能は、Mapオブジェクトのbind_to_environメソッドを呼び出すことですbind先ほど Map クラスの話をしたときにも分析しましたが、この 2 つのメソッドは主にMapAdapterオブジェクトを返します。

(3) ctx.pushを解析する

wsgi_appこの方法の 2 番目の主要なステップ。

wsgi メソッドでコンテキスト オブジェクトが取得された後、メソッドが呼び出されpush、コードは次のようになります (コア コードのみが保持されます)。

class RequestContext:
    def __init__(
        self,
        app: "Flask",
        environ: dict,
        request: t.Optional["Request"] = None,
        session: t.Optional["SessionMixin"] = None,
    ) -> None:
        # 代码省略
        pass

    def match_request(self) -> None:
        try:
          	# 1.调用了MapAdapter对象的match方法,返回了rule对象和参数对象
            result = self.url_adapter.match(return_rule=True) 
            # 2.把rule对象和参数对象放到请求上下文中
            self.request.url_rule, self.request.view_args = result
        except HTTPException as e:
            self.request.routing_exception = e

    def push(self) -> None:
        """Binds the request context to the current context."""
        # 此处省略了前置校验处理代码(上下文、session等处理)
        if self.url_adapter is not None:
          	# 调用了match_request方法
            self.match_request()

pushこのメソッドは主にmatch_requestメソッドを呼び出していることがわかります。このメソッドは主に次の 2 つのことを実行します。

  1. MapAdapter オブジェクトの match メソッドを呼び出すと、Mapオブジェクトに格納されているルーティング情報に従って現在のリクエストのルートが照合され、ルール オブジェクトとパラメーター オブジェクトが返されます。
  2. ルール オブジェクトとパラメーター オブジェクトをリクエスト コンテキストに配置します。

(4) full_dispatch_requestを解析する

wsgi_appこの方法の 3 番目の主要なステップ。

full_dispatch_requestメソッドのソースコードは次のとおりです。

def full_dispatch_request(self) -> Response:
    self.try_trigger_before_first_request_functions()
    try:
        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)

その中にはdispatch_request()コアメソッドがあります。

dispatch_request()メソッドのソースコードは次のとおりです。

def dispatch_request(self) -> ResponseReturnValue:
  	# 1.获取请求上下文对象
    req = _request_ctx_stack.top.request
    if req.routing_exception is not None:
        self.raise_routing_exception(req)
    # 2.从上下文中获取前面存在里面的Rule对象
    rule = req.url_rule
    # if we provide automatic options for this URL and the
    # request came with the OPTIONS method, reply automatically
    if (
        getattr(rule, "provide_automatic_options", False)
        and req.method == "OPTIONS"
    ):
        return self.make_default_options_response()
    # 这里是重点:根据rule对象的endpoint属性从self.view_functions属性中获取对应的视图函数,
    # 然后把上下文中的参数传到视图函数中并调用视图函数处理请求,返回处理结果
    return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)

この方法の主な手順は次のとおりです。

  1. req = _request_ctx_stack.top.request: リクエストコンテキストオブジェクトを取得します
  2. rule = req.url_ruleRule:コンテキストから既存のオブジェクトを取得します。これはctx.push()、コンテキストに組み込まれたメソッドです。
  3. ルール オブジェクトの endpoint プロパティに従って、self.view_functions プロパティから対応するビュー関数を取得し、コンテキスト内のパラメーターをビュー関数に渡し、ビュー関数を呼び出してリクエストを処理し、処理結果を返します。return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)

この時点で、完全なリクエストが処理されます。

3 まとめ

上記の分析によれば、ルーティング ルールとリクエストのマッチングは次のように要約されます。

アプリの起動時:

  1. app.route()呼び出しadd_url_ruleメソッド。
  2. add_url_ruleメソッド内:self.url_rule_class()インスタンス化されたRuleオブジェクトを呼び出します。
  3. add_url_ruleメソッド内:self.url_map.add()メソッドを呼び出し、Ruleオブジェクトendpointとビュー関数の間のマッピング関係を Map オブジェクトに保存します。
  4. add_url_ruleメソッド内: self.view_functions[endpoint] = view_funcendpoint と view_func のマッピングを追加します。

リクエスト照合プロセス:

  1. リクエストが届くと、WSGIサーバーは__call__Flask アプリのメソッドを処理して呼び出し、その後wsgi_appそのメソッドを呼び出します。
  2. wsgi_appメソッドでコンテキスト オブジェクトを作成しますctx = self.request_context(environ)次に、オブジェクトはMapAdapterコンテキスト オブジェクト プロパティとしてインスタンス化されます。
  3. wsgi_appメソッド内でコンテキスト オブジェクトを呼び出すメソッドpush: ctx.push()このメソッドは主にMapAdapterオブジェクトのメソッドを使用しますmatch
  4. MapAdapterオブジェクトのメソッド。オブジェクトのメソッドmatchを呼び出しますこのメソッドは、オブジェクトに格納されているルーティング情報に従って現在のリクエストのルートを照合し、オブジェクトとパラメータ オブジェクトをコンテキスト オブジェクトに取得します。rulematchMaprule
  5. wsgi_appfull_dispatch_requestメソッド内のメソッドを呼び出してから、dispatch_request()そのメソッド内のメソッドを呼び出します。
  6. dispatch_request()メソッド内: リクエスト コンテキスト オブジェクトを取得し、 内のオブジェクトを取得Ruleし、ルール オブジェクトのエンドポイント属性に従って属性から対応するビュー関数を取得しself.view_functions、コンテキスト内のパラメーターをビュー関数に渡し、ビュー関数を呼び出します。リクエストを処理し、処理結果を返します。

全体のフローチャートは次のとおりです。

ここに画像の説明を挿入

プロジェクトの前半では、ルーティング ルールを確立するための使用方法RuleMapオブジェクトの使用方法について説明し、後半では、リクエストが受信されたときに照合するためにルーティング ルールを使用する方法について説明します。

おすすめ

転載: blog.csdn.net/qq_43745578/article/details/129336596