From b596687f8bfb6e5317231a4e3811518ce268faef Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Fri, 5 Jun 2015 12:34:22 +0200 Subject: [PATCH] =htp #17657 fix supposedly compatible getHostString failing at runtime on Java 6 --- .../HttpRequestRendererFactory.scala | 16 +---- .../impl/util/EnhancedInetSocketAddress.scala | 60 +++++++++++++++++++ .../http/impl/util/SettingsCompanion.scala | 4 +- .../scala/akka/http/impl/util/package.scala | 4 ++ .../http/scaladsl/model/headers/headers.scala | 2 +- .../util/EnhancedInetSocketAddressSpec.scala | 34 +++++++++++ 6 files changed, 102 insertions(+), 18 deletions(-) create mode 100644 akka-http-core/src/main/scala/akka/http/impl/util/EnhancedInetSocketAddress.scala create mode 100644 akka-http-core/src/test/scala/akka/http/impl/util/EnhancedInetSocketAddressSpec.scala diff --git a/akka-http-core/src/main/scala/akka/http/impl/engine/rendering/HttpRequestRendererFactory.scala b/akka-http-core/src/main/scala/akka/http/impl/engine/rendering/HttpRequestRendererFactory.scala index b52575cc39..d18d1474ba 100644 --- a/akka-http-core/src/main/scala/akka/http/impl/engine/rendering/HttpRequestRendererFactory.scala +++ b/akka-http-core/src/main/scala/akka/http/impl/engine/rendering/HttpRequestRendererFactory.scala @@ -25,20 +25,6 @@ private[http] class HttpRequestRendererFactory(userAgentHeader: Option[headers.` def newRenderer: HttpRequestRenderer = new HttpRequestRenderer - /** - * Retrieve the original host string that was given (IP or DNS name) if running Java 7 or later - * using the getHostString() method. This avoids a reverse DNS query from calling getHostName() - * if the original host string is an IP address. - */ - private[http] val hostString: InetSocketAddress ⇒ String = - try { - val m = classOf[InetSocketAddress].getDeclaredMethod("getHostString") - require(m.getReturnType == classOf[String]) - address ⇒ m.invoke(address).asInstanceOf[String] - } catch { - case NonFatal(_) ⇒ _.getHostName - } - final class HttpRequestRenderer extends PushStage[RequestRenderingContext, Source[ByteString, Any]] { override def onPush(ctx: RequestRenderingContext, opCtx: Context[Source[ByteString, Any]]): SyncDirective = { @@ -107,7 +93,7 @@ private[http] class HttpRequestRendererFactory(userAgentHeader: Option[headers.` } case Nil ⇒ - if (!hostHeaderSeen) r ~~ Host(hostString(ctx.serverAddress), ctx.serverAddress.getPort) ~~ CrLf + if (!hostHeaderSeen) r ~~ Host(ctx.serverAddress) ~~ CrLf if (!userAgentSeen && userAgentHeader.isDefined) r ~~ userAgentHeader.get ~~ CrLf if (entity.isChunked && !entity.isKnownEmpty && !transferEncodingSeen) r ~~ `Transfer-Encoding` ~~ ChunkedBytes ~~ CrLf diff --git a/akka-http-core/src/main/scala/akka/http/impl/util/EnhancedInetSocketAddress.scala b/akka-http-core/src/main/scala/akka/http/impl/util/EnhancedInetSocketAddress.scala new file mode 100644 index 0000000000..ee00e6c833 --- /dev/null +++ b/akka-http-core/src/main/scala/akka/http/impl/util/EnhancedInetSocketAddress.scala @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2009-2015 Typesafe Inc. + */ + +package akka.http.impl.util + +import java.lang.reflect.{ InvocationTargetException, Method } +import java.net.InetSocketAddress + +import scala.util.control.NonFatal + +/** + * Provides getHostString support for Java 6. + * + * TODO: can be removed once support for Java 6 is dropped. + * + * Internal API + */ +private[http] class EnhancedInetSocketAddress(val address: InetSocketAddress) extends AnyVal { + /** + * Retrieve the original host string that was given (IP or DNS name) if the current JDK has + * a `getHostString` method with the right signature that can be made accessible. + * + * This avoids a reverse DNS query from calling getHostName() if the original host string is an IP address. + * If the reflective call doesn't work it falls back to getHostName. + */ + def getHostStringJava6Compatible: String = EnhancedInetSocketAddress.getHostStringFunction(address) +} + +/** + * Internal API + */ +private[http] object EnhancedInetSocketAddress { + private[http] val getHostStringFunction: InetSocketAddress ⇒ String = { + def fallbackToGetHostName = (_: InetSocketAddress).getHostName + def callReflectively(m: Method) = + (address: InetSocketAddress) ⇒ + try m.invoke(address).asInstanceOf[String] + catch { + case ite: InvocationTargetException ⇒ throw ite.getTargetException + } + + try { + val m = classOf[InetSocketAddress].getDeclaredMethod("getHostString") + + val candidate = + if (m.getReturnType == classOf[String] && m.getParameterTypes.isEmpty) { + if (!m.isAccessible) m.setAccessible(true) + callReflectively(m) + } else fallbackToGetHostName + + // probe so that we can be sure a reflective problem only turns up once + // here during construction + candidate(new InetSocketAddress("127.0.0.1", 80)) + candidate + } catch { + case NonFatal(_) ⇒ fallbackToGetHostName + } + } +} \ No newline at end of file diff --git a/akka-http-core/src/main/scala/akka/http/impl/util/SettingsCompanion.scala b/akka-http-core/src/main/scala/akka/http/impl/util/SettingsCompanion.scala index 5f4f71e4bf..0d3069f084 100644 --- a/akka-http-core/src/main/scala/akka/http/impl/util/SettingsCompanion.scala +++ b/akka-http-core/src/main/scala/akka/http/impl/util/SettingsCompanion.scala @@ -4,7 +4,7 @@ package akka.http.impl.util -import java.net.InetAddress +import java.net.{ InetSocketAddress, InetAddress } import com.typesafe.config.{ ConfigFactory, Config } import com.typesafe.config.ConfigFactory._ import scala.util.control.NonFatal @@ -49,7 +49,7 @@ private[http] abstract class SettingsCompanion[T](prefix: String) { private[http] object SettingsCompanion { lazy val configAdditions: Config = { val localHostName = - try InetAddress.getLocalHost.getHostName // TODO: upgrade to `getHostString` once we are on JDK7 + try new InetSocketAddress(InetAddress.getLocalHost, 80).getHostStringJava6Compatible catch { case NonFatal(_) ⇒ "" } ConfigFactory.parseMap(Map("akka.http.hostname" -> localHostName).asJava) } diff --git a/akka-http-core/src/main/scala/akka/http/impl/util/package.scala b/akka-http-core/src/main/scala/akka/http/impl/util/package.scala index 36e46def7a..574f8b3015 100644 --- a/akka-http-core/src/main/scala/akka/http/impl/util/package.scala +++ b/akka-http-core/src/main/scala/akka/http/impl/util/package.scala @@ -4,6 +4,8 @@ package akka.http.impl +import java.net.InetSocketAddress + import language.implicitConversions import language.higherKinds import java.nio.charset.Charset @@ -38,6 +40,8 @@ package object util { private[http] implicit def enhanceConfig(config: Config): EnhancedConfig = new EnhancedConfig(config) private[http] implicit def enhanceString_(s: String): EnhancedString = new EnhancedString(s) private[http] implicit def enhanceRegex(regex: Regex): EnhancedRegex = new EnhancedRegex(regex) + private[http] implicit def enhanceInetSocketAddress(address: InetSocketAddress): EnhancedInetSocketAddress = + new EnhancedInetSocketAddress(address) private[http] implicit def enhanceByteStrings(byteStrings: TraversableOnce[ByteString]): EnhancedByteStringTraversableOnce = new EnhancedByteStringTraversableOnce(byteStrings) private[http] implicit def enhanceByteStrings[Mat](byteStrings: Source[ByteString, Mat]): EnhancedByteStringSource[Mat] = diff --git a/akka-http-core/src/main/scala/akka/http/scaladsl/model/headers/headers.scala b/akka-http-core/src/main/scala/akka/http/scaladsl/model/headers/headers.scala index 37bc55c9bb..2cab77bae6 100644 --- a/akka-http-core/src/main/scala/akka/http/scaladsl/model/headers/headers.scala +++ b/akka-http-core/src/main/scala/akka/http/scaladsl/model/headers/headers.scala @@ -90,7 +90,7 @@ sealed abstract case class Expect private () extends ModeledHeader { // http://tools.ietf.org/html/rfc7230#section-5.4 object Host extends ModeledCompanion { - def apply(address: InetSocketAddress): Host = apply(address.getHostName, address.getPort) // TODO: upgrade to `getHostString` once we are on JDK7 + def apply(address: InetSocketAddress): Host = apply(address.getHostStringJava6Compatible, address.getPort) def apply(host: String): Host = apply(host, 0) def apply(host: String, port: Int): Host = apply(Uri.Host(host), port) val empty = Host("") diff --git a/akka-http-core/src/test/scala/akka/http/impl/util/EnhancedInetSocketAddressSpec.scala b/akka-http-core/src/test/scala/akka/http/impl/util/EnhancedInetSocketAddressSpec.scala new file mode 100644 index 0000000000..16d0fbc6b3 --- /dev/null +++ b/akka-http-core/src/test/scala/akka/http/impl/util/EnhancedInetSocketAddressSpec.scala @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2009-2015 Typesafe Inc. + */ + +package akka.http.impl.util + +import java.net.{ InetAddress, InetSocketAddress } + +import org.scalatest.{ Matchers, WordSpec } + +class EnhancedInetSocketAddressSpec extends WordSpec with Matchers { + "getHostStringJava6Compatible" should { + "return IPv4 address if InetSocketAddress was created with the address" in { + val addr = likelyReverseResolvableAddress + val socketAddress = new InetSocketAddress(addr, 80) + socketAddress.getHostStringJava6Compatible shouldEqual addr.getHostAddress + } + "return host name if InetSocketAddress was created with host name" in { + val address = new InetSocketAddress("github.com", 80) + address.getHostStringJava6Compatible shouldEqual "github.com" + } + } + + /** + * Returns an InetAddress that can likely be reverse looked up, so that + * getHostName returns a DNS address and not the IP. Unfortunately, we + * cannot be sure that a host name was already cached somewhere in which + * case getHostString may still return a host name even without doing + * a reverse lookup at this time. If this start to fail non-deterministically, + * it may be decided that this test needs to be disabled. + */ + def likelyReverseResolvableAddress: InetAddress = + InetAddress.getByAddress(InetAddress.getByName("google.com").getAddress) +}