Scala, Play, Akka, Websocket: how to pass actor messages through websocket - scala

I have an actor which is launched with application, running in the background watching for certain changes and if there are any reporting them. At the moment it just a println to the console. What I need to do is whenever there is a new message - send it to the front end using Websocket.
This is my Play Global object where the monitoring/listening actor is launched:
object Global extends GlobalSettings {
override def onStart(app: Application) {
class Listener extends Actor {
//This needs to be changed to pass messages to Websocket, how?
def receive = {
case Create(path) => println("CREATE " + path)
case Delete(path) => println("DELETE " + path)
case Modify(path) => println("MODIFY " + path)
}
}
val listener = Akka.system.actorOf(Props[Listener], "listener")
val swatch = Akka.system.actorOf(Props[SwatchActor], "swatch")
swatch ! Watch("/folder/path", Seq(Create, Modify, Delete), true, Some(listener))
}
}
This is my Play controller:
object Application extends Controller {
def test = WebSocket.using[String] { request =>
//This hopefully gets the listener actor reference?
val listener = Akka.system.actorSelection("/user/listener")
val (out, channel) = Concurrent.broadcast[String]
val in = Iteratee.foreach[String] { msg =>
//Actor messages must be pushed here, how?
channel push("RESPONSE: " + msg)
}
(in, out)
}
}
I understand that in order for websocket connection to be established there has to be an initial "in".
So my problems are:
How do I modify the Listener actor to push messages to Websocket?
What do I need to do to prepare the actor to push messages once the websocket connection is established?
How do I push messages from the listener actor to the websocket?

I have found the solution.
Case class that has to be imported from a separate file:
case class Start(out: Concurrent.Channel[String])
Global object:
object Global extends GlobalSettings {
override def onStart(app: Application) {
class Listener extends Actor {
var out = {
val (enum, chan) = Concurrent.broadcast[String]
chan
}
def receive = {
//Websocket channel out is set here
case Start(out) => this.out = out
//Pushing messages to Websocket
case Create(path) => this.out.push(path.toString)
case Delete(path) => this.out.push(path.toString)
case Modify(path) => this.out.push(path.toString)
}
}
val listener = Akka.system.actorOf(Props[Listener], "listener")
val swatch = Akka.system.actorOf(Props[SwatchActor], "swatch")
swatch ! Watch("/folder/path", Seq(Create, Modify, Delete), true, Option(listener))
}
}
Play controller:
object Application extends Controller {
def test = WebSocket.using[String] { request =>
val (out, channel) = Concurrent.broadcast[String]
val listener = Akka.system.actorSelection("akka://application/user/listener")
//This is where the websocket out channel is being passed to the listener actor
listener ! Start(channel)
val in = Iteratee.foreach[String] { msg =>
channel push("RESPONSE: " + msg)
}
(in, out)
}
}

Related

Response from Server Scala with Play

