Repository: ktorio/ktor
File: ktor-server/ktor-server-plugins/ktor-server-sessions/common/src/io/ktor/server/sessions/Sessions.kt (Kotlin)

Code before:
 * Typical use cases include storing a logged-in user's ID, the contents of a shopping basket,
 * or keeping user preferences on the client.
 * In Ktor, you can implement sessions by using cookies or custom headers,
 * choose whether to store session data on the server or pass it to the client,
 * sign and encrypt session data and more.
 *
 * You can learn more from [Sessions](https://ktor.io/docs/sessions.html).
 * @property providers list of session providers
 */
public val Sessions: RouteScopedPlugin<SessionsConfig> = createRouteScopedPlugin("Sessions", ::SessionsConfig) {
    val providers = pluginConfig.providers.toList()
    val sessionSupplier: suspend (ApplicationCall, List<SessionProvider<*>>) -> StatefulSession =
        if (pluginConfig.deferred) {
            ::createDeferredSession
        } else {
            ::createSession
        }

    application.attributes.put(SessionProvidersKey, providers)

    onCall { call ->
        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" }
        }
        call.attributes.put(SessionDataKey, sessionSupplier(call, providers))
    }

    // When response is being sent, call each provider to update/remove session data
    on(BeforeSend) { call ->

        /*
         If sessionData is not available it means response happened before Session plugin got a chance to deserialize the data.
         We should ignore this call in this case.

         An example would be CORS plugin responding with 403 Forbidden
         */
        val sessionData = call.attributes.getOrNull(SessionDataKey) ?: return@on

        sessionData.sendSessionData(call) { provider ->
            LOGGER.trace { "Sending session data for ${call.request.uri}: $provider" }
        }
    }
}

private suspend fun createSession(call: ApplicationCall, providers: List<SessionProvider<*>>): 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 }) {

Review comment (suggestion):
Minor
```suggestion
            LOGGER.trace {
                val sessions = providers.joinToString { it.name }
                "Sessions found for ${call.request.uri}: $sessions"
            }
```

Code after:
 * Typical use cases include storing a logged-in user's ID, the contents of a shopping basket,
 * or keeping user preferences on the client.
 * In Ktor, you can implement sessions by using cookies or custom headers,
 * choose whether to store session data on the server or pass it to the client,
 * sign and encrypt session data and more.
 *
 * You can learn more from [Sessions](https://ktor.io/docs/sessions.html).
 * @property providers list of session providers
 */
public val Sessions: RouteScopedPlugin<SessionsConfig> = createRouteScopedPlugin("Sessions", ::SessionsConfig) {
    val providers = pluginConfig.providers.toList()
    val sessionSupplier: suspend (ApplicationCall, List<SessionProvider<*>>) -> StatefulSession =
        if (isDeferredSessionsEnabled()) {
            ::createDeferredSession
        } else {
            ::createSession
        }

    application.attributes.put(SessionProvidersKey, providers)

    onCall { call ->
        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" }
        }
        call.attributes.put(SessionDataKey, sessionSupplier(call, providers))
    }

    // When response is being sent, call each provider to update/remove session data
    on(BeforeSend) { call ->

        /*
         If sessionData is not available it means response happened before Session plugin got a chance to deserialize the data.
         We should ignore this call in this case.

         An example would be CORS plugin responding with 403 Forbidden
         */
        val sessionData = call.attributes.getOrNull(SessionDataKey) ?: return@on

        sessionData.sendSessionData(call) { provider ->
            LOGGER.trace { "Sending session data for ${call.request.uri}: $provider" }
        }
    }
}

private suspend fun createSession(call: ApplicationCall, providers: List<SessionProvider<*>>): 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 }) {