The Scala Toolkit

How to use websockets?

Language

You can declare a dependency on Cask with the following using directive:

//> using dep com.lihaoyi::cask::0.9.2

In your build.sbt, you can add a dependency on Cask:

lazy val example = project.in(file("example"))
  .settings(
    scalaVersion := "3.4.2",
    libraryDependencies += "com.lihaoyi" %% "cask" % "0.9.2",
    fork := true
  )

In your build.sc, you can add a dependency on Cask:

object example extends RootModule with ScalaModule {
  def scalaVersion = "3.3.4"
  def ivyDeps = Agg(
    ivy"com.lihaoyi::cask::0.9.2"
  )
}

You can create a WebSocket endpoint with the @cask.websocket annotation. The endpoint method should return a cask.WsHandler instance defining how the communication should take place. It can also return a cask.Response, which rejects the attempt at forming a WebSocket connection.

The connection can also be closed by sending a cask.Ws.close() message through the WebSocket channel.

Create an HTML file named websockets.html with the following content and place it in the resources directory.

<!DOCTYPE html>
<html>
<body>
<div>
    <input type="text" id="input" placeholder="Provide city name">
    <button onclick="sendMessage()">Send</button>
</div>
<div id="time"></div>
<script>
    const ws = new WebSocket('ws://localhost:8080/websocket');
    ws.onmessage = function(event) {
        receiveMessage(event.data);
    };

    ws.onclose = function(event) {
        receiveMessage('The connection has been closed');
    };

    function sendMessage() {
        const inputElement = document.getElementById('input');
        const message = inputElement.value;
        ws.send(message);
    }

    function receiveMessage(message) {
        const timeElement = document.getElementById('time');
        timeElement.textContent = message;
    }
</script>
</body>
</html>

The JavaScript code opens a WebSocket connection using the ws://localhost:8080/websocket endpoint. The ws.onmessage event handler is executed when the server pushes a message to the browser and ws.onclose when the connection is closed.

Create an endpoint for serving static files using the @cask.staticResources annotation and an endpoint for handling the WebSocket connection.

@cask.staticResources("/static")
def static() = "."

private def getZoneIdForCity(city: String): Option[ZoneId] = {
  import scala.jdk.CollectionConverters._
  ZoneId.getAvailableZoneIds.asScala.find(_.endsWith("/" + city)).map(ZoneId.of)
}

@cask.websocket("/websocket")
def websocket(): cask.WsHandler = {
  cask.WsHandler { channel =>
    cask.WsActor {
      case cask.Ws.Text("") => channel.send(cask.Ws.Close())
      case cask.Ws.Text(city) =>
        val text = getZoneIdForCity(city) match {
          case Some(zoneId) => s"Current date is: ${ZonedDateTime.now().withZoneSameInstant(zoneId)}"
          case None => s"Couldn't find time zone for city $city"
        }
        channel.send(cask.Ws.Text(text))
    }
  }
}

initialize()
@cask.staticResources("/static")
def static() = "."

private def getZoneIdForCity(city: String): Option[ZoneId] =
  import scala.jdk.CollectionConverters.*
  ZoneId.getAvailableZoneIds.asScala.find(_.endsWith("/" + city)).map(ZoneId.of)

@cask.websocket("/websocket")
def websocket(): cask.WsHandler =
  cask.WsHandler { channel =>
    cask.WsActor {
      case cask.Ws.Text("") => channel.send(cask.Ws.Close())
      case cask.Ws.Text(city) =>
        val text = getZoneIdForCity(city) match
          case Some(zoneId) => s"Current date is: ${ZonedDateTime.now().withZoneSameInstant(zoneId)}"
          case None => s"Couldn't find time zone for city $city"
        channel.send(cask.Ws.Text(text))
    }
  }

initialize()

In the cask.WsHandler we define a cask.WsActor. It reacts to events (of type cask.util.Ws.Event) and uses the WebSocket channel to send messages. In this example, we receive the name of a city and return the current time there. If the server receives an empty message, the connection is closed.

Contributors to this page: