3

I need to send a private response to the client for which I'm currently handling a Play WebSocket message, while for other messages I need to broadcast responses to all clients.

In the Play chat example, incoming messages are immediately offloaded to a single Actor:

case Connected(enumerator) => 
  // Create an Iteratee to consume the feed, assuming all requests have
  // a JSON "text" node, and delegate all to the Akka Actor:
  val iteratee = Iteratee.foreach[JsValue] { event =>
    default ! Talk(username, (event \ "text").as[String])
  }.map { _ =>
    default ! Quit(username)
  }
  (iteratee,enumerator)

Above, once the connection is approved enumerator is passed back, which refers to the single chatEnumerator that was already created by that same Actor:

val (chatEnumerator, chatChannel) = Concurrent.broadcast[JsValue]

I assume this does not allow me to send a message to just a single client? Should I remove the broadcast functionality and create and keep track of enumerators for each client, and then iterate myself? Or can I somehow get a reference to a client-specific enumerator in the foreach?

(I understand that built-in flexibility depends on the implementation, but this use case seems quite common to me. Like when using WebSockets with Socket.IO, I can easily send messages to either all clients, all clients except the sender of the current request, or just a single client. That's what I'm trying to achieve in Play 2.1.x as well.)

Community
  • 1
  • 1
Arjan
  • 22,808
  • 11
  • 61
  • 71
  • ...[using Concurrent.patchPanel](http://lambdaz.blogspot.se/2012/12/play-21-multiplexing-enumerators-into.html) with both a private enumerator and the broadcast enumerator, and then unplugging the broadcast enumerator when needed, seems like a bad hack...? And might even drop messages if it's temporarily not plugged in while another message is handled? – Arjan Sep 11 '13 at 21:56
  • Ah, [Atmosphere for Play!](https://github.com/Atmosphere/atmosphere-play) might be a better starting point? – Arjan Sep 12 '13 at 11:52

2 Answers2

2

One approach is to interleave two enumerators using Enumerator.interleave.

Therefore, you can create two pairs of (Enumerator, Channel) using Concurrent.broadcast twice, one for broadcasting the other for private connection, and interleave it. (Or may just use Concurrent.unicast for private enumerator, but I could not figure out how to use it.)

Here's some sample play code which works on play 2.3.0 .

object Application extends Controller {
  val (publicOut,publicChannel) = Concurrent.broadcast[String]
  def chat = WebSocket.using[String]{ request =>
    val (privateOut,privateChannel) = Concurrent.broadcast[String]
    val in = Iteratee.foreach{
      msg:String => if(msg.startsWith("@")){
        publicChannel.push("Broadcasted: " + msg)
      }else{
        privateChannel.push("Private: " + msg)
      }
    }
    val out = Enumerator.interleave(publicOut,privateOut)
    (in, out)
  }
}

Sending message to specific client will be a little bit complicated code, but a concept would be same. Create an Actor which holds pair of (Enumerator, Channel) per websocket and send a message to the actor.

ymonad
  • 11,710
  • 1
  • 38
  • 49
1

This seems like a hack for scala. the java version of the websocket-chat app has uses a map to store each username and channel. Then loops through them. Changing/blocking the message is simply branching based on the username inside the loop. I'm also looking for a good solution.

Map<String, WebSocket.Out<JsonNode>> members = new HashMap<String, WebSocket.Out<JsonNode>>();

Full code is here.

Shayan Shafiq
  • 1,447
  • 5
  • 18
  • 25
ferk86
  • 2,325
  • 1
  • 23
  • 27
  • Ah, interesting difference! I've used the Scala broadcast in a demo for a while, but dropped connections would often not be restored unless some user interaction required the socket. (In another demo with Socket.IO all was just fine out of the box, on the very same server. Also, I didn't even know why the connection was dropped to start with.) Even if just for fallback techniques, I guess I'd use something like [Atmosphere for Play!](https://github.com/Atmosphere/atmosphere-play) to not have to reinvent the wheel. – Arjan Oct 09 '13 at 17:00