I'm doing my first application with Scala and Play! Where I use a WebSocket exactly like they do in the chatroom example, so far I can send messages to my server but I don't seem to understand where I can "handle" this messages that I get from my client, also I wanted to know if I can send Json Arrays from my server to my client:
#Singleton
class HomeController #Inject()(cc: ControllerComponents)
(implicit actorSystem: ActorSystem,
mat: Materializer,
executionContext: ExecutionContext)
extends AbstractController(cc) {
private type WSMessage = String
private val logger = Logger(getClass)
private implicit val logging = Logging(actorSystem.eventStream, logger.underlyingLogger.getName)
// chat room many clients -> merge hub -> broadcasthub -> many clients
private val (chatSink, chatSource) = {
// Don't log MergeHub$ProducerFailed as error if the client disconnects.
// recoverWithRetries -1 is essentially "recoverWith"
val source = MergeHub.source[WSMessage]
.log("source")
.recoverWithRetries(-1, { case _: Exception ⇒ Source.empty })
val sink = BroadcastHub.sink[WSMessage]
source.toMat(sink)(Keep.both).run()
}
private val userFlow: Flow[WSMessage, WSMessage, _] = {
Flow.fromSinkAndSource(chatSink, chatSource)
}
def index: Action[AnyContent] = Action { implicit request: RequestHeader =>
val webSocketUrl = routes.HomeController.chat().webSocketURL()
logger.info(s"index: ")
Ok(views.html.index(webSocketUrl))
}
def chat(): WebSocket = {
WebSocket.acceptOrResult[WSMessage, WSMessage] {
case rh if sameOriginCheck(rh) =>
Future.successful(userFlow).map { flow =>
Right(flow)
}.recover {
case e: Exception =>
val msg = "Cannot create websocket"
logger.error(msg, e)
val result = InternalServerError(msg)
Left(result)
}
case rejected =>
logger.error(s"Request ${rejected} failed same origin check")
Future.successful {
Left(Forbidden("forbidden"))
}
}
}
}
Btw I'm sending the messages from my client through a Jquery function.
Edit The way I want to handle this messages is by passing them as parameters to a function, the function will return an Array of strings or integers which I want to return to the client
All you need to do is add a flow stage to your between your source and sink which can store in DB and return some other message to clients.
Here's how you can fix the above problem:
Do whatever you want with the received message here.
val flow = Flow[WSMessage].map { element =>
println(s"Message: $element")
element
}
Note that for persisting messages in the database, you will most likely want to use an async stage, roughly:
val flow = Flow[WSMessage].mapAsync(parallelism) { element =>
println(s"Message: $element")
// assuming DB.write() returns a Future[Unit]
DB.write(element).map(_ => element)
}
And finally, you need to add the flow stage to the stream pipeline.
source.via(flow).toMat(sink)(Keep.both).run()

postRestart and preRestart methods are not getting invoke in akka actots

