Akka: How to wrap a message content into an HTTP response? - scala

In my Akka-http route I get a specific message back and I want to wrap its content as error message like:
val response:Future[T] = (actor ? command).mapTo[T]
response match {
case err : Future[InvalidRequest] =>
HttpResponse(408, entity = err.map(_.toJson).????)
case r : Future[T] => r.map(_.toJson)
}
case class InvalidRequest(error:String)
implicit val invalidRequestFormat = jsonFormat1(InvalidRequest)
but that doesn't work. How can I map it as text in json format?

I think I can provide a generic solution for what it is you are trying to do. You can start by creating a method that returns a Route as follows:
def service[T:ClassTag](actor:ActorRef, command:Any)
(implicit timeout:Timeout, _marshaller: ToResponseMarshaller[T]):Route = {
val fut = (actor ? command).mapTo[ServiceResponse]
onComplete(fut){
case util.Success(ir:InvalidRequest) =>
complete(StatusCodes.BadRequest, ir)
case util.Success(t:T) =>
complete(t)
case util.Failure(ex) =>
complete(StatusCodes.InternalServerError )
}
}
This method fires a request to a supplied actor, via ask, and gets the Future representing the result. It then uses the onComplete directive to apply special handling to the InvalidResponse case. It's important here that you have an implicit ToResponseMarshaller[T] in scope as you will need that for the success case.
Then, let's say you had the following classes and formatters defined:
trait ServiceResponse
case class Foo(id:Int) extends ServiceResponse
implicit val fooFormat = jsonFormat1(Foo)
case class InvalidRequest(error:String) extends ServiceResponse
implicit val invalidRequestFormat = jsonFormat1(InvalidRequest)
You could use your new service method within your routing tree as follows:
val routes:Route = {
path("api" / "foo"){
get{
service[Foo](fooActor, FooActor.DoFoo)
}
}
}
The problem with your example is that you were not waiting for the completion of the Future before building out the response. You were trying to match on the underlying type of the Future, which is eliminated by erasure at runtime, so is not a good idea to try and match against in that way. You instead need to wait until it's completed and then see the type that is behind the Future.

Related

What's the Akka-typed equivalent to pipeTo?

I'm currently trying to rewrite an existing untyped actor into a typed one. Since the actor is talking to a MySQL database using ScalikeJDBC, and since I'd like to have that done asynchronously, I'm dealing with Futures coming out of a separate (non-actor) repository class.
With untyped Akka, in an actor's receive method, I could do this:
import akka.pattern.pipe
val horseList : Future[Seq[Horse]] = horseRepository.listHorses(...)
horseList pipeTo sender()
And the sender actor would eventually receive a list of horses. I can't figure out how to do this inside a Behaviour, like:
val behaviour : Behavior[ListHorses] = Behaviors.receive {
(ctx,msg) => msg match {
case ListHorses(replyTo) =>
val horseListF : Future[Seq[Horse]] = horseRepository.listHorses(...)
// -> how do I make horseListF's content end up at replyTo? <-
Behaviors.same
}
}
The pipe pattern doesn't work (as it expects an untyped ActorRef), and so far I haven't found anything else in the akka-actor-typed (2.5.12) dependency I'm using to make this work.
How do I do this?
In Akka 2.5.22 (maybe earlier) there is context.pipeToSelf:
def pipeToSelf[Value](future: Future[Value])(mapResult: Try[Value] => T): Unit
You still have to provide a pattern match for Success and Failure, which in my code I've reduced with this sugar:
def mapPipe[A, T](success: A => T, failure: Throwable => T): Try[A] => T = {
case Success(value) => success(value)
case Failure(e) => failure(e)
}
Resulting in a call like this:
case class Horses(horses: Seq[Horse]) extends Command
case class HorseFailure(e: Throwable) extends Command
...
context.pipeToSelf(horseList) {
mapPipe(Horses,HorseFailure)
}
You can simply send a message to replyTo when the future completes successfully:
case ListHorses(replyTo) =>
horseRepository.listHorses(...) foreach { horses => replyTo ! horses }
Behaviors.same
Or if you want to handle errors as well:
case ListHorses(replyTo) =>
horseRepository.listHorses(...) onComplete {
case Success(horses) => replyTo ! horses
case Failure(e) => // error handling
}
Behaviors.same
In order for this to work, you need an ExecutionContext. It usually makes sense to use the same one as the actor, so you will have to make it available to onComplete or foreach first:
implicit val ec = ctx.executionContext

