Configuration issue for Spray https server with self-signed certificate? - scala

I am using Spray 1.3, Akka 2.3, and Scala 2.11 on Mac 10.9.4 to set up an HTTP server. I am following the Ch. 2 example in Manning's Akka in Action (sample code available here: https://github.com/RayRoestenburg/akka-in-action.git), which compiles, runs, and behaves as expected when I use http, but I am having trouble configuring it for use with https.
To run with https, I have generated a self-signed certificate as follows:
keytool -genkey -keyalg RSA -alias selfsigned -keystore myjks.jks -storepass abcdef -validity 360 -keysize 2048
Following this example, https://github.com/spray/spray/tree/v1.2-M8/examples/spray-can/simple-http-server/src/main/scala/spray/examples
I've added an SSL config class:
package com.goticks
import java.security.{SecureRandom, KeyStore}
import javax.net.ssl.{KeyManagerFactory, SSLContext, TrustManagerFactory}
import spray.io._
// for SSL support (if enabled in application.conf)
trait MySSLConfig {
// if there is no SSLContext in scope implicitly the HttpServer uses the default SSLContext,
// since we want non-default settings in this example we make a custom SSLContext available here
implicit def sslContext: SSLContext = {
val keyStoreResource = "myjks.jks"
val password = "abcdef"
val keyStore = KeyStore.getInstance("jks")
keyStore.load(getClass.getResourceAsStream(keyStoreResource), password.toCharArray)
val keyManagerFactory = KeyManagerFactory.getInstance("SunX509")
keyManagerFactory.init(keyStore, password.toCharArray)
val trustManagerFactory = TrustManagerFactory.getInstance("SunX509")
trustManagerFactory.init(keyStore)
val context = SSLContext.getInstance("TLS")
context.init(keyManagerFactory.getKeyManagers, trustManagerFactory.getTrustManagers, new SecureRandom)
context
}
// if there is no ServerSSLEngineProvider in scope implicitly the HttpServer uses the default one,
// since we want to explicitly enable cipher suites and protocols we make a custom ServerSSLEngineProvider
// available here
implicit def sslEngineProvider: ServerSSLEngineProvider = {
ServerSSLEngineProvider { engine =>
engine.setEnabledCipherSuites(Array("TLS_RSA_WITH_AES_256_CBC_SHA"))
engine.setEnabledProtocols(Array("SSLv3", "TLSv1"))
engine
}
}
}
I've updated the Main class to use the SSL config:
package com.goticks
import akka.actor._
import akka.io.IO
import spray.can.Http
import spray.can.server._
import com.typesafe.config.ConfigFactory
object Main extends App with MySSLConfig {
val config = ConfigFactory.load()
val host = config.getString("http.host")
val port = config.getInt("http.port")
implicit val system = ActorSystem("goticks")
val api = system.actorOf(Props(new RestInterface()), "httpInterface")
IO(Http) ! Http.Bind(listener = api, interface = host, port = port)
}
and I've updated the application.conf:
spray {
can {
server {
server-header = "GoTicks.com REST API"
ssl-encryption = on
}
}
}
After compiling and running the server, I get the following error when I try to do an https GET:
[ERROR] [09/15/2014 10:40:48.056] [goticks-akka.actor.default-dispatcher-4] [akka://goticks/user/IO-HTTP/listener-0/7] Aborting encrypted connection to localhost/0:0:0:0:0:0:0:1%0:59617 due to [SSLHandshakeException:no cipher suites in common] -> [SSLHandshakeException:no cipher suites in common]
I'm not sure if my problem is with the generated key, or with my configuration. Incidentally, my final goal is to use this configuration with a TCP socket (see my other question: TCP socket with SSL on Scala with Akka), but I was unable to find documentation for running secure TCP, so I thought I would start with HTTPS.
Any help is appreciated.

I was finally able to make it work using Apache Camel following the advice found here. Seems like overkill to bring in Camel just to set up the SSLContext, but this is what finally worked.
My SSLConfig ended up looking like this:
import javax.net.ssl.SSLContext
import spray.io._
import org.apache.camel.util.jsse._
trait MySSLConfig {
implicit def sslContext: SSLContext = {
//val keyStoreFile = "/Users/eschow/repo/services/jks/keystore.jks"
val keyStoreFile = "/Users/eschow/code/scala/akka-in-action/chapter2/myjks.jks"
val ksp = new KeyStoreParameters()
ksp.setResource(keyStoreFile);
ksp.setPassword("abcdef")
val kmp = new KeyManagersParameters()
kmp.setKeyStore(ksp)
kmp.setKeyPassword("abcdef")
val scp = new SSLContextParameters()
scp.setKeyManagers(kmp)
val context= scp.createSSLContext()
context
}
implicit def sslEngineProvider: ServerSSLEngineProvider = {
ServerSSLEngineProvider { engine =>
engine.setEnabledCipherSuites(Array("TLS_RSA_WITH_AES_256_CBC_SHA"))
engine.setEnabledProtocols(Array("SSLv3", "TLSv1"))
engine
}
}
}
BTW, the errors logged by Camel were much more helpful. Doing something silly like providing a bad path to the keystone or an incorrect password gives meaningful, human-readable errors rather than the silent failure I was seeing previously.

If you want to read the keystore file outside the project, you could use
new FileInputStream("/Users/eschow/code/scala/akka-in-action/chapter2/myjks.jks")
otherwise you need to put the file in project's resource folder, ex. /your_project/src/main/resource, and read it
getClass.getResourceAsStream("/myjks.jks")

Related

Akka gRPC/HTTP Interop giving 404

I have an application running a gRPC service alongside a simple Akka HTTP endpoint. I am following this guide: https://doc.akka.io/docs/akka-grpc/current/server/akka-http.html. The problem: when curling the HTTP endpoint I get 404 not found. I know it found the server because Akka-HTTP/10.2.5 is the server of the response header.
Some code:
object Server extends App {
val conf = ConfigFactory
.parseString("akka.http.server.preview.enable-http2 = on")
.withFallback(ConfigFactory.defaultApplication())
val system = ActorSystem("Interop", conf)
new Server(system).run()
}
class Server(system: ActorSystem) {
def run() = {
// implicit sys, ec...
val grpcService: HttpRequest => Future[HttpResponse] = ServiceHandler(new Service())
val greeter = get {
pathEndOrSingleSlash {
complete("I am alive")
}
}
// lifted this line straight out of the guide
val grpcRoute = { ctx => grpcService(ctx.request).map(RouteResult.Complete) }
val route = concat(greeter, grpcRoute)
val binding = Http().newServerAt("127.0.0.1", 8080).bind(route)
binding
}
}
When I take out the gRPC route, the greeter endpoint works as intended. Otherwise, when I curl http://localhost:8080, I get
*Mark bundle as not supporting multiuser
<HTTP/1.1 404 Not Found
<Server: akka-http/10.2.5
<other-stuff
I am using akka-gRPC 2.0.0
What should I do to ensure interop between the two routes?
gRPC uses HTTP/2 so try
curl --http2 http://localhost:8080
It should also get rid of the initial "Mark bundle..." message.

Lightbend ssl-config with a non-http TCP Server

I've found several resources that provide details on configuring ssl-config options within the application.conf file and I've identified how to access these configurations using AkkaSSLConfig.get(). I've seen that an https context can be created using a AkkaSSLConfig object as a parameter to ConnectionContext.https().
Is it possible to use this for non-http servers? Is the context returned somehow specific to http? I'm trying to take advantage of ssl-config but it isn't clear to me that it provides any advantages for non-http servers and I don't see any convenient way of building a context from the ssl-config definition, in which case it seems I may as well define the context manually.
Lastly, any examples of building the context for non-http servers are difficult to find. It seems the process may be the same as for http servers, but I'm finding that examples often include the use of classes/methods that have 'http' in the name. If anyone knows of a good example I'd be very appreciative.
import java.io.{File, FileInputStream}
import java.security.{KeyStore, SecureRandom}
import akka.actor.ActorSystem
import akka.http.scaladsl.Http.ServerBinding
import akka.http.scaladsl.model.{HttpResponse, StatusCodes}
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import akka.http.scaladsl.server.Directives.pathSingleSlash
import akka.http.scaladsl.{ConnectionContext, Http}
import akka.stream.{ActorMaterializer, TLSClientAuth}
import com.typesafe.sslconfig.akka.AkkaSSLConfig
import com.typesafe.sslconfig.ssl.{KeyManagerConfig, KeyManagerFactoryWrapper, KeyStoreConfig, SSLConfigFactory, SSLConfigSettings}
import javax.net.ssl.{SSLContext, TrustManagerFactory}
import scala.concurrent.{ExecutionContext, Future}
object Test extends App{
implicit val actorSystem: ActorSystem = ActorSystem("test")
implicit val materializer: ActorMaterializer = ActorMaterializer()
implicit val executionContext: ExecutionContext = actorSystem.dispatcher
val ksConfig: KeyStoreConfig = KeyStoreConfig.apply(data = None,
filePath = Some("/Users/mshaik/testApp/src/main/resources/keystore/localhost.p12")
).withPassword(Some("test"))
val kmConfig: KeyManagerConfig = KeyManagerConfig().withKeyStoreConfigs(List(ksConfig))
val sslConfigSettings: SSLConfigSettings = SSLConfigFactory.defaultConfig.withKeyManagerConfig(kmConfig)
val akkaSSLConfig: AkkaSSLConfig = AkkaSSLConfig.get(actorSystem).withSettings(sslConfigSettings)
val ks: KeyStore = KeyStore.getInstance("PKCS12")
ks.load(new FileInputStream(new File(ksConfig.filePath.get)), ksConfig.password.get.toCharArray)
val kmf: KeyManagerFactoryWrapper = akkaSSLConfig.buildKeyManagerFactory(sslConfigSettings)
kmf.init(ks, ksConfig.password.get.toCharArray)
val tmf: TrustManagerFactory = TrustManagerFactory.getInstance("SunX509")
tmf.init(ks)
val sslContext: SSLContext = SSLContext.getInstance("TLS")
sslContext.init(kmf.getKeyManagers, tmf.getTrustManagers, new SecureRandom)
val ctx: ConnectionContext = ConnectionContext.https(sslContext,
sslConfig = Some(akkaSSLConfig),
clientAuth = Some(TLSClientAuth.Want)
)
var bindingFuture: Future[ServerBinding] = _
Http().setDefaultServerHttpContext(ctx)
val route: Route = pathSingleSlash {
get {
complete(HttpResponse(StatusCodes.OK, entity = "Welcome to base path!"))
}
}
try{
bindingFuture = Http().bindAndHandle(route, "localhost", 8085, connectionContext = ctx)
println( s"Server online at https://localhost:8085/")
} catch {
case ex: Exception =>
println(this.getClass, ex.getMessage, ex)
materializer.shutdown()
actorSystem.terminate()
}
}
I believe the answer to my question is that there isn't much use in thoroughly configuring TLS options within ssl-config when creating a non-HTTP TLS connection.
Not a single example I found shows how to define keystore and truststore parameters within the config and then use those configurations to create the SSLContext object (all examples configure the keystore/truststore parameters manually, within the code). Ultimately I found it wasn't useful to use ssl-config for storing configurations. The only place I found it useful is to obtain the list of default ciphers and default protocols (and hence I still use it in my code).
For reference, below is what I ended up doing to configure the context and initial session structure and create the TCP server. This is very similar to other examples found within documentation as well as some responses here on SO. Some differences in this response: 1) This requires client certificates, 2) This is for a server (as opposed to a client), 3) This code shows how to use factory methods to create the TLS BidiFlow (note the Tcp().bindTls call) 4) This allows you to pass in the Flow that will handle the incoming communications.
object TcpServerBindTls extends StrictLogging {
def apply(hostInterface: String, tcpPort: Int, handler: Flow[ByteString, ByteString, NotUsed])(implicit system: ActorSystem, materializer: ActorMaterializer) = {
val sslContext = buildSSLContext
val firstSession = prepareFirstSession(sslContext)
val connections: Source[Tcp.IncomingConnection, Future[Tcp.ServerBinding]] = Tcp().bindTls(hostInterface, tcpPort, sslContext, firstSession)
connections runForeach { connection =>
logger.info(s"New connection: ${connection}")
connection.handleWith(handler)
}
}
def prepareFirstSession(sslContext: SSLContext)(implicit system: ActorSystem) = {
val sslConfig = AkkaSSLConfig.get(system);
val config = sslConfig.config;
val defaultParams = sslContext.getDefaultSSLParameters();
val defaultProtocols = defaultParams.getProtocols();
val defaultCiphers = defaultParams.getCipherSuites();
val clientAuth = TLSClientAuth.need
defaultParams.setProtocols(defaultProtocols)
defaultParams.setCipherSuites(defaultCiphers)
val firstSession = new TLSProtocol.NegotiateNewSession(None, None, None, None)
.withCipherSuites(defaultCiphers: _*)
.withProtocols(defaultProtocols: _*)
.withParameters(defaultParams)
firstSession
}
def buildSSLContext: SSLContext = {
val bufferedSource = io.Source.fromFile("/path/to/password/file")
val keyStorePassword = bufferedSource.getLines.mkString
bufferedSource.close
val keyStore = KeyStore.getInstance("PKCS12");
val keyStoreLocation = "/path/to/keystore/file/server.p12"
val keyStoreFIS = new FileInputStream(keyStoreLocation)
keyStore.load(keyStoreFIS, keyStorePassword.toCharArray())
val trustStore = KeyStore.getInstance("PKCS12");
val trustStoreLocation = settings.tls.keyStoreLocation;
val trustStoreFIS = new FileInputStream(keyStoreLocation)
trustStore.load(trustStoreFIS, keyStorePassword.toCharArray())
val kmf = KeyManagerFactory.getInstance("SunX509")
kmf.init(keyStore, keyStorePassword.toCharArray())
val tmf = TrustManagerFactory.getInstance("SunX509")
tmf.init(trustStore)
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(kmf.getKeyManagers, tmf.getTrustManagers, new SecureRandom())
sslContext
}
}

