diff --git a/ktor-client/ktor-client-core/jvm/src/io/ktor/client/plugins/cache/storage/FileCacheStorage.kt b/ktor-client/ktor-client-core/jvm/src/io/ktor/client/plugins/cache/storage/FileCacheStorage.kt index db3c00d2234..be2d7b03901 100644 --- a/ktor-client/ktor-client-core/jvm/src/io/ktor/client/plugins/cache/storage/FileCacheStorage.kt +++ b/ktor-client/ktor-client-core/jvm/src/io/ktor/client/plugins/cache/storage/FileCacheStorage.kt @@ -205,6 +205,15 @@ private class FileCacheStorage( channel.writeFully(cache.body) } + /** + * Deserialize a single CachedResponseData from the provided ByteReadChannel. + * + * Reads the cached-entry fields in the stored binary format: request URL, HTTP status and version, + * headers, request/response/expiration timestamps, vary keys (converted to lowercase), and the body bytes. + * + * @param channel Source channel positioned at the start of a serialized cache entry. + * @return The reconstructed CachedResponseData instance. + */ private suspend fun readCache(channel: ByteReadChannel): CachedResponseData { val url = channel.readUTF8Line()!! val status = HttpStatusCode(channel.readInt(), channel.readUTF8Line()!!) @@ -222,7 +231,7 @@ private class FileCacheStorage( val varyKeysCount = channel.readInt() val varyKeys = buildMap { for (j in 0 until varyKeysCount) { - val key = channel.readUTF8Line()!! + val key = channel.readUTF8Line()!!.lowercase() val value = channel.readUTF8Line()!! put(key, value) } @@ -242,4 +251,4 @@ private class FileCacheStorage( body = body ) } -} +} \ No newline at end of file diff --git a/ktor-test-server/src/main/kotlin/test/server/tests/Cache.kt b/ktor-test-server/src/main/kotlin/test/server/tests/Cache.kt index 2b426fae7d9..5c629548ea9 100644 --- a/ktor-test-server/src/main/kotlin/test/server/tests/Cache.kt +++ b/ktor-test-server/src/main/kotlin/test/server/tests/Cache.kt @@ -17,6 +17,14 @@ import java.util.concurrent.atomic.AtomicInteger internal val counter = AtomicInteger(0) +/** + * Registers test routes under `/cache` that exercise caching and conditional header behaviors. + * + * Installs the `CachingHeaders` and `ConditionalHeaders` plugins and declares endpoints used in tests: + * handlers for various `Cache-Control` directives, `Expires`, `ETag`, `Last-Modified`, and `Vary` + * header scenarios, including endpoints that return 304 Not Modified responses and case-sensitive + * vary-header behaviors. + */ internal fun Application.cacheTestServer() { routing { route("/cache") { @@ -149,6 +157,11 @@ internal fun Application.cacheTestServer() { else -> error("Should not be invoked") } } + + get("/vary-header-not-modified") { + call.response.header(HttpHeaders.Vary, HttpHeaders.AcceptLanguage) + call.respond(HttpStatusCode.NotModified) + } } } -} +} \ No newline at end of file