Ktor - WebSocket

  • WebSocket is a protocol that provides a full-duplex communication session between the user's browser and a server over a single TCP connection. It is particularly useful for creating applications that require real-time data transfer from and to the server.

    Introduction

    WebSockets shine in scenarios that require real-time communication. For example, they are widely used in chat applications, online multiplayer games, and real-time data dashboards.

    For a mobile application, WebSockets enable seamless interaction with a backend server, allowing mobile clients to maintain a live link to the server for push notifications or updates. In mobile development, WebSockets are useful for building apps where timely delivery of message updates is critical, such as collaborative editing tools or live sports score apps.

    If you are new to WebSockets, we recommend reading our Web - WebSocket overview first.

    Server

    To use WebSockets, you need to include the ktor-server-websockets dependency:

    dependencies:
    - $ktor.server.websockets

    Install the WebSockets plugin by passing it to the install function within your application module:

    import io.ktor.server.application.*
    import io.ktor.server.websocket.*
    // ...
    fun Application.module() {
    install(WebSockets)
    // ...
    }

    Configure WebSockets

    You can customize the plugin’s behavior within the install block using WebSocketOptions:

    • pingPeriod: Frequency of keep-alive pings.
    • timeout: Duration after which an inactive connection is closed.
    • maxFrameSize: Maximum size allowed for a single frame.
    • masking: Whether to enable frame masking.
    • contentConverter: A converter for automated serialization and deserialization.
    install(WebSockets) {
    pingPeriod = 15.seconds
    timeout = 15.seconds
    maxFrameSize = Long.MAX_VALUE
    masking = false
    }

    Handle WebSockets sessions

    Define a WebSocket endpoint by calling the webSocket function inside your routing block:

    routing {
    webSocket("/echo") {
    // Handle a WebSocket session
    }
    }

    With the default configuration, this server accepts requests at ws://localhost:8080/echo.

    Inside the block, the session is represented by DefaultWebSocketServerSession.

    Key properties and functions include:

    • send(): Sends text or binary content to the client.
    • incoming: A channel for receiving frames from the client.
    • outgoing: A channel for sending frames to the client.
    • close(): Terminate the session with a specific reason.

    Frames are typically handled by checking their type:

    • Frame.Text: Read content using readText().
    • Frame.Binary: Read content using readBytes().
    Ktor ServerBrowser / Mobile ClientKtor ServerBrowser / Mobile ClientWebSocket connection establishedOptional keep-alivealt[Server echoes][Broadcast to others]loop[Real-time messaging]HTTP GET /echo\nUpgrade: websocket1101 Switching Protocols2Ping3Pong4send(text | binary)5send(echo)6send(update)7Close frame (1000 Normal Closure)8Close acknowledgment9

    Example: Handle a single session

    This example demonstrates a basic echo handler that greets the user and closes the connection when they say “bye”:

    routing {
    webSocket("/echo") {
    send("Please enter your name")
    for (frame in incoming) {
    frame as? Frame.Text ?: continue
    val receivedText = frame.readText()
    if (receivedText.equals("bye", ignoreCase = true)) {
    close(CloseReason(CloseReason.Codes.NORMAL, "Client said BYE"))
    } else {
    send(Frame.Text("Hi, $receivedText!"))
    }
    }
    }
    }

    Download the websocat tool as explained in the Web - WebSocket overview.

    Connect to your server:

    Terminal window
    .\websocat.exe ws://localhost:8080/echo
    Terminal window
    Please enter your name
    david
    Hi, david

    Also, you can test with an http file on Idea like this:

    websocket.http

    Example: Handle multiple sessions

    To efficiently manage multiple WebSocket sessions and handle broadcasting, you can use Kotlin’s SharedFlow.

    This approach provides a scalable and concurrency-friendly method for managing WebSocket communications.

    Define a SharedFlow for broadcasting messages:

    val messageResponseFlow = MutableSharedFlow<MessageResponse>()
    val sharedFlow = messageResponseFlow.asSharedFlow()

    In your WebSocket route, implement the broadcasting and message handling logic:

    webSocket("/broadcast") {
    send("You are connected to WebSocket!")
    val job = launch {
    sharedFlow.collect { message ->
    send(message)
    }
    }
    runCatching {
    incoming.consumeEach { frame ->
    if (frame is Frame.Text) {
    val message = frame.readText()
    messageResponseFlow.emit(message)
    }
    }
    }.onFailure { exception ->
    println("WebSocket exception: ${exception.localizedMessage}")
    }.also {
    job.cancel()
    }
    }

    The runCatching block processes incoming messages and emits them to the SharedFlow, which then broadcasts to all collectors.

    By using this pattern, you can efficiently manage multiple WebSocket sessions without manually tracking individual connections. This approach scales well for applications with many concurrent WebSocket connections and provides a clean, reactive way to handle message broadcasting.

    Task

    Open two or more terminals and open a websocket connection to your server on each terminal:

    Terminal window
    .\websocat.exe ws://localhost:8080/broadcast

    To start a new session in a separate tab, click the Add button on the toolbar or press Ctr Shift T.

    Test that messages sent from one client are received by all connected clients.

    Handle Sessions

    HTMX

    htmx Web Socket extension

    Client

    Websockets in Ktor Client

    Pending