diff --git a/build.gradle.kts b/build.gradle.kts index 7f301b5..8d875c6 100755 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ allprojects { group = "com.ucasoft.ktor" - version = "0.63.3" + version = "0.70.2" repositories { mavenCentral() diff --git a/buildSrc/src/main/kotlin/Dependensies.kt b/buildSrc/src/main/kotlin/Dependensies.kt index 013e615..4f22628 100755 --- a/buildSrc/src/main/kotlin/Dependensies.kt +++ b/buildSrc/src/main/kotlin/Dependensies.kt @@ -1,7 +1,7 @@ import org.gradle.api.Project const val ktorVersion = "3.4.2" -const val kotestVersion = "6.1.10" +const val kotestVersion = "6.1.11" fun Project.ktor(module: String) = "io.ktor:ktor-$module:$ktorVersion" diff --git a/ktor-simple-cache/README.md b/ktor-simple-cache/README.md index 353de89..7748d46 100755 --- a/ktor-simple-cache/README.md +++ b/ktor-simple-cache/README.md @@ -1,7 +1,7 @@ # Ktor Simple Cache Base solution which provides the plugin implementation and abstract class for cache providers. -[![Maven Central with version prefix filter](https://img.shields.io/maven-central/v/com.ucasoft.ktor/ktor-simple-cache/0.63.3?color=blue)](https://search.maven.org/artifact/com.ucasoft.ktor/ktor-simple-cache/0.63.3/jar) +[![Maven Central with version prefix filter](https://img.shields.io/maven-central/v/com.ucasoft.ktor/ktor-simple-cache/0.70.2?color=blue)](https://search.maven.org/artifact/com.ucasoft.ktor/ktor-simple-cache/0.70.2/jar) ## Setup ### Gradle ```kotlin @@ -9,7 +9,7 @@ repositories { mavenCentral() } -implementation("com.ucasoft.ktor:ktor-simple-cache:0.63.3") +implementation("com.ucasoft.ktor:ktor-simple-cache:0.70.2") ``` ## Usage ```kotlin diff --git a/ktor-simple-cache/build.gradle.kts b/ktor-simple-cache/build.gradle.kts index b8f4e7a..47d594c 100755 --- a/ktor-simple-cache/build.gradle.kts +++ b/ktor-simple-cache/build.gradle.kts @@ -12,12 +12,14 @@ kotlin { useJUnitPlatform() } } + linuxArm64() linuxX64() - macosX64() + macosArm64() + mingwX64() sourceSets { val commonMain by getting { dependencies { - implementation(ktorServer("core")) + api(ktorServer("core")) } kotlin.srcDir("src/main/kotlin") } diff --git a/ktor-simple-memory-cache/README.md b/ktor-simple-memory-cache/README.md index 1ef8546..67ab013 100755 --- a/ktor-simple-memory-cache/README.md +++ b/ktor-simple-memory-cache/README.md @@ -1,7 +1,7 @@ # Ktor Simple Memory Cache Memory cache provider for Ktor Simple Cache plugin -[![Maven Central with version prefix filter](https://img.shields.io/maven-central/v/com.ucasoft.ktor/ktor-simple-memory-cache/0.63.3?color=blue)](https://search.maven.org/artifact/com.ucasoft.ktor/ktor-simple-memory-cache/0.63.3/jar) +[![Maven Central with version prefix filter](https://img.shields.io/maven-central/v/com.ucasoft.ktor/ktor-simple-memory-cache/0.70.2?color=blue)](https://search.maven.org/artifact/com.ucasoft.ktor/ktor-simple-memory-cache/0.70.2/jar) ## Setup ### Gradle ```kotlin @@ -9,7 +9,7 @@ repositories { mavenCentral() } -implementation("com.ucasoft.ktor:ktor-simple-memory-cache:0.63.3") +implementation("com.ucasoft.ktor:ktor-simple-memory-cache:0.70.2") ``` ## Usage ```kotlin diff --git a/ktor-simple-memory-cache/build.gradle.kts b/ktor-simple-memory-cache/build.gradle.kts index cd087e4..35b728c 100755 --- a/ktor-simple-memory-cache/build.gradle.kts +++ b/ktor-simple-memory-cache/build.gradle.kts @@ -8,8 +8,10 @@ plugins { kotlin { jvmToolchain(11) jvm() + linuxArm64() linuxX64() - macosX64() + macosArm64() + mingwX64() sourceSets { val commonMain by getting { dependencies { diff --git a/ktor-simple-redis-cache/README.md b/ktor-simple-redis-cache/README.md index 9c16649..123846c 100755 --- a/ktor-simple-redis-cache/README.md +++ b/ktor-simple-redis-cache/README.md @@ -1,7 +1,7 @@ # Ktor Simple Redis Cache Redis cache provider for Ktor Simple Cache plugin -[![Maven Central with version prefix filter](https://img.shields.io/maven-central/v/com.ucasoft.ktor/ktor-simple-redis-cache/0.63.3?color=blue)](https://search.maven.org/artifact/com.ucasoft.ktor/ktor-simple-redis-cache/0.63.3/jar) +[![Maven Central with version prefix filter](https://img.shields.io/maven-central/v/com.ucasoft.ktor/ktor-simple-redis-cache/0.70.2?color=blue)](https://search.maven.org/artifact/com.ucasoft.ktor/ktor-simple-redis-cache/0.70.2/jar) ## Setup ### Gradle ```kotlin @@ -9,7 +9,7 @@ repositories { mavenCentral() } -implementation("com.ucasoft.ktor:ktor-simple-redis-cache:0.63.3") +implementation("com.ucasoft.ktor:ktor-simple-redis-cache:0.70.2") ``` ## Usage ```kotlin diff --git a/ktor-simple-redis-cache/build.gradle.kts b/ktor-simple-redis-cache/build.gradle.kts index 3f72631..7482438 100755 --- a/ktor-simple-redis-cache/build.gradle.kts +++ b/ktor-simple-redis-cache/build.gradle.kts @@ -12,12 +12,16 @@ kotlin { useJUnitPlatform() } } + linuxArm64() + linuxX64() + macosArm64() + mingwX64() sourceSets { - val jvmMain by getting { + val commonMain by getting { dependencies { implementation(project(":ktor-simple-cache")) - implementation("redis.clients:jedis:7.4.0") - implementation("com.google.code.gson:gson:2.13.2") + implementation("io.github.domgew:kedis:0.0.12") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.11.0") } kotlin.srcDir("src/main/kotlin") } diff --git a/ktor-simple-redis-cache/src/main/kotlin/com/ucasoft/ktor/simpleRedisCache/SimpleRedisCacheProvider.kt b/ktor-simple-redis-cache/src/main/kotlin/com/ucasoft/ktor/simpleRedisCache/SimpleRedisCacheProvider.kt index 8e41b57..391e097 100755 --- a/ktor-simple-redis-cache/src/main/kotlin/com/ucasoft/ktor/simpleRedisCache/SimpleRedisCacheProvider.kt +++ b/ktor-simple-redis-cache/src/main/kotlin/com/ucasoft/ktor/simpleRedisCache/SimpleRedisCacheProvider.kt @@ -1,24 +1,40 @@ package com.ucasoft.ktor.simpleRedisCache -import com.google.gson.Gson import com.ucasoft.ktor.simpleCache.SimpleCacheConfig import com.ucasoft.ktor.simpleCache.SimpleCacheProvider -import redis.clients.jedis.DefaultJedisClientConfig -import redis.clients.jedis.RedisClient +import io.github.domgew.kedis.KedisClient +import io.github.domgew.kedis.arguments.value.SetOptions +import io.github.domgew.kedis.commands.KedisValueCommands +import io.ktor.http.ContentType +import io.ktor.http.HttpStatusCode +import io.ktor.http.content.OutgoingContent +import io.ktor.utils.io.ByteChannel +import io.ktor.utils.io.readRemaining +import kotlinx.io.readByteArray +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds class SimpleRedisCacheProvider(config: Config) : SimpleCacheProvider(config) { - private val jedis = RedisClient.builder().hostAndPort(config.host, config.port).clientConfig( - DefaultJedisClientConfig.builder().ssl(config.ssl).build() - ).build() + private val kedis = KedisClient.builder { + hostAndPort(config.host, config.port) + connectTimeout = 250.milliseconds + } override suspend fun getCache(key: String): Any? = - if (jedis.exists(key)) SimpleRedisCacheObject.fromCache(jedis[key]) else null + kedis.execute(KedisValueCommands.get(key))?.let { Json.decodeFromString(it).toOutgoingContent() } override suspend fun setCache(key: String, content: Any, invalidateAt: Duration?) { val expired = (invalidateAt ?: this.invalidateAt).inWholeMilliseconds - jedis.psetex(key, expired, SimpleRedisCacheObject.fromObject(content).toString()) + val outgoing = content as OutgoingContent + kedis.execute(KedisValueCommands.set(key, Json.encodeToString(CachedResponse( + bytes = outgoing.toByteArray(), + contentType = outgoing.contentType?.toString(), + status = outgoing.status?.value, + contentLength = outgoing.contentLength + )), SetOptions(expire = SetOptions.ExpireOption.ExpiresInMilliseconds(expired)))) } class Config internal constructor() : SimpleCacheProvider.Config() { @@ -26,23 +42,44 @@ class SimpleRedisCacheProvider(config: Config) : SimpleCacheProvider(config) { var host = "localhost" var port = 6379 - - var ssl = false } } -private class SimpleRedisCacheObject(val type: String, val content: String) { +@Serializable +private data class CachedResponse( + val bytes: ByteArray, + val contentType: String?, + val status: Int?, + val contentLength: Long? +) { - override fun toString() = "$type%#%$content" + fun toOutgoingContent() = object : OutgoingContent.ByteArrayContent() { + override fun bytes() = bytes + override val contentType = this@CachedResponse.contentType?.let { ContentType.parse(it) } + override val status = this@CachedResponse.status?.let { HttpStatusCode.fromValue(it) } + override val contentLength = this@CachedResponse.contentLength + } - companion object { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false - fun fromObject(`object`: Any) = SimpleRedisCacheObject(`object`::class.java.name, Gson().toJson(`object`)) + other as CachedResponse - fun fromCache(cache: String): Any { - val data = cache.split("%#%") - return Gson().fromJson(data.last(), Class.forName(data.first())) - } + if (status != other.status) return false + if (contentLength != other.contentLength) return false + if (!bytes.contentEquals(other.bytes)) return false + if (contentType != other.contentType) return false + + return true + } + + override fun hashCode(): Int { + var result = status ?: 0 + result = 31 * result + (contentLength?.hashCode() ?: 0) + result = 31 * result + bytes.contentHashCode() + result = 31 * result + (contentType?.hashCode() ?: 0) + return result } } @@ -50,4 +87,17 @@ fun SimpleCacheConfig.redisCache( configure : SimpleRedisCacheProvider.Config.() -> Unit ){ provider = SimpleRedisCacheProvider(SimpleRedisCacheProvider.Config().apply(configure)) +} + +private suspend fun OutgoingContent.toByteArray(): ByteArray = when (this) { + is OutgoingContent.ByteArrayContent -> bytes() + is OutgoingContent.NoContent -> byteArrayOf() + is OutgoingContent.ReadChannelContent -> readFrom().readRemaining().readByteArray() + is OutgoingContent.WriteChannelContent -> { + val channel = ByteChannel(autoFlush = true) + writeTo(channel) + channel.close() + channel.readRemaining().readByteArray() + } + else -> byteArrayOf() } \ No newline at end of file diff --git a/ktor-simple-redis-cache/src/test/kotlin/com/ucasoft/ktor/simpleRedisCache/RedisCacheTests.kt b/ktor-simple-redis-cache/src/test/kotlin/com/ucasoft/ktor/simpleRedisCache/RedisCacheTests.kt index aa98874..7c1abf1 100644 --- a/ktor-simple-redis-cache/src/test/kotlin/com/ucasoft/ktor/simpleRedisCache/RedisCacheTests.kt +++ b/ktor-simple-redis-cache/src/test/kotlin/com/ucasoft/ktor/simpleRedisCache/RedisCacheTests.kt @@ -10,6 +10,7 @@ import io.kotest.matchers.shouldNotBe import io.ktor.client.call.* import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.request.* +import io.ktor.client.statement.* import io.ktor.http.* import io.ktor.serialization.kotlinx.json.* import io.ktor.server.application.* @@ -100,6 +101,30 @@ internal class RedisCacheTests { } } + @Test + fun `test content type`() { + testApplication { + install(SimpleCache) { + redisCache { + invalidateAt = 10.seconds + this.host = redisContainer.host + this.port = redisContainer.firstMappedPort + } + } + + application(Application::testApplication) + + val textResponse1 = client.get("/text") + textResponse1.status.shouldBe(HttpStatusCode.OK) + textResponse1.contentType()?.withoutParameters().shouldBe(ContentType.Text.Plain) + + val textResponse2 = client.get("/text") + textResponse2.status shouldBe HttpStatusCode.OK + textResponse2.contentType()?.withoutParameters().shouldBe(ContentType.Text.Plain) + textResponse2.bodyAsText().shouldBe(textResponse1.bodyAsText()) + } + } + companion object { @Container diff --git a/ktor-simple-redis-cache/src/test/kotlin/com/ucasoft/ktor/simpleRedisCache/TestApplication.kt b/ktor-simple-redis-cache/src/test/kotlin/com/ucasoft/ktor/simpleRedisCache/TestApplication.kt index 8d41131..9fcd6a5 100644 --- a/ktor-simple-redis-cache/src/test/kotlin/com/ucasoft/ktor/simpleRedisCache/TestApplication.kt +++ b/ktor-simple-redis-cache/src/test/kotlin/com/ucasoft/ktor/simpleRedisCache/TestApplication.kt @@ -3,6 +3,7 @@ package com.ucasoft.ktor.simpleRedisCache import com.ucasoft.ktor.simpleCache.cacheOutput import io.ktor.serialization.kotlinx.json.* import io.ktor.server.application.* +import io.ktor.http.* import io.ktor.server.plugins.contentnegotiation.* import io.ktor.server.response.* import io.ktor.server.routing.* @@ -30,5 +31,10 @@ fun Application.testApplication() { call.respond(TestResponse()) } } + cacheOutput { + get("text") { + call.respondText("Hello World with ${Random.nextInt()}", ContentType.Text.Plain) + } + } } } \ No newline at end of file