import com.nixxcode.jvmbrotli.common.BrotliLoader
import com.nixxcode.jvmbrotli.enc.BrotliOutputStream
import com.nixxcode.jvmbrotli.enc.Encoder
-
+import java.security.MessageDigest
+import java.util.*
import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executors
import java.util.zip.GZIPOutputStream
doLast {
val pool = Executors.newWorkStealingPool()
+
val encoderParams = if (BrotliLoader.isBrotliAvailable()) Encoder.Parameters().setQuality(8) else null
- val resourceTree = fileTree(mapOf("dir" to outputs.files.asPath + "/static/", "exclude" to listOf("*.gz", "*.br")))
+ val base64 = Base64.getUrlEncoder()
+ val hashDigest = ThreadLocal.withInitial { MessageDigest.getInstance("SHA-256") }
+
+ val resourceTree = fileTree(mapOf("dir" to outputs.files.asPath + "/static/", "exclude" to listOf("*.gz", "*.br", "*.sha256")))
val countDownLatch = CountDownLatch(resourceTree.count())
for (file in resourceTree) {
pool.execute {
val bytes = file.readBytes()
+ val hashFile = File("${file.absolutePath}.sha256").bufferedWriter()
+ hashFile.write(base64.encodeToString(hashDigest.get().digest(bytes)).trimEnd('='))
+ hashFile.close()
+
val result = File("${file.absolutePath}.gz").outputStream()
val gzipStream = GZIPOutputStream(result)
gzipStream.write(bytes)
import io.ktor.routing.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
+import io.ktor.util.*
import io.ktor.websocket.*
import org.slf4j.event.Level
import starshipfights.auth.AuthProvider
object ResourceLoader {
fun getResource(resource: String): InputStream? = javaClass.getResourceAsStream(resource)
+
+ val SHA256AttributeKey = AttributeKey<String>("SHA256Hash")
}
fun main() {
}
}
+ install(ConditionalHeaders) {
+ version { outgoingContent ->
+ outgoingContent.getProperty(ResourceLoader.SHA256AttributeKey)?.let { hash ->
+ listOf(EntityTagVersion(hash))
+ }.orEmpty()
+ }
+ }
+
install(StatusPages) {
status(HttpStatusCode.NotFound) {
call.respondHtml(HttpStatusCode.NotFound, call.error404())
val staticContentPath = call.parameters.getAll("static-content")?.joinToString("/") ?: return@get
val contentPath = "/static/$staticContentPath"
+ val hashContentPath = "$contentPath.sha256"
+ val sha256Hash = ResourceLoader.getResource(hashContentPath)?.reader()?.readText()
+ val configureContent: OutgoingContent.() -> Unit = { setProperty(ResourceLoader.SHA256AttributeKey, sha256Hash) }
+
val brContentPath = "$contentPath.br"
val gzContentPath = "$contentPath.gz"
call.response.header(HttpHeaders.ContentEncoding, CompressedFileType.BROTLI.encoding)
- call.respondBytes(brContent.readBytes(), contentType)
+ call.respondBytes(brContent.readBytes(), contentType, configure = configureContent)
return@get
}
call.response.header(HttpHeaders.ContentEncoding, CompressedFileType.GZIP.encoding)
- call.respondBytes(gzContent.readBytes(), contentType)
+ call.respondBytes(gzContent.readBytes(), contentType, configure = configureContent)
return@get
}
}
- ResourceLoader.getResource(contentPath)?.let { call.respondBytes(it.readBytes(), contentType) }
+ ResourceLoader.getResource(contentPath)?.let { call.respondBytes(it.readBytes(), contentType, configure = configureContent) }
}
}
}