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

时间:2016-09-19 02:30:12

标签: kotlin vert.x

我在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中分享有趣问题的解决方案。

1 个答案:

答案 0 :(得分:2)

首先,最好是您的代理或负载均衡器可以为您执行此检查并重定向,因为它了解公共URL,并且在首次与用户联系时是一个更简单的过程。但是,您也可以通过更复杂的方式在服务器端执行此操作。

您要检查的标记context.request().isSSL仅对Vertx-web的传入连接有效,并且不认为最终用户连接到您的代理或负载均衡器。您需要使用X-Forwarded-Proto标头(有时是X-Forwarded-Scheme)并检查用户的实际协议。并且只有当该标头不存在时,您才能使用context.request().isSSL

您还需要外部化自己的网址,以便能够在服务器端重定向到浏览器可以用来查找您的公共网址。

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

然后知道您的公共URL,您可以使用以下处理程序,该处理程序具有预期公共HTTPS端口的默认值(默认443将从URL 中消失),这是重定向的形式( ie 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类,看看它是否具有正确的协议和端口,如果不是,则重定向:

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