ktor 在进行 https 重定向时重定向到 0.0.0.0

ktor redirecting to 0.0.0.0 when doing an https redirect

我已将 http 重定向添加到我的 Ktor 应用程序,它重定向到 https://0.0.0.0 而不是实际域的 https

@ExperimentalTime
fun Application.module() {

    if (ENV.env != LOCAL) {
        install(ForwardedHeaderSupport)
        install(XForwardedHeaderSupport)
        install(HttpsRedirect)
    }

拦截路由并打印出主机

    routing {

            intercept(ApplicationCallPipeline.Features) {
            val host = this.context.request.host()

我似乎正在为主机0:0:0:0:0:0:0:0

我是否需要向 Google Cloud 的负载平衡器添加任何特殊的 headers 才能使此 https 重定向正常工作?似乎没有选择正确的主机

由于您的 Ktor 服务器隐藏在反向代理之后,因此它没有绑定到您网站的“外部”主机。 Ktor 有特定的 feature to handle working behind reverse proxy,因此在配置和引用 request.origin.remoteHost 以获取实际主机时应该像 install(XForwardedHeaderSupport) 一样简单。

让我们看看发生了什么。

您在 http://example.org 下创建了一个服务。在 example.org 主机的 80 端口上有一个负载均衡器。它处理所有传入流量,将其路由到其背后的服务器。 您的实际应用程序是 运行 在另一台虚拟机上。它有自己的 IP 地址,位于您的云内部,可由负载均衡器访问。

让我们看看这个系统的 HTTP 请求和响应流程。

  1. 外部用户在 example.org 的端口 80 上使用 Host: example.orgGET / 发送 HTTP 请求。
  2. 负载均衡器获取请求,检查其规则并找到将请求定向到的内部服务器。
  3. 负载均衡器制作新的 HTTP 请求,主要是复制传入数据,但会更新 Host header 并添加几个 X-Forwarded-* header 以保留有关代理的信息请求(有关特定于 GCP 的信息,请参阅 here)。
  4. 请求到达您的服务器。此时您可以分析 X-Forwarded-* headers 以查看您是否在反向代理后面,并获取实际用户发送的实际查询所需的详细信息,如原始主机。
  5. 您制作 HTTP 响应,然后您的服务器将其发送回负载平衡器。
  6. 负载均衡器将此响应传递给外部用户。

请注意,虽然有RFC 7239用于指定请求转发的信息,但GCP负载均衡器似乎使用de-facto标准X-Forwarded-* header,因此您需要XForwardedHeaderSupport,而不是 ForwardedHeaderSupport(注意额外的 X)。

所以似乎 Google Cloud Load Balancer 发送了错误的 headers 或 Ktor 读取了错误的 headers 或两者都有。

我试过了

    install(ForwardedHeaderSupport)
    install(XForwardedHeaderSupport)
    install(HttpsRedirect)

    //install(ForwardedHeaderSupport)
    install(XForwardedHeaderSupport)
    install(HttpsRedirect)

    install(ForwardedHeaderSupport)
    //install(XForwardedHeaderSupport)
    install(HttpsRedirect)

    //install(ForwardedHeaderSupport)
    //install(XForwardedHeaderSupport)
    install(HttpsRedirect)

所有这些组合都在另一个项目上工作,但该项目使用的是旧版本的 Ktor(这是与 1.4 rc 一起发布的版本)并且该项目还使用了旧的 Google Cloud负载平衡器设置。

所以我决定自己动手。 此行将记录所有随您的请求而来的 headers,

                log.info(context.request.headers.toMap().toString())

然后选择相关的并构建一个 https 重定向:

routing {

    intercept(ApplicationCallPipeline.Features) {
        if (ENV.env != LOCAL) {

            log.info(context.request.headers.toMap().toString())

            // workaround for call.request.host that contains the wrong host
            // and not redirecting properly to the correct https url
            val proto = call.request.header("X-Forwarded-Proto")
            val host = call.request.header("Host")
            val path = call.request.path()

            if (host == null || proto == null) {
                log.error("Unknown host / port")
            } else if (proto == "http") {
                val newUrl = "https://$host$path"
                log.info("https redirecting to $newUrl")

                // redirect browser
                this.context.respondRedirect(url = newUrl, permanent = true)
                this.finish()
            }
        }
    }