Akka Http HTTPS configuration causing Chrome to return ERR_SSL_VERSION_OR_CIPHER_MISMATCH

I am trying to get secure connection to work on my akka-http server (localhost) using a self-signed certificate. I have not been successful.
The Akka-http docs on ssl is a little vague. I have tried to gather bits and pieces of information from all over but still can't get this to work.
I created an ssl trait like below:
trait MySslConfiguration {
def serverContext: HttpsContext = {
val password = "password".toCharArray
val context = SSLContext.getInstance("TLSv1.2")
val keyStore = KeyStore.getInstance("jks")
val keyStoreResource = "/scruples_keystore.jks"
val keyManagerFactory = KeyManagerFactory.getInstance("SunX509")
val trustManagerFactory = TrustManagerFactory.getInstance("SunX509")
keyStore.load(getClass.getClassLoader.getResourceAsStream(keyStoreResource), password)
keyManagerFactory.init(keyStore, password)
trustManagerFactory.init(keyStore)
context.init(keyManagerFactory.getKeyManagers, trustManagerFactory.getManagers, new SecureRandom())
val sslParams = context.getDefaultSSLParameters
sslParams.setEndpointIdentificationAlgorithm("HTTPS")
HttpsContext(sslContext = context
sslParameters = Some(sslParams),
enabledProtocols = Some(List("TLSv1.2", "TLSv1.1", "TLSv1")))
}
}
I add the trait to my server startup like so:
object Main extends App with Core with RestInterface with MySslConfiguration {
val metricRegistry = new com.codahale.metrics.MetricRegistry()
override implicit val injector = GlobalInjector.getInjector
override implicit val system: ActorSystem = injector.instance[ActorSystem]
override def config: Config = injector.instance[Config]
override implicit def executor: ExecutionContextExecutor = system.dispatcher
override val logger: LoggingAdapter = Logging(system, getClass)
override implicit val materializer: Materializer = ActorMaterializer()
val routes = allRoutes
Http().bindAndHandle(routes, config.getString("http.host"), config.getInt("http.port"), httpsContext = Some(serverContext))
The self-signed jks keystore was created as below:
keytool -genkey -keyalg RSA -keysize 2048 -keystore scruples_keystore.jks -alias myalias
Can someone please help?
What i'm I doing wrong? I am sure there is a configuration issue but can't seem to figure it out.
Another question is what would change if I were using a proper cerficate from a CA?
Many thanks
It so turned out that specifying the keystore location as val keyStoreResource = "/scruples_keystore.jks" was the issue. Specifying the keystore location as val keyStoreResource = "scruples_keystore" did the trick and all is well with the world again...

Spray-client sendReceive throws SSLHandshakeException

I am trying to make spray-client connect using https to restricted rest api. The problem is that the certificate of the remote server is not registered as trusted, the simple Get() connection is then refused with SSLHandshakeException and I struggle to find any information about how to make this work. This somehow does work from my local machine without a need to change something.
I have found tutorials about how to put the certificate into jvm truststore, however since I am using dokku/docker, AFAIK the jvm instance is container specific (or?). Even though, I may in the future redeploy application on different machines, I'd like to have it defined in the application rather than setting jvm up everytime.
This is the first time I am facing SSL programmatically, so I may make wrong assumptions about how it works. Can you help?
I am not an expert in scala and I have never used spray-client but I will try to help you based on my Java experience.
You have two options, initialize a SSLContext with a TrustManagerFactory from a keystore with the server certificate (SECURE)
File keyStoreFile = new File("./myKeyStore");
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(new FileInputStream(keyStoreFile), "keyStorePassword".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(ks);
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, tmf.getTrustManagers(), new java.security.SecureRandom());
or create a Dummy TrustManagerFactory which accepts any certificate (INSECURE)
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;
public class DummyTrustManager implements X509TrustManager {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
/* (non-Javadoc)
* #see javax.net.ssl.X509TrustManager#checkClientTrusted(X509Certificate[], java.lang.String)
*/
public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
}
/* (non-Javadoc)
* #see javax.net.ssl.X509TrustManager#checkServerTrusted(X509Certificate[], java.lang.String)
*/
public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
}
}
initialize SSLContext by this way (it is very similar in spray-client)
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, new TrustManager[] { new DummyTrustManager() }, new java.security.SecureRandom());
I don't know the Scala syntax but should not be difficult to translate it to you.
Hope this helps.
EDIT (suggested by Matej Briškár): The above is the correct approach, however for spray-client it is not that easy. To make sendReceive work with SSL, you need to first establish connection and then pass this connection to sendReceive.
First create implicit trust manager as described above. For example:
implicit def sslContext: SSLContext = {
val context = SSLContext.getInstance("TLS")
context.init(null, Array[TrustManager](new DummyTrustManager), new SecureRandom())
context
}
Note that this connection will time out after a while so you may want to change this default behaviour.
Then you need to establish the connection that will use this implicit like:
val connection = {
Await.result((IO(Http) ? HostConnectorSetup(host, port = 443, sslEncryption = true)).map { case HostConnectorInfo(hostConnector, _) => hostConnector }, timeout.duration)
}
Note: host means the URL you are trying to reach. Also timeout is coming from outside of this code snippet.
And finally you can use sendReceive(connection) to access the SSL encrypted host.
Note: The original edit had a reference:
According to discussion online the issue is going to be fixed though.
However, the discussion is from 2013 and now it's 2016. The problem of needing a connection be made to get SSL working seems still be there. Not sure if the discussion is relevant, any more.
Here is my 2 cents if you just want to do it in INSECURE way, I just create my sendReceive method to send (HttpRequest, HostConnectorSetup) instead of HttpRequest
import java.security.SecureRandom
import java.security.cert.X509Certificate
import javax.net.ssl.{SSLContext, TrustManager, X509TrustManager}
import akka.actor.ActorRefFactory
import akka.io.IO
import akka.pattern.ask
import akka.util.Timeout
import spray.can.Http
import spray.can.Http.HostConnectorSetup
import spray.client.pipelining._
import spray.http.{HttpResponse, HttpResponsePart}
import spray.io.ClientSSLEngineProvider
import spray.util._
import scala.concurrent.ExecutionContext
import scala.concurrent.duration._
object Test {
// prepare your sslContext and engine Provider
implicit lazy val engineProvider = ClientSSLEngineProvider(engine => engine)
implicit lazy val sslContext: SSLContext = {
val context = SSLContext.getInstance("TLS")
context.init(null, Array[TrustManager](new DummyTrustManager), new SecureRandom)
context
}
private class DummyTrustManager extends X509TrustManager {
def isClientTrusted(cert: Array[X509Certificate]): Boolean = true
def isServerTrusted(cert: Array[X509Certificate]): Boolean = true
override def getAcceptedIssuers: Array[X509Certificate] = Array.empty
override def checkClientTrusted(x509Certificates: Array[X509Certificate], s: String): Unit = {}
override def checkServerTrusted(x509Certificates: Array[X509Certificate], s: String): Unit = {}
}
// rewrite sendReceiveMethod fron spray.client.pipelining
def mySendReceive(implicit refFactory: ActorRefFactory, executionContext: ExecutionContext,
futureTimeout: Timeout = 60.seconds): SendReceive = {
val transport = IO(Http)(actorSystem)
// HttpManager actually also accepts Msg (HttpRequest, HostConnectorSetup)
request =>
val uri = request.uri
val setup = HostConnectorSetup(uri.authority.host.toString, uri.effectivePort, uri.scheme == "https")
transport ? (request, setup) map {
case x: HttpResponse => x
case x: HttpResponsePart => sys.error("sendReceive doesn't support chunked responses, try sendTo instead")
case x: Http.ConnectionClosed => sys.error("Connection closed before reception of response: " + x)
case x => sys.error("Unexpected response from HTTP transport: " + x)
}
}
// use mySendReceive instead spray.client.pipelining.sendReceive
}

