From 0f5ad717d9c9c5474b465b637c2edeacd243ce37 Mon Sep 17 00:00:00 2001 From: Bruce Hamilton Date: Wed, 15 Jan 2025 13:32:40 +0100 Subject: [PATCH 1/3] KTOR-7194 Deferred session fetching for public endpoints --- .../io/ktor/tests/auth/SessionAuthTest.kt | 5 +- .../io/ktor/tests/auth/SessionAuthJvmTest.kt | 83 +++++++++++++++++++ .../api/ktor-server-sessions.api | 2 + .../api/ktor-server-sessions.klib.api | 4 + .../io/ktor/server/sessions/SessionData.kt | 23 ++++- .../ktor/server/sessions/SessionDeferral.kt | 12 +++ .../src/io/ktor/server/sessions/Sessions.kt | 39 +++++---- .../io/ktor/server/sessions/SessionsConfig.kt | 7 ++ .../SessionDeferral.jsAndWasmShared.kt | 10 +++ .../sessions/SessionSerializerReflection.kt | 17 ++-- .../sessions/BlockingDeferredSessionData.kt | 82 ++++++++++++++++++ .../sessions/SessionDeferral.jvmAndPosix.kt | 20 +++++ 12 files changed, 274 insertions(+), 30 deletions(-) create mode 100644 ktor-server/ktor-server-plugins/ktor-server-auth/jvm/test/io/ktor/tests/auth/SessionAuthJvmTest.kt create mode 100644 ktor-server/ktor-server-plugins/ktor-server-sessions/common/src/io/ktor/server/sessions/SessionDeferral.kt create mode 100644 ktor-server/ktor-server-plugins/ktor-server-sessions/jsAndWasmShared/src/io/ktor/server/sessions/SessionDeferral.jsAndWasmShared.kt create mode 100644 ktor-server/ktor-server-plugins/ktor-server-sessions/jvmAndPosix/src/io/ktor/server/sessions/BlockingDeferredSessionData.kt create mode 100644 ktor-server/ktor-server-plugins/ktor-server-sessions/jvmAndPosix/src/io/ktor/server/sessions/SessionDeferral.jvmAndPosix.kt diff --git a/ktor-server/ktor-server-plugins/ktor-server-auth/common/test/io/ktor/tests/auth/SessionAuthTest.kt b/ktor-server/ktor-server-plugins/ktor-server-auth/common/test/io/ktor/tests/auth/SessionAuthTest.kt index c565add44a4..98f1c730830 100644 --- a/ktor-server/ktor-server-plugins/ktor-server-auth/common/test/io/ktor/tests/auth/SessionAuthTest.kt +++ b/ktor-server/ktor-server-plugins/ktor-server-auth/common/test/io/ktor/tests/auth/SessionAuthTest.kt @@ -14,8 +14,9 @@ import io.ktor.server.response.* import io.ktor.server.routing.* import io.ktor.server.sessions.* import io.ktor.server.testing.* -import kotlinx.serialization.* -import kotlin.test.* +import kotlinx.serialization.Serializable +import kotlin.test.Test +import kotlin.test.assertEquals class SessionAuthTest { @Test diff --git a/ktor-server/ktor-server-plugins/ktor-server-auth/jvm/test/io/ktor/tests/auth/SessionAuthJvmTest.kt b/ktor-server/ktor-server-plugins/ktor-server-auth/jvm/test/io/ktor/tests/auth/SessionAuthJvmTest.kt new file mode 100644 index 00000000000..9dec18fbf85 --- /dev/null +++ b/ktor-server/ktor-server-plugins/ktor-server-auth/jvm/test/io/ktor/tests/auth/SessionAuthJvmTest.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.tests.auth + +import io.ktor.client.request.HttpRequestBuilder +import io.ktor.client.request.get +import io.ktor.client.request.header +import io.ktor.client.request.post +import io.ktor.http.HttpStatusCode +import io.ktor.server.application.install +import io.ktor.server.auth.Authentication +import io.ktor.server.auth.authenticate +import io.ktor.server.auth.session +import io.ktor.server.response.respondText +import io.ktor.server.routing.get +import io.ktor.server.routing.post +import io.ktor.server.routing.routing +import io.ktor.server.sessions.SessionStorage +import io.ktor.server.sessions.Sessions +import io.ktor.server.sessions.cookie +import io.ktor.server.sessions.defaultSessionSerializer +import io.ktor.server.sessions.serialization.KotlinxSessionSerializer +import io.ktor.server.sessions.sessions +import io.ktor.server.sessions.set +import io.ktor.server.testing.testApplication +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class SessionAuthJvmTest { + + @Test + fun sessionIgnoredForNonPublicEndpoints() = testApplication { + val brokenStorage = object : SessionStorage { + override suspend fun write(id: String, value: String) = Unit + override suspend fun invalidate(id: String) = error("invalidate called") + override suspend fun read(id: String): String = error("read called") + } + application { + install(Sessions) { + cookie("S", storage = brokenStorage) { + serializer = KotlinxSessionSerializer(Json.Default) + } + deferred = true + } + install(Authentication.Companion) { + session { + validate { it } + } + } + routing { + authenticate { + get("/authenticated") { + call.respondText("Secret info") + } + } + post("/session") { + call.sessions.set(MySession(1)) + call.respondText("OK") + } + get("/public") { + call.respondText("Public info") + } + } + } + val withCookie: HttpRequestBuilder.() -> Unit = { + header("Cookie", "S=${defaultSessionSerializer().serialize(MySession(1))}") + } + + assertEquals(HttpStatusCode.Companion.OK, client.post("/session").status) + assertEquals(HttpStatusCode.Companion.OK, client.get("/public", withCookie).status) + assertFailsWith { + client.get("/authenticated", withCookie).status + } + } + + @Serializable + data class MySession(val id: Int) +} diff --git a/ktor-server/ktor-server-plugins/ktor-server-sessions/api/ktor-server-sessions.api b/ktor-server/ktor-server-plugins/ktor-server-sessions/api/ktor-server-sessions.api index 62c59714556..7bc469895af 100644 --- a/ktor-server/ktor-server-plugins/ktor-server-sessions/api/ktor-server-sessions.api +++ b/ktor-server/ktor-server-plugins/ktor-server-sessions/api/ktor-server-sessions.api @@ -268,8 +268,10 @@ public final class io/ktor/server/sessions/SessionsBuilderKt { public final class io/ktor/server/sessions/SessionsConfig { public fun ()V + public final fun getDeferred ()Z public final fun getProviders ()Ljava/util/List; public final fun register (Lio/ktor/server/sessions/SessionProvider;)V + public final fun setDeferred (Z)V } public final class io/ktor/server/sessions/SessionsKt { diff --git a/ktor-server/ktor-server-plugins/ktor-server-sessions/api/ktor-server-sessions.klib.api b/ktor-server/ktor-server-plugins/ktor-server-sessions/api/ktor-server-sessions.klib.api index 35e50a694ef..f72dadc6906 100644 --- a/ktor-server/ktor-server-plugins/ktor-server-sessions/api/ktor-server-sessions.klib.api +++ b/ktor-server/ktor-server-plugins/ktor-server-sessions/api/ktor-server-sessions.klib.api @@ -202,6 +202,10 @@ final class io.ktor.server.sessions/SessionsConfig { // io.ktor.server.sessions/ final val providers // io.ktor.server.sessions/SessionsConfig.providers|{}providers[0] final fun (): kotlin.collections/List> // io.ktor.server.sessions/SessionsConfig.providers.|(){}[0] + final var deferred // io.ktor.server.sessions/SessionsConfig.deferred|{}deferred[0] + final fun (): kotlin/Boolean // io.ktor.server.sessions/SessionsConfig.deferred.|(){}[0] + final fun (kotlin/Boolean) // io.ktor.server.sessions/SessionsConfig.deferred.|(kotlin.Boolean){}[0] + final fun register(io.ktor.server.sessions/SessionProvider<*>) // io.ktor.server.sessions/SessionsConfig.register|register(io.ktor.server.sessions.SessionProvider<*>){}[0] } diff --git a/ktor-server/ktor-server-plugins/ktor-server-sessions/common/src/io/ktor/server/sessions/SessionData.kt b/ktor-server/ktor-server-plugins/ktor-server-sessions/common/src/io/ktor/server/sessions/SessionData.kt index f431b146132..51f819eb81b 100644 --- a/ktor-server/ktor-server-plugins/ktor-server-sessions/common/src/io/ktor/server/sessions/SessionData.kt +++ b/ktor-server/ktor-server-plugins/ktor-server-sessions/common/src/io/ktor/server/sessions/SessionData.kt @@ -44,6 +44,19 @@ public interface CurrentSession { public fun findName(type: KClass<*>): String } +/** + * Extends [CurrentSession] with a call to include session data in the server response. + */ +internal interface StatefulSession : CurrentSession { + + /** + * Iterates over session data items and writes them to the application call. + * The session cannot be modified after this is called. + * This is called after the session data is sent to the response. + */ + suspend fun sendSessionData(call: ApplicationCall, onEach: (String) -> Unit = {}) +} + /** * Sets a session instance with the type [T]. * @throws IllegalStateException if no session provider is registered for the type [T] @@ -99,11 +112,15 @@ public inline fun CurrentSession.getOrSet(name: String = findN internal data class SessionData( val providerData: Map> -) : CurrentSession { +) : StatefulSession { private var committed = false - internal fun commit() { + override suspend fun sendSessionData(call: ApplicationCall, onEach: (String) -> Unit) { + providerData.values.forEach { data -> + onEach(data.provider.name) + data.sendSessionData(call) + } committed = true } @@ -175,7 +192,7 @@ internal data class SessionProviderData( val provider: SessionProvider ) -internal val SessionDataKey = AttributeKey("SessionKey") +internal val SessionDataKey = AttributeKey("SessionKey") private fun ApplicationCall.reportMissingSession(): Nothing { application.plugin(Sessions) // ensure the plugin is installed diff --git a/ktor-server/ktor-server-plugins/ktor-server-sessions/common/src/io/ktor/server/sessions/SessionDeferral.kt b/ktor-server/ktor-server-plugins/ktor-server-sessions/common/src/io/ktor/server/sessions/SessionDeferral.kt new file mode 100644 index 00000000000..8692e1a755a --- /dev/null +++ b/ktor-server/ktor-server-plugins/ktor-server-sessions/common/src/io/ktor/server/sessions/SessionDeferral.kt @@ -0,0 +1,12 @@ +/* + * Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.server.sessions + +import io.ktor.server.application.ApplicationCall + +/** + * Creates a lazy loading session from the given providers. + */ +internal expect fun createDeferredSession(call: ApplicationCall, providers: List>): StatefulSession diff --git a/ktor-server/ktor-server-plugins/ktor-server-sessions/common/src/io/ktor/server/sessions/Sessions.kt b/ktor-server/ktor-server-plugins/ktor-server-sessions/common/src/io/ktor/server/sessions/Sessions.kt index d3a4336f12b..18dffeeb86a 100644 --- a/ktor-server/ktor-server-plugins/ktor-server-sessions/common/src/io/ktor/server/sessions/Sessions.kt +++ b/ktor-server/ktor-server-plugins/ktor-server-sessions/common/src/io/ktor/server/sessions/Sessions.kt @@ -6,7 +6,6 @@ package io.ktor.server.sessions import io.ktor.server.application.* import io.ktor.server.request.* -import io.ktor.server.response.* import io.ktor.util.* import io.ktor.util.logging.* @@ -27,24 +26,23 @@ internal val LOGGER = KtorSimpleLogger("io.ktor.server.sessions.Sessions") */ public val Sessions: RouteScopedPlugin = createRouteScopedPlugin("Sessions", ::SessionsConfig) { val providers = pluginConfig.providers.toList() + val sessionSupplier: suspend (ApplicationCall, List>) -> StatefulSession = + if (pluginConfig.deferred) { + ::createDeferredSession + } else { + ::createSession + } application.attributes.put(SessionProvidersKey, providers) onCall { call -> - // For each call, call each provider and retrieve session data if needed. - // Capture data in the attribute's value - val providerData = providers.associateBy({ it.name }) { - it.receiveSessionData(call) - } - - if (providerData.isEmpty()) { - LOGGER.trace("No sessions found for ${call.request.uri}") + if (providers.isEmpty()) { + LOGGER.trace { "No sessions found for ${call.request.uri}" } } else { - val sessions = providerData.keys.joinToString() - LOGGER.trace("Sessions found for ${call.request.uri}: $sessions") + val sessions = providers.joinToString { it.name } + LOGGER.trace { "Sessions found for ${call.request.uri}: $sessions" } } - val sessionData = SessionData(providerData) - call.attributes.put(SessionDataKey, sessionData) + call.attributes.put(SessionDataKey, sessionSupplier(call, providers)) } // When response is being sent, call each provider to update/remove session data @@ -58,11 +56,18 @@ public val Sessions: RouteScopedPlugin = createRouteScopedPlugin */ val sessionData = call.attributes.getOrNull(SessionDataKey) ?: return@on - sessionData.providerData.values.forEach { data -> - LOGGER.trace("Sending session data for ${call.request.uri}: ${data.provider.name}") - data.sendSessionData(call) + sessionData.sendSessionData(call) { provider -> + LOGGER.trace { "Sending session data for ${call.request.uri}: $provider" } } + } +} - sessionData.commit() +private suspend fun createSession(call: ApplicationCall, providers: List>): StatefulSession { + // For each call, call each provider and retrieve session data if needed. + // Capture data in the attribute's value + val providerData = providers.associateBy({ it.name }) { + it.receiveSessionData(call) } + + return SessionData(providerData) } diff --git a/ktor-server/ktor-server-plugins/ktor-server-sessions/common/src/io/ktor/server/sessions/SessionsConfig.kt b/ktor-server/ktor-server-plugins/ktor-server-sessions/common/src/io/ktor/server/sessions/SessionsConfig.kt index 1dfec428d51..b12c970b19c 100644 --- a/ktor-server/ktor-server-plugins/ktor-server-sessions/common/src/io/ktor/server/sessions/SessionsConfig.kt +++ b/ktor-server/ktor-server-plugins/ktor-server-sessions/common/src/io/ktor/server/sessions/SessionsConfig.kt @@ -18,6 +18,13 @@ public class SessionsConfig { */ public val providers: List> get() = registered.toList() + /** + * When set to true, sessions will be lazily retrieved from storage. + * + * Note: this is only available for JVM in Ktor 3.0 + */ + public var deferred: Boolean = false + /** * Registers a session [provider]. */ diff --git a/ktor-server/ktor-server-plugins/ktor-server-sessions/jsAndWasmShared/src/io/ktor/server/sessions/SessionDeferral.jsAndWasmShared.kt b/ktor-server/ktor-server-plugins/ktor-server-sessions/jsAndWasmShared/src/io/ktor/server/sessions/SessionDeferral.jsAndWasmShared.kt new file mode 100644 index 00000000000..6acc7f33460 --- /dev/null +++ b/ktor-server/ktor-server-plugins/ktor-server-sessions/jsAndWasmShared/src/io/ktor/server/sessions/SessionDeferral.jsAndWasmShared.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.server.sessions + +import io.ktor.server.application.ApplicationCall + +internal actual fun createDeferredSession(call: ApplicationCall, providers: List>): StatefulSession = + TODO("Deferred session retrieval is currently only available for JVM") diff --git a/ktor-server/ktor-server-plugins/ktor-server-sessions/jvm/src/io/ktor/server/sessions/SessionSerializerReflection.kt b/ktor-server/ktor-server-plugins/ktor-server-sessions/jvm/src/io/ktor/server/sessions/SessionSerializerReflection.kt index 75127ceac4f..b098981dbeb 100644 --- a/ktor-server/ktor-server-plugins/ktor-server-sessions/jvm/src/io/ktor/server/sessions/SessionSerializerReflection.kt +++ b/ktor-server/ktor-server-plugins/ktor-server-sessions/jvm/src/io/ktor/server/sessions/SessionSerializerReflection.kt @@ -5,17 +5,18 @@ package io.ktor.server.sessions import io.ktor.http.* -import io.ktor.server.sessions.serialization.* import io.ktor.util.* -import kotlinx.serialization.* -import kotlinx.serialization.json.* -import java.lang.reflect.* -import java.math.* +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type +import java.math.BigDecimal +import java.math.BigInteger import java.util.* -import java.util.concurrent.* +import java.util.concurrent.ConcurrentHashMap import kotlin.reflect.* -import kotlin.reflect.full.* -import kotlin.reflect.jvm.* +import kotlin.reflect.full.memberProperties +import kotlin.reflect.full.superclasses +import kotlin.reflect.jvm.javaType +import kotlin.reflect.jvm.jvmErasure private const val TYPE_TOKEN_PARAMETER_NAME: String = "\$type" diff --git a/ktor-server/ktor-server-plugins/ktor-server-sessions/jvmAndPosix/src/io/ktor/server/sessions/BlockingDeferredSessionData.kt b/ktor-server/ktor-server-plugins/ktor-server-sessions/jvmAndPosix/src/io/ktor/server/sessions/BlockingDeferredSessionData.kt new file mode 100644 index 00000000000..616da68e144 --- /dev/null +++ b/ktor-server/ktor-server-plugins/ktor-server-sessions/jvmAndPosix/src/io/ktor/server/sessions/BlockingDeferredSessionData.kt @@ -0,0 +1,82 @@ +/* + * Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.server.sessions + +import io.ktor.server.application.ApplicationCall +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.runBlocking +import kotlin.coroutines.CoroutineContext +import kotlin.reflect.KClass + +/** + * An implementation of [StatefulSession] that lazily references session providers to + * avoid unnecessary calls to session storage. + * All access to the deferred providers is done through blocking calls. + */ +internal class BlockingDeferredSessionData( + val callContext: CoroutineContext, + val providerData: Map>>, +) : StatefulSession { + + private var committed = false + + @OptIn(ExperimentalCoroutinesApi::class) + override suspend fun sendSessionData(call: ApplicationCall, onEach: (String) -> Unit) { + for (deferredProvider in providerData.values) { + // skip non-completed providers because they were not modified + if (!deferredProvider.isCompleted) continue + val data = deferredProvider.getCompleted() + onEach(data.provider.name) + data.sendSessionData(call) + } + committed = true + } + + override fun findName(type: KClass<*>): String { + val entry = providerData.values.map { + it.awaitBlocking() + }.firstOrNull { + it.provider.type == type + } ?: throw IllegalArgumentException("Session data for type `$type` was not registered") + + return entry.provider.name + } + + override fun set(name: String, value: Any?) { + if (committed) { + throw TooLateSessionSetException() + } + val providerData = + providerData[name] ?: throw IllegalStateException("Session data for `$name` was not registered") + setTyped(providerData.awaitBlocking(), value) + } + + @Suppress("UNCHECKED_CAST") + private fun setTyped(data: SessionProviderData, value: Any?) { + if (value != null) { + data.provider.tracker.validate(value as S) + } + data.newValue = value as S + } + + override fun get(name: String): Any? { + val providerDataDeferred = + providerData[name] ?: throw IllegalStateException("Session data for `$name` was not registered") + val providerData = providerDataDeferred.awaitBlocking() + return providerData.newValue ?: providerData.oldValue + } + + override fun clear(name: String) { + val providerDataDeferred = + providerData[name] ?: throw IllegalStateException("Session data for `$name` was not registered") + val providerData = providerDataDeferred.awaitBlocking() + providerData.oldValue = null + providerData.newValue = null + } + + private fun Deferred>.awaitBlocking() = + runBlocking(callContext) { await() } +} diff --git a/ktor-server/ktor-server-plugins/ktor-server-sessions/jvmAndPosix/src/io/ktor/server/sessions/SessionDeferral.jvmAndPosix.kt b/ktor-server/ktor-server-plugins/ktor-server-sessions/jvmAndPosix/src/io/ktor/server/sessions/SessionDeferral.jvmAndPosix.kt new file mode 100644 index 00000000000..8e676ccd3c9 --- /dev/null +++ b/ktor-server/ktor-server-plugins/ktor-server-sessions/jvmAndPosix/src/io/ktor/server/sessions/SessionDeferral.jvmAndPosix.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package io.ktor.server.sessions + +import io.ktor.server.application.ApplicationCall +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.async + +internal actual fun createDeferredSession(call: ApplicationCall, providers: List>): StatefulSession = + BlockingDeferredSessionData( + call.coroutineContext, + providers.associateBy({ it.name }) { + CoroutineScope(call.coroutineContext).async(start = CoroutineStart.LAZY) { + it.receiveSessionData(call) + } + } + ) From 117edbd7153bf417ec8174565e6bfb163a56b66e Mon Sep 17 00:00:00 2001 From: Bruce Hamilton <150327496+bjhham@users.noreply.github.com> Date: Thu, 16 Jan 2025 16:06:19 +0100 Subject: [PATCH 2/3] Update ktor-server/ktor-server-plugins/ktor-server-sessions/common/src/io/ktor/server/sessions/Sessions.kt Co-authored-by: Osip Fatkullin --- .../common/src/io/ktor/server/sessions/Sessions.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ktor-server/ktor-server-plugins/ktor-server-sessions/common/src/io/ktor/server/sessions/Sessions.kt b/ktor-server/ktor-server-plugins/ktor-server-sessions/common/src/io/ktor/server/sessions/Sessions.kt index 18dffeeb86a..df54e8acb77 100644 --- a/ktor-server/ktor-server-plugins/ktor-server-sessions/common/src/io/ktor/server/sessions/Sessions.kt +++ b/ktor-server/ktor-server-plugins/ktor-server-sessions/common/src/io/ktor/server/sessions/Sessions.kt @@ -39,8 +39,10 @@ public val Sessions: RouteScopedPlugin = createRouteScopedPlugin if (providers.isEmpty()) { LOGGER.trace { "No sessions found for ${call.request.uri}" } } else { - val sessions = providers.joinToString { it.name } - LOGGER.trace { "Sessions found for ${call.request.uri}: $sessions" } + LOGGER.trace { + val sessions = providers.joinToString { it.name } + "Sessions found for ${call.request.uri}: $sessions" + } } call.attributes.put(SessionDataKey, sessionSupplier(call, providers)) } From 91b3dc361a480a36d1e0f11f76b1d9d28cb10f75 Mon Sep 17 00:00:00 2001 From: Bruce Hamilton <150327496+bjhham@users.noreply.github.com> Date: Thu, 16 Jan 2025 16:06:56 +0100 Subject: [PATCH 3/3] Update ktor-server/ktor-server-plugins/ktor-server-sessions/jvmAndPosix/src/io/ktor/server/sessions/BlockingDeferredSessionData.kt Co-authored-by: Osip Fatkullin --- .../src/io/ktor/server/sessions/BlockingDeferredSessionData.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ktor-server/ktor-server-plugins/ktor-server-sessions/jvmAndPosix/src/io/ktor/server/sessions/BlockingDeferredSessionData.kt b/ktor-server/ktor-server-plugins/ktor-server-sessions/jvmAndPosix/src/io/ktor/server/sessions/BlockingDeferredSessionData.kt index 616da68e144..2e5cca7d5e3 100644 --- a/ktor-server/ktor-server-plugins/ktor-server-sessions/jvmAndPosix/src/io/ktor/server/sessions/BlockingDeferredSessionData.kt +++ b/ktor-server/ktor-server-plugins/ktor-server-sessions/jvmAndPosix/src/io/ktor/server/sessions/BlockingDeferredSessionData.kt @@ -49,8 +49,7 @@ internal class BlockingDeferredSessionData( if (committed) { throw TooLateSessionSetException() } - val providerData = - providerData[name] ?: throw IllegalStateException("Session data for `$name` was not registered") + val providerData = checkNotNull(providerData[name]) { "Session data for `$name` was not registered" } setTyped(providerData.awaitBlocking(), value) }