-
Notifications
You must be signed in to change notification settings - Fork 1k
/
DefaultHeaders.kt
109 lines (95 loc) · 3.4 KB
/
DefaultHeaders.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
/*
* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/
package io.ktor.server.plugins.defaultheaders
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.plugins.defaultheaders.DefaultHeadersConfig.*
import io.ktor.server.response.*
import io.ktor.util.*
import io.ktor.util.date.*
import kotlinx.atomicfu.*
import java.util.*
/**
* A configuration for the [DefaultHeaders] plugin.
* Allows you to configure additional default headers.
*/
@KtorDsl
public class DefaultHeadersConfig {
/**
* Provides a builder to append any custom headers to be sent with each request
*/
internal val headers = HeadersBuilder()
/**
* Adds a standard header with the specified [name] and [value].
*/
public fun header(name: String, value: String): Unit = headers.append(name, value)
/**
* Provides a time source. Useful for testing.
*/
public var clock: Clock = Clock { System.currentTimeMillis() }
/**
* Utility interface for obtaining timestamp.
*/
public fun interface Clock {
/**
* Get current timestamp.
*/
public fun now(): Long
}
internal val cachedDateText: AtomicRef<String> = atomic("")
}
/**
* A plugin that adds the standard `Date` and `Server` HTTP headers into each response and allows you to:
* - add additional default headers;
* - override the `Server` header.
*
* The example below shows how to add a custom header:
* ```kotlin
* install(DefaultHeaders) {
* header("Custom-Header", "Some value")
* }
* ```
* You can learn more from [Default headers](https://ktor.io/docs/default-headers.html).
*/
public val DefaultHeaders: RouteScopedPlugin<DefaultHeadersConfig> = createRouteScopedPlugin(
"DefaultHeaders",
::DefaultHeadersConfig
) {
val ktorPackageVersion = if (pluginConfig.headers.getAll(HttpHeaders.Server) == null) {
pluginConfig::class.java.`package`.implementationVersion ?: "debug"
} else "debug"
val headers = pluginConfig.headers.build()
val DATE_CACHE_TIMEOUT_MILLISECONDS = 1000
val GMT_TIMEZONE = TimeZone.getTimeZone("GMT")!!
var cachedDateTimeStamp = 0L
val calendar = object : ThreadLocal<Calendar>() {
override fun initialValue(): Calendar {
return Calendar.getInstance(GMT_TIMEZONE, Locale.ROOT)
}
}
fun now(time: Long): GMTDate {
return calendar.get().toDate(time)
}
fun calculateDateHeader(): String {
val captureCached = cachedDateTimeStamp
val currentTimeStamp = pluginConfig.clock.now()
if (captureCached + DATE_CACHE_TIMEOUT_MILLISECONDS <= currentTimeStamp) {
cachedDateTimeStamp = currentTimeStamp
pluginConfig.cachedDateText.value = now(currentTimeStamp).toHttpDate()
}
return pluginConfig.cachedDateText.value
}
val serverHeader = "Ktor/$ktorPackageVersion"
onCallRespond { call, _ ->
headers.forEach { name, value ->
if (!call.response.headers.contains(name)) value.forEach { call.response.header(name, it) }
}
if (!call.response.headers.contains(HttpHeaders.Date)) {
call.response.header(HttpHeaders.Date, calculateDateHeader())
}
if (!call.response.headers.contains(HttpHeaders.Server)) {
call.response.header(HttpHeaders.Server, serverHeader)
}
}
}