Monday, March 24, 2014

Play framework, Akka: actor communication with controller

http://stackoverflow.com/questions/21779600/play-framework-akka-actor-communication-with-controller


In short - I'm building a Scala/Play application which will monitor certain files/folders for changes and use Server Sent Events to push data to the browser.
I'm using Swatch library to do the monitoring which launches with application. This is my Global object. It starts the monitoring by launching a worker actor (as far as I understand) and reports if there are any changes - it's working just fine:
import akka.actor.{Props, ActorSystem}
import play.api._
import play.api.libs.concurrent.Akka
import play.api.Play.current
import include.Swatch._
import include.SwatchActor

object Global extends GlobalSettings {

    override def onStart(app: Application) {
        Logger.info("Application has started")
        val swatch = Akka.system.actorOf(Props[SwatchActor])
        swatch ! Watch("/folder/path", Seq(Create, Modify, Delete), true)
    }

}
At the moment whenever a change happens the swatch actor just outputs this to the console:
[INFO] [02/14/2014 11:45:04.377] [application-akka.actor.default-dispatcher-3] [akka://application/deadLetters] Message [include.Swatch$Create] from Actor[akka://application/deadLetters] to Actor[akka://application/deadLetters] was not delivered. [1] dead letters encountered. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'.
This is my controller (so far it's not doing much):
package controllers

import play.api._
import play.api.mvc._

object Application extends Controller {

    def index = Action {
        Ok("index")
    }

}
The browser should establish a connection with the controller and then whenever my worker actor detects a change, server push to the browser happens. My problem so far is: how do I get my worker actor to send messages to my index action in the controller? Would the approach I've described above work at all?
UPDATE: I'll be using websockets to communicate with the browser. Another important thing - the worker actor must be running all the time despite of if there is an established communication with the browser or not.
UPDATE2: Thanks to drexin and others I've managed to scrape together a solution.

=======

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)

    }   

}

share|improve this answer

No comments: