How to add state to a Play Enumerator? - scala

To feed Play response I want to pass Enumerator to result's feed method. I need to pass a state from produce/consume iteration step to the next step (or to keep state). Here
http://engineering.klout.com/2013/01/iteratees-in-big-data-at-klout/
I have found an example but am not sure it is thread-safe:
def pagingEnumerator(url:String):Enumerator[JsValue]={
var maybeNextUrl = Some(url) //Next url to fetch
Enumerator.fromCallback[JsValue] ( retriever = {
val maybeResponsePromise =
maybeNextUrl map { nextUrl=>
WS.url(nextUrl).get.map { reponse =>
val json = response.json
maybeNextUrl = (json \ "next_url").asOpt[String]
val code = response.status //Potential error handling here
json
}
}
maybeResponsePromise match {
case Some(responsePromise) => responsePromise map Some.apply
case None => PlayPromise pure None
}
})
}
What is you way to add a state to Play Enumerator? Is this example thread-safe?
(in the above example old Play API related to Promise/Future is used; let's neglect this fact as far as it doesn't influence the issue itself)

You should use Enumerator.unfoldM:
Enumerator.unfoldM(Some(firstURL)) { maybeNextUrl =>
maybeNextUrl map { nextUrl =>
WS.url(nextUrl).get.map { response =>
val json = response.json
val newUrl = (json \ "next_url").asOpt[String]
val code = response.status //Potential error handling here
Some((newUrl, json))
}
} getOrElse (Future.successful(None))
}

Related

Avoid syncronous calls in Akka Actors due of Await result

Because I do some "complex" operations, I think my Actor became asyncronous. The main problem I think is that I use Await.result inside the method which return responses.
actor:
def process(subscribers: Set[ActorRef]): Receive = {
case Join(ref) => context become process(subscribers + ref)
case Leave(ref) => context become process(subscribers - ref)
case Push(request) =>
val filteredSubscribers = (subscribers - sender())
.filter(s => exists(s, request)) // just some actor filters
filteredSubscribers.foreach { subscriber =>
// here I have a Map with each actor requests
val actorOptions = getActorOptions(subscriber)
subscriber ? getResponse(actorOptions, request)
}
}
The problem is inside getResponse (I think).
getResponse(actorOptions: JsValue, request: SocketRequest): JsValue = {
(actorOptions \ "dashboardId").asOpt[Int] match {
case Some(id) => {
val response = widgetsService.getByDashboadId(id) map { widgets =>
val widgetsResponse: List[Future[String]] = widgets.map(w => {
widgetsService.getDataById(w.id) map {
data => s"""{ "widgetId": ${w.id}, "data": $data }"""
}
})
var responses: List[String] = List.empty
widgetsResponse.foreach(f => {
f.onComplete {
case Success(value) => responses = value :: responses
case Failure(e) => println(s"Something happened: ${e.getMessage}")
}
})
// first time when I use Await.result
// used to populate the responses list with data from all futures
Await.result(Future.sequence(widgetsResponse), Duration.Inf)
Json.parse(s"""{
"dashboardId": $id,
"widgets": [${response.mkString(", ")}]
}""".stripMargin)
}
// second time when I use Await.result
// used to return a JsValue instead of a Future[JsValue]
Await.result(response, Duration.Inf)
}
case None => buildDefaultJson // return default json value, unimportant for this example
}
}
Due of that, In frontend, if I have 2 sockets clients, the response for the second will be send only after first.
I found that I can obtain a "fake" increase of performance if I embrance the getResponse in a future inside of my Actor.
filteredSubscribers.foreach { subscriber =>
val actorOptions = getActorOptions(subscriber)
Future(subscriber ? getResponse(actorOptions, request))
}
So, for both subscribers the action will be started in same time, but when the first will reach the Await.result, the second will be locked until first is done.
I need to avoid using Await.result there, but I don't know how to get the results of a list of futures, without using for-comprehension (because is a dynamically list) for first time where I use it.
Because Akka ask operator (?) return a Future[Any], I tried that my getResponse method to return directly a JsValue to be mapped then in Future[JsValue]. If I remove the second Await.result and my method will return Future[JsValue], then the actor will return a Future[Future[JsValue]] which I don't think is too right.
After some more researches and solutions found on so, my code become:
Future.sequence(widgetsResponse) map { responses =>
Json.parse(
s"""
|{
|"dashboardId": $id,
|"tableSourceId": $tableSourceId,
|"widgets": [ ${responses.mkString(", ")}]
|}""".stripMargin
)
}
getResponse returns a Future[JsValue] now, removing both Await.result cases, and actor case become:
filteredSubscribers.foreach { subscriber =>
val actorOptions = getActorOptions(subscriber)
getResponse(actorOptions, request) map { data =>
subscriber ? data
}
}
I don't know why, still have a synchronous behavior. Damn, this can be due of my subscribers type: Set[ActorRef]? I tried to use parallel foreach and this looks like solving my problem:
filteredSubscribers.par.foreach { subscriber =>
val actorOptions = getActorOptions(subscriber)
getResponse(actorOptions, request) map { data =>
subscriber ? data
}
}

