7
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

KtorでCallIdを使ってクライアントを一意に特定する

Posted at

タイトルについてまず捕捉する。

そもそもログインしていないアカウントに関しては、サーバーで発行したセッションIDをCookieに持っていない。

これで困るのがロギングのシーンである。未ログイン状態でアクセスしてくるユーザーが入り混じるため、とあるユーザーの行動をログから追跡することができない。

このようなシチュエーションに有用なのがCallIdである。

インストール

まずはbuild.gradle.ktsにインストール。

dependencies {
    // 略

    // for call id
    implementation("io.ktor:ktor-server-call-id:$ktor_version")

    //略
}

次にApplication.ktからKtorにプラグインをインストールする。installの中のverifyなどについては後ほど詳しく説明する。

package example.koin

import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.plugins.callid.*
import io.ktor.server.plugins.callloging.*

fun main(args: Array<String>) {
    io.ktor.server.tomcat.EngineMain.main(args)
}

fun Application.module() {
    settingKoin()
    configureRouting()
    install(CallId) {
        header(HttpHeaders.XRequestId)
        verify { callId: String ->
            callId.isNotEmpty()
        }
    }
    install(CallLogging) {
        callIdMdc("call-id")
    }
}

最後にlogback.xmlX-Request-IDとして付与されるCallIdを出力するための設定をいれる。%X{call-id}がそう。

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %X{call-id} %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    <root level="debug">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

CallId を検証する

まずはX-Request-IDの有無によるログ出力やレスポンスをみてみる。今回はinorinrinrinというCallIdをマニュアルで付与する。

するとレスポンスヘッダにも同じものが設定されているのがわかる。これはheader()が取得→送信をひとまとめにしてくれているからである。

image.png

ログをみると、X-Request-IDに紐づいたCallIdが出力されているのがわかる。

image.png

今度はX-Request-IDを外してリクエストを送信する。

image.png

すると今度はログにCallIdが出力されていないことがわかる。

image.png

これはverifyX-Request-IDの妥当性を検証しているからである。それを確認するため、verifyの条件をinoから始まる文字列にした場合にどうなるかをみてみる。

    install(CallId) {
        header(HttpHeaders.XRequestId)
        verify { callId: String ->
+            callId.startsWith("ino")
        }
    }

X-Request-IDayaneruとしてみた。このとき、レスポンスヘッダにX-Request-IDが返ってこない。ログにもCallIdは出ていない。

image.png

image.png

以上から、X-Request-IDが付与されていれば以降の通信でそれを使いまわせること。ログに出力できることを確認できた。

CallIdを発行する

あとはX-Request-IDを初回リクエスト時に発行してあげればよい。CallIdを生成しX-Request-IDに設定するにはgenerateを使う。

生成方法にはドキュメントに記載があるとおり、文字の種類を渡して適当な長さのIDを生成させるか、自前で生成する方法がある。

今回は自前生成関数generateRandomStringを定義してみる。

fun generateRandomString(length: Int): String {
    val charPool = ('a'..'z') + ('A'..'Z') + ('0'..'9') // 小文字、大文字、数字
    return (1..length)
        .map { Random.nextInt(charPool.size) }
        .map(charPool::get)
        .joinToString("")
}

これをgenerateに渡す。

    install(CallId) {
        header(HttpHeaders.XRequestId)
+        generate { generateRandomString(10) }
        verify { callId: String ->
            callId.isNotEmpty()
        }
    }

ではX-Request-IDを付与しない初回のリクエストを見てみる。すると10文字のCallIdが生成され、レスポンスヘッダに付与されたのがわかる。

image.png

おわりに

Ktorがロギングまで気を配ってくれているので非常にありがたい。この手の仕組みを数行で実現できるのは利用者からするとかなり楽。

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?