CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-ktor--ktor-client-websockets

Ktor client WebSocket plugin - provides WebSocket support for the Ktor HTTP client on multiple platforms including iOS x64

Pending
Overview
Eval results
Files

content-serialization.mddocs/

Content Serialization

Ktor Client WebSockets provides automatic serialization and deserialization of objects through WebSocket frames using Ktor's content conversion system.

Serialization Functions

sendSerialized() - Send Serialized Objects

Send objects as WebSocket frames with automatic serialization:

suspend fun DefaultClientWebSocketSession.sendSerialized(
    data: Any?,
    typeInfo: TypeInfo
)

suspend inline fun <reified T> DefaultClientWebSocketSession.sendSerialized(
    data: T
)

Parameters:

  • data: Object to serialize and send
  • typeInfo: Type information for serialization (manual variant only)

Usage:

import io.ktor.client.plugins.websocket.*
import kotlinx.serialization.*

@Serializable
data class Message(val text: String, val timestamp: Long)

client.webSocket("ws://example.com/api") {
    val message = Message("Hello World", System.currentTimeMillis())
    
    // Send with reified type (recommended)
    sendSerialized(message)
    
    // Send with explicit TypeInfo
    sendSerialized(message, typeInfo<Message>())
}

receiveDeserialized() - Receive and Deserialize Objects

Receive WebSocket frames and automatically deserialize to objects:

suspend fun <T> DefaultClientWebSocketSession.receiveDeserialized(
    typeInfo: TypeInfo
): T

suspend inline fun <reified T> DefaultClientWebSocketSession.receiveDeserialized(): T

Parameters:

  • typeInfo: Type information for deserialization (manual variant only)

Returns:

  • Deserialized object of type T

Usage:

client.webSocket("ws://example.com/api") {
    // Receive with reified type (recommended)
    val message = receiveDeserialized<Message>()
    println("Received: ${message.text} at ${message.timestamp}")
    
    // Receive with explicit TypeInfo
    val message2 = receiveDeserialized<Message>(typeInfo<Message>())
    println("Received: ${message2.text}")
}

Content Converter Configuration

Content serialization requires a WebSocket content converter to be configured in the WebSockets plugin:

var WebSockets.Config.contentConverter: WebsocketContentConverter?

JSON Serialization Setup

Using kotlinx.serialization with JSON:

import io.ktor.client.*
import io.ktor.client.plugins.websocket.*
import io.ktor.serialization.kotlinx.*
import kotlinx.serialization.json.*

val client = HttpClient {
    install(WebSockets) {
        contentConverter = KotlinxWebsocketSerializationConverter(Json {
            prettyPrint = true
            isLenient = true
            ignoreUnknownKeys = true
        })
    }
}

Custom Content Converter

Implement custom serialization logic:

import io.ktor.websocket.*
import io.ktor.util.*
import io.ktor.util.reflect.*

class MyWebSocketConverter : WebsocketContentConverter {
    override suspend fun serialize(
        charset: Charset,
        typeInfo: TypeInfo,
        value: Any?
    ): Frame {
        // Custom serialization logic
        val serialized = mySerialize(value)
        return Frame.Text(serialized)
    }
    
    override suspend fun deserialize(
        charset: Charset,
        typeInfo: TypeInfo,
        content: Frame
    ): Any? {
        // Custom deserialization logic
        return when (content) {
            is Frame.Text -> myDeserialize(content.readText(), typeInfo)
            is Frame.Binary -> myDeserializeBinary(content.data, typeInfo)
            else -> throw WebSocketException("Unsupported frame type for deserialization")
        }
    }
    
    override fun isApplicable(frame: Frame): Boolean {
        return frame is Frame.Text || frame is Frame.Binary
    }
}

// Install custom converter
val client = HttpClient {
    install(WebSockets) {
        contentConverter = MyWebSocketConverter()
    }
}

Serialization Examples

Basic Object Serialization

import kotlinx.serialization.*

@Serializable
data class ChatMessage(
    val user: String,
    val message: String,
    val timestamp: Long = System.currentTimeMillis()
)

client.webSocket("ws://chat.example.com/room/general") {
    // Send chat message
    val chatMsg = ChatMessage("alice", "Hello everyone!")
    sendSerialized(chatMsg)
    
    // Receive chat messages
    for (frame in incoming) {
        when (frame) {
            is Frame.Text -> {
                try {
                    val received = receiveDeserialized<ChatMessage>()
                    println("${received.user}: ${received.message}")
                } catch (e: Exception) {
                    println("Failed to deserialize: ${frame.readText()}")
                }
            }
            is Frame.Close -> break
            else -> { /* Handle other frame types */ }
        }
    }
}

Complex Data Structures

@Serializable
data class GameState(
    val players: List<Player>,
    val currentTurn: String,
    val board: Map<String, String>,
    val settings: GameSettings
)

@Serializable  
data class Player(val id: String, val name: String, val score: Int)

@Serializable
data class GameSettings(val maxPlayers: Int, val timeLimit: Long)

client.webSocket("ws://game.example.com/session/123") {
    // Send complex game state
    val gameState = GameState(
        players = listOf(
            Player("p1", "Alice", 100),
            Player("p2", "Bob", 85)
        ),
        currentTurn = "p1",
        board = mapOf("a1" to "X", "b2" to "O"),
        settings = GameSettings(maxPlayers = 4, timeLimit = 300_000)
    )
    sendSerialized(gameState)
    
    // Receive updated game state
    val updatedState = receiveDeserialized<GameState>()
    println("Game updated: ${updatedState.players.size} players")
}

