2016-09-19 1 views
4

Kotlin에서 Vertx 3을 사용하고 있으며 때때로 같은 URL이 아닌 공개 URL의 관점에서 특정 URI를 반환해야합니다. Vertx-web 요청이 내 URL로 생각하는 것입니다. 이는 내로드 밸런서 또는 프록시가 하나의 URL을 수신 한 다음 내부 URL에서 내 애플리케이션으로 전달한 것이 원인 일 수 있습니다.Vertx 요청을 받았고 외부에서 볼 수있는 공개 URL을 계산해야합니다.

내가 이렇게 그래서 경우 :

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

내가 http://10.10.103.22:8080/some/page 대신 https://app.mydomain.com/some/page 같은 URL로 끝날. 해당 URL에 대해 모두 잘못되었습니다!

내가 가정 X-Forwarded-Host 등 원래 요청에 대한 자세한 설명을 헤더를 찾았지만 그것은 단지 app.mydomain.com 포함 또는 때때로 포트 app.mydomain:80을 가지고 있지만,이 URL의 모든 부분을 파악하는 것만으로는 충분하지 않습니다, 나는 결국 http://app.mydomain.com:8080/some/page과 같이 아직 정확한 공개 URL이 아닙니다.

"something/page1"페이지가 동일한 서버의 "something/page2"로 이동하는 것과 같이 현재 URL뿐만 아니라 피어 URL도 처리해야합니다. 공용 URL의 중요한 부분을 얻을 수 없기 때문에 다른 URL로 변환하려고 할 때 언급 한 것과 동일한 문제가 있습니다.

Vertx-web에 메서드가 있습니까?이 공개 URL을 확인하기 위해 누락되었거나이를 해결하는 관용적 인 방법이 있습니까?

저는 Kotlin을 코딩하고 있습니다. 따라서이 언어에 대한 예제는 훌륭합니다!

참고 :이 질문에 의도적으로 작성된 저자 (Self-Answered Questions) 응답되어, 흥미있는 문제에 대한 해결 방법은 SO에서 공유되도록.

답변

4

이것은 더 복잡한 문제이며, URL 외부화 기능을 제공하지 않는 경우 논리는 대부분의 App 서버에서 동일합니다. ""myhost.com "또는

  • X-Forwarded-Proto (X-Forwarded-Ssl: on 같은 또는 X-Forwarded-Scheme: https, 어쩌면 괴짜, Front-End-Https: on)
  • X-Forwarded-Host (:

    는이 모든 헤더를 처리 할 필요가 제대로 이렇게하려면 myhost.com:port ")
  • X-Forwarded-Port

그리고 당신은 해결하고 URL을 반환하려면 즉, 당신은 또한 고려해야하는 현재되지 않습니다 : 예를 들어 "//여기에 뭔가"또는 호스트없이

  • 부분 그뿐만 아니라 서버 공용 프로토콜, 호스트, 포트에 해결 "아래 나 /" abosolute 또는 상대 경로
  • 부분이 호스트/포트 (예 : "//somehost.com:8983/thing")는이 서버와 동일한 구성표 (http/https)를 추가하고 나머지는 그대로 유지합니다.
  • 이 함수 ("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 } 
} 
관련 문제