Search code examples
scalaroutesakkaactorspray

Implementing routing with Spray clients inside an ActorSystem


I've a Spray client that is used to invoke a REST web service. Currently I'm creating an instance of this client (using new GeoSprayWebClient) [see code below] and reusing it inside my actors to make the REST requests. However, a single instance of the service is not able to handle all the load. Therefore, I want to introduce replicas of the REST service.

I'm new to Spray and still trying to learn the fundamentals. My questions are

1) I know Spray internally uses Akka actors. In this particular case can I get an ActorRef for client instances so that I can create multiple client ActorRefs and use them to create an Akka router. 2) Does the Spray client API provide any kind of routing capability that will support my use case?

 import akka.actor.ActorSystem
 import spray.client.pipelining._
 import spray.http._
 import scala.concurrent.Future


trait GeoWebClient {
  def get(url: String, params: Map[String, String]): Future[String]
}

class GeoSprayWebClient(implicit system: ActorSystem) extends GeoWebClient {

  import system.dispatcher

  // create a function from HttpRequest to a Future of HttpResponse
  val pipeline: HttpRequest => Future[HttpResponse] = sendReceive

  // create a function to send a GET request and receive a string response
  def get(path: String, params: Map[String, String]): Future[String] = {
    val uri = Uri(path) withQuery params
    val request = Get(uri)
    val futureResponse = pipeline(request)
    futureResponse.map(_.entity.asString)
  }
}

Solution

  • Based on this I was able to get the ActorRef

    def createHttpRESTClient(host: String, port: Int): ActorRef = {
    
          // execution context for future transformations below
          import system.dispatcher
          implicit val timeout: Timeout = 10 seconds
    
          val ref: Future[ActorRef] = for {
            Http.HostConnectorInfo(hostConnector: ActorRef, _) <- IO(Http) ? Http.HostConnectorSetup(host, port)
          }
          yield {
            hostConnector
          }
          //FIXME - TODO fix this it's really bad. However, We are going to create this only once when we create the actor, so I guess it's okay for now.
          Await.result(ref, 10 seconds)
        }
    

    And this is how I'm sending the request and getting a response from the service using the ActorRef.

      def sendReq(text: String): Future[String] = {
        import spray.http._
        val params = Map(("key" -> text))
        val uri = Uri("/myservice") withQuery params
        val request = Get(uri)
        //send GET request using the "ask" pattern; the timeout
        //TODO - not sure if we can use tell instead of ask here ? 
        val response: Future[HttpResponse] = restSvrActorRef.ask(request).mapTo[HttpResponse]
        log.debug(s"done with sending a request to the REST service")
        response.map(_.entity.asString)
      }