diff --git a/build.gradle.kts b/build.gradle.kts index cb32f82..6ff0eb6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,6 +15,7 @@ repositories { dependencies { // Ktor dependencies + implementation(libs.ktor.server.cachingHeaders) implementation(libs.ktor.server.conditionalHeaders) implementation(libs.ktor.server.contentNegotiation) implementation(libs.ktor.server.core) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ed94d2f..b3fd2c7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,6 +21,7 @@ ktor-client-contentNegotiation = { module = "io.ktor:ktor-client-content-negotia ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" } ktor-serializationJson = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } +ktor-server-cachingHeaders = { module = "io.ktor:ktor-server-caching-headers", version.ref = "ktor" } ktor-server-conditionalHeaders = { module = "io.ktor:ktor-server-conditional-headers", version.ref = "ktor" } ktor-server-contentNegotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" } ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" } diff --git a/src/main/kotlin/server/plugins/CachingHeaders.kt b/src/main/kotlin/server/plugins/CachingHeaders.kt new file mode 100644 index 0000000..ca6e9e7 --- /dev/null +++ b/src/main/kotlin/server/plugins/CachingHeaders.kt @@ -0,0 +1,57 @@ +package server.plugins + +import io.ktor.http.CacheControl +import io.ktor.http.HttpHeaders +import io.ktor.http.content.CachingOptions +import io.ktor.server.http.content.CachingOptions +import io.ktor.server.plugins.cachingheaders.CachingHeadersConfig +import java.time.ZonedDateTime +import server.response.FileSource +import server.response.FileUUID +import server.response.ResourceId +import server.response.ResourceType + +/** + * Configures the maximum age in seconds that file requests should be cached for. + * + * Default: `24 hours` + */ +private const val FILE_MAX_AGE = 24 * 60 * 60 + +/** + * Configures the maximum age in seconds that data requests should be cached for. + * + * Default: `10 minutes` + */ +private const val DATA_MAX_AGE = 10 * 60 + +/** + * Generates [CachingOptions] for caching file requests. + * + * @param maxAge The maximum age in seconds that file requests should be cached for. + */ +private fun cachingOptions(maxAge: Int): CachingOptions { + return CachingOptions( + cacheControl = CacheControl.MaxAge(maxAge), + expires = ZonedDateTime.now().plusSeconds(maxAge.toLong()) + ) +} + +fun CachingHeadersConfig.configure() { + options { call, outgoingContent -> + val headers = call.response.headers + + val fileUUID = headers[HttpHeaders.FileUUID] + val fileSource = headers[HttpHeaders.FileSource] + val resourceType = headers[HttpHeaders.ResourceType] + val resourceId = headers[HttpHeaders.ResourceId]?.toIntOrNull() + + if (fileUUID != null && fileSource != null) { + return@options cachingOptions(FILE_MAX_AGE) + } else if (resourceType != null && resourceId != null) { + return@options cachingOptions(DATA_MAX_AGE) + } + + null + } +} diff --git a/src/main/kotlin/server/plugins/Plugins.kt b/src/main/kotlin/server/plugins/Plugins.kt index cb45d2e..53e02d5 100644 --- a/src/main/kotlin/server/plugins/Plugins.kt +++ b/src/main/kotlin/server/plugins/Plugins.kt @@ -4,6 +4,7 @@ import database.serialization.Json import io.ktor.serialization.kotlinx.json.json import io.ktor.server.application.Application import io.ktor.server.application.install +import io.ktor.server.plugins.cachingheaders.CachingHeaders import io.ktor.server.plugins.conditionalheaders.ConditionalHeaders import io.ktor.server.plugins.contentnegotiation.ContentNegotiation import io.ktor.server.plugins.statuspages.StatusPages @@ -17,6 +18,7 @@ import io.ktor.server.plugins.statuspages.StatusPages * @receiver The application on which this method is called. */ fun Application.installPlugins() { + install(CachingHeaders) { configure() } install(ConditionalHeaders) { configure() } install(ContentNegotiation) { json(Json) } install(StatusPages) { configureStatusPages() }