在 Vertx 中,我需要将所有 HTTP 请求重定向到相同的 URL 但对于 HTTPS

In Vertx I need to redirect all HTTP requests to the same URL but for HTTPS

我在 Koltin 中编写了一个 Vertx-web 处理程序,它将我收到的任何 HTTP 请求重定向到 HTTPS,我正在使用 context.request().isSSL 来确定请求是否不是 SSL,这工作正常直到我把我的代码放在负载均衡器后面。如果负载均衡器通过 HTTPS 与我的 Vertx-web 服务器通信,那么它认为所有用户请求都是 HTTPS,即使它们不是。如果我将负载平衡器更改为通过 HTTP 与 Vertx-web 通信,那么即使用户已经在使用 HTTPS,每个请求都会无休止地重定向。

然后我还看到另一个问题,使用 context.request().absoluteURI() 的重定向转到私有地址,而不是用户实际正在与之交谈的公共可用地址。

Vertx-web 中是否有我缺少的执行此操作的处理程序,或一些惯用的方法来解决此问题?我是否应该只从 JavaScript 执行此操作,因为它会看到真实的用户地址而不是尝试服务器端重定向?

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

注:此问题是作者(Self-Answered Questions)特意写下并回答的,所以有趣问题的解决方案分享到SO .

首先,最好是您的代理或负载均衡器可以为您执行此检查和重定向,因为它知道 public URL 并且在第一次接触时是一个更简单的过程用户。但是,您也可以 server-side 稍微复杂一点。

您正在检查的标志 context.request().isSSL 仅对 Vertx-web 的传入连接有效,不考虑 end-user 与您的代理或负载平衡器的连接。您需要使用 X-Forwarded-Proto header(有时 X-Forwarded-Scheme)并检查用户的实际协议。只有当 header 不存在时,您才可以使用 context.request().isSSL

您还需要外部化您自己的 URL 以便能够在服务器端重定向到浏览器可以用来找到您的东西,您的 public URL。

首先,RoutingContext.externalizeUrl() 的 Stack Overflow 答案中有一个 Kotlin 函数,您将在此处需要它:
I have a Vertx request and I need to calculate an externally visible (public) URL

然后知道您的 public URL 您可以使用以下处理程序,它具有预期 public HTTPS 端口的默认值(默认 443 将从 URL),哪种形式的重定向(即 302),以及路由应该失败或继续的任何异常:

fun Route.redirectToHttpsHandler(publicHttpsPort: Int = 443, redirectCode: Int = 302, failOnUrlBuilding: Boolean = true) {
    handler { context ->
        val proto = context.request().getHeader("X-Forwarded-Proto")
                ?: context.request().getHeader("X-Forwarded-Scheme")
        if (proto == "https") {
            context.next()
        } else if (proto.isNullOrBlank() && context.request().isSSL) {
            context.next()
        } else {
            try {
                val myPublicUri = URI(context.externalizeUrl())
                val myHttpsPublicUri = URI("https", 
                        myPublicUri.userInfo, 
                        myPublicUri.host, 
                        publicHttpsPort,
                        myPublicUri.rawPath, 
                        myPublicUri.rawQuery, 
                        myPublicUri.rawFragment)
                context.response().putHeader("location", myHttpsPublicUri.toString()).setStatusCode(redirectCode).end()
            } catch (ex: Throwable) {
                if (failOnUrlBuilding) context.fail(ex)
                else context.next()
            }
        }
    }
}

一个更简单的版本可能是只信任 context.externalizeUrl class 并查看它是否具有正确的协议和端口,如果不正确则重定向:

fun Route.simplifiedRedirectToHttpsHandler(publicHttpsPort: Int = 443, redirectCode: Int = 302, failOnUrlBuilding: Boolean = true) {
    handler { context ->
        try {
            val myPublicUri = URI(context.externalizeUrl())
            if (myPublicUri.scheme == "http") {
                val myHttpsPublicUri = URI("https",
                        myPublicUri.userInfo,
                        myPublicUri.host,
                        publicHttpsPort,
                        myPublicUri.rawPath,
                        myPublicUri.rawQuery,
                        myPublicUri.rawFragment)
                context.response().putHeader("location", myHttpsPublicUri.toString()).setStatusCode(redirectCode).end()
            }
            else {
                context.next()
            }
        } catch (ex: Throwable) {
            if (failOnUrlBuilding) context.fail(ex)
            else context.next()
        }
    }
}