I want to ban a URL from varnish. What I do first is collect all the healthy IP's from consul.
private def nodeInfo:Future[List[NodeInfo]] = {
val request = HttpRequest(method = HttpMethods.GET, uri = consulUrl)
Future.successful {
Http().singleRequest(request).flatMap(response =>
response.status match {
case OK => Unmarshal(response).to[List[NodeInfo]]
case _ =>
response.entity.toStrict(5.seconds).flatMap { entity =>
val body = entity.data.decodeString("UTF-8")
log.warning(errorMessage(request, response, body))
Future.failed(new IOException(errorMessage(response, body)))
}
})
}.flatMap(value => value)
}
This works as expected.
With the help of for comprehension I want to loop through all of them.
def banFromCache(toBanUrl:String): Future[String] = {
for {
nodes <- nodeInfo
result <- loopNodes(nodes, toBanUrl)
} yield result
}
With a foreach loop I send HttpRequest and get HttpResponses for each. But caused by the Future functionality the result yield is done before the request are completed.
private def loopNodes(nodes:List[NodeInfo], toBanUrl:String):Future[String] = Future {
val banResult = new ListBuffer[String]
nodes.foreach(node => {
banAllHealthy(node, toBanUrl).onComplete {
case Failure(err) =>
banResult += node.Node.Address + " " + err.getMessage
log.error("Request failed: " + node.Node.Address + " " + err.getMessage)
case Success(res) =>
banResult += node.Node.Address + " " + res.toString
log.info("Request success: " + node.Node.Address + " " + res.toString)
}
})
banResult.toList.toString()
}
private def banAllHealthy(nodeInfo:NodeInfo, toBanUrl: String):Future[HttpResponse] = {
def request(): Future[HttpResponse] =
Http().singleRequest(HttpRequest(method = HttpMethods.GET, uri = "http://localhost:9000/healthcheck"))
//Http().singleRequest(HttpRequest(method = HttpMethods.GET, uri = "http://" + nodeInfo.Node.Address + "/" + toBanUrl))
val responseFuture: Future[HttpResponse] = request()
responseFuture
}
The route is quite simple here:
} ~ pathPrefix("ban") {
pathPrefix(Segment) { banpath =>
pathEndOrSingleSlash {
get {
complete(banFromCache(banpath).map(_.asJson))
}
}
}
Is there a way to show all the responses at once ?
To accumulate the result strings, stay within the context of a Future:
private def loopNodes(nodes: List[NodeInfo], toBanUrl: String): Future[String] = {
val futures: List[Future[String]] = nodes.map { node =>
banAllHealthy(node, toBanUrl)
.map(res => s"${node.Node.Address} ${res}")
.recover { case err => s"${node.Node.Address} ${err.getMessage}" }
}
Future.reduceLeft(futures)(_ + "\n" + _)
}
If you put the callbacks to handle the result of each future you will lost the control of the execution. With the foreach, each future lives in its own parallel execution. The parent future yields because it does not to wait for anything. I would suggest to not use the ListBuffer and use a more immutable style. Anyway, try to build the whole computation as just one future which encapsulates the whole computation:
private def loopNodes(nodes:List[NodeInfo], toBanUrl:String):Future[String] = {
nodes.map { node =>
// Creates a tuple to store current node and http result
// (Node, result, HttpResult)
(node, "", banAllHealthy(node, toBanUrl))
}.foldLeft(Future(""))((str, b) =>
b match {
case (node, str ,response) => {
// Each response will be transformed to string
(response map (result => str + " " + node.Node.Address + " " + "Success"))
// In case of node is not available its suppose that the HttpClient will raise an execption
.recover {
case err: Throwable =>
str + " " + node.Node.Address + " " + "Error " + err.getMessage
case _ =>
str + " " + node.Node.Address + " " + "Unknown Error"
}
}
})
}
Related
I am working with a paginated API and would like to read the Json for each page dynamically. I have implemented this using a while loop and would like to break out of it when I have read the last page's Json
var pageNumber = 1
val pageSize = 1000
while (pageNumber >= 1) {
println("Inside the while loop!" + pageNumber)
url = url.concat("OWNER_MI_KEY&dataset_version=v1.1&pageNumber=$pageNumber&pageSize=$pageSize")
val apiResponse = MakePostHttpRestCalls(url) // makes request to external rest endpoint
if (apiResponse != ""){
println("Got api response")
println("apiResponse :" + apiResponse)
val apiJson = parseApiResponse(apiResponse)//parses the json response
println("apiJson :" + apiJson)
if (apiJson.length == 0){
pageNumber = pageNumber + 1
}
}
}
Any ideas on how I could achieve this in my code?
Further to my comment, here is a rough idea of how this might look as a recursive function, which is the functional way of doing this kind of thing that Scala is designed for.
def readResponsePages(pageSize: Int) = {
def loop(pageNumber: Int, responses: List[String]): List[String] = {
println("Inside the loop!" + pageNumber)
val thisUrl = url.concat(
s"OWNER_MI_KEY&dataset_version=v1.1&pageNumber=$pageNumber&pageSize=$pageSize"
)
MakePostHttpRestCalls(thisUrl) match {
case "" =>
responses.reverse
case apiResponse =>
println("Got api response")
println("apiResponse :" + apiResponse)
val apiJson = parseApiResponse(apiResponse) //parses the json response
println("apiJson :" + apiJson)
loop(pageNumber + 1, apiJson +: responses)
}
}
loop(1, Nil)
}
val responsePages = readResponsePages(1000)
I am new to Akka and scala. I have an API that returns the POJO class. Something like this
pathPrefix("dataprofile") {
get {
ctx =>
(sActor ? GetDataAccessProfileByUserAndAccount(account, user)).mapTo[Either[Rejection, DataAccessProfile]] map {
case Left(x) => ctx.reject(x)
case Right(x) => ctx.complete(x)
}
}
} ~
Now I want to use this sActor in some other API and use two of the element(List and String) from DataAccessProfile. And after getting these two elements I want to set it in CompleteInput. I wrote something like
pathPrefix("complete") {
post {
entity(as[CompleteInput]) { completeInput =>
complete {
var session = user.username.get + "_" + account.id.get
var uNameAccountId = user.username.get +"|" + account.id.get
if(jwt_token == null || !validateJwt(jwt_token)){
jwt_token = encodeJwt(uNameAccountId)
}
val result = (sActor ? GetDataAccessProfileByUserAndAccount(account, user)).mapTo[Either[Rejection, DataAccessProfile]]
implicit val timeout = Timeout(2 seconds)
DataAccessProfile future = (DataAccessProfile) Await.result(result, timeout.duration)
println(future.studyIds)
autoCompleteService(session,jwt_token,completeInput)
}
}
}
} ~
I am getting an error saying expected ';' but found "."
My question is how we can retrieve element(s) from sActor or any other solution
Now I have tried this:
val future2: Future[DataAccessProfile] = (sActor ? GetDataAccessProfileByUserAndAccount(account, user)).mapTo[DataAccessProfile]
implicit val timeout = Timeout(2 seconds)
val result = Await.result(future2, timeout.duration)
println("---------------------------" +result)
This time runtime exception saying exception java.lang.ClassCastException: Cannot cast scala.util.Right to com.comprehend.protobuf.rbac.DataAccessProfile
I think you got compilation error at:
DataAccessProfile future = (DataAccessProfile) Await.result(result, timeout.duration)
it look like more Java style declaration, but because you are writing on Scala it should look like:
val future: DataAccessProfile = Await.result(result, timeout.duration).get.asInstanceOf[DataAccessProfile]
But, this solution has a number of issues: it is blocking, because it uses await, and it is unsafe, because Await.result and Either.get can throw an errors, so they need to be properly handled.
I suggest you to take a look closer at this page: https://doc.akka.io/docs/akka-http/current/routing-dsl/directives/future-directives/onComplete.html
And probably implement something like :
onComplete(result) {
case Success(Right(profile)) => autoCompleteService(session, jwt_token, completeInput)
case Success(Left(rejection)) => complete((InternalServerError, "Message was rejected - specify error details here"))
case Failure(ex) => complete((InternalServerError, s"An error occurred: ${ex.getMessage}"))
}
UPDATE #1:
so the final solution would look like:
pathPrefix("complete") {
post {
entity(as[CompleteInput]) { completeInput =>
val session = user.username.get + "_" + account.id.get
val uNameAccountId = user.username.get +"|" + account.id.get
if(jwt_token == null || !validateJwt(jwt_token)){
jwt_token = encodeJwt(uNameAccountId)
}
val result = (sActor ? GetDataAccessProfileByUserAndAccount(account, user)).mapTo[Either[Rejection, DataAccessProfile]]
onComplete(result) {
case Success(Right(profile)) =>
println(future.studyIds)
autoCompleteService(session, jwt_token, completeInput)
case Success(Left(rejection)) =>
complete((InternalServerError, "Message was rejected - specify error details here"))
case Failure(ex) =>
complete((InternalServerError, s"An error occurred: ${ex.getMessage}"))
}
}
}
} ~
UPDATE #2:
According to your last update, I've removed Either usage:
pathPrefix("complete") {
post {
entity(as[CompleteInput]) { completeInput =>
val session = user.username.get + "_" + account.id.get
val uNameAccountId = user.username.get +"|" + account.id.get
if(jwt_token == null || !validateJwt(jwt_token)){
jwt_token = encodeJwt(uNameAccountId)
}
val result: Future[DataAccessProfile] = (sActor ? GetDataAccessProfileByUserAndAccount(account, user)).mapTo[DataAccessProfile]
onComplete(result) {
case Success(profile) =>
println(future.studyIds)
val updatedInput = completeInput.copy(dataAccessControl = JavaConversions.seqAsJavaList(list2))
autoCompleteService(session, jwt_token, updatedInput)
case Failure(ex) =>
complete((InternalServerError, s"An error occurred: ${ex.getMessage}"))
}
}
}
} ~
Hope this helps!
I am quite new with functional programming in scala and play-framework
I have an API request that need to add a chapter to a comic book authored by that particular user, thus I have to verify that the comic exist and belongs to that user. What complicates is the Future, Option etc and I can't make sure that the last command is executed last. Indeed, my problem occurs because the last command is executed before all my db access is completed
Here are the code, hope it is clear
def addchapter(): Action[AnyContent] = Action.async { implicit request =>
var hashMap: HashMap[String, JsValue] = HashMap()
var apiResult: ApiResult = ApiResult(
ReturnCode.COMIC_ADDCHAPTER.id,
ReturnCode.COMIC_ADDCHAPTER.toString(),
ReturnResult.RESULT_ERROR.toString(),
"init"
)
var fHashMap: Future[HashMap[String, JsValue]] = Future {
hashMap
}
try {
val oReq = request.body.asJson
val jsReq = oReq.getOrElse(JsString("null"))
val sessionid = (jsReq \
"sessionid").getOrElse(JsString("0")).as[String]
val comicid = (jsReq \
"comicid").getOrElse(JsString("comicid")).as[String]
val foUser = userRepo.getUserBySessionId(sessionid)
LogManager.DebugLog(this, "add chapter: " + sessionid + " => " +
comicid)
fHashMap = foUser.flatMap( oUser => {
oUser match {
case Some(user) => {
val foComic = comicRepo.getComicByComicId(comicid)
fHashMap = foComic.flatMap( oComic => {
oComic match {
case Some(comic) => {
LogManager.DebugLog(this, "about to add chapter")
val fTup = comicRepo.addChapterToComic(comic)
fHashMap = fTup.map( tuple => {
val wr = tuple._1
val mc = tuple._2
apiResult = ApiResult(
ReturnCode.COMIC_ADDCHAPTER.id,
ReturnCode.COMIC_ADDCHAPTER.toString(),
ReturnResult.RESULT_ERROR.toString(),
"successfully added chapter!"
)
val payloadcomic =
PayloadComicFactory.createWithComic(mc)
hashMap("apiresult") = Json.toJson(apiResult)
hashMap += "comic" -> Json.toJson(payloadcomic)
LogManager.DebugLog(this, "successfully added
chapter!")
hashMap
})
// return
fHashMap
}
case None => {
apiResult = ApiResult(
ReturnCode.COMIC_ADDCHAPTER.id,
ReturnCode.COMIC_ADDCHAPTER.toString(),
ReturnResult.RESULT_ERROR.toString(),
"comic not found"
)
hashMap("apiresult") = Json.toJson(apiResult)
Future { hashMap }
}
}
})
Future { hashMap }
}
case None => {
apiResult = ApiResult(
ReturnCode.COMIC_ADDCHAPTER.id,
ReturnCode.COMIC_ADDCHAPTER.toString(),
ReturnResult.RESULT_ERROR.toString(),
"unauthorized to add chapter to this comic"
)
hashMap("apiresult") = Json.toJson(apiResult)
Future { hashMap }
}
}
})
// I cannot put the return here, it is compile error saying that the return value is a Unit
// fHashMap.map( hashmap => {
// Ok(Json.toJson(hashmap))
// })
} catch {
case t: Throwable => {
LogManager.DebugException(this, "ex: ", t)
// I cannot put it here, it is compile error saying that the return value is Unit
// fHashMap.map( hashmap => {
// Ok(Json.toJson(hashmap))
// })
fHashMap
}
}
// I have to return here, but "return of the request" will be executed first before all my db access is completed, thus the ApiResult is still returning wrong state
LogManager.DebugLog(this, "return of the request")
fHashMap.map( hashmap => {
Ok(Json.toJson(hashmap))
})
}
I've got a question that concerns scoping in scala. I have a function like this:
def getElements(id: Int): Seq[Element] = {
var test = ""
dto.getElementIds(id).map {
elementIds => {
test += " hello "
elementIds.foreach(elementId => dto.getElement(elementId).map {
case Some(element) => test += " hi "
println("ThirdPrint: " + test)
})
println("SecondPrint: " + test)
}
}
println("FirstPrint: " + test)
}
Lets say "elementsIds" has a lenght of 2.
The console says:
FirstPrint:
SecondPrint: hello
ThirdPrint: hello hi
ThirdPrint: hello hi hi
Why is scala doing that? I would assume that third print is executed first. By the time I get to the "FirstPrint" the "hi"'s are gone. Why is the last line of code executed first?
I'm working with slick and Futures, does this have something to do with it? Thanks!
UPDATE
Thanks, works fine.
Is it possible to return a seq instead? Like this:
def getElements(id: Int): Future[Seq[Element]] = {
var mySequence: Seq[Element] = Seq()
val elementsIds: Future[Seq[Int]] = dto.getElementIds(id)
var test = ""
val elementsF = elementsIds.flatMap {
elementIds => {
test += " hello "
val idsAsElements: Seq[Future[Element]] = elementIds.map(elementId => dto.getElement(elementId).collect {
case Some(element) => mySequence = mySequence :+ element
})
val idsAsElementsF: Future[Seq[Element]] = Future.sequence(idsAsElements)
idsAsElementsF.onComplete(_ => println("SecondPrint: " + test))
idsAsElementsF
}
}
elementsF.onComplete(_ => println("FirstPrint: " + test))
elementsF
}
Is it possible to return "mySequence" whenever idsAsElements is "onComplete"?
object X {
import scala.concurrent.ExecutionContext.Implicits.global
case class Element()
object dto{
def getElementIds(i: Int): Future[Seq[Int]] = Future(Seq(1,2,3))
def getElement(i: Int): Future[Option[Element]] = Future(Some(Element()))
}
def main(args: Array[String]): Unit = {
getElements(0)
Thread.sleep(10000) // waiting logs
}
def getElements(id: Int): Future[Seq[Element]] = {
val elementsIds: Future[Seq[Int]] = dto.getElementIds(id)
var test = ""
val elementsF = elementsIds.flatMap {
elementIds => {
test += " hello "
val idsAsElements: Seq[Future[Element]] = elementIds.map(elementId => dto.getElement(elementId).collect {
case Some(element) => test += " hi "
println("ThirdPrint: " + test)
element
})
val idsAsElementsF: Future[Seq[Element]] = Future.sequence(idsAsElements)
idsAsElementsF.onComplete(_ => println("SecondPrint: " + test))
idsAsElementsF
}
}
elementsF.onComplete(_ => println("FirstPrint: " + test))
elementsF
}
}
output:
ThirdPrint: hello hi
ThirdPrint: hello hi hi
ThirdPrint: hello hi hi hi
SecondPrint: hello hi hi hi
FirstPrint: hello hi hi hi
Yes, that's not a good solution. You should avoid blocking in your code, except, maybe at the highest possible level (main method). Also, mutable state is bad, especially, when combined with concurrency (futures).
Your function should return a Future. Something like this will work (I am not sure if I guess the intent of your code correctly - your function was declared to return Seq[Element], but written to return a Unit ... I assume, that what you really wanted to return was the result of getElement calls for every id):
def getElements(id: Int): Future[Seq[Element]] = dto
.getElementIds(id)
.map { ids => ids.map(dto.getElement) }
.flatMap(Future.sequence)
.map(_.flatten)
I removed your printouts, because wasn't sure what was the purpose they serve (since the calls to dto.getElement are also happening in parallel, it is not obvious where and in which order you want those strings printed).
You could simulate your "expected output", by adding another transformation at the end for example:
.andThen { case Success(results) =>
val str = results.foldLeft("hello") { case (a,b) =>
println("ThirdPrint: " + a + " hi")
a + " hi"
}
println("SecondPrint: " + str)
println("FirstPrint: " + str
}
I want to do something like this:
For every item in the collection, ask an actor and block while waiting for the response for some timeout interval, if the time out exception is thrown, I want to move on to the next item.
Here's the code pattern:
implicit val timeout: akka.util.Timeout = 3 seconds
collection.foreach { item =>
val future = (actor ? Request(msg = item)).mapTo[Response]
future.onComplete {
case Failure(ex) => // log ex
case Success(resp) => // use resp
}
Await.result(future, 3 seconds)
}
The actor is itself calling other actors which might take longer than my 3 second time out.
This doesn't work as expected: after the first item times out, the whole thing crashes and stops. There are some dead letter notifications, I suppose that's because when the actors that my actor is calling finish, the original sender is invalid (it took more than 3 seconds). So my question is how do I tell it to just forget the time out item and continue with the rest as if nothing happened?
#stefanobaghino is right. See here, as written in documentation if a future contains an exception then Await.result throws is so that it can be handled properly.
Here you are matching Failure case of future but you are not recovering from it. A better approach would be like following -
collection.foreach { item =>
val future = (actor ? Request(msg = item)).mapTo[Response]
future.recover {
case ex: Exception =>
// log ex
Response(ex.message) // some other object of type Response
}
val response = Await.result(future, 3 seconds)
// use response here
}
After reading answer by #Dimitri I tried logging timestamps in milliseconds to see where it was causing lag in whole process and i found rather strange behavior. I observed that whenever there were dead-letters there was huge lag in even starting processing of next message to actor. Not sure why this is happening. Below is the code i tried to check it -
package com.lightbend.akka.sample
import akka.actor.{ Actor, ActorLogging, ActorRef, ActorSystem, Props }
import akka.pattern.{ ask, pipe, AskTimeoutException }
import scala.concurrent.Await
import scala.concurrent.duration._
import scala.io.StdIn
import scala.util.{ Try, Success, Failure }
import scala.concurrent.ExecutionContext.Implicits.global
import java.util.concurrent.TimeoutException
object AkkaQuickStart {
class NumberActor extends Actor {
override def receive: Receive = {
case (num: Int, startAt: Long) =>
println("B " + num.toString + " : " + System.currentTimeMillis().toString + " : " + (System.currentTimeMillis() - startAt).toString)
Thread.sleep(500 * num)
sender() ! "OK"
}
}
def main(args: Array[String]): Unit = {
implicit val timeout: akka.util.Timeout = 1 seconds
val numActor = ActorSystem("system").actorOf(Props(new NumberActor()))
val range = (1 to 5) ++ (4 to 1 by -1)
println(range)
def lag(implicit startAt: Long): String = (System.currentTimeMillis() - startAt).toString
range.map { r =>
implicit val startAt = System.currentTimeMillis()
println("A " + r.toString + " : " + System.currentTimeMillis().toString + " : " + lag)
val future = (numActor ? (r, startAt))
.recover {
case ex: AskTimeoutException =>
println("E " + r.toString + " : " + System.currentTimeMillis().toString + " : " + lag)
"Ask timeout"
}
.mapTo[String]
future.onComplete{
case Success(reply) =>
println("C " + r.toString + " : " + System.currentTimeMillis().toString + " : " + lag + " : success " + reply)
case Failure(reply) =>
println("C " + r.toString + " : " + System.currentTimeMillis().toString + " : " + lag + " : failure")
}
Try(Await.result(future, 1 seconds)) match {
case Success(reply) =>
println("D " + r.toString + " : " + System.currentTimeMillis().toString + " : " + lag + " : " + reply)
case Failure(ex) =>
println("D " + r.toString + " : " + System.currentTimeMillis().toString + " : " + lag + " : Await timeout ")
}
}
}
}
I tried different combinations of Ask timeout and Await timeout and found following lags in starting processing of actor message sent at the end of iteration -
Ask timeout = 1 Await Timeout = 1 => 3000 - 4500 ms causes dead-letters
Ask timeout = 1 Await Timeout = 3 => 3000 - 4500 ms causes dead-letters
Ask timeout = 3 Await Timeout = 1 => 3000 - 4500 ms causes dead-letters
Ask timeout = 3 Await timeout = 3 => 0 - 500 ms does not cause dead-letters
I am not sure but a guess is that dispatcher takes time in handling dead-letters and thus can not start processing messages of our Actor. May be some more experienced can explain it.
#stefanobaghino #Tarun Thanks for your help, I think I got it now.
So the thing is there are 2 timeouts that can cause an exception:
The Ask (?) timeout throws akka.pattern.AskTimeoutException if we have to wait longer than the actor takes to respond.
The Await.result throws java.util.concurrent.TimeoutException if we don't wait long enough for the future to finish.
Both of these can cause the whole thing to crash. For the first one, as you mentioned, we can add recover to return some default value. For the second one, we also should catch and handle the exception.
You can see different behaviours when changing the two timeouts and removing the recover/Try in the following example:
object Example {
class NumberActor extends Actor {
override def receive: Receive = {
case num: Int =>
Thread.sleep(250 * num)
sender() ! "OK"
}
}
def main(): Unit = {
implicit val timeout: akka.util.Timeout = 1 seconds
val numActor = ActorSystem("system").actorOf(Props(new NumberActor()))
val range = (1 to 5) ++ (4 to 1 by -1)
println(range)
range.map { r =>
val future = (numActor ? r)
.recover { case ex: TimeoutException => "FAIL" }
.mapTo[String]
Try(Await.result(future, 1 seconds)) match {
case Success(reply) => println(reply)
case Failure(ex) => println(ex)
}
}
}
}