Search code examples
javascriptscalaplayframeworkplayframework-2.0server-sent-events

Scala Play: Concurent.broadcast not working with EventSource


I'm having trouble in Scala Play to connect Conncurrent.broadcast with EventSource() to create working SSE chat.

This code below is not working. All I see are debug messages when users are connecting to feed and that's all. No data is being sent down to browsers. And I'm sure data is send successfully to server and pushed to chatChannel. What's wrong? How can I debug this?

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

def postMessage = Action(parse.json) { req =>
  chatChannel.push(req.body)
  Ok
}

def chatFeed = Action { req =>
  println("User connected to chat: " + req.remoteAddress)
  Ok.chunked(chatOut
    &> EventSource()
  ).as("text/event-stream")
}

This simple debugging code below is working, and I see data sent from browser, through chatChannel, in console, so this side is working fine.

val (chatOut, chatChannel) = Concurrent.broadcast[JsValue]
val chatDebug = Iteratee.foreach[JsValue](m => println("Debug: " + m.toString))
chatOut |>>> chatDebug

def postMessage = Action(parse.json) { req =>
  chatChannel.push(req.body)
  Ok
}

And this is working as well, and I see random strings being send to browser. So JS part is also OK.

def chatFeed = Action { req =>
  val producer = Enumerator.generateM[String](Promise.timeout(Some(Random.nextString(5)),3 second))
  Ok.chunked(producer &> EventSource()).as("text/event-stream")
}

And somehow when I connect this two parts, messages are not broadcasted to browser.


Solution

  • Wow ! I was ready to give up but I found the origin of the problem.

    In the routes file, you use the dependency injected router:

    GET        /                    @controllers.Application.index
    POST       /message             @controllers.Application.postMessage
    GET        /feed                @controllers.Application.chatFeed
    

    Using the static router (without @ and the default router) works in your example:

    GET        /                    @controllers.Application.index
    POST       /message             controllers.Application.postMessage
    GET        /feed                controllers.Application.chatFeed
    

    From the play doc:

    Play supports generating two types of routers, one is a dependency injected router, the other is a static router. The default is the static router, but if you created a new Play application using the Play seed Activator templates, your project will include the following configuration in build.sbt telling it to use the injected router:

    routesGenerator := InjectedRoutesGenerator

    The code samples in Play’s documentation assumes that you are using the injected routes generator. If you are not using this, you can trivially adapt the code samples for the static routes generator, either by prefixing the controller invocation part of the route with an @ symbol, or by declaring each of your controllers as an object rather than a class.

    I still dont quite understand the last sentence since the controller doesnt seem to use the injected routes generator and therefore having an @ should use the static router