Search code examples
scalawebsocketplayframework-2.4playframework-2.6playframework-webservice

Converting WebSockets in Play framework from version 2.4 to 2.6


I'm trying to convert this code, that uses the Play version 2.4 to the current version (2.6) and I'm having some issues because I'm still a noob in Scala.

def wsWeatherIntervals = WebSocket.using[String] {
  request =>
    val url = "http://api.openweathermap.org/data/2.5/weather?q=Amsterdam,nl"
    val outEnumerator = Enumerator.repeatM[String]({
      Thread.sleep(3000)
      ws.url(url).get().map(r => s"${new java.util.Date()}\n ${r.body}")
    })
    (Iteratee.ignore[String], outEnumerator)
}

I followed this guide, but now I'm stuck on the stuff that I should return on the method. This is the code that I'm trying to run using the version 2.6:

  import play.api.mvc._
  import scala.concurrent.Future
  import akka.stream.scaladsl._
  def wsWeatherIntervals = WebSocket.accept[String, Future[String]] { res =>
    val url = "http://api.openweathermap.org/data/2.5/weather?q=Amsterdam,nl"
    val source = Source.repeat({
      Thread.sleep(3000)
      ws.url(url).get().map(r => s"${new java.util.Date()}\n ${r.body}")
    })
    Flow.fromSinkAndSource(Sink.ignore, source)
  }

But I'm getting this error when running the server, that points to the first line of the method:

could not find implicit value for parameter transformer: play.api.mvc.WebSocket.MessageFlowTransformer[String,scala.concurrent.Future[String]]

Note: I also tried to call WebSocket.apply instead of WebSocket.accept and I did some search about the differences between the two but didn't find anything useful. Can someone explain the difference between the two? Thanks.


Solution

  • The superficial error is that Play doesn't know how to turn a Future[String] into a Websocket message, for which you'd normally use an implicit transformer. However, in this case you don't want to return a Future[String] anyway but just a plain string which can be automatically marshalled (using the provided stringMessageFlowTransformer as it happens.) Here's something that should work:

    def wsWeatherIntervals = WebSocket.accept[String, String] { res =>
      val url = "http://api.openweathermap.org/data/2.5/weather?q=Amsterdam,nl"
      def f = ws.url(url).get().map(r => s"${new java.util.Date()}\n ${r.body}")
    
      val source = Source.unfoldAsync(f)(last => {
        Thread.sleep(3000)
        f.map(next => Some((last, next)))
      })
      Flow.fromSinkAndSource(Sink.ignore, source)
    }
    

    The unfoldAsync source lets us repeated run a function returning a future of the next element in the stream. (Since we want the stream to go on forever we return the value wrapped as Some.)

    The Websocket.apply method is basically a more complicated version of accept which allows you to reject a websocket connection for some reason by returning a response, but if you need to do this it's better to use acceptOrResult, which handles transforming whatever your flow emits into websocket messages.