What is the wrong while trying to parse Future response?

I have a function which will take a Token via ajax request. It will return a response a list of directories while the token is valid.
def all = Action.async(parse.json) {
implicit request => tokenForm.bind(request.body).fold(
formWithErrors => Future.successful(BadRequest(formWithErrors.toString)),
form => checkToken(form.token).map(token => {
val directories = Directories.all.map(directory => {
Json.toJson(directory)
})
Ok(Json.obj("status" -> {if (token.get.id.getOrElse(0) >= 1) true else false}, "message" -> {if (token.get.id.getOrElse(0) >= 1) Json.toJson(directories) else "Invalid token"}))
})
)
}
While I run the above code It says [As ^ sign indicate the position the error found]
No Json serializer found for type scala.concurrent.Future[play.api.libs.json.JsValue]. Try to implement an implicit Writes or Format for this type.
Ok(Json.obj("status" -> {if (token.get.id.getOrElse(0) >= 1) true else false}, "message" -> {if (token.get.id.getOrElse(0) >= 1) Json.toJson(directories) else "Invalid token"}))
^
Here is what I come up with
def all = Action.async(parse.json) {
implicit request => tokenForm.bind(request.body).fold(
formWithErrors => Future.successful(BadRequest(formWithErrors.toString)),
form => for {
(token, directories) <- checkToken(form.token) zip Directories.all
} yield {
val isTokenValid = token.isDefined
val responseBody = Json.obj(
"status" -> isTokenValid,
"message" -> {
if (isTokenValid)
Json.toJson(directories)
else
"Invalid token"
}
)
Ok(responseBody)
}
)
}
asuming checkToken and Directories.all returns Future.
Your function needs to return Future[Result]. When you are folding, the first branch is correct
formWithErrors => Future.successful(BadRequest(formWithErrors.toString))
However in the second branch it seems like you started a new Future by calling Directories.all, and you want to serialize it and return as json. There is no serializer for Future[JsValue] as this makes no sense, it would have to block to get result. You need to somehow get to the values of both checkToken(form.token) and Directories.all.
You can do this as I did above, or for example like this:
form => {
val futureToken = checkToken(form.token)
val futureDirectories = Directories.all
for {
token <- futureToken
directories <- futureDirectories
} yield {
// same code as above
}
}
Notice that if you would inline futureToken and futureDirectories they would be executed serially.
Also note that you are converting your directory list to json twice.
Once here
val directories = Directories.all.map(directory => {
Json.toJson(directory)
})
Asuming Directories.all returns Future[List[Directory]], then when you use map, the function you pass operates on List[Directory] so the variable should be named directories not directory. It will work, play easly converts list of anything convertible to json, no need to do it manually.
Second time you did it here
Json.toJson(directories)

Calling multiple web services

I'm using Play 2.5 with Scala, I created a class that will call multiple times external web services.
External web services are called on some condition and get a simple response of ok or nok. If it is ok then I should update internal objects status and if nok I do nothing for now.
Here is my class, it takes a list of list of String as paramters and return a Future list of object to be handled in the controller.
def callWSAndGetResponse(listOfList: List[List[String]]): Future[List[MyObject]] = {
val res = listOfList map { listOfIds =>
listOfIds map { id =>
val foundObj = allMyObject.find(obj => obj.id == id)
if(foundObj.isDefined) {
foundObj.get.urls map { url =>
val futureReponse: Future[WSResponse] = ws.url(url).get()
futureResponse map { response =>
(response.json \ "response").as[String]
}
}
}
}
}
// if responses are ok create a list of MyObject to return for example.
}
val res is of type list of list but I would like it to be just a simple list of response.
1) How to simplify and correct my code in order to get just a list of response, for later check if ok or not ?
2) How to check if responses are ok or have failed ?
It looks like you want this block to return a List[Future[Json]], from then you can use "List[Future] to Future[List] disregarding failed futures" or similar.
To do this you should use map and flatMap (rather than isDefined/get):
val res = listOfList map {
_.map { id =>
allMyObject
.find(obj => obj.id == id)
.map {
_.flatMap {url =>
val futureReponse: Future[WSResponse] = ws.url(url).get()
futureResponse map { response =>
(response.json \ "response").as[String]
}
}
}
}
}
if blocks assign to AnyVal rather than to a specific type (which will cause you issues here):
scala> if (true) 23
23: AnyVal
scala> if (false) 23
(): AnyVal
Using map keeps you in the Option monad:
scala> (None: Option[Int]).map(_ * 2)
None: Option[Int]

Handling Future[WSResponse] to find success or error state

