According to the Play documentation on WebSockets the standard way to establish a WebSocket is to use ActorFlow.actorRef, which takes a function returning the Props of my actor. My goal is to get a reference to this underlying ActorRef, for instance in order to send a first message or to pass the ActorRef to another actor's constructor.
In terms of the minimal example from the documentation, I'm trying to achieve this:
class WebSocketController #Inject() (implicit system: ActorSystem, materializer: Materializer) {
def socket = WebSocket.accept[String, String] { request =>
val flow = ActorFlow.actorRef { out => MyWebSocketActor.props(out) }
// How to get the ActorRef that is created by MyWebSocketActor.props(out)?
// Fictitious syntax (does not work)
flow.underlyingActor ! "first message send"
flow
}
}
How can I get a reference to the actor that is created?
If it is not possible to get an ActorRef at this point (does it require materialization of the flow?), what would be the easiest way to store a reference to the created actor?
Using Actor.preStart() hook you can do some tricks to access the actorRef:
class MyWebSocketActor(
out: ActorRef,
firstMessage: Any,
actorRegistry: ActorRef
) extends Actor {
import play.api.libs.json.JsValue
override def preStart(): Unit = {
self ! firstMessage
actorRegistry ! self
}
...
}
def socket = WebSocket.accept[String, String] { request =>
ActorFlow.actorRef { out =>
Props(new MyWebSocketActor(out, "First Message", someRegistryActorRef))
}
}
Related
I am trying to create a web socket server using Play Framework where response from server should be synchronous or asynchronous based on request.
The request will be processed in Parent actor .Based on the action in the request, child actor will be created and message will be passed to child actor for processing and response will be sent back to the controller.
There are predefined actions and sample request for some actions are as follows,
[,,]
["1234","Boot","{"system":"ABCD"}"]
["5678","Start","{"system":"EFGH", "currenTime":"1559548762638"}"]
#Singleton
class RequestController #Inject()(cc: ControllerComponents)(implicit system: ActorSystem, mat: Materializer) extends AbstractController(cc) {
def ws = WebSocket.accept[String, String] {req =>
ActorFlow.actorRef { out =>
ParentActor.props(out)
}
}
}
object ParentActor {
def props(out: ActorRef) = Props(new ParentActor(out))
}
class ParentActor(out : ActorRef) extends Actor {
override def receive: Receive = {
case msg: String =>
//String split opeartion to find the action.
//create child actor for the action and pass the message to the child actor
val action = msg.split(",")[2]
if("Boot".equals(action)){
val bootActor: ActorRef = actorSystem.actorOf(Props[BootActor])
childActor ! msg
}else if("Start".equals(action)){
val startActor: ActorRef = actorSystem.actorOf(Props[StartActor])
startActor ! msg
}
case msg: Response => out ! msg
}
}
case class Response(name:String, msg:String)
class BootActor extends Actor{
override def receive: Receive = {
case msg : String =>
sender() ! Response("ABC",msg)
}
}
class StartActor extends Actor{
override def receive: Receive = {
case msg : String =>
sender() ! Response("Efgh",msg)
}
}
Right now i am getting the action from the request and create a child actor for the action and pass the message to the child actor for processing.
But i am not sure is there any better way or design pattern to process the request and create a child actor instead of String operation?
First of all, there appears to be a typo in your code:
if ("Boot".equals(action)) {
val bootActor: ActorRef = actorSystem.actorOf(Props[BootActor])
childActor ! msg
} else if ("Start".equals(action)) {
val startActor: ActorRef = actorSystem.actorOf(Props[StartActor])
startActor ! msg
}
The message in the first conditional clause should be sent to bootActor instead of childActor, which is undefined in your code snippet.
Another issue is that you're using actorSystem.actorOf to create the child actors. This method creates "top-level" actors, which should be kept to a minimum. Actors created with actorSystem.actorOf are under the supervision of the guardian actor. What this means in relation to your code is that when ParentActor is stopped (i.e., when a WebSocket is closed, Play stops the actor used in ActorFlow, as documented here), the multiple instances of BootActor and StartActor will not be stopped, leaving you with a bunch of idle top-level actors. The remedy is to use context.actorOf to create instances of BootActor and StartActor: doing so makes these instances children of ParentActor.
Also, you should use the == operator instead of the equals method.
Here are the aforementioned changes:
if ("Boot" == action) {
val bootActor: ActorRef = context.actorOf(Props[BootActor])
bootActor ! msg
} else if ("Start" == action) {
val startActor: ActorRef = context.actorOf(Props[StartActor])
startActor ! msg
}
The above could be slightly simplified to the following:
val childActor =
if (action == "Boot") context.actorOf(Props[BootActor])
else context.actorOf(Props[StartActor])
childActor ! msg
To further simplify your code, don't create child actors, which in this case aren't necessary. Move all the logic of interacting with the out actor into a single actor.
A future from the main method of a program sends a msg to its actor asking for an iterable object. The actor then creates another future that asks for the iterable object (say an ArrayBuffer) from a remote actor. After receiving the ArrayBuffer from the remote actor, how would the actor send it back to the first future in the main method? It seems creating a local alias of sender and creating a separate case class to represent the iterable does not prevent dead letters from being encountered.
Here is a sample code:
case class SequenceObject(sqnce:Seq[someArrayBuffer])
//...
implicit val timeout = Timeout(10 seconds)
val fut1: Future[Any] = myActor ? iNeedAnArrayBufferObject
fut1.onSuccess {
case listOfItems: SequenceObject => {
//do sth with listofItems.sqnce
}
class myActor extends Actor {
implicit val timeout = Timeout(1 seconds)
def receive = {
case a: iNeedAnArrayBufferObject => {
val originalSender = sender
val fut: Future[Any] = (remoteActor ? a)
fut.onSuccess {
case list: SequenceObject => {
originalSender ! SequenceObject(list.sqnce)
}
}
The remote actor code is:
class ServerActorClass extends Actor {
def receive = {
case a: iNeedAnArrayBufferObject => {
val closer = sender()
closer ! SequenceObject(ArrayBufferObject[information])
}
}
The above does not seem to work. The remote actor and the local actor can communicate and messages are received correctly. However, the iterable object is never send back to fut1. Why is that? Thanks in advance.
Check pipeTo pattern in Ask: Send-And-Receive-Future section
I'm new to Actors and I'm trying to understand how to chain actors properly. I've read Ask: Send-And-Receive-Future section of documentation.
Here is how I did it now
case class Message1(text : String)
class RootActor extends Actor {
def receive: Actor.Receive = {
case Message1(message) => {
(context.actorOf(Props( new TestActor1)) ? message)(5.seconds) pipeTo sender
}
}
}
class TestActor1 extends Actor {
def receive: Actor.Receive = {
case message : String => {
sender ! (message + " TestActor1")
}
}
}
object Test {
def main(args: Array[String]) {
println("Start!")
implicit val system = ActorSystem()
val rootActor = system.actorOf(Props( new RootActor))
for (reply1 <- (rootActor ? Message1("Begin"))(5.seconds).mapTo[String])
println(reply1)
}
}
So I manually relay reply. But shouldn't replies from child actor bubble up to its parent automatically ? Is this right way of doing it ?
Also I'm concerned that in manual way of bubbling up reply I use ask which requires timeout. But what if some child actor requires more time to do its job than others but I'm limited by one timeout value when I send message to root actor ? It seems to me that maintaining proper timeouts across entire chain is very cumbersome.
How can I simplify it ?
Just use forward instead. That's what it's there for. Use it like so :
context.actorOf(Props( new TestActor1)).forward(message)
I'm trying to log all messages received by a TestKit TestProbe, which is proving to be somewhat difficult. I'm aware of the Actor Logging section in the docs where it says one should use the debug.receive option in combination with a LogginReceive block. This however doesn't work when I'm not in control of the actor's implementation.
The only idea I had was subclassing akka.testkit.TestActor to use a LoggingReceive and then subclass TestKit to make it create instances of my TestActor subclass instead, but that didn't work because most functionality there is private to the akka namespace (and for good reason, I suppose).
There is a (probably surprisingly) simple answer:
probe.setAutoPilot(new TestActor.AutoPilot {
def run(sender: ActorRef, msg: Any) = {
log.debug("whatever")
this
}
})
Using Ronald's answer I wrote this to have a simpler way to define my probes:
object LoggingTestProbe {
def apply()(implicit system: ActorSystem) : TestProbe = {
val probe = TestProbe()
probe.setAutoPilot(new TestActor.AutoPilot {
def run(sender: ActorRef, msg: Any) = {
val other = sender.path
val me = probe.ref.path
system.log.debug(s"$me received $msg from $other")
this
}
})
probe
}
}
Having this I define my probes using LoggingTestProbe() instead of TestProbe().
I'm new to Scala so this might not be optimal but works great for me.
Sorry, got your question a little wrong at first, so here is my approach.
Create a wrapper actor, that logs the messages:
class LoggingActor(fac: => Actor) extends Actor {
val underlying = context.system.actorOf(Props(fac))
def receive = {
LoggingReceive {
case x ⇒ underlying.tell(x, sender)
}
}
}
and then just create your TestActorRef with your actor wrapped in the LoggingActor:
val echo = TestActorRef(new LoggingActor(new FooActor))
echo ! "hello world"
Recently I was switching from scala actors to akka actors, but noticed that akka actors use ActorRef instead of the instance object:
val actorRef: ActorRef = Actor.actorOf(new MyActor)
So I tried:
val myActor = new MyActor
val actorRef: ActorRef = Actor.actorOf(x)
... to have both: 1) ActorRef to send messages and 2) MyActor to call methods on.
But I got:
akka.actor.ActorInitializationException: ActorRef for instance of actor [MyActor] is not in scope.
So my question is: How can I obtain an instance (of some type) on which I can call ActorRef-like methods like ! AND also methods from the MyActor instance?
What you're doing is a terrible idea. So just stop right now, step away from the keyboard, and go to the Akka Documentation and read up on Actors.
Consider this:
class YourActor extends Actor {
var mutableStuff = ...
def receive = {
case _ =>
// mess with mutableStuff
}
def publicMethod = // mess with mutableStuff
}
Now, set up your system and start sending messages and calling that method from other threads. Boom!
You're doing precisely what Akka and the Actor model help you prevent. You're actually bending over backwards to break what they've already fixed :) They won't let you do it.
Now, you can unit test by accessing methods directly but you need a TestActorRef for that. While you're reading the docs, read through the section on Testing.
The best that I can up with is the following, quite dirty:
Is there a better way?
import akka.actor._
trait ActorCom {
var actorRefForInitialization: ActorRef = _
lazy val actorRef: ActorRef = actorRefForInitialization
def ?(message: Any)(implicit channel: UntypedChannel = NullChannel, timeout: Actor.Timeout = Actor.defaultTimeout) = actorRef ? message
def !(msg: Any)(implicit sender: UntypedChannel) = actorRef ! msg
def start = actorRef.start
}
object AkkaActorFactory {
def apply[A <: Actor](newInstance: => A with ActorCom): A with ActorCom = {
var instance: Option[A with ActorCom] = None
val actorRef = Actor.actorOf({
instance = Some(newInstance)
instance.get
})
instance.get.actorRefForInitialization = actorRef
instance.get.actorRef // touch lazy val in ActorCom, to make it equal to actorRef and then its fixed (immutable)
instance.get
}
}
class MyActor extends Actor {
def receive = {
case "test1" => println("good")
case "test2" => println("fine")
case _ => println("bad")
}
def sendTestMsg2Myself = self ! "test2"
}
val myActor = AkkaActorFactory(newInstance = new MyActor with ActorCom)
myActor.start
myActor ! "test1"
myActor.sendTestMsg2Myself // example for calling methods on MyActor-instance
myActor ! PoisonPill