I have a Vertx request and I need to calculate the external visible (public) URL

I use Vertx 3 with Kotlin, and sometimes I need to return a specific URI in terms of a public URL, which does not match what the Vertx-web request considers as my URL. This is probably due to the fact that my load balancer or proxy server gets one URL and then redirects my application to the internal URL.

So, if I do this:

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

In the end, I get the URL as http://10.10.103.22:8080/some/pageinstead https://app.mydomain.com/some/page. There is nothing wrong with this url!

I found a headline that supposedly tells me more about the original request, such as X-Forwarded-Host, but it only includes app.mydomain.comor sometimes has a port app.mydomain:80, but this is not enough to figure out all parts of the URL, I get something like http://app.mydomain.com:8080/some/page, which is still not the correct public Url.

I also need to process not only my current URL, but the peer-to-peer network URL, for example, on the page "something / page1" go to "something / page2" on the same server. The same problems that I mentioned when I try to solve a different URL, because important parts of the public URL are not available.

Is there a method on Vertx-web that is missing to determine this public url or some idiomatic way to solve this problem?

Kotlin, !

: (), SO.

+4
1

, , URL-.

, :

  • X-Forwarded-Proto ( X-Forwarded-Scheme: https, , , , X-Forwarded-Ssl: on, Front-End-Https: on)
  • X-Forwarded-Host ( "myhost.com" "myhost.com:port" )
  • X-Forwarded-Port

URL-, , :

  • , "/something/here" "under/me", , , ,
  • /, "//somehost.com:8983/thing" (http/https), ,
  • full, URL, , , ( "http://...", "https://..." )

RoutingContext, , / , , , . URL ( ), .

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

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

:

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 }
}
+4

Source: https://habr.com/ru/post/1655011/


All Articles