Storages
Ktor allows you to store session data on the server and pass only a session ID between the server and the client. In this case, you can choose where to keep the payload on the server.
Built-in storages
The following storage types are available out-of-the-box:
SessionStorageMemory enables storing a session's content in memory. This storage keeps data while the server is running and discards information once the server stops.
directorySessionStorage can be used to store a session's data in a file under the specified directory.
To create the required storage type, pass it as a second parameter to the cookie or header method. For example, you can store cookies in the server memory as follows:
install(Sessions) {
cookie<SampleSession>("SAMPLE_SESSION", storage = SessionStorageMemory())
}
To store cookies in a file under the .sessions
directory, create the directorySessionStorage
in this way:
import java.io.File
// ...
install(Sessions) {
cookie<SampleSession>("SAMPLE_SESSION", storage = directorySessionStorage(File(".sessions")))
}
Custom storage
Ktor provides the SessionStorage interface that allows you to implement a custom storage.
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
}
All three functions are suspending and use ByteWriteChannel and ByteReadChannel to read and write data from/to an asynchronous channel. The example below shows how to implement the SessionStorage
interface to store session data in a 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(CoroutineScope(Dispatchers.IO).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()
}
Last modified: 28 May 2021