Question

I'm not sure if I'm experiencing a default time-out (can I set this somewhere?), but maybe I am missing something. When a client's browser establishes a websocket connection, I keep persistence. Then, on disconnect, I delete that persisted object. Simple enough. The disconnect normally gets triggered when a client closes the browser, but not when the client turns off their wi-fi connection (testing on a MacBook Pro, not that this should matter).

In the Scala controller, I am logging every in message, but nothing comes through when the wi-fi is turned off (from the docs, I would expect an EOF?).

I think that this must be a bug, as taken from how I interpret the WS protocol the server MUST Close the WebSocket Connection, and SHOULD log the problem. But the latter doesn't happen.

val in = Iteratee.foreach[String](x => {
  logger.info("Websocket msg: " + x)
   // expect EOF?
   x match {
     case "persist" => // persist some object
   }
}).mapDone { x =>
   // delete my persisted object (never happens unless browser/tab closed)
}

Has anybody experienced this? I've tried both a simple controller and something that matches my configuration. Neither ws3 or ws2 controllers below do the trick. Play! code below:

object Application extends Controller {

  private def ws(out: PushEnumerator[String]) = {

    Logger.logger.info("ws()")

    val in = Iteratee.foreach[String](x => {
      Logger.logger.info("Websocket msg: " + x)
      try {
        x match {
          case "persist" => Logger.logger.info("PERSIST")
        }
      } catch {
        case e: Exception => {
          Logger.logger.info("NOT RECOGNIZED COMMAND, NO PERSIST")
        }
      }
    }).mapDone { x =>
      Logger.logger.info("STOP PERSIST")
    }
    in
  }

  def ws2() = WebSocket.using[String] { request =>

    Logger.logger.info("ws2()")

    val out = Enumerator.imperative[String]()
    val in = ws(out)

    (in, out)
  }

  def ws3() = WebSocket.using[String] { request =>

    Logger.logger.info("ws3()")

    val out = Enumerator.imperative[String]()
    val in = Iteratee.foreach[String](x => {
      Logger.logger.info("Websocket msg: " + x)
      try {
        x match {
          case "persist" => Logger.logger.info("PERSIST")
        }
      } catch {
        case e: Exception => {
          Logger.logger.info("NOT RECOGNIZED COMMAND, NO PERSIST")
        }
      }
    }).mapDone { x =>
      Logger.logger.info("STOP PERSIST")
    }
    (in, out)
  }

  def view() = Action { implicit request =>
    Ok(views.html.index.render(""))
  }
}

The view is a simple:

@(message: String) @main("Welcome to Play 2.0") {

@play20.welcome(message) }

<script type="text/javascript" charset="utf-8">
    var sock = new WebSocket("ws://192.168.1.120:9001/ws3");

    sock.onopen = function(event) {
        sock.send('persist');
    }
</script>

Routes:

GET     /ws2                          controllers.Application.ws2
GET     /ws3                          controllers.Application.ws3
GET     /view                         controllers.Application.view
Was it helpful?

Solution

This is not Play Framework specific, rather a network related issue. What happens is that if you disconnect normally, other end sends FIN packet and the server knows about it hanging up. If the other end does not have a network connection, it can not do so.

How to determine if a socket is open then? You will get an exception when trying to read or write. But with non-blocking model that does not happen by itself. So in your case I would:

  • Have a timeout
  • Make JavaScript send pings within timeout period
  • If no pings arrive, disconnect

or send pings from the server side (write to the socket).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top