您可以将cookie或自定义HTTP标头用于会话. 代码大致相同,但是您必须调用cookie
或header
方法,具体取决于要将会话信息发送到的位置.
根据消费者的不同,您可能希望使用cookie或标头传输sessionId或有效负载. 例如,对于网站,通常将使用cookie,而对于API,则可能需要使用标头.
Sessions.Configuration提供了两种方法cookie
和header
选择如何传输会话:
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 = "/"
/* ... */
}
}
Header方法适用于API,既可以在JavaScript XHR请求中使用,也可以从服务器端请求. 对于API客户端来说,读取和生成自定义标头通常比处理cookie更容易.
install(Sessions) {
header<SampleSession>("HTTP_HEADER_NAME") { /* ... */ }
}
application.install(Sessions) {
header<MySession>("SESSION")
}
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
和被设计成完全异步和使用ByteWriteChannel
和ByteReadChannel
从kotlinx.coroutines.io
,对于阅读和异步通道写作提供的API.
在实现中,您必须调用回调函数,以提供必须提供的ByteWriteChannel和ByteReadChannel:打开和关闭它们是您的责任. 您可以在其库文档中阅读有关ByteWriteChannel
和ByteReadChannel
更多信息. 如果只需要加载或存储ByteArray,则可以使用此代码段,它提供了简化的会话存储:
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)
}
}
}