Skip to content

Commit

Permalink
Allow Http2 without negotiation/TLS (#1934)
Browse files Browse the repository at this point in the history
  • Loading branch information
raboof committed Apr 13, 2018
1 parent b191b6d commit 174a694
Show file tree
Hide file tree
Showing 20 changed files with 379 additions and 106 deletions.
Expand Up @@ -84,5 +84,4 @@ ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.scaladsl.setting
# Method additions to @DoNotInherit class
ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.javadsl.TimeoutAccess.getTimeout")
ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.scaladsl.TimeoutAccess.timeout")
ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.scaladsl.TimeoutAccess.getTimeout")

ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.scaladsl.TimeoutAccess.getTimeout")
Expand Up @@ -3,3 +3,12 @@ ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.javadsl.model.ws
ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.javadsl.model.ws.BinaryMessage.toStrict")
ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.scaladsl.model.ws.TextMessage.toStrict")
ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.scaladsl.model.ws.BinaryMessage.toStrict")

# Constructors and methods on classes that should have had @DoNotInherit in #1934
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.http.javadsl.HttpsConnectionContext.this")
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.http.javadsl.HttpConnectionContext.this")
ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.javadsl.ConnectionContext.http2")
ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.http.javadsl.ConnectHttp.http2")

ProblemFilters.exclude[DirectMissingMethodProblem]("akka.http.javadsl.ConnectHttpImpl.this")
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.http.javadsl.ConnectHttpsImpl.this")
Expand Up @@ -8,7 +8,7 @@ import akka.actor.{ ActorSystem, ExtendedActorSystem }
import akka.annotation.InternalApi
import akka.event.LoggingAdapter
import akka.http.scaladsl.Http.ServerBinding
import akka.http.scaladsl.{ ConnectionContext, HttpsConnectionContext }
import akka.http.scaladsl.ConnectionContext
import akka.http.scaladsl.model.{ HttpRequest, HttpResponse }
import akka.http.scaladsl.settings.ServerSettings
import akka.stream.{ ActorMaterializerHelper, Materializer }
Expand All @@ -24,25 +24,21 @@ private[akka] object Http2Shadow {
def bindAndHandleAsync(
handler: HttpRequest Future[HttpResponse],
interface: String, port: Int,
httpsContext: HttpsConnectionContext,
settings: ServerSettings,
parallelism: Int,
log: LoggingAdapter)(implicit fm: Materializer): Future[ServerBinding]
httpContext: ConnectionContext,
settings: ServerSettings,
parallelism: Int,
log: LoggingAdapter)(implicit fm: Materializer): Future[ServerBinding]
}
/**
* @param httpsContext is required to be an HTTPS context, however we validate this internally,
* and throw in runtime if it's not. We do this since we support the existing Http() API.
*/

