Cookie/Header Sessions

预计阅读时间:3分钟

您可以将cookie或自定义HTTP标头用于会话. 代码大致相同,但是您必须调用cookieheader方法,具体取决于要将会话信息发送到的位置.

Cookies vs Headers sessions

根据消费者的不同,您可能希望使用cookie或标头传输sessionId或有效负载. 例如,对于网站,通常将使用cookie,而对于API,则可能需要使用标头.

Sessions.Configuration提供了两种方法cookieheader选择如何传输会话:

Cookies

application.install(Sessions) {
    cookie<MySession>("SESSION")
} 

您可以通过提供其他块来配置cookie. 有一个cookie属性可以配置它,例如通过添加SameSite扩展

application.install(Sessions) {
    cookie<MySession>("SESSION") {
        cookie.extensions["SameSite"] = "lax"
    }
} 

Cookie方法适用于浏览器会话. 它将使用标准的Set-Cookie标头 . 在cookie块内,您可以访问cookie属性,该属性允许您配置Set-Cookie标头,例如,通过设置cookie的path或有效期,域或https相关的内容.

install(Sessions) {
    cookie<SampleSession>("COOKIE_NAME") {
        cookie.path = "/"
        /* ... */
    }
}

Headers

Header方法适用于API,既可以在JavaScript XHR请求中使用,也可以从服务器端请求. 对于API客户端来说,读取和生成自定义标头通常比处理cookie更容易.

install(Sessions) {
    header<SampleSession>("HTTP_HEADER_NAME") { /* ... */ }
}
application.install(Sessions) {
    header<MySession>("SESSION")
} 

Custom storages

Sessions API提供了一个SessionStorage接口,如下所示:

interface SessionStorage {
    suspend fun write(id: String, provider: suspend (ByteWriteChannel) -> Unit)
    suspend fun invalidate(id: String)
    suspend fun <R> read(id: String, consumer: suspend (ByteReadChannel) -> R): R
}

所有这三个功能都标记为suspend和被设计成完全异步和使用ByteWriteChannelByteReadChannelkotlinx.coroutines.io ,对于阅读和异步通道写作提供的API.

在实现中,您必须调用回调函数,以提供必须提供的ByteWriteChannel和ByteReadChannel:打开和关闭它们是您的责任. 您可以在其库文档中阅读有关ByteWriteChannelByteReadChannel更多信息. 如果只需要加载或存储ByteArray,则可以使用此代码段,它提供了简化的会话存储:

SimplifiedSessionStorage.kt
abstract class SimplifiedSessionStorage : SessionStorage {
    abstract suspend fun read(id: String): ByteArray?
    abstract suspend fun write(id: String, data: ByteArray?): Unit

    override suspend fun invalidate(id: String) {
        write(id, null)
    }

    override suspend fun <R> read(id: String, consumer: suspend (ByteReadChannel) -> R): R {
        val data = read(id) ?: throw NoSuchElementException("Session $id not found")
        return consumer(ByteReadChannel(data))
    }

    override suspend fun write(id: String, provider: suspend (ByteWriteChannel) -> Unit) {
        return provider(reader(coroutineContext, autoFlush = true) {
            write(id, channel.readAvailable())
        }.channel)
    }
}

suspend fun ByteReadChannel.readAvailable(): ByteArray {
    val data = ByteArrayOutputStream()
    val temp = ByteArray(1024)
    while (!isClosedForRead) {
        val read = readAvailable(temp)
        if (read <= 0) break
        data.write(temp, 0, read)
    }
    return data.toByteArray()
}

使用这种简化的存储,您只需实现两个简单的方法:

abstract class SimplifiedSessionStorage : SessionStorage {
    abstract suspend fun read(id: String): ByteArray?
    abstract suspend fun write(id: String, data: ByteArray?): Unit
}

因此,例如,redis会话存储如下所示:

class RedisSessionStorage(val redis: Redis, val prefix: String = "session_", val ttlSeconds: Int = 3600) :
    SimplifiedSessionStorage() {
    private fun buildKey(id: String) = "$prefix$id"

    override suspend fun read(id: String): ByteArray? {
        val key = buildKey(id)
        return redis.get(key)?.unhex?.apply {
            redis.expire(key, ttlSeconds) // refresh
        }
    }

    override suspend fun write(id: String, data: ByteArray?) {
        val key = buildKey(id)
        if (data == null) {
            redis.del(buildKey(id))
        } else {
            redis.set(key, data.hex)
            redis.expire(key, ttlSeconds)
        }
    }
}

by  ICOPY.SITE