A Scala/JavaFX WebSocket client

As a brief note today, I started to create a little Scala/JavaFX WebSocket client based on the Java-WebSocket project. I initially created it to test my Play Framework WebSocket example. I had hoped to be able to easily get to the server response request headers, but atm I don’t see a way to do that.

That being said, this is what the Scala/JavaFX WebSocket client currently looks like:

A Scala/JavaFX WebSocket client

And here’s its source code:

package websockets

// JavaFx
import javafx.application.Application
import javafx.event.ActionEvent
import javafx.event.EventHandler
import javafx.scene.Scene
import javafx.scene.control._
import javafx.scene.layout._
import javafx.stage.Stage
import javafx.application.Platform
import javafx.geometry.Insets

// WebSocket client
import java.net.URI
import java.net.URISyntaxException
import java.util.Map
import org.java_websocket.client.WebSocketClient
import org.java_websocket.drafts.Draft
import org.java_websocket.drafts.Draft_6455
import org.java_websocket.framing.Framedata
import org.java_websocket.handshake.ServerHandshake

object ScalaJavaFxWebSocketClientMain {
    def main(args: Array[String]) {
        Application.launch(classOf[ScalaJavaFxWebSocketClient], args: _*)
    }
}

trait HandleServerMessageReceived {
    def handleServerMessageReceived(msg: String): Unit
}

class ScalaJavaFxWebSocketClient extends Application with HandleServerMessageReceived
{
    val urlField = new TextField
    val messageField = new TextArea
    val replyField = new TextArea
    var wsClient: ScalaWebSocketClient = null

    def handleServerMessageReceived(msg: String): Unit =  {
        replyField.setText(msg)
    }

    var btnShowsConnectText = true
    override def start(primaryStage: Stage)
    {
        Platform.setImplicitExit(true)

        urlField.setText("ws://localhost:9000/ws")
        primaryStage.setTitle("WebSocket Client")
        messageField.setDisable(true)
        replyField.setDisable(true)

        // connect button
        val btnConnect = new Button
        btnConnect.setText("Connect")
        btnConnect.setOnAction((e: ActionEvent) => {
            val url = urlField.getText
            println(s"URL: $url")
            wsClient = new ScalaWebSocketClient(
                new URI(url),
                this
            )
            val doConnectAction = if (btnShowsConnectText) true else false
            connectToServer(doConnectAction, wsClient, btnConnect)
        })

        // spacer
        val spacer = new Region
        spacer.setPrefHeight(40)

        // message field
        val messageLabel = new Label("Message To Send")
        messageField.setPrefHeight(200)
        messageField.setPromptText("your message")
        messageField.setText("{\"message\":\"Hello\"}")

        // reply field
        val replyLabel = new Label("Server’s Reply")
        replyField.setPrefHeight(200)
        replyField.setPromptText("")

        // send button
        val btnSend = new Button
        btnSend.setText("Send")
        btnSend.setOnAction((e: ActionEvent) => {
            println("SendButton pressed")
            val ourMsg = messageField.getText
            wsClient.send(ourMsg)
        })

        val vbox = new VBox
        vbox.setSpacing(20)
        vbox.getChildren.addAll(
            urlField,
            btnConnect, spacer, 
            messageLabel, messageField, 
            replyLabel, replyField,
            btnSend
        )

        val borderPane = new BorderPane(vbox)
        borderPane.setPadding(new Insets(20, 20, 20, 20))

        primaryStage.setScene(new Scene(borderPane, 800, 600))
        primaryStage.show

        primaryStage.setOnCloseRequest(event => {
            // TODO figure out what’s really needed here
            Platform.exit()
            primaryStage.close
            System.exit(0)
        })

    }

    def connectToServer(b: Boolean, wsClient: ScalaWebSocketClient, btnConnect: Button): Unit = {
        if (b) {
            println("Trying to connect to the server ...")
            wsClient.connect
            btnConnect.setText("Disconnect")
            btnShowsConnectText = false
            messageField.setDisable(false)
            replyField.setDisable(false)
        } else {
            println("Closing the connection to the server ...")
            // TODO might need to call closeBlocking or closeConnection here instead
            wsClient.close()
            btnConnect.setText("Connect")
            btnShowsConnectText = true
            messageField.setDisable(true)
            replyField.setDisable(true)
        }
    }

}


class ScalaWebSocketClient(uri: URI, serverMsgHandler: HandleServerMessageReceived)
extends WebSocketClient(uri) {

    @Override
    def onOpen(handshakedata: ServerHandshake) {
        send("{\"message\":\"Hello\"}")
        send("{\"message\":\"world\"}")
        println( "opened connection" )
    }

    @Override
    def onMessage(message: String) {
        println( "received: " + message )
        serverMsgHandler.handleServerMessageReceived(message)
    }

    @Override
    def onClose(code: Int, reason: String, remote: Boolean) {
        // The codecodes are documented in class org.java_websocket.framing.CloseFrame
        println( "Connection closed by " 
        + ( if (remote) "remote peer" else "us" ) + " Code: " + code + " Reason: " + reason )
    }

    @Override
    def onError(e: Exception) {
        e.printStackTrace()
    }

}

Also, here’s its build.sbt file:

name := "JavaScalaWebSocketClient"
version := "0.1"
scalaVersion := "2.12.11"

// need this to stop JavaFX clients within sbt
// see: https://stackoverflow.com/questions/5137460/sbt-stop-run-without-exiting
fork in run := true

libraryDependencies ++= Seq(
    "org.java-websocket" % "Java-WebSocket" % "1.4.1"
)

for scalacOptions descriptions
scalacOptions ++= Seq(
    "-deprecation",     //emit warning and location for usages of deprecated APIs
    "-unchecked",       //enable additional warnings where generated code depends on assumptions
    "-explaintypes",    //explain type errors in more detail
    "-Ywarn-dead-code", //warn when dead code is identified
    "-Xfatal-warnings"  //fail the compilation if there are any warnings
)