, , URL-.
, :
X-Forwarded-Proto ( X-Forwarded-Scheme: https, , , , X-Forwarded-Ssl: on, Front-End-Https: on)X-Forwarded-Host ( "myhost.com" "myhost.com:port" )X-Forwarded-Port
URL-, , :
- , "/something/here" "under/me", , , ,
- /, "//somehost.com:8983/thing" (http/https), ,
- full, URL, , , ( "http://...", "https://..." )
RoutingContext, , / , , , . URL ( ), .
fun RoutingContext.externalizeUrl(): String {
return externalizeUrl(URI(request().absoluteURI()).pathPlusParmsOfUrl())
}
fun RoutingContext.externalizeUrl(resolveUrl: String): String {
val cleanHeaders = request().headers().filter { it.value.isNullOrBlank() }
.map { it.key to it.value }.toMap()
return externalizeURI(URI(request().absoluteURI()), resolveUrl, cleanHeaders).toString()
}
, ( , RoutingContext):
internal fun externalizeURI(requestUri: URI, resolveUrl: String, headers: Map<String, String>): URI {
if (resolveUrl.startsWith("http://") || resolveUrl.startsWith("https://")) return URI(resolveUrl)
val forwardedScheme = headers.get("X-Forwarded-Proto")
?: headers.get("X-Forwarded-Scheme")
?: requestUri.getScheme()
if (resolveUrl.startsWith("//")) return URI("$forwardedScheme:$resolveUrl")
val (forwardedHost, forwardedHostOptionalPort) =
dividePort(headers.get("X-Forwarded-Host") ?: requestUri.getHost())
val fallbackPort = requestUri.getPort().let { explicitPort ->
if (explicitPort <= 0) {
if ("https" == forwardedScheme) 443 else 80
} else {
explicitPort
}
}
val requestPort = headers.get("X-Forwarded-Port")?.toInt()
?: forwardedHostOptionalPort
?: fallbackPort
val finalPort = when {
forwardedScheme == "https" && requestPort == 443 -> ""
forwardedScheme == "http" && requestPort == 80 -> ""
else -> ":$requestPort"
}
val restOfUrl = requestUri.pathPlusParmsOfUrl()
return URI("$forwardedScheme://$forwardedHost$finalPort$restOfUrl").resolve(resolveUrl)
}
:
internal fun URI.pathPlusParmsOfUrl(): String {
val path = this.getRawPath().let { if (it.isNullOrBlank()) "" else it.mustStartWith('/') }
val query = this.getRawQuery().let { if (it.isNullOrBlank()) "" else it.mustStartWith('?') }
val fragment = this.getRawFragment().let { if (it.isNullOrBlank()) "" else it.mustStartWith('#') }
return "$path$query$fragment"
}
internal fun dividePort(hostWithOptionalPort: String): Pair<String, String?> {
val parts = if (hostWithOptionalPort.startsWith('[')) {
Pair(hostWithOptionalPort.substringBefore(']') + ']', hostWithOptionalPort.substringAfter("]:", ""))
} else {
Pair(hostWithOptionalPort.substringBefore(':'), hostWithOptionalPort.substringAfter(':', ""))
}
return Pair(parts.first, if (parts.second.isNullOrBlank()) null else parts.second)
}
fun String.mustStartWith(prefix: Char): String {
return if (this.startsWith(prefix)) { this } else { prefix + this }
}