Mixed Content Communication

Combine serialized objects with raw frames:

client.webSocket("ws://api.example.com/mixed") {
    // Send serialized object
    sendSerialized(ChatMessage("system", "User connected"))
    
    // Send raw text frame
    send("Raw status message")
    
    // Receive mixed content
    for (frame in incoming) {
        when (frame) {
            is Frame.Text -> {
                val text = frame.readText()
                
                // Try to deserialize as known type
                try {
                    val message = receiveDeserialized<ChatMessage>()
                    println("Structured: ${message.user} - ${message.message}")
                } catch (e: Exception) {
                    // Handle as raw text
                    println("Raw: $text")
                }
            }
            is Frame.Close -> break
            else -> { /* Handle other frames */ }
        }
    }
}

Error Handling

Serialization Errors

Handle serialization failures gracefully:

client.webSocket("ws://example.com/api") {
    try {
        val message = Message("test", System.currentTimeMillis())
        sendSerialized(message)
    } catch (e: SerializationException) {
        println("Failed to serialize message: ${e.message}")
        // Fallback to raw frame
        send("Serialization failed - sending raw text")
    } catch (e: Exception) {
        println("Unexpected error during serialization: ${e.message}")
    }
}

Deserialization Errors

Handle deserialization failures:

client.webSocket("ws://example.com/api") {
    for (frame in incoming) {
        when (frame) {
            is Frame.Text -> {
                try {
                    val message = receiveDeserialized<Message>()
                    processMessage(message)
                } catch (e: SerializationException) {
                    println("Failed to deserialize frame: ${e.message}")
                    // Handle as raw text
                    val rawText = frame.readText()
                    processRawMessage(rawText)
                } catch (e: Exception) {
                    println("Unexpected deserialization error: ${e.message}")
                }
            }
            is Frame.Close -> break
            else -> { /* Handle other frame types */ }
        }
    }
}

Missing Content Converter

Handle cases where no content converter is configured:

client.webSocket("ws://example.com/api") {
    if (converter != null) {
        // Use serialization
        sendSerialized(MyData("test"))
        val response = receiveDeserialized<MyResponse>()
    } else {
        // Fall back to raw frames
        send("Raw message without serialization")
        val frame = incoming.receive()
        if (frame is Frame.Text) {
            val response = frame.readText()
            println("Raw response: $response")
        }
    }
}

Binary Serialization

Handle binary data serialization:

@Serializable
data class BinaryMessage(
    val header: String,
    val payload: ByteArray
) {
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is BinaryMessage) return false
        if (header != other.header) return false
        if (!payload.contentEquals(other.payload)) return false
        return true
    }
    
    override fun hashCode(): Int {
        var result = header.hashCode()
        result = 31 * result + payload.contentHashCode()
        return result
    }
}

// Configure for binary serialization
val client = HttpClient {
    install(WebSockets) {
        contentConverter = KotlinxWebsocketSerializationConverter(
            ProtoBuf // or other binary format
        )
    }
}

client.webSocket("ws://example.com/binary") {
    // Send binary message
    val binaryMsg = BinaryMessage("header", byteArrayOf(1, 2, 3, 4))
    sendSerialized(binaryMsg)
    
    // Receive binary message
    val received = receiveDeserialized<BinaryMessage>()
    println("Header: ${received.header}, Payload size: ${received.payload.size}")
}

Performance Considerations

Chunked Serialization

For large objects, consider chunking:

@Serializable
data class LargeDataSet(val items: List<DataItem>)

client.webSocket("ws://example.com/large-data") {
    val largeData = generateLargeDataSet()
    
    // Check frame size limits
    if (maxFrameSize < estimateSerializedSize(largeData)) {
        // Chunk the data
        largeData.items.chunked(100).forEach { chunk ->
            sendSerialized(DataChunk(chunk))
        }
        sendSerialized(EndOfData())
    } else {
        // Send as single frame
        sendSerialized(largeData)
    }
}

Streaming Serialization

For continuous data streams:

client.webSocket("ws://example.com/stream") {
    // Stream data as individual serialized frames
    generateDataStream().collect { dataPoint ->
        sendSerialized(dataPoint)
        
        // Optional: Add throttling
        delay(10) // 100 FPS max
    }
}

Content Type Handling

Different serialization formats can be configured:

// JSON serialization
install(WebSockets) {
    contentConverter = KotlinxWebsocketSerializationConverter(Json)
}

// Protocol Buffers serialization
install(WebSockets) {
    contentConverter = KotlinxWebsocketSerializationConverter(ProtoBuf)
}

// CBOR serialization  
install(WebSockets) {
    contentConverter = KotlinxWebsocketSerializationConverter(Cbor)
}

// XML serialization
install(WebSockets) {
    contentConverter = KotlinxWebsocketSerializationConverter(XML)
}

Install with Tessl CLI

npx tessl i tessl/maven-io-ktor--ktor-client-websockets

docs

content-serialization.md

index.md

plugin-configuration.md

raw-websocket-operations.md

session-operations.md

websocket-connections.md

tile.json