lombardo-chcg / websocket-scala

a simple Websocket client library for Scala

Version Matrix

websocket-scala

Actions Status

websocket-scala is a simple Scala Websocket client library. It is based on the WebSocket interface as defined in the MDN web docs, which is available as a JavaScript object in HTML5-compliant web browsers. The goal of the library is to provide the same simple & intuitive Websocket client api for JVM Scala apps.

installation

Supported scala versions:

  • 2.11
  • 2.12
  • 2.13
sbt
libraryDependencies += "net.domlom" %% "websocket-scala" % "0.0.3"

gradle
scalaC = "2.12"

compile "net.domlom:websocket-scala_$scalaC:0.0.3"
ammonite
import $ivy.`net.domlom::websocket-scala:0.0.3`

Hello World

an example using echo.websocket.org:

import net.domlom.websocket._

// setup a behavior to println received messages
val behavior = {
  WebsocketBehavior.empty
    .setOnMessage { (_, message) =>
      println(s"Message from server: ${message.value}")
    }
    .setOnClose(reason => println(reason))
  }

// initialize a client
val socket = Websocket("wss://echo.websocket.org", behavior)

// say hello
for {
  _ <- socket.connect()
  _ <- socket.send(s"Hello World")
  _ = Thread.sleep(500)
  r <- socket.close()
} yield r


// Message from server: Hello World

Usage

Step 1 - Define a WebsocketBehavior

Define listener functions to handle the events of the ws connection: onOpen,onMessage, onClose and onError

The listener definition is done via a case class config object called WebsocketBehavior:

case class WebsocketBehavior(
  onOpen: Websocket => Unit,
  onMessage: (Websocket, WsMessage) => Unit,
  onClose: ConnectionClosedDetails => Unit,
  onError: (Websocket, Throwable) => Unit
)

A WebsocketBehavior instance has dedicated extension methods for transforming it into a new WebsocketBehavior. (This is done via the underlying copy semantics of a Scala case class).

  
// Step 1 - define a base template with common behaviors 
val myBaseWsTemplate =
  WebsocketBehavior.empty
    .setOnOpen { connection =>
      connection.send("subscribe request")
    }
    .setOnClose { reason =>
      println(reason)
    }
    .setOnError { (connection, throwable) =>
      println(throwable.getMessage)
    }

// Step 2 - extend the base template with custom message handling
val mySpecificUseCase = myBaseWsTemplate
  .setOnMessage { (connection, message) =>
    val response = doBusinessValueStuff(message)
    connection.send(response)
  }

Note that we are only describing behavior via function definitions.
In order to use that behavior to process data, we must create a Websocket connection.

Step 2 - Create a Websocket instance

Use a behavior to define a connection:

val myBehavior: WebsocketBehavior = ??? // see above

val socket = Websocket("wss://echo.websocket.org", myBehavior)

socket.connect()

Once the connection is open the callbacks from WebsocketBehavior will be used for the duration of the connection.

Step 3 - Interact with the client.

Interactions with the Websocket client object can happen outside of the predefined WebsocketBehavior callback methods, as long as the connection stays open:

val socket = Websocket("wss://echo.websocket.org", myBehavior)

socket.connect() 

socket.sendSync("example text")

socket.close()

Error handling

Interactions with a Websocket instance will return Try[WsResponse]

  • WsResponse(message: String) is like akka's Done, which signifies successful completion with no return value. WsResponse signifies success, and also includes a context message that can be used for logging.
val sock = Websocket("wss://echo.websocket.org", behavior)

println(sock.connect().map(_.message))

// Success(Websocket Connected)

Request Headers

val myHeaders = Map("Authorization" -> "Bearer xxxxxxxxxxxxxx")

val socket = Websocket(
  url = "wss://echo.websocket.org", 
  behavior = WebsocketBehavior.empty, 
  headers = myHeaders
)

socket.connect()

Message Handling

According the WebSocket Protocol (RFC 6455) a websocket message may be sent either complete, or in chunks. At this time, websocket-scala only deals in complete Text messages.

This does not mean that partial Text messages are not processed however. websocket-scala takes advantage of a feature in Project Tyrus which caches partial messages and delivers the full payload to the event listener.

Future Work

  • handle partial text message
  • support binary messages

Debug Mode

Set debugMode = true to get helpful messages sent to stdout during login attempts. Use to troubleshoot connection issues.

val sock = Websocket(myEndpoint, behavior, debugMode = true)

sock.connect()

> Session xxxxx [47 ms]: Sending handshake request:
> Connection: Upgrade
...

Building the project

  • build files for intellij
./mill mill.scalalib.GenIdea/idea
  • run tests
./mill examples.test

License

GNU General Public License, version 2 with the GNU Classpath Exception, as required by Project Tyrus, its main dependency. Project Tyrus