我有一个Vertx请求,我需要计算一个外部可见(公共)URL [英] I have a Vertx request and I need to calculate an externally visible (public) URL

查看:73
本文介绍了我有一个Vertx请求,我需要计算一个外部可见(公共)URL的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我将Kotlin与Vertx 3结合使用,有时我需要从公共URL的角度返回特定的URI,这与Vertx-Web请求认为我的URL是不同的.这可能是由于我的负载均衡器或代理接收了一个URL,然后通过内部URL转发到我的应用程序.

I am using Vertx 3 with Kotlin, and at times I need to return a specific URI from the perspective of the public URL which is not the same as what the Vertx-web request thinks my URL is. This is likely due to my load balancer or proxy receiving one URL, and then forwarding to my application on an internal URL.

所以,如果我这样做:

val publicUrl = context.request().absoluteURI() 

我最终得到的是类似http://10.10.103.22:8080/some/page的URL,而不是https://app.mydomain.com/some/page.该网址的所有内容都不对!

I end up with a URL like http://10.10.103.22:8080/some/page instead of https://app.mydomain.com/some/page. Everything is wrong about that URL!

我发现了一个标头,据说可以告诉我更多有关原始请求的信息,例如X-Forwarded-Host,但是它仅包含app.mydomain.com,有时它具有端口app.mydomain:80,但这不足以弄清标头的所有部分.网址,最后出现类似http://app.mydomain.com:8080/some/page之类的内容,但这仍然不是正确的公共网址.

I found a header that supposedly tell me more about the original request such as X-Forwarded-Host but it only includes app.mydomain.com or sometimes it has the port app.mydomain:80 but that isn't enough to figure out all parts of the URL, I end up with something like http://app.mydomain.com:8080/some/page which is still not the correct public URL.

我还不仅需要处理我的当前URL,还需要处理对等URL,例如在同一服务器上的页面"something/page1"上转到"something/page2".由于无法获得公共URL的重要部分,因此当我尝试解析为另一个URL时也提到了相同的问题.

I also need to handle not just my current URL, but peer URL's, like while on page "something/page1" go to "something/page2" on same server. The same problems mentioned about when I try to resolve to another URL because important parts of the public URL are unobtainable.

在Vertx-web中是否缺少确定该公共URL的方法,或者某些惯用的方法来解决此问题?

Is there a method in Vertx-web I'm missing to determine this public URL, or some idiomatic way to solve this?

我正在用Kotlin编写代码,因此该语言的任何示例都很棒!

I'm coding in Kotlin, so any examples for that language are great!

注意: 该问题是作者故意写并回答的(

Note: this question is intentionally written and answered by the author (Self-Answered Questions), so that solutions for interesting problems are shared in SO.

推荐答案

这是一个更复杂的问题,如果大多数App服务器尚未提供URL外部化功能,则其逻辑是相同的.

This is a more complicated issue, and the logic is the same for most App servers if they do not already provide an URL externalization function.

要正确执行此操作,您需要处理所有这些标头:

To do this correctly, you need to handle all of these headers:

  • X-Forwarded-Proto(或X-Forwarded-Scheme: https,也许还有X-Forwarded-Ssl: onFront-End-Https: on之类的奇数球)
  • X-Forwarded-Host(作为"myhost.com"或"myhost.com:端口")
  • X-Forwarded-Port
  • X-Forwarded-Proto (or X-Forwarded-Scheme: https, and maybe oddballs like X-Forwarded-Ssl: on, Front-End-Https: on)
  • X-Forwarded-Host (as "myhost.com" or "myhost.com:port")
  • X-Forwarded-Port

如果您要解析并返回一个不是当前URL的URL,则还需要考虑:

And if you want to resolve and return a URL that is not the current one you need to also consider:

  • 不带主机的部分主机,例如解析服务器公共协议,主机,端口以及绝对路径或相对路径的"/something/here"或"under/me"
  • 具有主机/端口的部分,例如"//somehost.com:8983/thing"将添加与此服务器相同的方案(http/https),并保留其余方案
  • 完整,完全合格的URL保持不变,因此可以安全地传递给此功能("http://...","https://..."),并且不会被修改

这是RoutingContext的一对扩展功能,将处理所有这些情况,并在不存在负载均衡器/代理标头时退回,因此在直接连接到服务器以及通过服务器进行直接连接的情况下都可以使用中介.您输入绝对或相对URL(到当前页面),它将返回该URL的公共版本.

Here is a pair of extension functions to RoutingContext that will handle all these cases and fall back when the load balancer / proxy headers are not present so will work in both cases of direct connections to the server and those going through the intermediary. You pass in the absolute or relative URL (to the current page) and it will return a public version of the same.

// 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()
}

调用一个完成实际工作的内部函数(,由于无需模拟RoutingContext ,因此更具可测试性):

Which call an internal function that does the real work (and is more testable since there is no need to mock the 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)
}

以及一些相关的帮助器功能:

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 }
}

这篇关于我有一个Vertx请求,我需要计算一个外部可见(公共)URL的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