ルーティング
ルーティングは、サーバーアプリケーションで受信リクエストを処理するためのKtorのコアプラグインです。クライアントが特定のURL(例: /hello
)にリクエストを送信すると、ルーティングメカニズムによって、このリクエストをどのように処理するかを定義できます。
ルーティングのインストール
「ルーティング」プラグインは、次の方法でインストールできます。
import io.ktor.server.routing.*
install(RoutingRoot) {
// ...
}
「ルーティング」プラグインはあらゆるアプリケーションで非常に一般的であるため、ルーティングのインストールを簡素化する便利な routing
関数があります。以下のコードスニペットでは、install(RoutingRoot)
が routing
関数に置き換えられています。
import io.ktor.server.routing.*
routing {
// ...
}
ルートハンドラーの定義
「ルーティング」プラグインをインストールした後、routing
内でroute 関数を呼び出してルートを定義できます。
import io.ktor.server.routing.*
import io.ktor.http.*
import io.ktor.server.response.*
routing {
route("/hello", HttpMethod.Get) {
handle {
call.respondText("Hello")
}
}
}
Ktorはまた、ルートハンドラーの定義をはるかに簡単かつ簡潔にする一連の関数も提供しています。例えば、前のコードは、URLとリクエストを処理するコードのみを必要とするget 関数に置き換えることができます。
import io.ktor.server.routing.*
import io.ktor.server.response.*
routing {
get("/hello") {
call.respondText("Hello")
}
}
同様に、Ktorは他のすべての動詞、つまり put
、post
、head
などについても関数を提供しています。
要約すると、ルートを定義するには以下の設定を指定する必要があります。
HTTP動詞
GET
、POST
、PUT
などのHTTP動詞を選択します。最も便利な方法は、get
、post
、put
などの専用動詞関数を使用することです。パスパターン
/hello
、/customer/{id}
などのURLパスを照合するために使用されるパスパターンを指定します。パスパターンをget
/post
/などの関数に直接渡すことも、route
関数を使用してルートハンドラーをグループ化し、ネストされたルートを定義することもできます。ハンドラー
リクエストとレスポンスを処理する方法を指定します。ハンドラー内では、
ApplicationCall
にアクセスし、クライアントリクエストを処理し、レスポンスを送信できます。
パスパターンの指定
ルーティング関数(route
、get
、post
など)に渡されるパスパターンは、URLの_パス_コンポーネントを照合するために使用されます。パスはスラッシュ /
文字で区切られた一連のパスセグメントを含むことができます。
Ktorは末尾のスラッシュの有無でパスを区別することに注意してください。この動作は、
IgnoreTrailingSlash
プラグインをインストールすることで変更できます。
以下にいくつかのパスの例を示します。
/hello
単一のパスセグメントを含むパス。/order/shipment
複数のパスセグメントを含むパス。このようなパスは、そのままroute/get/etc.関数に渡すことも、複数のroute
関数をネストしてサブルートを整理することもできます。/user/{login}
login
パスパラメータを持つパスで、その値はルートハンドラー内でアクセスできます。/user/*
任意のパスセグメントにマッチするワイルドカード文字を持つパス。/user/{...}
URLパスの残りのすべてにマッチするテールカードを持つパス。/user/{param...}
テールカード付きパスパラメータを含むパス。Regex("/.+/hello")
/hello
の最後の出現箇所を含むパスセグメントまでを照合する正規表現を含むパス。
ワイルドカード
ワイルドカード (*
) は任意のパスセグメントにマッチし、省略することはできません。例えば、/user/*
は /user/john
にマッチしますが、/user
にはマッチしません。
テールカード
テールカード ({...}
) はURLパスの残りのすべてにマッチし、複数のパスセグメントを含めることができ、空にすることもできます。例えば、/user/{...}
は /user/john/settings
および /user
にマッチします。
パスパラメータ
パスパラメータ ({param}
) はパスセグメントにマッチし、param
という名前のパラメータとしてキャプチャします。このパスセグメントは必須ですが、疑問符を追加することでオプションにできます: {param?}
。例えば、
/user/{login}
は/user/john
にマッチしますが、/user
にはマッチしません。/user/{login?}
は/user/john
および/user
にマッチします。
オプションのパスパラメータ
{param?}
は、パスの末尾でのみ使用できることに注意してください。
ルートハンドラー内でパラメータ値にアクセスするには、call.parameters
プロパティを使用します。例えば、以下のコードスニペットの call.parameters["login"]
は、/user/admin
パスに対して admin を返します。
get("/user/{login}") {
if (call.parameters["login"] == "admin") {
// ...
}
}
リクエストにクエリ文字列が含まれている場合、
call.parameters
にはそのクエリ文字列のパラメータも含まれます。ハンドラー内でクエリ文字列とそのパラメータにアクセスする方法については、クエリパラメータを参照してください。
テールカード付きパスパラメータ
テールカード付きパスパラメータ ({param...}
) はURLパスの残りのすべてにマッチし、各パスセグメントの複数の値を param
をキーとしてパラメータに格納します。例えば、/user/{param...}
は /user/john/settings
にマッチします。 ルートハンドラー内でパスセグメントの値にアクセスするには、call.parameters.getAll("param")
を使用します。上記の例では、getAll
関数は john と settings の値を含む配列を返します。
正規表現
正規表現は、route
、get
、post
など、すべてのルートハンドラー定義関数で使用できます。
正規表現の詳細については、Kotlinドキュメントを参照してください。
/hello
で終わる任意のパスにマッチするルートを記述してみましょう。
import io.ktor.server.routing.*
import io.ktor.server.response.*
routing {
get(Regex(".+/hello")) {
call.respondText("Hello")
}
}
このルート定義により、/foo/hello
、/bar/baz/hello
など、/hello
で終わるパスへの受信リクエストはすべてマッチします。
ハンドラーでのパスの要素へのアクセス
正規表現において、名前付きグループは、パターンにマッチする文字列の特定の部分をキャプチャし、それに名前を割り当てる方法です。 (?<name>pattern)
という構文は名前付きグループを定義するために使用され、name
はグループの名前、 pattern
はグループにマッチする正規表現パターンです。
ルート関数で名前付きグループを定義することで、パスの一部をキャプチャでき、その後ハンドラー関数で、 call.parameters
オブジェクトを使用してキャプチャされたパラメータにアクセスできます。
例えば、整数識別子が続き、その後 /hello
が続くパスへのリクエストにマッチするルートを定義できます。
import io.ktor.server.routing.*
import io.ktor.server.response.*
routing {
get(Regex("""(?<id>\d+)/hello""")) {
val id = call.parameters["id"]!!
call.respondText(id)
}
}
以下のコードでは、(?<id>\d+)
名前付きグループがリクエストされたパスから整数識別子 id
をキャプチャするために使用され、 call.parameters
プロパティがハンドラー関数でキャプチャされた id
パラメータにアクセスするために使用されています。
無名グループは正規表現ルートハンドラー内ではアクセスできませんが、パスをマッチさせるために使用できます。例えば、hello/world
というパスはマッチしますが、hello/World
はマッチしません。
import io.ktor.server.routing.*
import io.ktor.server.response.*
routing {
get(Regex("hello/([a-z]+)")) {
call.respondText("Hello")
}
}
また、パスセグメント全体が正規表現によって消費される必要があります。例えば、パスパターン get(Regex("[a-z]+"))
はパス "hello1"
にはマッチせず、パス hello/1
の hello
部分にマッチし、/1
を次のルートに残します。
複数のルートハンドラーの定義
動詞関数によるルートのグループ化
複数のルートハンドラーを定義したい場合(これはもちろんあらゆるアプリケーションに当てはまります)、それらを routing
関数に追加するだけで済みます。
routing {
get("/customer/{id}") {
}
post("/customer") {
}
get("/order") {
}
get("/order/{id}") {
}
}
この場合、各ルートは独自の関数を持ち、特定のエンドポイントとHTTP動詞に応答します。
パスによるルートのグループ化
別の方法として、パスでグループ化する方法があります。これは、パスを定義し、そのパスの動詞をネストされた関数として route
関数を使用して配置します。
routing {
route("/customer") {
get {
}
post {
}
}
route("/order") {
get {
}
get("/{id}") {
}
}
}
ネストされたルート
グループ化の方法に関わらず、Ktorは route
関数のパラメータとしてサブルートを持つことも許可しています。これは、論理的に他のリソースの子であるリソースを定義するのに役立ちます。以下の例は、/order/shipment
への GET
および POST
リクエストに応答する方法を示しています。
routing {
route("/order") {
route("/shipment") {
get {
}
post {
}
}
}
}
このように、各 route
呼び出しは個別のパスセグメントを生成します。
ルーティング関数(route
、get
、post
など)に渡されるパスパターンは、URLの_パス_コンポーネントを照合するために使用されます。パスはスラッシュ /
文字で区切られた一連のパスセグメントを含むことができます。
ルート拡張関数
一般的なパターンとして、Route
型の拡張関数を使用して実際のルートを定義する方法があります。これにより、動詞への簡単なアクセスが可能になり、すべてのルートを単一のルーティング関数にまとめることによる煩雑さを解消できます。このパターンは、ルートのグループ化方法に関わらず適用できます。そのため、最初の例はよりクリーンな方法で表現できます。
routing {
listOrdersRoute()
getOrderRoute()
totalizeOrderRoute()
}
fun Route.listOrdersRoute() {
get("/order") {
}
}
fun Route.getOrderRoute() {
get("/order/{id}") {
}
}
fun Route.totalizeOrderRoute() {
get("/order/{id}/total") {
}
}
このアプローチをデモンストレーションする完全な例については、legacy-interactive-website を参照してください。
アプリケーションの保守性を考慮してスケールさせるには、特定の構造パターンに従うことが推奨されます。
ルートのトレース
ロギングが構成されている場合、Ktorはルートトレースを有効にし、一部のルートが実行されない理由を特定するのに役立ちます。例えば、アプリケーションを実行し、指定されたエンドポイントにリクエストを行うと、アプリケーションの出力は次のようになる場合があります。
TRACE Application - Trace for [missing-page]
/, segment:0 -> SUCCESS @ /
/, segment:0 -> SUCCESS @ /
/(method:GET), segment:0 -> FAILURE "Not all segments matched" @ /(method:GET)
Matched routes:
No results
Route resolve result:
FAILURE "No matched subtrees found" @ /
Nativeサーバーでルートトレースを有効にするには、アプリケーションを実行する際に
KTOR_LOG_LEVEL
環境変数に TRACE 値を渡します。