0

I have Play! on Scala application which communicates with another server by sending http request. That system has limitation: only 5 http requests can be proceeded simultaniously for one token.

I've written this method:

import scala.concurrent.ExecutionContext.Implicits.global

private def sendApiRequest(method: String, params: Option[JsValue] = None)(implicit token: String): Future[JsObject] = {

if (concurrentRequests.get(token).isEmpty) {
  concurrentRequests += token -> 1
} else {
  concurrentRequests += token -> (concurrentRequests.get(token).get + 1)
}

println(s"$token: ${concurrentRequests.get(token).get}")

val request = WS.url(API_URL)
                .withMethod("POST")
                .withBody(Json.obj(
                  "application_id" -> clientId,
                  "method" -> method,
                  "token" -> token,
                  "param" -> params
                ))

request.execute().map(response => {
  val result = response.json.as[JsObject]
  if (!result.keys.contains("data")) {
    throw new Exception(result.toString())
  } else {
    result
  }
})
}

And there are actors which use this method and i get that exception after couple seconds.

My question is: How can i control number of features in 'RUNNING MODE'? May be i should use another execution context instead of default one? Explain me please or give good introduction for execution context, threads, etc

I want to get information from remote service as fast as possible not by sending one by one

Thank you!

Alexander Kondaurov
  • 3,677
  • 5
  • 42
  • 64

2 Answers2

0

Create a manager object or actor to coordinate the requests. When you submit a request to the manager it creates a handler object or actor for that token or uses an existing handler for that token, if one is available.

Each handler has a counter and a queue. It receives requests on the queue and runs them if the counter is < 5. The counter records how many requests are currently running. When requests finish, the counter is decreased and the next item is pulled off the queue.

To prevent memory leaks, handlers should clean themselves up if the counter reaches 0 and the queue is empty. The manager will recreate the handler again if the same token is received again in the future.

Rich Dougherty
  • 3,231
  • 21
  • 24
0

I found solution:

private def sendApiRequest(method: String, params: Option[JsValue] = None): Future[JsObject] = {
val request = WS.url(API_URL)
  .withMethod("POST")
  .withBody(Json.obj(
  "application_id" -> clientId,
  "method" -> method,
  "token" -> token,
  "param" -> params
))

request.execute().map(response => {
  response.json.asOpt[JsObject] match {
    case Some(directResponse) =>
      (directResponse \ "error_code").asOpt[Int] match {
        case Some(error) if error == 52 || error == 506 =>
          sendApiRequest(method, params)
        case Some(error) => throw new UnknownApiException(error)
        case _ => Future.successful(directResponse)
      }
    case None => throw NotJsonResponseException
  }
}).flatMap(f => f)

}

If i get json response with error_code field 52 or 506 (which means that more than 5 request are processing right now) i make the same request again and so i get Future[Future[Future[JsObject]]] and then i do flatMap to get only one Future :) This code works and i've got all data i need in 19x times faster

Alexander Kondaurov
  • 3,677
  • 5
  • 42
  • 64