2
votes

I have a REST service (Play Framework 2.0 w/Scala) that receives messages via a POST request.

I want to allow a user to see the queue of messages received in a webpage. I wanted to create a SSE channel between browser and server, so the server pushes new messages to the browser.

To create that SSE stream, as per documentation, I'm using a chain of Enumerator/Enumeratee/Iteratee.

My problem is: how do I inject the messages received from the POST request to the enumerator. So given a code like follows:

def receive(msg: String) = Action {
  sendToEnumerator() 
  Ok
}

val enumerator =  Enumerator.fromCallback( ??? )

def sseStream() = Action {

  Ok.stream(enumerator &> anotherEnumeratee ><> EventStrem()).as("text/evetn-stream")

}

What should I put in both sendToEnumerator and enumerator (where the ??? are). Or should I just use WebSockets and Actors instead? (I favour SEE due to broader compatibility, so would like to use SSE if possible)

1
Just suggestion: last time I built the a'la FB wall page I was using websockets playframework.org/documentation/2.0.4/ScalaWebSockets and it did the trick for me. - biesior
@biesior if I can I'd like to use SSE, although I see websockets is a simpler approach. But as Websockets seem to be less supported... - Pere Villega

1 Answers

3
votes

Ok, found a way:

 // The enum for pushing data to spread to all connected users
  val hubEnum = Enumerator.imperative[String]()

  // The hub used to get multiple output of a common input (the hubEnum)
  val hub = Concurrent.hub[String](hubEnum)

  // Converts message to Json for the web version
  private val asJson: Enumeratee[String, JsValue] = Enumeratee.map[String] {
    text => JsObject(
      List(
        "eventName" -> JsString("eventName"),
        "text" -> JsString(text)
      )
    )
  }

  // loads data into hubEnum
  def receiveData(msg: String) = Action { implicit request =>
    hubEnum push msg
  } 

  // read the Hub iterator and pushes back to clients
  def stream = Action { implicit request =>
     Ok.stream(hub.getPatchCord &> asJson ><> EventSource()).as("text/event-stream")
  }

The trick is to create an imperative Enumerator. This enumerator allows you to push data into it when it becomes available. With this then you can follow the standard procedure: create a Hub based on the enumerator, convert it with some Enumeratee and send it back to browsers via SSE.

Thanks to this website for giving me the solution :)