Reputation: 5499
I'm looking at the example at https://github.com/playframework/Play20/tree/master/samples/scala/websocket-chat
To make a websocket controller, you write something like:
def chat(username: String) = WebSocket.async[JsValue] { request =>
ChatRoom.join(username)
}
Chatroom.join returns a scala.concurrent.Future[(Iteratee[JsValue,_],Enumerator[JsValue])] . But where are the iteratee and the enumerator used within the Play! framework? The WebSocket class (WebSocket.scala) seems to ignore the inputs:
case class WebSocket[A](f: RequestHeader => (Enumerator[A], Iteratee[A, Unit]) => Unit) (implicit val frameFormatter: WebSocket.FrameFormatter[A]) extends Handler {
type FRAMES_TYPE = A
/**
* Returns itself, for better support in the routes file.
*
* @return itself
*/
def apply() = this
}
How does Play! manage the changing state of the iteratee as it consumes input?
Upvotes: 4
Views: 1111
Reputation: 3307
It's worth noting that the WebSocket
itself is just a dumb container. The magic happens in various classes within play.core.server.netty
.
To understand what that magic is, it's instructive to look at the signature of f (the function that a WebSocket
contains:
RequestHeader => (Enumerator[A], Iteratee[A, Unit]) => Unit
This is a function that takes a RequestHeader
, an Enumerator
, and an Iteratee
, and does something with them.
So, at some point in the future, the framework will provide our WebSocket
with a RequestHeader
(which should be self explanatory), an Enumerator[A]
(Enumerators are sources, in this case, these are the messages being received from the client), and an Iteratee[A, Unit]
(Iteratees are sinks, in this case, this is where we send messages to go back to the client).
In the case of WebSocket.adapter
, the WebSocket will connect the Enumerator
to the Iteratee
via an Enumeratee
. In the case of WebSocket.using
, the WebSocket will connect the remote Enumerator
to a local Iteratee
, and connect the remove Iteratee
to a local Enumerator
.
Rather than defining WebSocket directly, it's likely to be easier to use one of the convenience methods in the WebSocket
object. The following code will echo the previous message received:
def mySocket = WebSocket.adapter {implicit req =>
var lastMessage = "No previous message"
Enumeratee.map[String] {msg =>
val response = lastMessage
lastMessage = msg
response
}
}
Note that this code almost certainly has thread safety issues - in Scala, you should try to avoid mutable state whenever possible, or use actors or similar if not.
Or, try WebSocket.using
, and look at a pushee Enumerator
, in conjunction with a foreach Iteratee
, although it's a little fiddler. Perhaps understandably, the pushee enumerator is deprecated in Play 2.1, as it's being superseded by the new channels system.
Upvotes: 5