Skip to content

Instantly share code, notes, and snippets.

@eyalroth
Last active October 17, 2018 14:43
Show Gist options
  • Save eyalroth/05a24c1ba44a7c74d6d3e8b4e940441a to your computer and use it in GitHub Desktop.
Save eyalroth/05a24c1ba44a7c74d6d3e8b4e940441a to your computer and use it in GitHub Desktop.
akka-http #2257 - Http server
import java.net.InetSocketAddress
import java.security.{KeyStore, SecureRandom}
import akka.actor.ActorSystem
import akka.http.scaladsl.Http.{HttpServerTerminated, HttpTerminated, ServerBinding}
import akka.http.scaladsl.server.{Route, RouteResult, RoutingLog}
import akka.http.scaladsl.settings.{ParserSettings, RoutingSettings}
import akka.http.scaladsl.{ConnectionContext, Http}
import akka.stream.{ActorMaterializer, ActorMaterializerSettings}
import com.typesafe.scalalogging.StrictLogging
import javax.net.ssl.{KeyManagerFactory, SSLContext, TrustManagerFactory}
import java.io.Closeable
import scala.concurrent.duration._
import scala.concurrent.{Await, Future}
import scala.util.control.NonFatal
import scala.util.{Failure, Success}
class HttpServer(host: String, port: Int, route: Route, secure: Boolean)(implicit val materializer: ActorMaterializer)
extends Closeable with StrictLogging {
/* --- Data Members --- */
private var connectedBinding: Option[ServerBinding] = None
/* --- Constructors --- */
def this(host: String, port: Int, route: Route, secure: Boolean, system: ActorSystem, dispatcherName: String) = {
this(host, port, route, secure)(
ActorMaterializer(ActorMaterializerSettings(system).withDispatcher(s"akka.dispatchers.$dispatcherName"))(system))
}
def this(configuration: HttpServerConfig, route: Route, system: ActorSystem) = {
this(configuration.host, configuration.port, route, configuration.secure, system, configuration.dispatcherName)
}
/* --- Methods --- */
/* --- Public Methods --- */
def start(): InetSocketAddress = {
connectedBinding match {
case Some(binding) =>
throw new IllegalStateException(s"Already binding: ${binding.localAddress}")
case None =>
logger.info(s"Starting HTTP server on $host:$port")
implicit val routingSettings = RoutingSettings(materializer.system.settings.config)
implicit val parserSettings = ParserSettings(materializer.system.settings.config)
implicit val routingLog = RoutingLog.fromActorSystem(materializer.system)
val flow = RouteResult.route2HandlerFlow(route)
val bindFuture = Http()(materializer.system).bindAndHandle(flow, host, port, context())
try {
val binding = Await.result(bindFuture, BIND_TIMEOUT)
connectedBinding = Some(binding)
logger.info(s"HTTP server started on ${binding.localAddress}")
binding.localAddress
} catch {
case NonFatal(e) =>
logger.error(s"HTTP server failed to start", e)
throw e
}
}
}
def stop(): Future[HttpTerminated] = {
connectedBinding match {
case Some(binding) =>
connectedBinding = None
binding.terminate(10 seconds)
case None => Future.successful(HttpServerTerminated)
}
}
/* --- Private Methods --- */
private def context(): ConnectionContext = {
if (secure) {
val ks: KeyStore = KeyStore.getInstance("PKCS12")
val keystore = getClass.getClassLoader.getResourceAsStream("mycert.p12")
val pw = "imabarbiegirl".toCharArray
require(keystore != null, "Keystore required!")
ks.load(keystore, pw)
val keyManagerFactory: KeyManagerFactory = KeyManagerFactory.getInstance("SunX509")
keyManagerFactory.init(ks, pw)
val tmf: TrustManagerFactory = TrustManagerFactory.getInstance("SunX509")
tmf.init(ks)
val sslContext: SSLContext = SSLContext.getInstance("TLS")
sslContext.init(keyManagerFactory.getKeyManagers, tmf.getTrustManagers, new SecureRandom)
ConnectionContext.https(sslContext)
} else {
ConnectionContext.noEncryption()
}
}
override def close(): Unit = {
Await.ready(stop(), 1 minute).value.get match {
case Success(_) =>
case Failure(exception) => logger.error("Server shut down failed", exception)
}
}
}
object HttpServer {
/* --- Constants --- */
private val BIND_TIMEOUT = 60 seconds
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment