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")
}
Related
Consider the following code sample:
class MyActor (httpClient: HttpClient) {
var canSendMore = true
override def receive: Receive = {
case PayloadA(name: String) => send(urlA)
case PayloadB(name: String) => send(urlB)
def send(url: String){
if (canSendMore)
httpClient.post(url).map(response => canSendMore = response.canSendMore)
else {
Thread.sleep(5000) //this will be done in a more elegant way, it's just for the example.
httpClient.post(url).map(response => canSendMore = response.canSendMore)
}
}
}
Each message handling will result in an async http request. (post return value is a Future[Response])
My problem is that I want to safely update counter ( At the moment there is a race condition)
BTW, I must somehow update counter in the same thread, or at least before any other message is processed by this actor.
Is this possible?
You can use become + stash combination to keep on stashing messages when the http request future is in process.
object FreeToProcess
case PayloadA(name: String)
class MyActor (httpClient: HttpClient) extends Actor with Stash {
def canProcessReceive: Receive = {
case PayloadA(name: String) => {
// become an actor which just stashes messages
context.become(canNotProcessReceive, discardOld = false)
httpClient.post(urlA).onComplete({
case Success(x) => {
// Use your result
self ! FreeToProcess
}
case Failure(e) => {
// Use your failure
self ! FreeToProcess
}
})
}
}
def canNotProcessReceive: Receive = {
case CanProcess => {
// replay stash to mailbox
unstashAll()
// start processing messages
context.unbecome()
}
case msg => {
stash()
}
}
}
I have implemented a receiver that is supposed to connect to a WebSocket stream and get the messages for processing. Here is the implementation that I have done so far:
class WebSocketReader (wsConfig: WebSocketConfig, stringMessageHandler: String => Option[String],
storageLevel: StorageLevel) extends Receiver[String] (storageLevel) {
// TODO: avoid using a var
private var wsClient: WebSocketClient = _
def sendRequest(isRequest: Boolean, msgCount: Int) = {
while (isRequest) {
wsClient.send(msgCount.toString)
Thread.sleep(1000)
}
}
// TODO: avoid using Synchronization...
private def connect(): Unit = {
Try {
wsClient = createWsClient
} match {
case Success(_) =>
wsClient.connect().map {
case result if result.isSuccess =>
sendRequest(true, 10)
case _ =>
connect()
}
case Failure(ex) =>
// TODO: how to signal a failure so that it is tried the next time....
ex.printStackTrace()
}
}
def onStart(): Unit = {
new Thread(getClass.getSimpleName) {
override def run() { connect() }
}.start()
}
override def onStop(): Unit =
if (wsClient != null) wsClient.disconnect()
private def createWsClient = {
new DefaultHookupClient(new HookupClientConfig(new URI(wsConfig.wsUrl))) {
override def receive: Receive = {
case Disconnected(_) =>
// TODO: use Logging framework, try reconnecting....
println(s"the web socket is disconnected")
case TextMessage(message) =>
stringMessageHandler(message).foreach(store)
case JsonMessage(jsValue) =>
stringMessageHandler(jsValue.toString).foreach(store)
}
}
}
}
How is this Receiver being run? Does this Receiver run on the worker nodes or on the driver node? Is this way of sleeping a thread a correct approach?
The reason why I want to do this is that the server that is exposing the WebSocket end point would need a count on the messages that I want to receive. Say if I ask the server for 100 messages, it would give me 100 messages and so on. So I need a way to periodically schedule this request to the server. Currently, I'm using the Thread.sleep mechanism. Is this advisable? What could be the alternative?
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!!
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)
}
}
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).