9

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?

enter image description here

Soumya Simanta
  • 10,777
  • 23
  • 95
  • 153
  • 2
    Not 100% I understand the question - but if you just want simple forwarding, why not put Apache or some webserver in front, and let it do that via a proxypass? A reverse proxy/load balancer may do a better job at routing traffic that a spray server. – Bruce Lowe Jan 17 '15 at 12:13
  • I could have done that if I only wanted routing. However, I need to provide some extra APIs (API-3, API-4) in the *new* REST service as well as wrap the calls to *existing* APIs (API-1, API-2). – Soumya Simanta Jan 17 '15 at 13:08
  • 1
    You could still use something like nginx/apache to support your scenario without having to do the proxying in the scala code. You could set up rules in the webserver config to support proxying certain urls only (APIs 1 and 2) and then let the rest (3 and 4) fall through to your underlying spray server. We do this exact thing with nginx. – cmbaxter Jan 17 '15 at 14:33
  • @cmbaxter - this is helpful. If possible, can you point me some place to get started with nginx configuration. – Soumya Simanta Jan 17 '15 at 14:41
  • 1
    Here is the docs for the proxy_pass directive. Should serve as a starting point. http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass – cmbaxter Jan 17 '15 at 14:49
  • Glad the CakeSolutions blog helped. Did you check out ProxySettings or ask about this on the Spray issue I mentioned? You can also ask for help on the spray-user google group; there you will get a response from the Spray folks themselves. – AmigoNico Jan 19 '15 at 15:07
  • @AmigoNico I have accepted your answer. I have already asked this question on the Spray mailing list and hopefully will get a response. I'm using Nginx for now and will get back to this once I've some more cycles. – Soumya Simanta Jan 19 '15 at 15:17
  • Got it -- thanks. I'm going to put a link to your inquiry in the accepted answer to make this a better resource for others. – AmigoNico Jan 19 '15 at 15:24
  • @AmigoNico - thanks. The Ngnix solution works very nicely and I think I did read the Spray lead developer recommend it as well. So the only reason why I would prefer a Spray only solution is to ease deployment (just deploy the Spray fatjar). – Soumya Simanta Jan 19 '15 at 15:42

2 Answers2

4

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

AmigoNico
  • 6,142
  • 1
  • 30
  • 43
3

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)
}
Soumya Simanta
  • 10,777
  • 23
  • 95
  • 153