Permalink
Browse files

! can: move `client.ssl-encryption` setting from reference.conf into …

…`Http.Connect` message, fixes #396

This has several implications:

 * user-level API is smaller now
 * there usually will be only one pipeline for all connections (because
   there will be only one distinct settingsGroup in the HttpManager)
 * we will throw an error if we find a user using the old
   `spray.can.client.ssl-encryption` setting to make sure they don't rely on
   it
  • Loading branch information...
jrudolph committed Jul 30, 2013
1 parent 8a4e617 commit e922cd48390ab975c8b2145409aa6b74f0ac9f67
@@ -106,11 +106,18 @@ Additionally *spray-can* will render a
SSL Support
-----------
-If enabled via the ``ssl-encryption`` config setting the *spray-can* connection actors pipe all IO traffic through an
-``SslTlsSupport`` module, which can perform transparent SSL/TLS encryption. This module is configured via the implicit
+SSL support is enabled
+
+ - for the connection-level API by setting ``Http.Connect(sslEncryption = true)`` when connecting to a server
+ - for the host-level API by setting ``Http.HostConnectorSetup(sslEncryption = true`` when creating a host connector
+ - for the request-level API by using an ``https`` URL in the request
+
+Particular SSL settings can be configured via the implicit
``ClientSSLEngineProvider`` member on the ``Http.Connect`` and ``Http.HostConnectorSetup`` command messages.
An ``ClientSSLEngineProvider`` is essentially a function ``PipelineContext ⇒ Option[SSLEngine]`` which determines
-whether encryption is to be performed and, if so, which ``javax.net.ssl.SSLEngine`` instance is to be used.
+whether encryption is to be performed and, if so, which ``javax.net.ssl.SSLEngine`` instance is to be used. By returning
+``None`` the ``ClientSSLEngineProvider`` can decide to disable SSL support even if SSL support was requested by the means
+described above.
If you'd like to apply some custom configuration to your ``SSLEngine`` instances an easy way would be to bring a custom
engine provider into scope, e.g. like this:
@@ -43,7 +43,7 @@ trait ConnectionLevelApiDemo {
sender ! request
context.become(waitingForResponse(commander))
- case Http.CommandFailed(Http.Connect(address, _, _, _)) =>
+ case Http.CommandFailed(Http.Connect(address, _, _, _, _)) =>
log.warning("Could not connect to {}", address)
commander ! Status.Failure(new RuntimeException("Connection error"))
context.stop(self)
@@ -162,7 +162,7 @@ class SprayCanClientSpec extends Specification {
}
val probe = TestProbe()
- probe.send(IO(Http), Http.HostConnectorSetup(hostname, port, true))
+ probe.send(IO(Http), Http.HostConnectorSetup(hostname, port, sslEncryption = true))
val Http.HostConnectorInfo(hostConnector, _) = probe.expectMsgType[Http.HostConnectorInfo]
probe.sender === hostConnector
probe.reply(Get("/"))
@@ -158,12 +158,6 @@ spray.can {
# `User-Agent` header.
user-agent-header = spray-can/${spray.version}
- # Enables/disables SSL encryption
- # If enabled the client uses the implicit `ClientSSLEngineProvider` member
- # of the Connect command to create `SSLEngine` instances for the underlying
- # IO connection.
- ssl-encryption = off
-
# The time after which an idle connection will be automatically closed.
# Set to `infinite` to completely disable idle timeouts.
idle-timeout = 60 s
@@ -33,13 +33,14 @@ object Http extends ExtensionKey[HttpExt] {
type Command = Tcp.Command
case class Connect(remoteAddress: InetSocketAddress,
+ sslEncryption: Boolean,
localAddress: Option[InetSocketAddress],
options: immutable.Traversable[Inet.SocketOption],
settings: Option[ClientConnectionSettings])(implicit val sslEngineProvider: ClientSSLEngineProvider) extends Command
object Connect {
- def apply(host: String, port: Int = 80, localAddress: Option[InetSocketAddress] = None,
+ def apply(host: String, port: Int = 80, sslEncryption: Boolean = false, localAddress: Option[InetSocketAddress] = None,
options: immutable.Traversable[Inet.SocketOption] = Nil, settings: Option[ClientConnectionSettings] = None)(implicit sslEngineProvider: ClientSSLEngineProvider): Connect =
- apply(new InetSocketAddress(host, port), localAddress, options, settings)
+ apply(new InetSocketAddress(host, port), sslEncryption, localAddress, options, settings)
}
case class Bind(listener: ActorRef,
@@ -54,6 +55,7 @@ object Http extends ExtensionKey[HttpExt] {
}
case class HostConnectorSetup(host: String, port: Int = 80,
+ sslEncryption: Boolean = false,
options: immutable.Traversable[Inet.SocketOption] = Nil,
settings: Option[HostConnectorSettings] = None)(implicit val sslEngineProvider: ClientSSLEngineProvider) extends Command {
private[can] def normalized(implicit refFactory: ActorRefFactory) =
@@ -62,8 +64,11 @@ object Http extends ExtensionKey[HttpExt] {
}
object HostConnectorSetup {
def apply(host: String, port: Int, sslEncryption: Boolean)(implicit refFactory: ActorRefFactory, sslEngineProvider: ClientSSLEngineProvider): HostConnectorSetup = {
- val connectionSettings = ClientConnectionSettings(actorSystem).copy(sslEncryption = sslEncryption)
- apply(host, port, settings = Some(HostConnectorSettings(actorSystem).copy(connectionSettings = connectionSettings)))
+ val connectionSettings = ClientConnectionSettings(actorSystem)
+ apply(
+ host, port,
+ sslEncryption = sslEncryption,
+ settings = Some(HostConnectorSettings(actorSystem).copy(connectionSettings = connectionSettings)))
}
}
@@ -40,7 +40,7 @@ private[can] class HttpManager(httpSettings: HttpExt#Settings) extends Actor wit
val req = request.withEffectiveUri(securedConnection = false)
val Uri.Authority(host, port, _) = req.uri.authority
val effectivePort = if (port == 0) Uri.defaultPorts(req.uri.scheme) else port
- val connector = hostConnectorFor(HostConnectorSetup(host.toString, effectivePort, req.uri.scheme == "https"))
+ val connector = hostConnectorFor(HostConnectorSetup(host.toString, effectivePort, sslEncryption = req.uri.scheme == "https"))
// never render absolute URIs here and we also drop any potentially existing fragment
val relativeUri = Uri(
path = if (req.uri.path.isEmpty) Uri.Path./ else req.uri.path,
@@ -24,7 +24,6 @@ import spray.util._
case class ClientConnectionSettings(
userAgentHeader: String,
- sslEncryption: Boolean,
idleTimeout: Duration,
requestTimeout: Duration,
reapingCycle: Duration,
@@ -44,16 +43,22 @@ case class ClientConnectionSettings(
}
object ClientConnectionSettings extends SettingsCompanion[ClientConnectionSettings]("spray.can.client") {
- def fromSubConfig(c: Config) = apply(
- c getString "user-agent-header",
- c getBoolean "ssl-encryption",
- c getDuration "idle-timeout",
- c getDuration "request-timeout",
- c getDuration "reaping-cycle",
- c getBytes "response-chunk-aggregation-limit" toInt,
- c getBytes "request-size-hint" toInt,
- c getDuration "connecting-timeout",
- ParserSettings fromSubConfig c.getConfig("parsing"))
+ def fromSubConfig(c: Config) = {
+ if (c.hasPath("ssl-encryption"))
+ throw new IllegalArgumentException(
+ "spray.can.client.ssl-encryption not supported any more. " +
+ "Use Http.Connect(sslEncryption = true) to enable ssl encryption for a connection.")
+
+ apply(
+ c getString "user-agent-header",
+ c getDuration "idle-timeout",
+ c getDuration "request-timeout",
+ c getDuration "reaping-cycle",
+ c getBytes "response-chunk-aggregation-limit" toInt,
+ c getBytes "request-size-hint" toInt,
+ c getDuration "connecting-timeout",
+ ParserSettings fromSubConfig c.getConfig("parsing"))
+ }
def apply(optionalSettings: Option[ClientConnectionSettings])(implicit actorRefFactory: ActorRefFactory): ClientConnectionSettings =
optionalSettings getOrElse apply(actorSystem)
@@ -71,7 +71,7 @@ private[can] class HttpClientConnection(connectCommander: ActorRef,
def remoteAddress = connected.remoteAddress
def localAddress = connected.localAddress
def log = actor.log
- def sslEngine = sslEngineProvider(this)
+ def sslEngine = if (connect.sslEncryption) sslEngineProvider(this) else None
}
}
@@ -84,7 +84,7 @@ private[can] object HttpClientConnection {
ResponseParsing(parserSettings) >>
RequestRendering(settings) >>
ConnectionTimeouts(idleTimeout) ? (reapingCycle.isFinite && idleTimeout.isFinite) >>
- SslTlsSupport ? sslEncryption >>
+ SslTlsSupport >>
TickGenerator(reapingCycle) ? (idleTimeout.isFinite || requestTimeout.isFinite)
}
@@ -28,6 +28,7 @@ import spray.io.ClientSSLEngineProvider
import spray.http._
private[client] class HttpHostConnectionSlot(host: String, port: Int,
+ sslEncryption: Boolean,
options: immutable.Traversable[Inet.SocketOption],
idleTimeout: Duration,
clientConnectionSettingsGroup: ActorRef)(implicit sslEngineProvider: ClientSSLEngineProvider)
@@ -44,7 +45,7 @@ private[client] class HttpHostConnectionSlot(host: String, port: Int,
{
case ctx: RequestContext
log.debug("Attempting new connection to {}:{}", host, port)
- clientConnectionSettingsGroup ! Http.Connect(host, port, None, options, None)
+ clientConnectionSettingsGroup ! Http.Connect(host, port, sslEncryption, None, options, None)
context.setReceiveTimeout(Duration.Undefined)
context.become(connecting(Queue(ctx)))
@@ -33,7 +33,7 @@ private[can] class HttpHostConnector(normalizedSetup: Http.HostConnectorSetup, c
private[this] val dispatchStrategy = if (settings.pipelining) new PipelinedStrategy else new NonPipelinedStrategy
private[this] var openRequestCounts = Map.empty[ActorRef, Int] // open requests per child, holds -1 if unconnected
private[this] val hostHeader = {
- val encrypted = settings.connectionSettings.sslEncryption
+ val encrypted = normalizedSetup.sslEncryption
val port = normalizedSetup.port match {
case 443 if encrypted 0
case 80 if !encrypted 0
@@ -122,7 +122,7 @@ private[can] class HttpHostConnector(normalizedSetup: Http.HostConnectorSetup, c
def newConnectionChild(): ActorRef = {
val child = context.watch {
context.actorOf(
- props = Props(new HttpHostConnectionSlot(host, port, options, settings.idleTimeout,
+ props = Props(new HttpHostConnectionSlot(host, port, sslEncryption, options, settings.idleTimeout,
clientConnectionSettingsGroup)),
name = counter.next().toString)
}
@@ -121,7 +121,7 @@ class HttpHostConnectorSpec extends Specification with NoTimeConversions {
def newPipeline(pipelined: Boolean, maxConnections: Int = 4) = {
val settings = HostConnectorSettings(system).copy(maxConnections = maxConnections, pipelining = pipelined)
- val Http.HostConnectorInfo(connector, _) = IO(Http).ask(Http.HostConnectorSetup(interface, port, Nil, Some(settings))).await
+ val Http.HostConnectorInfo(connector, _) = IO(Http).ask(Http.HostConnectorSetup(interface, port, false, Nil, Some(settings))).await
sendReceive(connector)
}

0 comments on commit e922cd4

Please sign in to comment.