I am following this tutorial here is my code
case class ArtGroupDeleteFromES (uuidList:List[String])
class ArtGroupDeleteESActor extends Actor{
val log = LoggerFactory.getLogger(this.getClass)
override def preStart() {
log.debug("preStart Starting ArtGroupDeleteESActor instance hashcode # {}",
this.hashCode())
}
override def postStop() {
log.debug("postStop Stopping ArtGroupDeleteESActor instance hashcode # {}",
this.hashCode())
}
override def preRestart(reason: Throwable, message: Option[Any]) {
log.debug("I am restarting")
log.debug("ArtGroupDeleteESActor: preRestart")
log.debug(s" MESSAGE: ${message.getOrElse("")}")
log.debug(s" REASON: ${reason.getMessage}")
super.preRestart(reason, message)
}
override def postRestart(reason: Throwable) {
log.debug("restart completed!")
log.debug("ArtGroupDeleteESActor: postRestart")
log.debug(s" REASON: ${reason.getMessage}")
super.postRestart(reason)
}
def receive = {
case ArtGroupDeleteFromES(uuidList) =>
throw new Exception("Booom")
sender ! true
}
case message =>
log.warn("Received unknown message: {}", message)
unhandled(message)
}
}
and here is the how i am sending this actor a message
class ArtGroupDeletionActor extends Actor{
val log = LoggerFactory.getLogger(this.getClass)
override val supervisorStrategy = OneForOneStrategy(
maxNrOfRetries = 10, withinTimeRange = 10 seconds) {
case _:Exception => Restart
}
val artGroupDeleteESActor=context.actorOf(Props[ArtGroupDeleteESActor]
.withDispatcher("akka.actor.ArtGroupDeleteESActor-dispatcher")
,name = "ArtGroupDeleteESActor")
def receive = {
case DeleteArtGroup(uuidList) =>
val future1 = ask(artGroupDeleteESActor, ArtGroupDeleteFromES(uuidList)).mapTo[Boolean]
var isDeletedfromES = Await.result(future1, timeout.duration)
case message =>
log.warn("Unhandled message received : {}", message)
unhandled(message)
}
}
object test extends App{
val artGroupDeletionActor=system.actorOf(Props[ArtGroupDeletionActor]
.withDispatcher("akka.actor.ArtGroupDeletionActor-dispatcher")
,name = "ArtGroupDeletionActor")
artGroupDeletionActor ! DeleteArtGroup(List("123"))
}
the PostRestart() and preRestart() methods are not invoking,but preStart() and postStop() gets called, please guide me where i am doing wrong
(for simplicity I'll call your actors Parent and Child from now on)
What happens here is that when an exception occurs inside Child.receive, it doesn't send a response to Parent, instead, the actor system sends some control instruction for the supervision strategy. However, Parent is blocked on Await waiting for completion of future1, which only happens after the timeout exceeds, and then, in turn, a TimeoutException is thrown inside Parent.receive, killing (restarting) the Parent actor itself, and thus the supervising message of an exception in Child is then passed to deadLetters, never restarting the Child.
You should never, ever, ever block inside an actor, so this is incorrect:
val future1 = ask(artGroupDeleteESActor, ArtGroupDeleteFromES(uuidList)).mapTo[Boolean]
var isDeletedfromES = Await.result(future1, timeout.duration)
Instead, you have to either utilize some kind of message identification to distinguish one reply from another in concurrent environment, or add an onComplete to the Future and send a message to self in the closure (beware: no logic other than sending a message should be executed inside the closure to the Future!).
So, option A:
case class ArtGroupDeleteFromES(id: Long, uuidList: List[String])
case class ArtGroupDeleteFromESResult(id: Long, success: Boolean)
class Parent extends Actor {
override val supervisionStrategy = ...
var msgId = 0L
var pendingRequesters = Map.empty[Long, ActorRef]
val child = context.actorOf(Props[Child])
def nextId = {
msgId += 1
msgId
}
def receive = {
case DeleteArtGroup(uuidList) =>
val id = nextId
pendingRequesters += id -> sender() // store a reference to the sender so that you can send it a message when everything completes
child ! DeleteArtGroupFromES(nextId, uuidList)
case ArtGroupDeleteFromESResult(id, success) =>
// process result...
pendingRequesters(id) ! "done"
pendingRequesters -= id
}
}
And option B:
case class ArtGroupDeleteFromES(uuidList: List[String])
case class ArtGroupDeleteFromESResult(replyTo: ActorRef, success: Boolean)
class Parent extends Actor {
override val supervisionStrategy = ...
val child = context.actorOf(Props[Child])
def receive = {
case DeleteArtGroup(uuidList) =>
val requester = sender() // when the future completes, sender may have already changed, so you need to remember it
(child ? DeleteArtGroupFromES(uuidList)).onComplete {
case Success(success) => self ! ArtGroupDeleteFromESResult(requester, success)
case Failure(e) =>
log.warn("Could not delete...", e)
self ! ArtGroupDeleteFromESResult(requester, success = false)
}
}

PlayFramework 2.3.x How to use websockets to both broadcast to all and to specific clients

UPDATE
This was answered here #
https://groups.google.com/forum/#!topic/play-framework/P-tG6b_SEyg
--
First Play/Scala app here and I am struggling a bit. What I am trying to achieve is collaboration between users on specific channels (websocket urls). I am trying to follow the example from the book "Play Framework Essentials" and trying to adapt to my use case. Depending on the request from the client, I would either like to broadcast the information to all the clients talking on this channel or only send information back to the client that sent the initial request. The broadcast part is working but I am unable to figure out how to send the information back to just the client of the request themselves.
In my controller I have
def ws(channelId: String) = WebSocket.tryAccept[JsValue] { implicit request =>
getEnumerator(channelId).map { out =>
val in = Iteratee.foreach[JsValue] { jsMsg =>
val id = (jsMsg \ "id").as[String]
// This works and broadcasts that the user has connected
connect(id, request.session.get("email").get)
// Not sure how to return the result of this just to the client of the request
retrieve(id)
}
Right((in, out))
}
}
private def getEnumerator(id: String): Future[Enumerator[JsValue]] = {
(myActor ? GetEnumerator(id)).mapTo[Enumerator[JsValue]]
}
private def connect(id: String, email: String): Unit = {
(myActor ! Connect(id, email))
}
// I have a feeling this is an incorrect return type and I need to return a Future[JsValue]
//and somehow feed that to the enumerator
private def retrieve(id: String): Future[Enumerator[JsValue]] = {
(myActor ? RetrieveInfo(id)).mapTo[Enumerator[JsValue]]
}
In my Actor
class MyActor extends Actor {
var communicationChannels = Map.empty[String, CommunicationChannel]
override def receive: Receive = {
case GetEnumerator(id) => sender() ! getOrCreateCommunicationChannel(id).enumerator
case Connect(id, email) => getOrCreateCommunicationChannel(id).connect(email)
case RetrieveInfo(id) => sender() ! Json.toJson(Info(id, "details for " + id))
}
private def getOrCreateCommunicationChannel(id: String): CommunicationChannel = {
communicationChannels.getOrElse(id, {
val communicationChannel = new CommunicationChannel
communicationChannels += id -> communicationChannel
communicationChannel
})
}
}
object MyActor {
def props: Props = Props[MyActor]
class CommunicationChannel {
val (enumerator, channel) = Concurrent.broadcast[JsValue]
def connect(email: String) = {
channel.push(Json.toJson(Connected(email)))
}
}
}
Where Connect, Connected, Info etc are just case classes with Reads and Writes defined
Can someone please tell me if it is possible to push message to specific user(s) with this approach rather than broadcast it to everyone? Or if I need to implement this in another way
Also is there a way to broadcast to everyone except yourself
Any help will be greatly appreciated
thanks!!

Play 2.2.2-WebSocket / Equivalent of in.onClose() in Scala

I use Play 2.2.2 with Scala.
I have this code in my controller:
def wsTest = WebSocket.using[JsValue] {
implicit request =>
val (out, channel) = Concurrent.broadcast[JsValue]
val in = Iteratee.foreach[JsValue] {
msg => println(msg)
}
userAuthenticatorRequest.tracked match { //detecting wheter the user is authenticated
case Some(u) =>
mySubscriber.start(u.id, channel)
case _ =>
channel push Json.toJson("{error: Sorry, you aren't authenticated yet}")
}
(in, out)
}
calling this code:
object MySubscriber {
def start(userId: String, channel: Concurrent.Channel[JsValue]) {
ctx.getBean(classOf[ActorSystem]).actorOf(Props(classOf[MySubscriber], Seq("comment"), channel), name = "mySubscriber") ! "start"
//a simple refresh would involve a duplication of this actor!
}
}
class MySubscriber(redisChannels: Seq[String], channel: Concurrent.Channel[JsValue]) extends RedisSubscriberActor(new InetSocketAddress("localhost", 6379), redisChannels, Nil) with ActorLogging {
def onMessage(message: Message) {
println(s"message received: $message")
channel.push(Json.parse(message.data))
}
override def onPMessage(pmessage: PMessage) {
//not used
println(s"message received: $pmessage")
}
}
The problem is that when the user refreshes the page, then a new websocket restarts involving a duplication of Actors named mySubscriber.
I noticed that the Play's Java version has a way to detect a closed connection, in order to shutdown an actor.
Example:
// When the socket is closed.
in.onClose(new Callback0() {
public void invoke() {
// Shutdown the actor
defaultRoom.shutdown();
}
});
How to handle the same thing with the Scala WebSocket API? I want to close the actor each time the socket is closed.
As #Mik378 suggested, Iteratee.map serves the role of onClose.
val in = Iteratee.foreach[JsValue] {
msg => println(msg)
} map { _ =>
println("Connection has closed")
}

Asynchronous http requests using Netty and Scala actors

Asynchronous http requests using Netty and Scala actors
Hey hope someone can give me a hand with this.
I am trying to use the Scala Actors and Netty.io libraries to get make asynchronous http requests. (Yes I know Scala actors are being deprecated but this is a learning exercise for me)
I have written an actor HttpRequestActor that accepts a message in the form of a case class RequestPage(uri:URI).
When it receives the message it creates the necessary Netty objects need to make a http request, I have based most of the code from the [HttpSnoopClient] (http://static.netty.io/3.5/xref/org/jboss/netty/example/http/snoop/HttpSnoopClient.html) example.
I create a client and pass the current actor instance to my implementation of ChannelPipelineFactory which also passes the actor to my implementation of SimpleChannelUpstreamHandler, where I have overridden the messageReceived function.
The actor instance is passed as a listener, I create a request using the DefaultHttpRequest class and write to the channel to make the request.
There is a blocking call to an actor object using the ChannelFuture object returned from writing to the channel. When the messageRecieved function of my handler class is called I parse the response of the netty http request as a string, send a message back to actor with the content of the response and close the channel.
After the future is completed my code attempts to send a reply to the calling actor with the http content response received.
The code works, and I am able to get a reply, send it to my actor instance, print out the content and send a message to the actor instance release resources being used.
Problem is when I test it, the original call to the actor does not get a reply and the thread just stays open.
Code Sample - HttpRequestActor
my code for my HttpRequestActor class
import scala.actors.Actor
import java.net.{InetSocketAddress,URI}
import org.jboss.netty.handler.codec.http._
import org.jboss.netty.bootstrap.ClientBootstrap
import org.jboss.netty.channel.Channel
import org.jboss.netty.channel._
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory
import org.jboss.netty.channel.group.DefaultChannelGroup
import java.util.concurrent.{Executors,CancellationException}
import org.jboss.netty.util.CharsetUtil
import scala.concurrent.{ Promise, Future }
import scala.concurrent.ExecutionContext.Implicits.global
/**
* #author mebinum
*
*/
class HttpRequestActor extends Actor {
//initialize response with default uninitialized value
private var resp:Response = _
private val executor = Executors.newCachedThreadPool
private val executor2 = Executors.newCachedThreadPool
private val factory = new NioClientSocketChannelFactory(
executor,
executor2);
private val allChannels = new DefaultChannelGroup("httpRequester")
def act = loop {
react {
case RequestPage(uri) => requestUri(uri)
case Reply(msg) => setResponse(Reply(msg))
case NoReply => println("didnt get a reply");setResponse(NoReply)
case NotReadable => println("got a reply but its not readable");setResponse(NotReadable)
case ShutDown => shutDown()
}
}
private def requestUri(uri:URI) = {
makeChannel(uri) map {
channel => {
allChannels.add(channel)
val request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri.toString)
request.setHeader(HttpHeaders.Names.HOST, uri.getHost())
request.setHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.CLOSE)
request.setHeader(HttpHeaders.Names.ACCEPT_ENCODING, HttpHeaders.Values.GZIP)
val writeFuture = channel.write(request).awaitUninterruptibly()
FutureReactor !? writeFuture match {
case future : ChannelFuture => {
future.addListener(new ChannelFutureListener() {
def operationComplete(future:ChannelFuture) {
// Perform post-closure operation
println("current response is " + resp)
sendResponse("look ma I finished")
}
})
future.getChannel().close()
}
}
this ! ShutDown
}
}
//thread ends only if you send a reply from here
//println("this is final sender " + sender)
//reply("I am the true end")
}
private def makeChannel(uri:URI) = {
val scheme = Some(uri.getScheme()).getOrElse("http")
val host = Some(uri.getHost()).getOrElse("localhost")
val port = Utils.getPort(uri.getPort, uri.getScheme)
// Set up the event pipeline factory.
val client = new ClientBootstrap(factory)
client.setPipelineFactory(new PipelineFactory(this))
//get the promised channel
val channel = NettyFutureBridge(client.connect(new InetSocketAddress(host, port)))
channel
}
private def setResponse(aResponse:Response) = resp = aResponse
private def sendResponse(msg:String) = {
println("Sending the response " + msg)
reply(resp)
}
private def shutDown() = {
println("got a shutdown message")
val groupFuture = allChannels.close().awaitUninterruptibly()
factory.releaseExternalResources()
}
override def exceptionHandler = {
case e : CancellationException => println("The request was cancelled"); throw e
case tr: Throwable => println("An unknown exception happened " + tr.getCause()); throw tr
}
}
trait Response
case class RequestPage(url:URI)
case class Reply(content:String) extends Response
case object NoReply extends Response
case object NotReadable extends Response
case object ShutDown
object FutureReactor extends Actor{
def act = //loop {
react {
case future: ChannelFuture => {
if (future.isCancelled) {
throw new CancellationException()
}
if (!future.isSuccess()) {
future.getCause().printStackTrace()
throw future.getCause()
}
if(future.isSuccess() && future.isDone()){
future.getChannel().getCloseFuture().awaitUninterruptibly()
reply(future)
}
}
}
//}
this.start
}
class ClientHandler(listener:Actor) extends SimpleChannelUpstreamHandler {
override def exceptionCaught( ctx:ChannelHandlerContext, e:ExceptionEvent){
e.getCause().printStackTrace()
e.getChannel().close();
throw e.getCause()
}
override def messageReceived(ctx:ChannelHandlerContext, e:MessageEvent) = {
var contentString = ""
var httpResponse:Response = null.asInstanceOf[Response]
e.getMessage match {
case (response: HttpResponse) if !response.isChunked => {
println("STATUS: " + response.getStatus);
println("VERSION: " + response.getProtocolVersion);
println
val content = response.getContent();
if (content.readable()) {
contentString = content.toString(CharsetUtil.UTF_8)
httpResponse = Reply(contentString)
//notify actor
}else{
httpResponse = NotReadable
}
}
case chunk: HttpChunk if !chunk.isLast => {
//get chunked content
contentString = chunk.getContent().toString(CharsetUtil.UTF_8)
httpResponse = Reply(contentString)
}
case _ => httpResponse = NoReply
}
println("sending actor my response")
listener ! httpResponse
println("closing the channel")
e.getChannel().close()
//send the close event
}
}
class PipelineFactory(listener:Actor) extends ChannelPipelineFactory {
def getPipeline(): ChannelPipeline = {
// Create a default pipeline implementation.
val pipeline = org.jboss.netty.channel.Channels.pipeline()
pipeline.addLast("codec", new HttpClientCodec())
// Remove the following line if you don't want automatic content decompression.
pipeline.addLast("inflater", new HttpContentDecompressor())
// Uncomment the following line if you don't want to handle HttpChunks.
//pipeline.addLast("aggregator", new HttpChunkAggregator(1048576))
pipeline.addLast("decoder", new HttpRequestDecoder())
//assign the handler
pipeline.addLast("handler", new ClientHandler(listener))
pipeline;
}
}
object NettyFutureBridge {
import scala.concurrent.{ Promise, Future }
import scala.util.Try
import java.util.concurrent.CancellationException
import org.jboss.netty.channel.{ Channel, ChannelFuture, ChannelFutureListener }
def apply(nettyFuture: ChannelFuture): Future[Channel] = {
val p = Promise[Channel]()
nettyFuture.addListener(new ChannelFutureListener {
def operationComplete(future: ChannelFuture): Unit = p complete Try(
if (future.isSuccess) {
println("Success")
future.getChannel
}
else if (future.isCancelled) {
println("Was cancelled")
throw new CancellationException
}
else {
future.getCause.printStackTrace()
throw future.getCause
})
})
p.future
}
}
Code to test it
val url = "http://hiverides.com"
test("Http Request Actor can recieve and react to message"){
val actor = new HttpRequestActor()
actor.start
val response = actor !? new RequestPage(new URI(url))
match {
case Reply(msg) => {
println("this is the reply response in test")
assert(msg != "")
println(msg)
}
case NoReply => println("Got No Reply")
case NotReadable => println("Got a not Reachable")
case None => println("Got a timeout")
case s:Response => println("response string \n" + s)
case x => {println("Got a value not sure what it is"); println(x);}
}
}
Libraries used:
- Scala 2.9.2
- Netty.io 3.6.1.Final
- Junit 4.7
- scalatest 1.8
- I am also using #viktorklang NettyFutureBridge object gist to create a scala future for the Channel object returned
How can I send a reply back to the actor object with the content of response from Netty and end the thread?
Any help will be much appreciated
I don't know Scala, but I had a similar issue. Try specifying the content-length header of the response.
In plain java:
HttpRequest r = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, uri);
ChannelBuffer buffer = ChannelBuffers.copiedBuffer(input);
r.setHeader(HttpHeaders.Names.HOST, "host");
r.setHeader(HttpHeaders.Names.CONTENT_TYPE, "application/octet-stream");
r.setHeader(HttpHeaders.Names.CONTENT_LENGTH, buffer.readableBytes());
r.setHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.CLOSE);
r.setContent(buffer);
Otherwise the server has no idea when the content is completed from the client, unless the client closes the connection.
You can also use chunked encoding, but you'll have to implement the chunk encoding yourself (At least I don't know of a library in Netty that does it).