Add exception handling in http4s with rho

I'm using http4s & rho (mainly for Swagger integration)
My services are using this DAO object, that methods that can throw Exceptions (fail the Task)
case class BasicMatchDao() {
def readAll(): Task[List[BasicMatch]] = Task.fail(ActionNotImplemented("readAll"))
def read(id: String): Task[Option[BasicMatch]] = readQuery(id).option.transact(xa)
}
In my RhoService I can handle these like
private def exceptionToJson(t: Throwable):Json = Json.obj("error" -> t.getMessage.asJson)
val rhoService = new RhoService {
GET / path |>> { (request: Request) =>
Ok(dao.readAll.map(_.asJson)).handleWith {
case t:ActionNotImplemented => NotImplemented(exceptionToJson(t))
case t:Throwable => InternalServerError(exceptionToJson(t))
}
}
This way I make sure that whatever I return, it's always a Json
Since I don't want to pollute every RhoRoute with a similar errorhandling I want to do something which is possible with the default http4s.dsl, but I can't seem to get working with rho:
1. Create default error handler
e.g. add
...
Ok(dao.readAll.map(_.asJson)).handleWith(errorHandler)
...
private def errorHandler(): PartialFunction[Throwable, Task[Response]] = {
case t:ActionNotImplemented => NotImplemented(exceptionToJson(t))
case t:Throwable => InternalServerError(exceptionToJson(t))
}
This will fail because NotImplemented is not a Response (I can call .pure on these to make type checking work)
But then the code will compile, but I get this exception:
Cannot convert from fs2.Task[Product with Serializable]
to an Entity, because no EntityEncoder[fs2.Task[Product with
Serializable]] instance could be found.
Ok(dao.readAll.map(_.asJson)).handleWith(errorHandler)
2. Add errorhandler to each RhoRoute
After defining the rhoRoute I'd like to map over it and add the errorhandler to each route, so do something at the r that let's me add the 'handleWith' somewhere (below will not work)
new RhoService(rhoService.getRoutes.map(_.handleWith(errorHandler))
If I can't get this to work, I'll probably move back to the default dsl, but I really liked rho
So Part 1 is fixed for now. Defining the Task as Task[BaseResult] instead of Task[Response] will work
import org.http4s.rho.Result.BaseResult
val errorHandler: PartialFunction[Throwable, Task[BaseResult]] = {
case t:ActionNotImplemented => NotImplemented(exceptionToJson(t))
case t:Throwable => InternalServerError(exceptionToJson(t))
}
I'm looking into part 2 as well. All help is welcome :-)

Akka-http approach to handle websocket commands

Let's say I have controller that handles commands that I receive from websocket.
class WebSocketController(implicit M: Materializer)
extends Controller
with CirceSupport {
override def route: Route = path("ws") {
handleWebSocketMessages(wsFlow)
}
def wsFlow: Flow[Message, Message, Any] =
Flow[Message].mapConcat {
case tm: TextMessage =>
decode[Command](tm.getStrictText) match {
// todo pass this command to some actor or service
// and get response and reply back.
case Right(AuthorizeUser(login, password)) =>
TextMessage(s"Authorized: $login, $password") :: Nil
case _ =>
Nil
}
case bm: BinaryMessage =>
bm.dataStream.runWith(Sink.ignore)
Nil
}
}
So, I get a command, deserialize it and next step I want to do is to pass it to some service or actor that will return me Future[SomeReply].
The question is:
What is the basic approach of handling such flow with akka streams?
When handling Futures inside a Flow, mapAsync is usually what you're looking for. To add to your specific example:
def asyncOp(msg: TextMessage): Future[SomeReply] = ???
def tailorResponse(r: SomeReply): TextMessage = ???
def wsFlow: Flow[Message, Message, Any] =
Flow[Message]
.mapConcat {...}
.mapAsync(parallelism = 4)(asyncOp)
.via(tailorResponse)
mapAsyncUnordered can also be used, in case the order of the Futures result is not relevant.
Parallelism indicates how many Futures can be run at the same time, before the stage backpressures.
See also
stage docs
how to use in conjunction with ask - here

Akka Http - How to Unmarshall ResponseEntity to CustomClass?

I am using Akka Http to make requests to a 3rd party API. The responses are "application/json", and I would like to use Akka Http to convert them to a custom case class. I would like to do something like this:
val request = RequestBuilding.Get("https://service.com/v1/api/items")
val response : Future[ItemsResponse] = http.singleRequest(request).flatMap({ response =>
Unmarshal(response.entity).to[ItemsResponse]
})
This fails to compile, because I am missing an implicit unmarshaller of type akka.http.scaladsl.unmarshalling.Unmarshaller[akka.http.scaladsl.model.ResponseEntity, com.mycompany.models.ItemsResponse].
It's unclear to me what the idiomatic way to do this with akka http is. I am aware that I could use spray-json, but I'd like to understand how to do this without importing another library. It seems possible with Akka Http, but the documentation isn't clear (to me at least).
The simplest way is to use spray-json as it comes as part of Akka HTTP:
import spray.json._
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
// change 2 to the number of attributes of ItemsResponse
implicit val ItemsResponseFormat = jsonFormat2(ItemsResponse)
This should make your existing code compile.
I think your question is valid, and there are cases where avoiding extra dependencies makes sense. Mine is from making an authentication library, where I don't want to impose my JSON library preferences to the users of such library. The library needs JSON unmarshalling for understanding a token info response.
To the code! :)
case class TokenInfo private (uid: String, realm: String, scope: Seq[String])
object TokenInfo {
private
def parseOpt(s: String): Option[TokenInfo] = {
util.parsing.json.JSON.parseFull(s) match {
case Some(map: Map[String,Any] #unchecked) =>
val tmp: Map[String,Any] = map.collect {
case (k# "uid",x: String) => k -> x
case (k# "realm",x: String) => k -> x
case (k# "scope",x: Seq[String] #unchecked) => k -> x
// other keys are ignored
}.toMap
if (tmp.size == 3) {
Some( TokenInfo( tmp("uid").asInstanceOf[String], tmp("realm").asInstanceOf[String], tmp("scope").asInstanceOf[Seq[String]]) )
} else {
None
}
case _ => None
}
}
implicit
val unm: FromEntityUnmarshaller[TokenInfo] = {
PredefinedFromEntityUnmarshallers.stringUnmarshaller.map{ s => parseOpt(s).getOrElse{
throw new RuntimeException(s"Unknown TokenInfo: $s")
}}
}
}
I chose to use util.parsing.json which comes within Scala. The other option was simply regex's, but in that case I'm either fixing the expected order of fields, or the code might get complex.

on demand actor get or else create

I can create actors with actorOf and look them with actorFor. I now want to get an actor by some id:String and if it doesnt exist, I want it to be created. Something like this:
def getRCActor(id: String):ActorRef = {
Logger.info("getting actor %s".format(id))
var a = system.actorFor(id)
if(a.isTerminated){
Logger.info("actor is terminated, creating new one")
return system.actorOf(Props[RC], id:String)
}else{
return a
}
}
But this doesn't work as isTerminated is always true and I get actor name 1 is not unique! exception for the second call. I guess I am using the wrong pattern here. Can someone help how to achieve this? I need
Create actors on demand
Lookup actors by id and if not present create them
Ability to destroy on, as I don't know if I will need it again
Should I use a Dispatcher or Router for this?
Solution
As proposed I use a concrete Supervisor that holds the available actors in a map. It can be asked to provide one of his children.
class RCSupervisor extends Actor {
implicit val timeout = Timeout(1 second)
var as = Map.empty[String, ActorRef]
def getRCActor(id: String) = as get id getOrElse {
val c = context actorOf Props[RC]
as += id -> c
context watch c
Logger.info("created actor")
c
}
def receive = {
case Find(id) => {
sender ! getRCActor(id)
}
case Terminated(ref) => {
Logger.info("actor terminated")
as = as filterNot { case (_, v) => v == ref }
}
}
}
His companion object
object RCSupervisor {
// this is specific to Playframework (Play's default actor system)
var supervisor = Akka.system.actorOf(Props[RCSupervisor])
implicit val timeout = Timeout(1 second)
def findA(id: String): ActorRef = {
val f = (supervisor ? Find(id))
Await.result(f, timeout.duration).asInstanceOf[ActorRef]
}
...
}
I've not been using akka for that long, but the creator of the actors is by default their supervisor. Hence the parent can listen for their termination;
var as = Map.empty[String, ActorRef]
def getRCActor(id: String) = as get id getOrElse {
val c = context actorOf Props[RC]
as += id -> c
context watch c
c
}
But obviously you need to watch for their Termination;
def receive = {
case Terminated(ref) => as = as filterNot { case (_, v) => v == ref }
Is that a solution? I must say I didn't completely understand what you meant by "terminated is always true => actor name 1 is not unique!"
Actors can only be created by their parent, and from your description I assume that you are trying to have the system create a non-toplevel actor, which will always fail. What you should do is to send a message to the parent saying “give me that child here”, then the parent can check whether that currently exists, is in good health, etc., possibly create a new one and then respond with an appropriate result message.
To reiterate this extremely important point: get-or-create can ONLY ever be done by the direct parent.
I based my solution to this problem on oxbow_lakes' code/suggestion, but instead of creating a simple collection of all the children actors I used a (bidirectional) map, which might be beneficial if the number of child actors is significant.
import play.api._
import akka.actor._
import scala.collection.mutable.Map
trait ResponsibleActor[K] extends Actor {
val keyActorRefMap: Map[K, ActorRef] = Map[K, ActorRef]()
val actorRefKeyMap: Map[ActorRef, K] = Map[ActorRef, K]()
def getOrCreateActor(key: K, props: => Props, name: => String): ActorRef = {
keyActorRefMap get key match {
case Some(ar) => ar
case None => {
val newRef: ActorRef = context.actorOf(props, name)
//newRef shouldn't be present in the map already (if the key is different)
actorRefKeyMap get newRef match{
case Some(x) => throw new Exception{}
case None =>
}
keyActorRefMap += Tuple2(key, newRef)
actorRefKeyMap += Tuple2(newRef, key)
newRef
}
}
}
def getOrCreateActorSimple(key: K, props: => Props): ActorRef = getOrCreateActor(key, props, key.toString)
/**
* method analogous to Actor's receive. Any subclasses should implement this method to handle all messages
* except for the Terminate(ref) message passed from children
*/
def responsibleReceive: Receive
def receive: Receive = {
case Terminated(ref) => {
//removing both key and actor ref from both maps
val pr: Option[Tuple2[K, ActorRef]] = for{
key <- actorRefKeyMap.get(ref)
reref <- keyActorRefMap.get(key)
} yield (key, reref)
pr match {
case None => //error
case Some((key, reref)) => {
actorRefKeyMap -= ref
keyActorRefMap -= key
}
}
}
case sth => responsibleReceive(sth)
}
}
To use this functionality you inherit from ResponsibleActor and implement responsibleReceive. Note: this code isn't yet thoroughly tested and might still have some issues. I ommited some error handling to improve readability.
Currently you can use Guice dependency injection with Akka, which is explained at http://www.lightbend.com/activator/template/activator-akka-scala-guice. You have to create an accompanying module for the actor. In its configure method you then need to create a named binding to the actor class and some properties. The properties could come from a configuration where, for example, a router is configured for the actor. You can also put the router configuration in there programmatically. Anywhere you need a reference to the actor you inject it with #Named("actorname"). The configured router will create an actor instance when needed.