当前位置: 首页 > 知识库问答 >
问题:

我有一个Vertx请求,我需要计算一个外部可见(公共)URL

戈嘉慕
2023-03-14

我将Vertx 3与Kotlin一起使用,有时我需要从公共URL的角度返回特定的URI,这与Vertx web请求认为我的URL不同。这可能是因为我的负载平衡器或代理收到一个URL,然后通过内部URL转发到我的应用程序。

所以如果我这么做:

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

我最终得到了一个像http://10.10.103.22:8080/some/page而不是https://app.mydomain.com/some/page。那个网址的一切都错了!

我发现了一个标题,它应该告诉我更多关于原始请求的信息,比如X-Forwarded-Host,但它只包括app。我的域名。com或有时它有端口应用程序。mydomain:80但这还不足以计算出URL的所有部分,我最终得到了类似于http://app.mydomain.com:8080/some/page这仍然不是正确的公共URL。

我不仅需要处理当前的URL,还需要处理对等URL,比如在同一服务器上的“某物/page1”页面转到“某物/page2”。当我试图解析到另一个URL时,也提到了同样的问题,因为公共URL的重要部分是无法获取的。

Vertx-web中有没有我缺少的方法来确定这个公共网址,或者一些惯用的方法来解决这个问题?

我是用Kotlin编写代码的,所以这门语言的任何例子都很棒!

注意:这个问题是由作者(自答问题)有意写和回答的,所以有趣问题的解决方案在SO中共享。

共有1个答案

唐伟
2023-03-14

这是一个更复杂的问题,对于大多数应用服务器来说,如果它们还没有提供URL外部化功能,那么逻辑也是一样的。

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

  • X-Forwarded-Proto(或X-Forwarded-Schem: https,也许还有像X-Forwarded-Ssl: on前端Https: on
  • X-Forwarded-Host(myhost.com或myhost.com: port)
  • X-转发-端口

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

  • 部分没有主机,例如/某物/这里或下/我解决服务器公共协议,主机,端口以及该abosolute或相对路径
  • 部分与主机/端口,例如//somehost.com:8983/thing将添加相同的方案(超文本传输协议/https)作为这个服务器,并保持其余的
  • 完整,完全合格的URL将原封不动地返回,因此它们可以安全地传递给此函数("超文本传输协议://...","https://..."),并且不会被修改

下面是一对到RoutingContext的扩展函数,它们将处理所有这些情况,并在负载平衡器/代理头不存在时返回,因此将在直接连接到服务器和通过中介的情况下工作。您传入绝对或相对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()
}

它调用一个内部函数来完成真正的工作(由于不需要模拟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 }
}
 类似资料:
  • 我在Koltin中编写了一个Vertx web处理程序,它将我收到的任何HTTP请求重定向到HTTPS,我使用的是来确定请求是否不是SSL,在我将代码放在负载平衡器后面之前,这一切都很正常。如果负载平衡器通过HTTPS与我的Vertx web服务器通信,那么它会认为所有用户请求都是HTTPS,即使它们不是。如果我将负载平衡器改为在HTTP上与Vertex web对话,那么即使用户已经在使用HTTP

  • 我还不明白的是,喷口是否也是这样。如果一个spout发出一个元组(即,spout中的函数被执行),并且spout运行的计算机在此后不久崩溃,该元组会被zookeeper复活吗?还是我们需要Kafka来保证这一点? 附注。我理解,在对的调用中,必须为spout发出的元组分配唯一的ID。 P.P.S.我在书中看到的示例代码使用之类的东西来跟踪哪些喷出的元组尚未被加密。这是不是自动被Zookeeper坚

  • 这是我的密码 此错误org.hibernate.tool.schema.spi.命令接受异常:通过JDBC语句执行DDL“创建表扇区(id bigint not null,group varchar(255),name_sectorvarchar(255),主键(id))引擎=InnoDB”的错误 org.hibernate.tool.schema.spi.命令验收异常:通过JDBC语句执行DDL

  • 问题内容: 所以我在Rails应用程序中有一个表单,该表单使用自定义FormBuilder给我一些自定义字段标签 现在,此部分位于表单的区域中,该区域将被AJAX回调替换。我最终响应AJAX请求从控制器执行的操作是: 但是,如果我这样做,则表单将中断,因为我在form_for中使用的FormBuilder对象不再可用。我有什么办法可以在用于AJAX回调的部分内部使用自定义FormBuilder对象

  • 问题内容: 我一直在使用Bootstrap 3,但是我发现,如果您在没有容器的情况下使用Grid System,它会变得流畅,有人告诉我,我不应该这样做,因为该系统被设计为位于容器内。如果我不使用容器类会发生什么?我需要吗?如果可以,那么可以使容器类的宽度为100%,而不会弄乱引导程序的媒体查询,或者还有其他或更好的方法来构建流体吗?引导程序布局3。 问题答案: 更新Bootstrap 4 行也应

  • 我在这里(有点)了解jdk 5 Reentry antLock的功能 但为什么我们想要一个“再进入者”锁呢?i、 e如果一个线程已经锁定了一个对象,为什么它需要再次获取它?