def bindAndHandleAsync(
handler: HttpRequest Future[HttpResponse],
interface: String, port: Int,
httpsContext: ConnectionContext,
settings: ServerSettings,
parallelism: Int,
log: LoggingAdapter)(implicit fm: Materializer): Future[ServerBinding] = {
httpContext: ConnectionContext,
settings: ServerSettings,
parallelism: Int,
log: LoggingAdapter)(implicit fm: Materializer): Future[ServerBinding] = {

val mat = ActorMaterializerHelper.downcast(fm)
require(httpsContext.isSecure, "In order to use HTTP/2 you MUST provide an HttpsConnectionContext.")

try {
val system = mat.system.asInstanceOf[ExtendedActorSystem]
Expand All @@ -55,7 +51,7 @@ private[akka] object Http2Shadow {
extensionInstance.bindAndHandleAsync(
handler,
interface, port,
httpsContext.asInstanceOf[HttpsConnectionContext],
httpContext,
settings,
parallelism,
log)(fm)
Expand Down
Expand Up @@ -254,33 +254,6 @@ private[http] object StreamUtils {
x.runWith(Sink.ignore)(mat)
}

private trait Fuser {
def aggressive[S <: Shape, M](g: Graph[S, M]): Graph[S, M]
}
private val fuser: Fuser =
try {
val cl = Class.forName("akka.stream.Fusing$")
val field = cl.getDeclaredField("MODULE$")
field.setAccessible(true)
type Fusing = { def aggressive[S <: Shape, M](g: Graph[S, M]): Graph[S, M] }
val instance = field.get(null).asInstanceOf[Fusing]

new Fuser {
def aggressive[S <: Shape, M](g: Graph[S, M]): Graph[S, M] = instance.aggressive(g)
}
} catch {
case ex: ClassNotFoundException
new Fuser {
def aggressive[S <: Shape, M](g: Graph[S, M]): Graph[S, M] = g // no fusing
}
}

/**
* Try to fuse flow using Fusing.aggressive reflectively on Akka 2.4 or do nothing on Akka >= 2.5 (where explicit
* fusing is neither supported nor necessary).
*/
def fuseAggressive[S <: Shape, M](g: Graph[S, M]): Graph[S, M] = fuser.aggressive(g)

/**
* INTERNAL API
*/
Expand Down
59 changes: 48 additions & 11 deletions akka-http-core/src/main/scala/akka/http/javadsl/ConnectHttp.scala
Expand Up @@ -7,15 +7,18 @@ package akka.http.javadsl
import java.util.Locale
import java.util.Optional

import akka.annotation.InternalApi
import akka.annotation.{ DoNotInherit, InternalApi }
import akka.http.javadsl.model.Uri
import akka.http.scaladsl.UseHttp2.Negotiated

@DoNotInherit
abstract class ConnectHttp {
def host: String
def port: Int

def isHttps: Boolean
def connectionContext: Optional[HttpsConnectionContext]
def http2: UseHttp2

final def effectiveHttpsConnectionContext(fallbackContext: HttpsConnectionContext): HttpsConnectionContext =
connectionContext.orElse(fallbackContext)
Expand All @@ -24,7 +27,7 @@ abstract class ConnectHttp {
if (connectionContext.isPresent) connectionContext.get()
else fallbackContext

override def toString = s"ConnectHttp($host,$port,$isHttps,$connectionContext)"
override def toString = s"ConnectHttp($host,$port,$isHttps,$connectionContext,$http2)"
}

object ConnectHttp {
Expand Down Expand Up @@ -57,10 +60,24 @@ object ConnectHttp {
toHost(createUriWithScheme("http", host), port)
}

private def toHost(uriHost: Uri, port: Int): ConnectHttp = {
/**
* Extracts HTTP or HTTPS connection data from given host and port.
*
* The host string may contain a URI or a <host>:<port> pair. In both cases the
* port is ignored.
*
* If the given port is 0, a new local port will be assigned by the operating system,
* which can then be retrieved by the materialized [[akka.http.javadsl.Http.ServerBinding]].
*/
def toHost(host: String, port: Int, http2: UseHttp2): ConnectHttp = {
require(port >= 0, "port must be >= 0")
toHost(createUriWithScheme("http", host), port, http2)
}

private def toHost(uriHost: Uri, port: Int, http2: UseHttp2 = Negotiated): ConnectHttp = {
val s = uriHost.scheme.toLowerCase(Locale.ROOT)
if (s == "https") new ConnectHttpsImpl(uriHost.host.address, effectivePort(s, port))
else new ConnectHttpImpl(uriHost.host.address, effectivePort(s, port))
if (s == "https") new ConnectHttpsImpl(uriHost.host.address, effectivePort(s, port), context = Optional.empty(), http2)
else new ConnectHttpImpl(uriHost.host.address, effectivePort(s, port), http2)
}

/**
Expand Down Expand Up @@ -100,10 +117,27 @@ object ConnectHttp {
toHostHttps(createUriWithScheme("https", host), port)
}

private def toHostHttps(uriHost: Uri, port: Int): ConnectWithHttps = {
/**
* Extracts HTTPS connection data from given host and port, using the default HTTPS context.
*
* The host string may contain a URI or a <host>:<port> pair. In both cases the
* port is ignored.
*
* If the given port is 0, a new local port will be assigned by the operating system,
* which can then be retrieved by the materialized [[akka.http.javadsl.Http.ServerBinding]].
*
* Uses the default HTTPS context.
*/
@throws(classOf[IllegalArgumentException])
def toHostHttps(host: String, port: Int, http2: UseHttp2): ConnectWithHttps = {
require(port >= 0, "port must be >= 0")
toHostHttps(createUriWithScheme("https", host), port, http2)
}

private def toHostHttps(uriHost: Uri, port: Int, http2: UseHttp2 = Negotiated): ConnectWithHttps = {
val s = uriHost.scheme.toLowerCase(Locale.ROOT)
require(s == "" || s == "https", "toHostHttps used with non https scheme! Was: " + uriHost)
new ConnectHttpsImpl(uriHost.host.address, effectivePort("https", port))
new ConnectHttpsImpl(uriHost.host.address, effectivePort("https", port), context = Optional.empty(), http2)
}

private def createUriWithScheme(defaultScheme: String, host: String) = {
Expand All @@ -121,29 +155,32 @@ object ConnectHttp {

}

@DoNotInherit
abstract class ConnectWithHttps extends ConnectHttp {
def withCustomHttpsContext(context: HttpsConnectionContext): ConnectWithHttps
def withDefaultHttpsContext(): ConnectWithHttps
}

/** INTERNAL API */
@InternalApi
final class ConnectHttpImpl(val host: String, val port: Int) extends ConnectHttp {
final class ConnectHttpImpl(val host: String, val port: Int, val http2: UseHttp2) extends ConnectHttp {
def isHttps: Boolean = false

def connectionContext: Optional[HttpsConnectionContext] = Optional.empty()
}

final class ConnectHttpsImpl(val host: String, val port: Int, val context: Optional[HttpsConnectionContext] = Optional.empty())
/** INTERNAL API */
@InternalApi
final class ConnectHttpsImpl(val host: String, val port: Int, val context: Optional[HttpsConnectionContext] = Optional.empty(), val http2: UseHttp2)
extends ConnectWithHttps {

override def isHttps: Boolean = true

override def withCustomHttpsContext(context: HttpsConnectionContext): ConnectWithHttps =
new ConnectHttpsImpl(host, port, Optional.of(context))
new ConnectHttpsImpl(host, port, Optional.of(context), http2)

override def withDefaultHttpsContext(): ConnectWithHttps =
new ConnectHttpsImpl(host, port, Optional.empty())
new ConnectHttpsImpl(host, port, Optional.empty(), http2)

override def connectionContext: Optional[HttpsConnectionContext] = context

Expand Down
Expand Up @@ -4,7 +4,9 @@

package akka.http.javadsl

import java.util.{ Collection JCollection, Optional }
import java.util.{ Optional, Collection JCollection }

import akka.annotation.DoNotInherit
import javax.net.ssl.{ SSLContext, SSLParameters }
import akka.http.scaladsl
import akka.japi.Util
Expand Down Expand Up @@ -58,21 +60,25 @@ object ConnectionContext {
scaladsl.ConnectionContext.noEncryption()
}

@DoNotInherit
abstract class ConnectionContext {
def isSecure: Boolean
def sslConfig: Option[AkkaSSLConfig]
def http2: UseHttp2

@deprecated("'default-http-port' and 'default-https-port' configuration properties are used instead", since = "10.0.11")
def getDefaultPort: Int
}

abstract class HttpConnectionContext extends akka.http.javadsl.ConnectionContext {
@DoNotInherit
abstract class HttpConnectionContext(override val http2: UseHttp2) extends akka.http.javadsl.ConnectionContext {
override final def isSecure = false
override final def getDefaultPort = 80
override def sslConfig: Option[AkkaSSLConfig] = None
}

abstract class HttpsConnectionContext extends akka.http.javadsl.ConnectionContext {
@DoNotInherit
abstract class HttpsConnectionContext(override val http2: UseHttp2) extends akka.http.javadsl.ConnectionContext {
override final def isSecure = true
override final def getDefaultPort = 443

Expand Down
20 changes: 20 additions & 0 deletions akka-http-core/src/main/scala/akka/http/javadsl/UseHttp2.scala
@@ -0,0 +1,20 @@
/*
* Copyright (C) 2018 Lightbend Inc. <https://www.lightbend.com>
*/

package akka.http.javadsl

import akka.annotation.DoNotInherit
import akka.http.scaladsl
import akka.http.scaladsl.UseHttp2._

@DoNotInherit
abstract class UseHttp2 {
def asScala: scaladsl.UseHttp2
}

object UseHttp2 {
def always: UseHttp2 = Always
def negotiated: UseHttp2 = Negotiated
def never: UseHttp2 = Never
}
Expand Up @@ -10,6 +10,8 @@ import com.typesafe.sslconfig.akka.AkkaSSLConfig

import scala.collection.JavaConverters._
import java.util.{ Optional, Collection JCollection }

import akka.http.scaladsl.UseHttp2.Negotiated
import javax.net.ssl._

import scala.collection.immutable
Expand All @@ -28,18 +30,29 @@ object ConnectionContext {
enabledCipherSuites: Option[immutable.Seq[String]] = None,
enabledProtocols: Option[immutable.Seq[String]] = None,
clientAuth: Option[TLSClientAuth] = None,
sslParameters: Option[SSLParameters] = None) =
new HttpsConnectionContext(sslContext, sslConfig, enabledCipherSuites, enabledProtocols, clientAuth, sslParameters)
sslParameters: Option[SSLParameters] = None,
http2: UseHttp2 = UseHttp2.Negotiated) =
new HttpsConnectionContext(sslContext, sslConfig, enabledCipherSuites, enabledProtocols, clientAuth, sslParameters, http2)
//#https-context-creation

// for binary-compatibility, since 2.4.7
@deprecated("for binary-compatibility", "10.1.2")
def https(
sslContext: SSLContext,
sslConfig: Option[AkkaSSLConfig],
enabledCipherSuites: Option[immutable.Seq[String]],
enabledProtocols: Option[immutable.Seq[String]],
clientAuth: Option[TLSClientAuth],
sslParameters: Option[SSLParameters]) =
new HttpsConnectionContext(sslContext, sslConfig, enabledCipherSuites, enabledProtocols, clientAuth, sslParameters, http2 = Negotiated)

@deprecated("for binary-compatibility", "2.4.7")
def https(
sslContext: SSLContext,
enabledCipherSuites: Option[immutable.Seq[String]],
enabledProtocols: Option[immutable.Seq[String]],
clientAuth: Option[TLSClientAuth],
sslParameters: Option[SSLParameters]) =
new HttpsConnectionContext(sslContext, None, enabledCipherSuites, enabledProtocols, clientAuth, sslParameters)
new HttpsConnectionContext(sslContext, sslConfig = None, enabledCipherSuites, enabledProtocols, clientAuth, sslParameters, http2 = Negotiated)

def noEncryption() = HttpConnectionContext
}
Expand All @@ -50,17 +63,28 @@ final class HttpsConnectionContext(
val enabledCipherSuites: Option[immutable.Seq[String]] = None,
val enabledProtocols: Option[immutable.Seq[String]] = None,
val clientAuth: Option[TLSClientAuth] = None,
val sslParameters: Option[SSLParameters] = None)
extends akka.http.javadsl.HttpsConnectionContext with ConnectionContext {
val sslParameters: Option[SSLParameters] = None,
http2: UseHttp2 = Negotiated)
extends akka.http.javadsl.HttpsConnectionContext(http2) with ConnectionContext {

// for binary-compatibility, since 2.4.7
@deprecated("for binary-compatibility", since = "10.1.2")
def this(
sslContext: SSLContext,
sslConfig: Option[AkkaSSLConfig],
enabledCipherSuites: Option[immutable.Seq[String]],
enabledProtocols: Option[immutable.Seq[String]],
clientAuth: Option[TLSClientAuth],
sslParameters: Option[SSLParameters]) =
this(sslContext, None, enabledCipherSuites, enabledProtocols, clientAuth, sslParameters)
this(sslContext, sslConfig, enabledCipherSuites, enabledProtocols, clientAuth, sslParameters, http2 = Negotiated)

@deprecated("for binary-compatibility", since = "2.4.7")
def this(
sslContext: SSLContext,
enabledCipherSuites: Option[immutable.Seq[String]],
enabledProtocols: Option[immutable.Seq[String]],
clientAuth: Option[TLSClientAuth],
sslParameters: Option[SSLParameters]) =
this(sslContext, sslConfig = None, enabledCipherSuites, enabledProtocols, clientAuth, sslParameters, http2 = Negotiated)

def firstSession = NegotiateNewSession(enabledCipherSuites, enabledProtocols, clientAuth, sslParameters)

Expand All @@ -71,8 +95,16 @@ final class HttpsConnectionContext(
override def getSslParameters: Optional[SSLParameters] = sslParameters.asJava
}

sealed class HttpConnectionContext extends akka.http.javadsl.HttpConnectionContext with ConnectionContext
final object HttpConnectionContext extends HttpConnectionContext {
sealed class HttpConnectionContext(http2: UseHttp2) extends akka.http.javadsl.HttpConnectionContext(http2) with ConnectionContext {
def this() = this(Negotiated)
}
final object HttpConnectionContext extends HttpConnectionContext(Negotiated) {
/** Java API */
def getInstance() = this

/** Java API */
def create(http2: UseHttp2) = HttpConnectionContext(http2)

def apply() = new HttpConnectionContext(http2 = Negotiated)
def apply(http2: UseHttp2) = new HttpConnectionContext(http2)
}

0 comments on commit 174a694

Please sign in to comment.