mirror of
https://github.com/jlengrand/ktor.git
synced 2026-03-10 08:31:20 +00:00
Reduce required JDK version for DefaultHeaders feature
This commit is contained in:
@@ -507,6 +507,7 @@ public final class io/ktor/util/date/DateJvmKt {
|
||||
public static final fun GMTDate (IIIILio/ktor/util/date/Month;I)Lio/ktor/util/date/GMTDate;
|
||||
public static final fun GMTDate (Ljava/lang/Long;)Lio/ktor/util/date/GMTDate;
|
||||
public static synthetic fun GMTDate$default (Ljava/lang/Long;ILjava/lang/Object;)Lio/ktor/util/date/GMTDate;
|
||||
public static final fun toDate (Ljava/util/Calendar;Ljava/lang/Long;)Lio/ktor/util/date/GMTDate;
|
||||
public static final fun toJvmDate (Lio/ktor/util/date/GMTDate;)Ljava/util/Date;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,9 @@ import io.ktor.application.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.response.*
|
||||
import io.ktor.util.*
|
||||
import java.time.*
|
||||
import io.ktor.util.date.*
|
||||
import kotlinx.atomicfu.*
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Adds standard HTTP headers `Date` and `Server` and provides ability to specify other headers
|
||||
@@ -16,12 +18,10 @@ import java.time.*
|
||||
*/
|
||||
class DefaultHeaders(config: Configuration) {
|
||||
private val headers = config.headers.build()
|
||||
|
||||
private val UTC = ZoneId.of("UTC")!! // it is very important to get it like that
|
||||
private val zoneUTCRules = UTC.rules
|
||||
private val clock = config.clock
|
||||
|
||||
private var cachedDateTimeStamp: Long = 0L
|
||||
@Volatile private var cachedDateText: String = ZonedDateTime.now(GreenwichMeanTime).toHttpDateString()
|
||||
private val cachedDateText = atomic("")
|
||||
|
||||
/**
|
||||
* Configuration for [DefaultHeaders] feature.
|
||||
@@ -36,6 +36,12 @@ class DefaultHeaders(config: Configuration) {
|
||||
* Adds standard header property [name] with the specified [value].
|
||||
*/
|
||||
fun header(name: String, value: String) = headers.append(name, value)
|
||||
|
||||
/**
|
||||
* Provides time source. Useful for testing.
|
||||
*/
|
||||
@InternalAPI
|
||||
var clock: () -> Long = { System.currentTimeMillis() }
|
||||
}
|
||||
|
||||
private fun intercept(call: ApplicationCall) {
|
||||
@@ -43,31 +49,33 @@ class DefaultHeaders(config: Configuration) {
|
||||
headers.forEach { name, value -> value.forEach { call.response.header(name, it) } }
|
||||
}
|
||||
|
||||
// ZonedDateTime.now allocates too much so we reimplement it
|
||||
private fun now(): ZonedDateTime {
|
||||
// we shouldn't use ZoneOffset.UTC here otherwise we get to many allocations inside of Java Time implementation
|
||||
val instant = Clock.system(UTC).instant()
|
||||
val offset = zoneUTCRules.getOffset(instant)
|
||||
val ldt = LocalDateTime.ofEpochSecond(instant.epochSecond, instant.nano, offset)
|
||||
|
||||
return ZonedDateTime.ofInstant(ldt, offset, UTC)
|
||||
}
|
||||
|
||||
private fun appendDateHeader(call: ApplicationCall) {
|
||||
val captureCached = cachedDateTimeStamp
|
||||
val currentTimeStamp = System.currentTimeMillis()
|
||||
if (captureCached + 1000 < currentTimeStamp) {
|
||||
val currentTimeStamp = clock()
|
||||
if (captureCached + DATE_CACHE_TIMEOUT_MILLISECONDS <= currentTimeStamp) {
|
||||
cachedDateTimeStamp = currentTimeStamp
|
||||
cachedDateText = now().toHttpDateString()
|
||||
cachedDateText.value = now(currentTimeStamp).toHttpDate()
|
||||
}
|
||||
call.response.header("Date", cachedDateText)
|
||||
call.response.header(HttpHeaders.Date, cachedDateText.value)
|
||||
}
|
||||
|
||||
private fun now(time: Long): GMTDate {
|
||||
return calendar.get().toDate(time)
|
||||
}
|
||||
|
||||
/**
|
||||
* Installable feature for [DefaultHeaders].
|
||||
*/
|
||||
companion object Feature : ApplicationFeature<Application, Configuration, DefaultHeaders> {
|
||||
private val GreenwichMeanTime: ZoneId = ZoneId.of("GMT")
|
||||
private const val DATE_CACHE_TIMEOUT_MILLISECONDS = 1000
|
||||
|
||||
private val GMT_TIMEZONE = TimeZone.getTimeZone("GMT")!!
|
||||
|
||||
private val calendar = object : ThreadLocal<Calendar>() {
|
||||
override fun initialValue(): Calendar {
|
||||
return Calendar.getInstance(GMT_TIMEZONE, Locale.ROOT)
|
||||
}
|
||||
}
|
||||
|
||||
override val key = AttributeKey<DefaultHeaders>("Default Headers")
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
*/
|
||||
|
||||
package io.ktor.tests.server.features
|
||||
|
||||
import io.ktor.application.*
|
||||
import io.ktor.features.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.response.*
|
||||
import io.ktor.server.testing.*
|
||||
import kotlin.test.*
|
||||
|
||||
class DefaultHeadersTest {
|
||||
@Test
|
||||
fun testDate(): Unit = withTestApplication {
|
||||
var now = 1569882841014
|
||||
application.install(DefaultHeaders) {
|
||||
clock = { now }
|
||||
}
|
||||
|
||||
application.intercept(ApplicationCallPipeline.Call) {
|
||||
call.respondText("OK")
|
||||
}
|
||||
|
||||
handleRequest(HttpMethod.Get, "/").let { call ->
|
||||
assertEquals("Mon, 30 Sep 2019 22:34:01 GMT", call.response.headers[HttpHeaders.Date])
|
||||
}
|
||||
|
||||
now += 999
|
||||
|
||||
handleRequest(HttpMethod.Get, "/").let { call ->
|
||||
assertEquals("Mon, 30 Sep 2019 22:34:01 GMT", call.response.headers[HttpHeaders.Date])
|
||||
}
|
||||
|
||||
now++
|
||||
|
||||
handleRequest(HttpMethod.Get, "/").let { call ->
|
||||
assertEquals("Mon, 30 Sep 2019 22:34:02 GMT", call.response.headers[HttpHeaders.Date])
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCustomHeader(): Unit = withTestApplication {
|
||||
application.install(DefaultHeaders) {
|
||||
header("X-Test", "123")
|
||||
}
|
||||
|
||||
handleRequest(HttpMethod.Get, "/").let { call ->
|
||||
assertEquals("123", call.response.headers["X-Test"])
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDefaultServerHeader(): Unit = withTestApplication {
|
||||
application.install(DefaultHeaders) {
|
||||
}
|
||||
|
||||
handleRequest(HttpMethod.Get, "/").let { call ->
|
||||
assertTrue { "ktor" in call.response.headers[HttpHeaders.Server]!! }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCustomServerHeader(): Unit = withTestApplication {
|
||||
application.install(DefaultHeaders) {
|
||||
header(HttpHeaders.Server, "MyServer")
|
||||
}
|
||||
|
||||
handleRequest(HttpMethod.Get, "/").let { call ->
|
||||
assertEquals("MyServer", call.response.headers[HttpHeaders.Server])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
package io.ktor.util.date
|
||||
|
||||
import io.ktor.util.*
|
||||
import java.util.*
|
||||
|
||||
private val GMT_TIMEZONE = TimeZone.getTimeZone("GMT")
|
||||
@@ -31,8 +32,8 @@ actual fun GMTDate(
|
||||
set(Calendar.MILLISECOND, 0)
|
||||
}.toDate(timestamp = null)
|
||||
|
||||
|
||||
private fun Calendar.toDate(timestamp: Long?): GMTDate {
|
||||
@InternalAPI
|
||||
fun Calendar.toDate(timestamp: Long?): GMTDate {
|
||||
timestamp?.let { timeInMillis = it }
|
||||
|
||||
val seconds = get(Calendar.SECOND)
|
||||
|
||||
Reference in New Issue
Block a user