smootoo / simple-spray-websockets

Simple example of REST/WebSockets on Spray Can.

Version Matrix

simple-spray-websockets

Build Status

Simple example of REST/WebSockets on Spray Can.

Available on maven central

This small project introduces convenience classes / patterns to set up a server that can handle both REST and WebSockets as well as the example server and client actors below.

spray-websocket by Wandou Labs is an extension to spray adding the WebSocket asynchronous upgrade to HTTP. Use of spray-websocket is quite low level and requires some boilerplate to get an example up and running.

Actor per Request

An added advantage of using this pattern is that it automatically provides the net-a-porter actor-per-request pattern (by accident). This is because an Actor is spawned to deal with every incoming request and will serve a REST request unless a stateful WebSocket Upgrade is obtained, at which point it switches to serving async messages until closed.

Server Backpressure

Back-pressure on the HTTP Routes is provided by spray-can if enabled.

Back-pressure with wandoulabs WebSockets is an afterthought and must be carefully crafted. We provide a sendWithAck(f: TextFrame) method in the server which will allow us to receive an Ack. Backpressure must therefore be written in the application tier using the become principles outlined in the akka-io documentation. The documentation on sendWithAck provides more information about how to implement acks/nacks for other types of frames.

Performance

The SimpleWebSocketComboWorker is simple to understand and implement but if you want high performance, you will want to use the lower level WebSocketComboWorker and share your runRoute between actors to save on the initialisation costs per connection.

Example

The following example is created and tested in WebSocketsSpec.scala.

val Ping = TextFrame("PING")
val Pong = TextFrame("PONG")

class ExampleServer(conn: ActorRef) extends SimpleWebSocketComboWorker(conn) {
  def websockets = {
    case UpgradedToWebSocket =>
    case Ping => sendWithAck(Pong)
    case Ack => // useful for backpressure
  }

  def route = path("hello") {
    get {
      complete {
        <h1>Greetings!</h1>
      }
    }
  }
}

class ExampleClient extends WebSocketClient(host, port) {
  def websockets: Receive = {
    case UpgradedToWebSocket =>
      connection ! Ping
    case Pong =>
  }
}