In Scala I have a call to service in controller which is returning me Future[WSResponse]. I want to make sure service is returning valid result so send Ok(..) otherwise send BadRequest(...). I don't think I can use map. Any other suggestion?
def someWork = Action.async(parse.xml) { request =>
val result:Future[WSResponse] = someService.processData(request.body.toString())
//Need to send back Ok or BadRequest Message
}
EDIT
Solution from #alextsc is working fine. Now moving to test my existing test is failing. It is getting 400 instead of 200.
test("should post something") {
val requestBody = <value>{UUID.randomUUID}</value>
val mockResponse = mock[WSResponse]
val expectedResponse: Future[WSResponse] = Future.successful(mockResponse)
val request = FakeRequest(Helpers.POST, "/posthere").withXmlBody(requestBody)
when(mockResponse.body).thenReturn("SOME_RESPONSE")
when(someService.processData(any[String])).thenReturn(expectedResponse)
val response: Future[Result] = call(controller.someWork , request)
whenReady(response) { response =>
assert(response.header.status == 200)
}
}
You're on the right track and yes, you can use map.
Since you're using Action.async already and your service returns a future as it stands all you need to do is map that future to a Future[Result] so Play can handle it:
def someWork = Action.async(parse.xml) { request =>
someService.processData(request.body.toString()).map {
// Assuming status 200 (OK) is a valid result for you.
case resp : WSResponse if resp.getStatus == 200 => Ok(...)
case _ => BadRequest(...)
}
}
(I note that your service returns WSResponse (from the play ws java library) and not play.api.libs.ws.Response (the scala version of it), hence getStatus and not just status)

Concurrent.patchPanel not sending data from multiple enumerators - only sends from last enumerator added

I'm hoping someone else has used patchPanel to combine multiple enumerators together going down to a client over a websocket. The issue i'm running into is that the patchPanel is only sending the data feed from the last enumerator added into it.
I followed the example from; http://lambdaz.blogspot.ca/2012/12/play-21-multiplexing-enumerators-into.html which is the only reference I've been able to find regarding patchPanel.
Versions; play! 2.1.1 (using Java 1.7.0_11 and Scala 2.10.0)
The web socket method;
def monitorStream = WebSocket.async[JsValue] { request =>
val promiseIn = promise[Iteratee[JsValue, Unit]]
val out = Concurrent.patchPanel[JsValue] { patcher =>
val in = Iteratee.foreach[JsValue] { json =>
val event:Option[String] = (json \ "event").asOpt[String]
val systemId = (json \ "systemId").as[Long]
event.getOrElse("") match {
case "join" =>
val physicalSystem = SystemIdHandler.getById(systemId)
val monitorOut = (MonitorStreamActor.joinMonitor(physicalSystem))
monitorOut map { enum =>
val success = patcher.patchIn(enum)
}
}
}.mapDone { _ => Logger.info("Disconnected") }
promiseIn.success(in)
}
future(Iteratee.flatten(promiseIn.future),out)
}
The MonitorStreamActor call;
def joinMonitor(physicalSystem: PhysicalSystem):
scala.concurrent.Future[Enumerator[JsValue]]
= {
val monitorActor = ActorBase.akkaSystem.actorFor("/user/system-" + physicalSystem.name +"/stream")
(monitorActor ? MonitorJoin()).map {
case MonitorConnected(enumerator) =>
enumerator
}
}
The enumerator is returned fine, and the data fed into it is coming from a timer calling the actor. Actor definition, the timer hits the UpdatedTranStates case;
class MonitorStreamActor() extends Actor {
val (monitorEnumerator, monitorChannel) = Concurrent.broadcast[JsValue]
import play.api.Play.current
def receive = {
case MonitorJoin() => {
Logger.debug ("Actor monitor join")
sender ! MonitorConnected(monitorEnumerator)
}
case UpdatedTranStates(systemName,tranStates) => {
//println("Got updated Tran States")
val json = Json.toJson(tranStates.map(m => Map("State" -> m._1, "Count" -> m._2) ))
//println("Pushing updates to monitorChannel")
sendUpdateToClients(systemName, "states", json)
}
def sendUpdateToClients(systemName:String, updateType:String, json:JsValue) {
monitorChannel.push(Json.toJson(
Map(
"dataType"->Json.toJson(updateType),
"systemName" -> Json.toJson(systemName),
"data"->json)))
}
}
}
I've poked around for a while on this and haven't found a reason why only the last enumerator that is added into the patchPanel has the data sent. the API docs are not of much help, it really sounds like all you have to do is call patchIn and it should combine all enumerators to an iteratee, but that doesn't seem to be the case.
The PatchPanel by design replaces the current enumerator with the new one provided by the patchIn method.
In order to have multiple Enumerators combined together you need to use interleave or andThen methods to combine enumerators together. Interleave is preferred for this case as it will take events from each Enumerator as they are available, vs emptying one then moving to the next (as with andThen operator).
ie, in monitorStream;
val monitorOut = (MonitorStreamActor.joinMonitor(physicalSystem))
monitorOut map { enum =>
mappedEnums += ((physicalSystem.name, enum))
patcher.patchIn(Enumerator.interleave[JsValue]( mappedEnums.values.toSeq))
}
patcher is the patch panel, and mappedEnums is a HashMap[String,Enumerator[JsValue]] - re-add the patcher each time the Enumerators change (add or delete) - it works, not sure if it's the best way, but it'll do for now :)