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!
Related
I know this question has been asked here already, but I haven't found any working solution to the problem. Keep getting this WARNING all the time: Sending an 2xx 'early' response before end of request was received.
Here is my code:
pathPrefix("upload") {
(post & extractRequest) { _ => {
extractRequestContext {
requestCtx => {
println(requestCtx.request.toString)
implicit val materializer = requestCtx.materializer
implicit val executionContext = requestCtx.executionContext
fileUpload("file") {
case (metadata, byteSource) => {
val completesOnceUploadCompleted: Future[Done] =
byteSource.runWith(FileIO.toPath(Paths.get(metadata.fileName))).map(
iores => iores.status.get
)
val futureResponseBody = completesOnceUploadCompleted.map(res => res)
complete(futureResponseBody)
}
}
}
}
}
}
}
Please help me fixing it.
I am not sure of the cause of the underlying problem. However, one "quick fix" would be to use the onComplete directive to wait for the writing to complete:
pathPrefix("upload") {
post {
extractRequestContext { requestCtx =>
println(requestCtx.request.toString)
implicit val materializer = requestCtx.materializer
implicit val executionContext = requestCtx.executionContext
fileUpload("file") { (metadata, byteSource) =>
val writeFileFut : Future[Try[Done]] =
byteSource.runWith(FileIO.toPath(Paths.get(metadata.fileName)))
.map(_.status)
onComplete(writeFileFut) {
case Success(statusTry) => statusTry match {
case Success(done) => complete(StatusCodes.Ok)
case Failure(ex) => complete(StatusCodes.InternalServerError -> ex.toString)
}
case Failure(ex) => complete(StatusCodes.InternalServerError -> ex.toString)
}
}
}
}
}
I had the same issue and simply adding implicit request: Request[AnyContent] => (which I had by mistake removed) solved it!
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 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"
}
}
})
}
I would like to remove the timeout definition from the code below so it doesn't time out within the defined period. How can i achieve that?
val futureString: Future[String] = myTestActor.ask(Message).mapTo[String]
val timeoutFuture: Future[String] = play.api.libs.concurrent.Promise.timeout(throw new TimeoutException(), 5 seconds)
Async {
Future.firstCompletedOf(Seq(futureString, timeoutFuture)) map {
case result: String => println("got message " + result)
} recover {
case _: TimeoutException => "Timed out?"
}
}
Integrating redis with my Scala application using Akka but for some reason it does not receive any messages. I can confirm that redis does have a ton of traffic on its side by opening the redis-cli on the command line.
After a pSubscribe it receives: subscribed to * and count = 1
My guess is that it might be related to the way Akka is set up to receive callbacks. I had to strip out Scala Actors in the scala-redis lib and replace them with Akka actors due to some conflicts.
Here's the code:
The Subscriber Actor
class Subscriber(client: RedisClient) extends Actor {
var callback: PubSubMessage => Any = { m => }
def receive: Receive = {
case Subscribe(channels) =>
client.subscribe(channels.head, channels.tail: _*)(callback)
case pSubscribe(channels) =>
client.pSubscribe(channels.head, channels.tail: _*)(callback)
case pSubscribeAll(channels) =>
Logger.info("Subscribing to all channels")
client.pSubscribe(channels.head, channels.tail: _*)(callback)
case Register(cb) =>
Logger.info("Callback is registered")
callback = cb
case Unsubscribe(channels) =>
client.unsubscribe(channels.head, channels.tail: _*)
case UnsubscribeAll =>
client.unsubscribe
}
}
Initializing the Subscriber
class RelaySub extends Actor {
// important config values
val system = ActorSystem("pubsub")
val conf = play.api.Play.current.configuration
val relayPubHost = conf.getString("relays.redis.host").get
val relayPubPort = conf.getInt("relays.redis.port").get
val rs = new RedisClient(relayPubHost, relayPubPort)
val s = system.actorOf(Props(new Subscriber(rs)))
s ! Register(callback)
s ! pSubscribeAll(Array("*"))
Logger.info("Engine Relay Subscriber has started up")
def receive: Receive = {
case Register(callback) =>
}
def callback(pubsub: PubSubMessage) = pubsub match {
case S(channel, no) => Logger.info("subscribed to " + channel + " and count = " + no)
case U(channel, no) => Logger.info("unsubscribed from " + channel + " and count = " + no)
case M(channel, msg) =>
msg match {
// exit will unsubscribe from all channels and stop subscription service
case "exit" =>
Logger.info("unsubscribe all ... no handler yet ;)")
// message "+x" will subscribe to channel x
case x if x startsWith "+" =>
Logger.info("subscribe to ... no handler yet ;)")
// message "-x" will unsubscribe from channel x
case x if x startsWith "-" =>
Logger.info("unsubscribe from ... no handler yet ;)")
// other message receive
case x =>
Logger.info("Engine: received redis message")
val channelVars = channel.split(".").toArray[String]
if(channelVars(0)!=Engine.instanceID)
channelVars(1) match {
case "relay" =>
EngineSyncLocal.constructRelay(channel, msg)
case _ =>
Logger.error("Engine: received unknown redis message")
}
}
}
}
Thanks for your help!
I found the problem. It appears to be a bug in the scala-redis client.
I added some logging in the consumer class and began receiving Engine: weird message errors which means that it doesn't recognize the incoming traffic. I'll contact the author and put in a pull request.
The code:
class Consumer(fn: PubSubMessage => Any) extends Runnable {
def start () {
val myThread = new Thread(this) ;
myThread.start() ;
}
def run {
whileTrue {
asList match {
case Some(Some(msgType) :: Some(channel) :: Some(data) :: Nil) =>
Logger.info("Engine: redis traffic")
msgType match {
case "subscribe" | "psubscribe" => fn(S(channel, data.toInt))
case "unsubscribe" if (data.toInt == 0) =>
fn(U(channel, data.toInt))
break
case "punsubscribe" if (data.toInt == 0) =>
fn(U(channel, data.toInt))
break
case "unsubscribe" | "punsubscribe" =>
fn(U(channel, data.toInt))
case "message" | "pmessage" =>
fn(M(channel, data))
case x => throw new RuntimeException("unhandled message: " + x)
}
case _ => Logger.error("Engine: weird redis message")
}
}
}
}
case x => throw new RuntimeException("unhandled message: " + x)
}
case Some(Some("pmessage")::Some(pattern)::Some(channel):: Some(message)::Nil)=>
fn(M(channel, message))
asList match is missing a case