I am using an actor as a Sink as the following:
import akka.Done
import akka.actor.{Actor, ActorLogging, Props}
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.ws.{Message, TextMessage, WebSocketRequest}
import akka.stream.{ActorMaterializer, Materializer}
import akka.stream.scaladsl.{Flow, Keep, Sink, Source}
import scala.concurrent._
import scala.util.{Failure, Success}
object WsActor {
def props: Props = Props(new WsActor)
}
final class WsActor extends Actor with ActorLogging {
import com.sweetsoft.WsConnector._
implicit val materializer: Materializer = ActorMaterializer()
implicit val ec: ExecutionContextExecutor = context.system.dispatcher
implicit val actor = context.system
// Future[Done] is the materialized value of Sink.foreach,
// emitted when the stream completes
private val incoming: Sink[Message, Future[Done]] =
Sink.foreach[Message] {
case message: TextMessage.Strict =>
println(message.text)
case _ =>
println("Unknown messages.")
}
//private val outgoing: Source[Message, Promise[Option[Message]]] =
// Source.maybe[Message]
log.info("Websocket actor started.")
override def receive: Receive = {
case Initialized =>
log.info("Initialization to receive messages via stream.")
sender() ! Ack
case Completed =>
log.info("Streams completed.")
sender() ! Ack
case Msg(value) =>
// the Future[Done] is the materialized value of Sink.foreach
// and it is completed when the stream completes
val flow: Flow[Message, Message, Future[Done]] =
Flow.fromSinkAndSourceMat(incoming, Source.single(TextMessage(value)))(Keep.left)
// upgradeResponse is a Future[WebSocketUpgradeResponse] that
// completes or fails when the connection succeeds or fails
// and closed is a Future[Done] representing the stream completion from above
val (upgradeResponse, _) =
Http().singleWebSocketRequest(WebSocketRequest("ws://echo.websocket.org"), flow)
upgradeResponse.flatMap { upgrade =>
if (upgrade.response.status == StatusCodes.SwitchingProtocols) {
Future.successful(Done)
} else {
throw new RuntimeException(s"Connection failed: ${upgrade.response.status}")
}
}.andThen {
case Success(_) =>
log.info("Sending ACK")
sender() ! Ack
}.onComplete {
case Success(_) =>
log.info("Success proceed")
case Failure(ex) => log.error(ex.getMessage)
}
//sender() ! Ack
case Failed(ex) =>
log.info(s"Stream failed with ${ex.getMessage}.")
}
}
The actor consumes the messages and redirect further to websocket server.
Somewhere in the code, I send an Ack to the Sender actor to signalize, that it ready to receive further messages. But the sender actor never receive the Ack message.
If I put sender() ! Ack the FUTURE chain, then it works as expected.
Is it possible to put sender() ! Ack within FUTURE` chain.
Try remembering the sender on message receive and then using it later in the code instead of sender() instead of calling sender() downstream, this that value might not be constant during processing (e.g when receiving other messages in the meantime when ur current task does not block processing of the message queue due to futures )
case Msg(value) =>
val sender = sender()
.
.
sender ! Ack
Your mistake is the following lines:
}.andThen {
case Success(_) =>
log.info("Sending ACK")
sender() ! Ack
}.onComplete {
The andThen callback will be performed by another thread, and the sender() call may be empty (relatively lucky) or even another actor alltogether. You need to capture the sender before performing the first asynchronous action.
val respondTo = sender()
upgradeResponse.flatMap { upgrade =>
if (upgrade.response.status == StatusCodes.SwitchingProtocols) {
Future.successful(Done)
} else {
throw new RuntimeException(s"Connection failed: ${upgrade.response.status}")
}
}.andThen {
case Success(_) =>
log.info("Sending ACK")
respondTo ! Ack
}.onComplete {
case Success(_) =>
log.info("Success proceed")
case Failure(ex) => log.error(ex.getMessage)
}
Related
I am starting to develop in Scala, so I started witha really simple RESTful API using AKKA HTTP actors and then wanted to add a PostgreSQL database to "close up" the project. The thing is that somewhere in the project, a Future that is returned by a db.run method is converted into a Promise and returning me errors. When I run the Main object and start the API and hit somewhere, I get this error:
Cannot cast scala.concurrent.impl.Promise$DefaultPromise to scala.collection.immutable.Seq or Cannot cast scala.concurrent.impl.Promise$DefaultPromise to api.Item depending on which route I an hitting.
Here is the main api.scala file:
package api
import akka.actor.{Actor, ActorSystem, Props}
import akka.http.scaladsl.Http
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import akka.pattern.ask
import akka.util.Timeout
import api.Main.ItemActor._
import slick.jdbc.JdbcBackend.Database
import spray.json.DefaultJsonProtocol._
import scala.concurrent.Future
import scala.concurrent.duration.DurationInt
import scala.util.{Failure, Success}
object Main extends App {
val db = Database.forConfig("scaladb");
val itemDao = new handler(db)
val system = ActorSystem("mySystem")
val itemActor = system.actorOf(Props(new ItemActor(db)))
implicit val actorSystem = system
implicit val itemFormat = jsonFormat3(Item)
implicit val timeout: Timeout = Timeout(5.seconds)
class ItemActor(db: Database) extends Actor {
import api.Main.ItemActor._
def receive = {
case CreateItem(item) =>
sender() ! itemDao.create(item)
case ReadItem(id) =>
sender() ! itemDao.read(id)
case ReadAllItems =>
sender() ! itemDao.readAll
case UpdateItem(item) =>
sender() ! itemDao.update(item)
case DeleteItem(id) =>
sender() ! itemDao.delete(id)
}
}
object ItemActor {
case class CreateItem(item: Item)
case class ReadItem(id: Int)
case object ReadAllItems
case class UpdateItem(item: Item)
case class DeleteItem(id: Int)
}
def handleResponse(futureResponse: Future[Item]): Route = {
onComplete(futureResponse) {
case Success(response) => complete(response)
case Failure(ex) => complete(StatusCodes.InternalServerError, s"An error occurred: ${ex.getMessage}")
}
}
def handleResponseSeq(futureResponse: Future[Seq[Item]]): Route = {
onComplete(futureResponse) {
case Success(response) => complete(response)
case Failure(ex) => complete(StatusCodes.InternalServerError, s"An error occurred: ${ex.getMessage}")
}
}
val routes = pathPrefix("items") {
pathEnd {
post {
entity(as[Item]) { item =>
handleResponse((itemActor ? CreateItem(item)).mapTo[Item])
}
} ~
get {
handleResponseSeq((itemActor ? ReadAllItems).mapTo[Seq[Item]])
}
} ~
path(IntNumber) { id =>
get {
handleResponse((itemActor ? ReadItem(id)).mapTo[Item])
} ~
put {
entity(as[Item]) { item =>
handleResponse((itemActor ? UpdateItem(item)).mapTo[Item])
}
} ~
delete {
handleResponse((itemActor ? DeleteItem(id)).mapTo[Item])
}
}
}
val bindRoutes = Http().bindAndHandle(routes, "localhost", 8888)
println("Server online at http://localhost:8888/")
}
Then the handler (Where I definde the methods that access the PostgreSQL database):
package api
import slick.jdbc.PostgresProfile.api._
import scala.concurrent.Future
class handler (db:Database){
val items = TableQuery[Items]
def create(item:Item): Future[Item] = {
db.run((items returning items.map(_.id.?) into ((item, id) => item.copy(id = id))) += item)
}
def read(id: Int): Future[Option[Item]] = {
db.run(items.filter(_.id === id).result.headOption)
}
def readAll: Future[Seq[Item]] = {
println((db.run(items.result)).getClass)
db.run(items.result)
}
def update(item: Item): Future[Int] = {
db.run(items.filter(_.id === item.id).update(item))
}
def delete(id: Int): Future[Int] = {
db.run(items.filter(_.id === id).delete)
}
}
And the items file:
package api
import slick.jdbc.PostgresProfile.api._
case class Item(id: Option[Int] = None, name: String, description: String)
class Items(tag: Tag) extends Table[Item](tag, "items") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def description = column[String]("description")
def * = (id.?, name, description) <> (Item.tupled, Item.unapply)
}
I've tried to use a getClass next to the db.run(items.result) in the handler file, and it prits class scala.concurrent.impl.Promise$DefaultPromise so it must be something of an implicit converter. Thanks.
You're mixing Futures and actors, which is generally not a great idea.
In your ItemActor, instead of sending the future as a reply, it's a better idea to pipe the future as a reply (the reply won't actually happen until the future is complete, that is to say, the DAO has a result).
import akka.pattern.pipe
class ItemActor(db: Database) extends Actor {
import ItemActor._
import context.dispatcher
def receive = {
case CreateItem(item) =>
itemDao.create(item).pipeTo(sender())
case ReadItem(id) =>
itemDao.read(id).pipeTo(sender())
}
}
That said, at least in this code, there doesn't really seem to be a good reason for ItemActor to exist, given that it's just forwarding operations to the DAO. Making the itemDao visible in the routes, you could just as well do:
handleResponse(itemDao.create(item))
Here: handleResponse((itemActor ? CreateItem(item)).mapTo[Item])
Actor returns Future[Item], mapTo[Item] tries to cast it to item and fails.
You want your actor to return the actual item, not Future result from db.run.
I haven't used akka in a while, but I think, something like this should work:
val replyTo = sender
...
case CreateItem(item) => itemDao.create(item).onComplete {
case Success(i) => replyTo ! i
case Failure(e) => throw e
}
...
I'm trying to create simple tcp server with help of akka.
My main
import java.net.InetSocketAddress
import akka.actor._
import akka.io._
import Tcp._
object Main extends App {
implicit val actorSystem: ActorSystem = ActorSystem()
val serverRef = actorSystem.actorOf(server.Server.props)
val tcpManager = IO(Tcp)
tcpManager ! Bind(serverRef, new InetSocketAddress("localhost", 8099))
}
My server actor:
package server
import akka.actor._
import akka.io.Tcp
import Tcp._
object Server {
def props = Props(new Server())
class SimplisticHandler extends Actor {
def receive = {
case Received(data) => sender() ! Write(data)
case PeerClosed => context.stop(self)
}
}
}
class Server extends Actor {
override def receive: Receive = {
case b # Bound(localAddress) =>
println(s"connected to ${localAddress.toString}")
context.parent ! b
case CommandFailed(_: Bind) => context.stop(self)
case c # Connected(remote, local) =>
val handler = context.actorOf(Server.props)
val connection = sender()
connection ! Register(handler)
case msg =>
println("Unknown message")
}
}
But when i run it i get dead letter log event:
table [INFO] [11/08/2019 18:34:11.558]
[default-akka.actor.default-dispatcher-6] [akka://default/deadLetters]
Message [akka.io.Tcp$Bound] from
Actor[akka://default/system/IO-TCP/selectors/$a/0#-176629096] to
Actor[akka://default/deadLetters] was not delivered. [1] dead letters
encountered. If this is not an expected behavior then
Actor[akka://default/deadLetters] may have terminated unexpectedly.
This logging can be turned off or adjusted with configuration settings
'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'.
The Bind message should be sent from an actor. That actor will eventually receive Bound message from TCP manager (or CommandFailed). In this case, Server actor should send Bind message. For instance:
class Server extends Actor {
IO(Tcp) ! Bind(self, new InetSocketAddress("localhost", 8099)) //<--------
override def receive: Receive = {
case b # Bound(localAddress) =>
println(s"connected to ${localAddress.toString}")
context.parent ! b
case CommandFailed(_: Bind) => context.stop(self)
case c # Connected(remote, local) =>
val handler = context.actorOf(Server.props)
val connection = sender()
connection ! Register(handler)
case msg =>
println("Unknown message")
}
}
I have the following code, that does not keep the connection open to the websocket server:
import akka.Done
import akka.actor.{Actor, ActorLogging, Props}
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.ws.{Message, TextMessage, WebSocketRequest}
import akka.stream.{ActorMaterializer, Materializer}
import akka.stream.scaladsl.{Flow, Keep, Sink, Source}
import scala.concurrent._
import scala.util.{Failure, Success}
object WsActor {
def props: Props = Props(new WsActor)
}
final class WsActor extends Actor with ActorLogging {
import com.sweetsoft.WsConnector._
implicit val materializer: Materializer = ActorMaterializer()
implicit val ec: ExecutionContextExecutor = context.system.dispatcher
implicit val actor = context.system
// Future[Done] is the materialized value of Sink.foreach,
// emitted when the stream completes
private val incoming: Sink[Message, Future[Done]] =
Sink.foreach[Message] {
case message: TextMessage.Strict =>
println(message.text)
case _ =>
println("Unknown messages.")
}
//private val outgoing: Source[Message, Promise[Option[Message]]] =
// Source.maybe[Message]
// val flow: Flow[Message, Message, Promise[Option[Message]]] =
// Flow.fromSinkAndSourceMat(incoming, Source.maybe[Message])(Keep.right)
log.info("Websocket actor started.")
override def receive: Receive = {
case Initialized =>
log.info("Initialization to receive messages via stream.")
sender() ! Ack
case Completed =>
log.info("Streams completed.")
sender() ! Ack
case Msg(value) =>
val replyTo = sender()
val flow: Flow[Message, Message, Promise[Option[Message]]] =
Flow.fromSinkAndSourceMat(incoming, Source.single(TextMessage(value)).concatMat(Source.maybe[Message])(Keep.right))(Keep.right)
val (upgradeResponse, _) =
Http().singleWebSocketRequest(WebSocketRequest("ws://127.0.0.1:7000/ws"), flow.mapAsync(4)(msg => Future(msg)))
upgradeResponse.flatMap { upgrade =>
if (upgrade.response.status == StatusCodes.SwitchingProtocols) {
Future.successful(Done)
} else {
throw new RuntimeException(s"Connection failed: ${upgrade.response.status}")
}
}.onComplete {
case Success(_) =>
replyTo ! Ack
log.info("Done")
case Failure(ex) => log.error(ex.getMessage)
}
case Failed(ex) =>
log.info(s"Stream failed with ${ex.getMessage}.")
}
}
So every time, when a message is received, it will close the connection and open a new connection for the next request.
The question is, how can I keep the connection open?
Http().webSocketClientFlow in stead of Http().singleWebSocketRequest
Http().webSocketClientFlow will give you a Flow Flow[Message, Message, Future[WebSocketUpgradeResponse]]
This will not create a new connection every time.
You should declare it in the companion object, so every instance of the class can use the same connection.
First declare your actor system for entire application in a separate package.
object ActorEssentials {
implicit val actorSystem = ActorSystem("test")
}
Then you can declare the following
object WsActor {
import ActorEssentials._
def props: Props = Props[WsActor]
val flow = Http()(actorSystem).webSocketClientFlow(...)
}
I have two actors coded as follows.
class Actor1 extends Actor {
val r : ActorRef = actorOf (Props[Actor2], "master")
def receive: Receive = {
case class Mul (a,b) =>
r ! CalcMul (a,b)
case class MulReply (ans) =>
println("Multiply result : " + ans)
// want to send "ans" value to testObject..
}
}
class Actor2 extends Actor {
def receive: Receive = {
case class CalcMul (a,b) =>
sender ! MulReply (a*b)
}
}
object testObject extends App {
val a = ActorSystem("ActorSystem").actorOf(Props[Actor1], "root")
a ! CalcMul (5, 15)
// how to receive "ans" value here?
}
I am able to receive and print the result in Actor1 but need those values in testObject so I can use them for future operations. Cannot have a receive method in testObject as done to receive a message in Actor1 from Actor2, so cannot send them with tell method.
As you want to receive a response from an actor you can use ask pattern for this purpose.
import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import akka.pattern._
import akka.util.Timeout
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.concurrent.duration.SECONDS
case class CalsMul(a: Int, b: Int)
class Actor1 extends Actor {
val r: ActorRef = context.actorOf(Props[Actor2], "master")
def receive: Receive = {
case req: CalsMul =>
println("received message by Actor1")
r forward req
}
}
class Actor2 extends Actor {
def receive: Receive = {
case request: CalsMul =>
println("received message by Actor2")
Future.successful(request.a * request.b) pipeTo sender
}
}
object testObject extends App {
implicit val system: ActorSystem = ActorSystem("ActorSystem")
val a: ActorRef = system.actorOf(Props[Actor1], "root")
implicit val timeout: Timeout = Timeout(20, SECONDS)
println(system, "sending message to Actor1")
val ans: Future[Int] = (a ? CalsMul(5, 15)).mapTo[Int] // as you are returning the multiplication of a*b
ans.foreach(println)
}
Note: CPU bound operations are not advised to use with actors it can have adverse effect on the performance of your application
I am designing an actor system. centerActor need to wait result from both actor1 and actor2 to response to the client.
In the centerActor:
def receive: Receive ={
case request => {
implicit val timeout = Timeout(1.seconds)
val ask1 = actor1 ? Update1.mapTo[Int]
val ask2 = actor2 ? Update2.mapTo[String]
val response =
(for(x <- ask1; y <- ask2) yield Done).recover{case _ => FailReply}
response pipeTo sender
}
}
In this design, my sender(client) cannot receive Done message. I don't know why.
So I am thinking about the second design:
def receive: Receive = {
implicit val timeout = Timeout(1.seconds)
case request => {
val ask1 = actor1 ? Update1.mapTo[Int]
val ask2 = actor2 ? Update2.mapTo[String]
val response =
(for(x <- ask1; y <- ask2) yield Done).recover{case _ => FailReply})
response pipeTo self
context.become(waiting(sender))
}
}
def waiting(s: ActorRef) = {
case Done =>
s ! Done
unstashAll()
context.unbecome()
case FailReply => s ! FailReply
case _ => stash()
}