This is a simple exercise I came up with. The point was to slightly change Play's chatroom example so that newly opened pages showed the history of the conversation so far.
This is in now meant to be production-ready or scalable. It's just a simple exercise to try to get used to Play and Akka's concept of streams and actors.
This is a simple chatroom using Play and Websockets with the Scala API.
This project makes use of dynamic streams from Akka Streams, notably BroadcastHub
and MergeHub
. By combining MergeHub and BroadcastHub, you can get publish/subscribe functionality.
The flow is defined once in the controller, and used everywhere from the chat
action:
class HomeController extends Controller {
// chat room many clients -> merge hub -> broadcasthub -> many clients
private val (chatSink, chatSource) = {
// Don't log MergeHub$ProducerFailed as error if the client disconnects.
// recoverWithRetries -1 is essentially "recoverWith"
val source = MergeHub.source[WSMessage]
.log("source")
.recoverWithRetries(-1, { case _: Exception ⇒ Source.empty })
val sink = BroadcastHub.sink[WSMessage]
source.toMat(sink)(Keep.both).run()
}
private val userFlow: Flow[WSMessage, WSMessage, _] = {
Flow[WSMessage].via(Flow.fromSinkAndSource(chatSink, chatSource)).log("userFlow")
}
def chat: WebSocket = {
WebSocket.acceptOrResult[WSMessage, WSMessage] {
case rh if sameOriginCheck(rh) =>
Future.successful(userFlow).map { flow =>
Right(flow)
}.recover {
case e: Exception =>
val msg = "Cannot create websocket"
logger.error(msg, e)
val result = InternalServerError(msg)
Left(result)
}
case rejected =>
logger.error(s"Request ${rejected} failed same origin check")
Future.successful {
Left(Forbidden("forbidden"))
}
}
}
}
You will need JDK 1.8 and sbt installed.
sbt run
Go to http://localhost:9000 and open it in two different browsers. Typing into one browser will cause it to show up in another browser.
This project is originally taken from Johan Andrén's Akka-HTTP version:
Johan also has a blog post explaining dynamic streams in more detail: