我将Kotlin与Vertx 3结合使用,有时我需要从公共URL的角度返回特定的URI,这与Vertx-Web请求认为我的URL的URL不同。这可能是由于我的负载均衡器或代理接收了一个URL,然后通过内部URL转发到我的应用程序。
因此,如果我这样做:
val publicUrl = context.request().absoluteURI()
Run Code Online (Sandbox Code Playgroud)
我最终得到一个URL http://10.10.103.22:8080/some/page而不是https://app.mydomain.com/some/page。该网址的所有内容都不对!
我发现了一个头,理应告诉我更多的原始请求,如X-Forwarded-Host但只包括app.mydomain.com或有时它的端口app.mydomain:80,但是这是不够找出网址的各个部分,我结束了像http://app.mydomain.com:8080/some/page这仍然是不正确的公共URL。
我还不仅需要处理我的当前URL,还需要处理对等URL,例如在同一服务器上的页面“ something / page1”上转到“ something / page2”。由于无法获得公共URL的重要部分,因此当我尝试解析为另一个URL时也提到了相同的问题。
我找不到Vertx-web中的方法来确定此公共URL,还是一些惯用的方法来解决此问题?
我使用Kotlin进行编码,因此该语言的任何示例都很棒!
注意: 这个问题是作者故意写和回答的(自我回答的问题),因此,有趣的问题的解决方案在SO中共享。
这是一个更复杂的问题,如果大多数App服务器尚未提供URL外部化功能,则其逻辑是相同的。
要正确执行此操作,您需要处理所有这些标头:
X-Forwarded-Proto(或者X-Forwarded-Scheme: https,也许oddballs等X-Forwarded-Ssl: on,Front-End-Https: on)X-Forwarded-Host (作为“ myhost.com”或“ myhost.com:port”)X-Forwarded-Port而且,如果您要解析并返回一个不是当前URL的URL,则还需要考虑以下几点:
这是一对扩展功能RoutingContext,可以处理所有这些情况,并在不存在负载平衡器/代理标头时退回,因此在直接连接到服务器以及通过中介的情况下都可以使用。您输入绝对或相对URL(到当前页面),它将返回该URL的公共版本。
// return current URL as public URL
fun RoutingContext.externalizeUrl(): String {
return externalizeUrl(URI(request().absoluteURI()).pathPlusParmsOfUrl())
}
// resolve a related URL as a public URL
fun RoutingContext.externalizeUrl(resolveUrl: String): String {
val cleanHeaders = request().headers().filterNot { it.value.isNullOrBlank() }
.map { it.key to it.value }.toMap()
return externalizeURI(URI(request().absoluteURI()), resolveUrl, cleanHeaders).toString()
}
Run Code Online (Sandbox Code Playgroud)
哪个函数调用完成实际工作的内部函数(并且由于无需模拟,因此更易于测试RoutingContext):
internal fun externalizeURI(requestUri: URI, resolveUrl: String, headers: Map<String, String>): URI {
// special case of not touching fully qualified resolve URL's
if (resolveUrl.startsWith("http://") || resolveUrl.startsWith("https://")) return URI(resolveUrl)
val forwardedScheme = headers.get("X-Forwarded-Proto")
?: headers.get("X-Forwarded-Scheme")
?: requestUri.getScheme()
// special case of //host/something URL's
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: Int = 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)
}
Run Code Online (Sandbox Code Playgroud)
以及一些相关的辅助函数:
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, Int?> {
val parts = if (hostWithOptionalPort.startsWith('[')) { // ipv6
Pair(hostWithOptionalPort.substringBefore(']') + ']', hostWithOptionalPort.substringAfter("]:", ""))
} else { // ipv4
Pair(hostWithOptionalPort.substringBefore(':'), hostWithOptionalPort.substringAfter(':', ""))
}
return Pair(parts.first, if (parts.second.isNullOrBlank()) null else parts.second.toInt())
}
fun String.mustStartWith(prefix: Char): String {
return if (this.startsWith(prefix)) { this } else { prefix + this }
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
570 次 |
| 最近记录: |