Forwarding HTTP/REST Request to another REST server in Spray

I've a bunch of existing REST services (#1 and #2 below) that are running on different endpoints that are used internally only. Now I want to expose some of these REST APIs (API-1 and API-2) externally using Spray because this external endpoint will also provide some additional APIs (API-3, API-4).
Is there a simple/recommended way to forward the external REST requests to my new endpoint to existing REST endpoints?
It sounds like what you want is the proposed proxyTo directive:
path("foo") {
get {
proxyTo("http://oldapi.example.com")
}
}
(or, more likely, proxyToUnmatchedPath). There is an issue open for it:
https://github.com/spray/spray/issues/145
Looks like somebody has been working on this; here is a commit in a Spray fork:
https://github.com/bthuillier/spray/commit/d31fc1b5e1415e1b908fe7d1f01f364a727e2593
But the commit appears not yet to be in the master Spray repo. You could ask about its status on the issue page.
Also, here is a blog post from CakeSolutions about how you can do the proxying manually:
http://www.cakesolutions.net/teamblogs/http-proxy-with-spray
A comment on that page points out that Spray has an undocumented thing called ProxySettings, and points to the following tests for it:
https://github.com/spray/spray/blob/master/spray-can-tests/src/test/scala/spray/can/client/ProxySpec.scala
UPDATE; Soumya has asked the Spray team about this on the spray-user Google Group:
https://groups.google.com/forum/#!topic/spray-user/MlUn-y4X8RE
I was able to proxy a single service with the help of the CakeSolution blog. In the following example, the proxy is running on http://localhost:20000 and the actual REST endpoint is running at http://localhost:7001.
Not sure how proxy multiple services using this approach.
I like #cmbaxter's solution of using Nginx as the proxy but I'm still curious if there is a way to extend the following approach to do it in Spray.
import akka.actor.{ActorRef, Props}
import akka.io.IO
import akka.util.Timeout
import spray.can.Http
import spray.can.Http.ClientConnectionType
import spray.http.HttpResponse
import spray.routing.{RequestContext, HttpServiceActor, Route}
import scala.concurrent.duration._
import akka.pattern.ask
object ProxyRESTService {
def main(args: Array[String]) {
//create an actor system
implicit val actorSystem = akka.actor.ActorSystem("proxy-actor-system")
implicit val timeout: Timeout = Timeout(5 seconds)
implicit val dis = actorSystem.dispatcher
//host on which proxy is running
val proxyHost = "localhost"
//port on which proxy is listening
val proxyPort = 20000
//host where REST service is running
val restServiceHost = "localhost"
//port where REST service is running
val restServicePort = 7001
val setup = Http.HostConnectorSetup(
proxyHost,
proxyPort,
connectionType = ClientConnectionType.Proxied(restServiceHost, restServicePort)
)
IO(Http)(actorSystem).ask(setup).map {
case Http.HostConnectorInfo(connector, _) =>
val service = actorSystem.actorOf(Props(new ProxyService(connector)))
IO(Http) ! Http.Bind(service, proxyHost, port = proxyPort)
}
}
}
.
class ProxyService(connector: ActorRef) extends HttpServiceActor {
implicit val timeout: Timeout = Timeout(5 seconds)
implicit def executionContext = actorRefFactory.dispatcher
val route: Route = (ctx: RequestContext) => ctx.complete(connector.ask(ctx.request).mapTo[HttpResponse])
def receive: Receive = runRoute(route)
}