Tengo una request de Vertx y necesito calcular una URL externa visible (pública)

Estoy utilizando Vertx 3 con Kotlin, y en ocasiones tengo que devolver un URI específico desde la perspectiva de la URL pública, que no es lo mismo que la request de Vertx-web cree que es mi URL. Es probable que esto se deba a que mi equilibrador de carga o proxy recibe una URL y luego la reenvía a mi aplicación en una URL interna.

Entonces si hago esto:

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

Terminé con una URL como http://10.10.103.22:8080/some/page lugar de https://app.mydomain.com/some/page . ¡Todo está mal con esa URL!

Encontré un encabezado que supuestamente me dice más sobre la request original, como X-Forwarded-Host pero solo incluye app.mydomain.com o a veces tiene la aplicación de app.mydomain:80 pero eso no es suficiente para descubrir todo partes de la URL, termino con algo como http://app.mydomain.com:8080/some/page que aún no es la URL pública correcta.

También necesito manejar no solo mi URL actual, sino también mis URL, como en la página "algo / página1" ir a "algo / página2" en el mismo server. Los mismos problemas mencionados cuando trato de resolver a otra URL porque partes importantes de la URL pública no se pueden get.

¿Existe algún método en Vertx-web que me falte para determinar esta URL pública, o alguna forma idiomática de resolver esto?

Estoy codificando en Kotlin, ¡así que cualquier ejemplo para ese idioma es genial!

Nota: esta pregunta fue escrita y respondida intencionalmente por el autor ( Preguntas que responden por sí mismas ), de modo que las soluciones para problemas interesantes se comparten en SO.

Este es un problema más complicado, y la lógica es la misma para la mayoría de los serveres de aplicaciones si aún no proporcionan una function de externalización de URL.

Para hacerlo correctamente, necesita manejar todos estos encabezados:

  • X-Forwarded-Proto (o X-Forwarded-Scheme: https , y quizás oddballs como X-Forwarded-Ssl: on , Front-End-Https: on )
  • X-Forwarded-Host (como "myhost.com" o "myhost.com:port")
  • X-Forwarded-Port

Y si desea resolver y devolver una URL que no es la actual, también debe considerar:

  • parcial sin host, por ejemplo "/ something / here" o "under / me" resolviendo a los serveres protocolo público, host, puerto así como aquel abosolute o ruta relativa
  • parcial con host / puerto, por ejemplo "//somehost.com:8983/thing" agregaría el mismo esquema (http / https) como este server y mantendría el rest
  • completo, las URL que están totalmente calificadas se devuelven intactas, por lo que son seguras para pasar a esta function ("http: // …", "https: // …") y no se modificarán

Aquí hay un par de funciones de extensión para RoutingContext que manejarán todos estos casos y retrocederán cuando los encabezados del equilibrador de carga / proxy no estén presentes, por lo que funcionarán tanto en los casos de conexiones directas al server como en los que atraviesan el intermediario. Usted pasa la URL absoluta o relativa (a la página actual) y devolverá una versión pública de la misma.

 // 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().filter { it.value.isNullOrBlank() } .map { it.key to it.value }.toMap() return externalizeURI(URI(request().absoluteURI()), resolveUrl, cleanHeaders).toString() } 

Que llama a una function interna que hace el trabajo real ( y es más comprobable ya que no hay necesidad de burlarse de 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 = 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) } 

Y algunas funciones auxiliares relacionadas:

 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, String?> { 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) } fun String.mustStartWith(prefix: Char): String { return if (this.startsWith(prefix)) { this } else { prefix + this } } 
  • Cómo crear verticle de fábrica en VertX?
  • Retrofit-Vertx con RxJava2 en Kotlin IllegalStateException message == null
  • Controlador de respuesta de coincidencia con request en VertX
  • Verticle (s) de Vert.x configuration JSON / YAML (preferible por entorno)
  • "La respuesta ya se ha escrito" con Vertx
  • Vertx plus Koutlin coroutines cuelga para siempre
  • VertX Web no elimina cookies