handle etags in WebMapTileServer
This commit is contained in:
parent
f0590b9df4
commit
7c77083c89
1 changed files with 109 additions and 4 deletions
|
|
@ -1,10 +1,16 @@
|
||||||
package dev.adamko.kafkatorio.processor.tileserver
|
package dev.adamko.kafkatorio.processor.tileserver
|
||||||
|
|
||||||
import dev.adamko.kafkatorio.processor.config.ApplicationProperties
|
import dev.adamko.kafkatorio.processor.config.ApplicationProperties
|
||||||
|
import java.security.MessageDigest
|
||||||
import kotlin.io.path.absolutePathString
|
import kotlin.io.path.absolutePathString
|
||||||
|
import org.http4k.core.Filter
|
||||||
import org.http4k.core.HttpHandler
|
import org.http4k.core.HttpHandler
|
||||||
|
import org.http4k.core.MemoryBody
|
||||||
|
import org.http4k.core.Method.GET
|
||||||
|
import org.http4k.core.Request
|
||||||
|
import org.http4k.core.Response
|
||||||
|
import org.http4k.core.Status
|
||||||
import org.http4k.core.then
|
import org.http4k.core.then
|
||||||
import org.http4k.filter.CachingFilters
|
|
||||||
import org.http4k.routing.ResourceLoader
|
import org.http4k.routing.ResourceLoader
|
||||||
import org.http4k.routing.RoutingHttpHandler
|
import org.http4k.routing.RoutingHttpHandler
|
||||||
import org.http4k.routing.bind
|
import org.http4k.routing.bind
|
||||||
|
|
@ -12,15 +18,114 @@ import org.http4k.routing.routes
|
||||||
import org.http4k.routing.static
|
import org.http4k.routing.static
|
||||||
|
|
||||||
internal class WebMapTileServer(
|
internal class WebMapTileServer(
|
||||||
appProps: ApplicationProperties = ApplicationProperties()
|
private val appProps: ApplicationProperties = ApplicationProperties()
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
private val tilesLoader = ResourceLoader.Directory(appProps.webmapTileDir.absolutePathString())
|
||||||
|
|
||||||
|
private val filters = listOf(
|
||||||
|
ETagChecker()(),
|
||||||
|
).reduce(Filter::then)
|
||||||
|
|
||||||
private val routes: RoutingHttpHandler = routes(
|
private val routes: RoutingHttpHandler = routes(
|
||||||
"/tiles" bind CachingFilters.Response.NoCache()
|
"/tiles" bind GET to
|
||||||
.then(static(ResourceLoader.Directory(appProps.webmapTileDir.absolutePathString())))
|
static(tilesLoader).withFilter(filters)
|
||||||
)
|
)
|
||||||
|
|
||||||
fun build(): HttpHandler {
|
fun build(): HttpHandler {
|
||||||
return routes
|
return routes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
inner class ETagChecker
|
||||||
|
// : CoroutineScope
|
||||||
|
{
|
||||||
|
|
||||||
|
// private val tagState: MutableStateFlow<MutableMap<String, String>> =
|
||||||
|
// MutableStateFlow(mutableMapOf())
|
||||||
|
//
|
||||||
|
// override val coroutineContext: CoroutineContext =
|
||||||
|
// CoroutineName("ETagChecker") +
|
||||||
|
// Dispatchers.Unconfined +
|
||||||
|
// KafkatorioTopology.rootJob
|
||||||
|
|
||||||
|
// init {
|
||||||
|
// launch {
|
||||||
|
// appProps.webmapTileDir
|
||||||
|
// .fileEventsFlow()
|
||||||
|
// .filterIsInstance<KWatchEvent.PathEvent>()
|
||||||
|
// .filter { it.path.isRegularFile() }
|
||||||
|
//// .filter { it.path.extension == "png" }
|
||||||
|
// .runningFold(mutableMapOf<String, String>()) { tags, event ->
|
||||||
|
//// println("[WebMapTileServer] handling file update ${event.path}")
|
||||||
|
//
|
||||||
|
// val relativePath = event.path
|
||||||
|
// .relativeTo(appProps.webmapTileDir)
|
||||||
|
// .invariantSeparatorsPathString
|
||||||
|
//
|
||||||
|
// when (event) {
|
||||||
|
// is KWatchEvent.Deleted -> tags.remove(relativePath)
|
||||||
|
// is KWatchEvent.Created,
|
||||||
|
// is KWatchEvent.Modified -> {
|
||||||
|
// val md5 = event.path.md5()
|
||||||
|
// if (md5 != null) {
|
||||||
|
// tags[relativePath] = md5
|
||||||
|
// } else {
|
||||||
|
// println("null md5 for ${event.path}")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// tags
|
||||||
|
// }.cancellable()
|
||||||
|
// .collect(tagState)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
private val Request.etagKey: String get() = uri.path.removePrefix("/tiles/")
|
||||||
|
|
||||||
|
|
||||||
|
operator fun invoke() = Filter { next ->
|
||||||
|
{ request ->
|
||||||
|
val requestedETag = request.header("If-None-Match")
|
||||||
|
|
||||||
|
var response = next(request)
|
||||||
|
val responseBytes = response.body.stream.readAllBytes()
|
||||||
|
response.body.stream.close()
|
||||||
|
val currentETag = responseBytes.md5()
|
||||||
|
|
||||||
|
response = response.body(MemoryBody(responseBytes))
|
||||||
|
|
||||||
|
when {
|
||||||
|
currentETag == null -> {
|
||||||
|
// println("[${request.etagKey}] currentETag is null (requested: $requestedETag), executing request")
|
||||||
|
response
|
||||||
|
.header("Cache-Control", "no-cache")
|
||||||
|
}
|
||||||
|
requestedETag == currentETag -> {
|
||||||
|
println("[${request.etagKey}] currentETag == requested, returning 302 $requestedETag")
|
||||||
|
Response(Status.NOT_MODIFIED)
|
||||||
|
.header("Cache-Control", "no-cache")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
println("[${request.etagKey}] currentETag != requested, executing request and adding currentETag:$currentETag (requested:$requestedETag)")
|
||||||
|
response
|
||||||
|
.header("ETag", currentETag)
|
||||||
|
.header("Cache-Control", "public, max-age=1, stale-while-revalidate=60, stale-if-error=300")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ByteArray.md5(): String? {
|
||||||
|
return if (isEmpty()) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
MessageDigest.getInstance("MD5")
|
||||||
|
.digest(this)
|
||||||
|
.joinToString("") { "%02x".format(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue