First, it is best if your proxy or load balancer can do this check and redirect for you since it has knowledge of the public URL and is a simpler process at that first contact with the user. But, you can also do it server-side with a little more complexity.
The flag you are checking, context.request().isSSL
is only valid for the incoming connection to Vertx-web and does not consider the end-user's connect to your proxy or load balancer. You need to use the X-Forwarded-Proto
header (and sometimes X-Forwarded-Scheme
) and check the actual protocol of the user. And only if that header is not present you can use context.request().isSSL
You also need to externalize your own URL to be able to redirect on the server side to something that the browser can use to find you, your public URL.
First, there is a Kotlin function in this Stack Overflow answer for RoutingContext.externalizeUrl()
, you will need it here:
I have a Vertx request and I need to calculate an externally visible (public) URL
Then knowing your public URL you can use the following handler which has default values for the intended public HTTPS port (default 443 will vanish from URL), which form of redirect (i.e. 302), and on any exceptions if the route should be failed or continued:
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()
}
}
}
}
A simpler version might be to just trust the context.externalizeUrl
class and see if it has the correct protocol and port and redirect if not:
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()
}
}
}