Skip to content

Commit

Permalink
config setting for multiple ports (#1278)
Browse files Browse the repository at this point in the history
Updates the server config so it is easy to run on multiple
ports. SSL config can optionally be provided for a given
port for HTTPS.
  • Loading branch information
brharrington authored Mar 19, 2021
1 parent 75337ff commit d6425cd
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 13 deletions.
16 changes: 14 additions & 2 deletions atlas-akka/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,20 @@ atlas.akka {
# Name of the actor system
name = "atlas"

# Port to use for the web server
port = 7101
# Ports to use for the web server
ports = [
{
port = 7101
secure = false
},
#{
# port = 7102
# secure = true
# ssl-config {
# // See https://lightbend.github.io/ssl-config/KeyStores.html
# }
#}
]

# How long to wait before giving up on bind
bind-timeout = 5 seconds
Expand Down
123 changes: 113 additions & 10 deletions atlas-akka/src/main/scala/com/netflix/atlas/akka/WebServer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,29 @@ package com.netflix.atlas.akka
import javax.inject.Inject
import javax.inject.Singleton
import akka.actor.ActorSystem
import akka.http.scaladsl.ConnectionContext
import akka.http.scaladsl.Http
import akka.http.scaladsl.Http.ServerBinding
import akka.http.scaladsl.HttpsConnectionContext
import akka.http.scaladsl.server.Route
import akka.stream.Materializer
import com.netflix.iep.service.AbstractService
import com.netflix.iep.service.ClassFactory
import com.netflix.spectator.api.Registry
import com.typesafe.config.Config
import com.typesafe.scalalogging.StrictLogging
import com.typesafe.sslconfig.ssl.ClientAuth
import com.typesafe.sslconfig.ssl.ConfigSSLContextBuilder
import com.typesafe.sslconfig.ssl.DefaultKeyManagerFactoryWrapper
import com.typesafe.sslconfig.ssl.DefaultTrustManagerFactoryWrapper
import com.typesafe.sslconfig.ssl.SSLConfigFactory
import com.typesafe.sslconfig.ssl.SSLConfigSettings
import com.typesafe.sslconfig.util.LoggerFactory
import com.typesafe.sslconfig.util.NoDepsLogger
import org.slf4j.Logger

import scala.concurrent.Await
import scala.concurrent.ExecutionContext
import scala.concurrent.Future
import scala.concurrent.duration.Duration

Expand Down Expand Up @@ -56,25 +68,116 @@ class WebServer @Inject() (
) extends AbstractService
with StrictLogging {

private implicit val executionContext = system.dispatcher
private implicit val executionContext: ExecutionContext = system.dispatcher

private val port = config.getInt("atlas.akka.port")
private val portConfigs = WebServer.getPortConfigs(config, "atlas.akka.ports")

private var bindingFuture: Future[ServerBinding] = _
private var bindingFutures: List[Future[ServerBinding]] = Nil

protected def startImpl(): Unit = {
val handler = new RequestHandler(config, classFactory)
bindingFuture = Http()
.newServerAt("0.0.0.0", port)
.bindFlow(Route.toFlow(handler.routes))
logger.info(s"started $name on port $port")
val routes = Route.toFlow(handler.routes)
bindingFutures = portConfigs.map { portConfig =>
var builder = Http().newServerAt("0.0.0.0", portConfig.port)
if (portConfig.secure) {
builder = builder.enableHttps(portConfig.createConnectionContext)
}
val future = builder.bindFlow(routes)
logger.info(s"started $name on port ${portConfig.port}")
future
}
}

protected def stopImpl(): Unit = {
if (bindingFuture != null) {
Await.ready(bindingFuture.flatMap(_.unbind()), Duration.Inf)
}
val shutdownFuture = Future.sequence(bindingFutures.map(_.flatMap(_.unbind())))
Await.ready(shutdownFuture, Duration.Inf)
}

def actorSystem: ActorSystem = system
}

object WebServer {

private case class PortConfig(
port: Int,
secure: Boolean,
sslConfigOption: Option[SSLConfigSettings]
) {
require(!secure || sslConfigOption.isDefined, s"ssl-config is not set for secure port $port")

private val sslContext = sslConfigOption.map { sslConfig =>
val keyManager = new DefaultKeyManagerFactoryWrapper(sslConfig.keyManagerConfig.algorithm)
val trustManager = new DefaultTrustManagerFactoryWrapper(
sslConfig.trustManagerConfig.algorithm
)
new ConfigSSLContextBuilder(SslLoggerFactory, sslConfig, keyManager, trustManager).build()
}.orNull

private val clientAuth = sslConfigOption.map(_.sslParametersConfig.clientAuth).orNull

def createConnectionContext: HttpsConnectionContext = {
ConnectionContext.httpsServer { () =>
val sslEngine = sslContext.createSSLEngine()
sslEngine.setUseClientMode(false)
clientAuth match {
case ClientAuth.Default => sslEngine.setNeedClientAuth(true)
case ClientAuth.None =>
case ClientAuth.Need => sslEngine.setNeedClientAuth(true)
case ClientAuth.Want => sslEngine.setWantClientAuth(true)
}
sslEngine
}
}
}

private def getPortConfigs(config: Config, path: String): List[PortConfig] = {
import scala.jdk.CollectionConverters._
config
.getConfigList(path)
.asScala
.map(toPortConfig)
.toList
}

private def toPortConfig(config: Config): PortConfig = {
PortConfig(
config.getInt("port"),
config.getBoolean("secure"),
if (config.hasPath("ssl-config"))
Some(SSLConfigFactory.parse(config.getConfig("ssl-config")))
else
None
)
}

/**
* The sslconfig library has its own logging interface to avoid dependencies. Map it
* to slf4j.
*/
private object SslLoggerFactory extends LoggerFactory {

override def apply(clazz: Class[_]): NoDepsLogger = {
apply(clazz.getName)
}

override def apply(name: String): NoDepsLogger = {
val logger = org.slf4j.LoggerFactory.getLogger(name)
new SslLogger(logger)
}
}

private class SslLogger(logger: Logger) extends NoDepsLogger {

override def isDebugEnabled: Boolean = logger.isDebugEnabled

override def debug(msg: String): Unit = logger.debug(msg)

override def info(msg: String): Unit = logger.info(msg)

override def warn(msg: String): Unit = logger.warn(msg)

override def error(msg: String): Unit = logger.error(msg)

override def error(msg: String, throwable: Throwable): Unit = logger.error(msg, throwable)
}
}
2 changes: 1 addition & 1 deletion atlas-standalone/src/main/resources/log4j2.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" shutdownHook="disable">
<Properties>
<Property name="dfltPattern">%d{yyyy-MM-dd'T'HH:mm:ss.SSS} %-5level [%t] %class: %msg%n</Property>
<Property name="dfltPattern">%d{yyyy-MM-dd'T'HH:mm:ss.SSS} %-5level [%t] %logger: %msg%n</Property>
</Properties>
<Appenders>
<Console name="STDERR" target="SYSTEM_ERR">
Expand Down

0 comments on commit d6425cd

Please sign in to comment.