ルーター
ルーティングとは
Router は、HTTPリクエストをどのミドルウェアと Handler で処理するかを定義します。これはSalvoの中で最も基本的かつ中核的な機能です。
Router の内部は実際には一連のフィルターで構成されています。リクエストが到着すると、ルーターは追加された順序で、上から下へと自身とその子孫がリクエストにマッチするかどうかを順次テストします。マッチが成功した場合、ルーターとその子孫ルーターによって形成されるチェーン上のミドルウェアを順次実行します。処理中に Response の状態がエラー(4XX、5XX)またはリダイレクト(3XX)に設定された場合、後続のミドルウェアと Handler はスキップされます。また、手動で ctrl.skip_rest() を呼び出して後続のミドルウェアと Handler をスキップすることもできます。
マッチングの過程では、URLパス情報が存在します。これは、マッチング過程で完全にFilterによって消費される必要があるオブジェクトと考えることができます。あるRouterですべてのFilterがマッチングに成功し、かつこのURLパス情報が完全に消費された場合、「マッチング成功」と見なされます。
例:
これは実際には以下と同等です:
GET /articles/ にアクセスするとマッチング成功と見なされ、list_articles が実行されます。しかし、GET /articles/123 にアクセスするとルーティングのマッチングに失敗し、404エラーが返されます。なぜなら、Router::with_path("articles") はURLパス情報の /articles のみを消費し、/123 部分が消費されていないため、マッチング失敗と見なされるからです。マッチングを成功させるには、ルーターを次のように変更できます:
ここでの {**} は任意の余分なパスにマッチするため、GET /articles/123 にマッチングし、list_articles を実行できます。
フラット定義
ルーティングをフラットスタイルで定義できます:
ツリー構造定義
ルーティングをツリー構造で定義することもでき、これは推奨される定義方法です:
この形式の定義は、複雑なプロジェクトにおいて、Routerの定義を階層的に明確かつシンプルにすることができます。
Router には、メソッド呼び出し後に自身(Self)を返すものが多くあり、チェーン形式でのコード記述が容易になっています。場合によっては、特定の条件に基づいてルーティング方法を決定する必要があり、ルーティングシステムは then 関数も提供しており、簡単に使用できます:
この例は、サーバーが admin_mode の場合のみ、記事の作成、編集、削除などのルートを追加することを表しています。
ルートからパラメータを取得
上記のコードでは、{id} がパラメータを定義しています。Request インスタンスを通じてその値を取得できます:
{id} はパス内の1つのセグメントにマッチします。通常、記事の id は単なる数字です。この場合、正規表現を使用して id のマッチングルールを制限できます:r"{id|\d+}"。
このような数値型の場合、もっと簡単な方法は <id:num> を使用することです。具体的な書き方は以下の通りです:
{id:num}:任意の数の数字文字にマッチ;{id:num[10]}:特定の数の数字文字のみにマッチ。ここでの10は、ちょうど10個の数字文字にマッチすることを意味します;{id:num(..10)}:1から9個の数字文字にマッチ;{id:num(3..10)}:3から9個の数字文字にマッチ;{id:num(..=10)}:1から10個の数字文字にマッチ;{id:num(3..=10)}:3から10個の数字文字にマッチ;{id:num(10..)}:少なくとも10個の数字文字にマッチ。
また、{**}, {*+}, または {*?} を使用して残りのすべてのパスセグメントにマッチさせることもできます。コードの可読性を高めるために、適切な名前を追加してパスの意味をより明確にすることもできます。例:{**file_path}。
{**}:ワイルドカードがマッチする部分は空文字列でもよいことを表します。例:パスが/files/{**rest_path}の場合、/files、/files/abc.txt、/files/dir/abc.txtにマッチします;{*+}:ワイルドカードがマッチする部分が存在しなければならず、空文字列にはマッチしないことを表します。例:パスが/files/{*+rest_path}の場合、/filesにはマッチしませんが、/files/abc.txt、/files/dir/abc.txtにはマッチします;{*?}:ワイルドカードがマッチする部分は空文字列でもよいが、1つのパスセグメントのみを含むことができることを表します。例:パスが/files/{*?rest_path}の場合、/files/dir/abc.txtにはマッチしませんが、/files、/files/abc.txtにはマッチします;
複数の式を組み合わせて同じパスセグメントにマッチさせることもできます。例:/articles/article_{id:num}/、/images/{name}.{ext}。
ミドルウェアの追加
ルーターの hoop 関数を通じてミドルウェアを追加できます:
この例では、ルートルーターは check_authed を使用して現在のユーザーがすでにログインしているかどうかをチェックします。すべての子孫ルーターはこのミドルウェアの影響を受けます。
ユーザーが単に writer の情報や記事を閲覧するだけであれば、ログインなしで閲覧できるようにしたい場合があります。ルーティングを次のように定義できます:
2つのルーターが同じパス定義 path("articles") を持っていても、同じ親ルーターに追加することができます。
フィルター
Router の内部では、すべてフィルターを通じてルーティングがマッチするかどうかを決定します。フィルターは or または and を使用した基本的な論理演算をサポートしています。1つのルーターは複数のフィルターを含むことができ、すべてのフィルターがマッチングに成功した場合、ルーティングのマッチングが成功します。
ウェブサイトのパス情報はツリー構造ですが、このツリー構造はルーティングを組織するツリー構造と同じではありません。ウェブサイトの1つのパスは、複数のルーターノードに対応する場合があります。例えば、articles/ パス下の一部のコンテンツはログインが必要で、一部はログインが不要です。ログイン確認が必要なサブパスを、ログイン確認ミドルウェアを含むルーターの下に組織し、ログイン確認が不要なものを別のログイン確認なしのルーターの下に組織できます:
ルーティングは、フィルターを使用してリクエストをフィルタリングし、対応するミドルウェアと Handler に送信して処理します。
path と method は2つの最も一般的に使用されるフィルターです。path はパス情報のマッチングに使用され、method はリクエストのMethod(例:GET、POST、PATCHなど)のマッチングに使用されます。
ルーターのフィルターを and、or で接続できます:
パスフィルター
リクエストパスに基づくフィルターが最も頻繁に使用されます。パスフィルターではパラメータを定義できます。例:
Handler では、Request オブジェクトの get_param 関数を通じて取得できます:
メソッドフィルター
HTTP リクエストの Method に基づいてリクエストをフィルタリングします。例:
ここでの get、patch、delete はすべてMethodフィルターです。実際には以下と同等です:
カスタムWisp
頻繁に出現するマッチング式については、PathFilter::register_wisp_regex または PathFilter::register_wisp_builder を使用して短い名前を付けることができます。例えば、GUID形式はパスで頻繁に出現します。通常の書き方は、マッチングが必要なたびに次のように書くことです:
毎回この複雑な正規表現を書くのはエラーが発生しやすく、コードも美しくありません。次のようにすることができます:
一度登録するだけで、今後は {id:guid} のような簡単な書き方でGUIDにマッチングでき、コードの記述が簡素化されます。
以前に学んだControllerクラスのWebフレームワークからRouterをどう理解するか?
ルーティング設計のWebフレームワーク(Salvoなど)と従来のMVCまたはController設計フレームワークの主な違いは以下の通りです:
-
柔軟性:ルーティング設計では、リクエスト処理フローをより柔軟に定義でき、各パスの処理ロジックを正確に制御できます。例えば、Salvoでは特定のパスの処理関数を直接定義できます:
一方、Controller設計では通常、まずコントローラークラスを定義し、クラス内で複数のメソッドを定義して異なるリクエストを処理する必要があります:
-
ミドルウェア統合:ルーティングフレームワークは通常、より簡潔なミドルウェア統合方法を提供し、特定のルートにミドルウェアを適用できます。Salvoのミドルウェアは特定のルートに正確に適用できます:
-
コード組織:ルーティング設計は、MVCのモデル-ビュー-コントローラー階層ではなく、機能やAPIエンドポイントに基づいてコード