CtrlK
BlogDocsLog inGet started
Tessl Logo

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

Ktor client WebSockets plugin for Linux x64 platform - enables full-duplex communication between client and server over TCP connection

Pending
Overview
Eval results
Files

serialization-support.mddocs/

Serialization Support

Content converter integration for automatic serialization/deserialization of objects to/from WebSocket frames, enabling seamless object-based communication over WebSocket connections.

Capabilities

Send Serialized Data

Serialize objects to WebSocket frames and send them through the connection.

/**
 * Serialize data to WebSocket frame and send it
 * May suspend if outgoing queue is full
 * @param data Object to serialize and send
 * @param typeInfo Type information for serialization
 * @throws WebsocketConverterNotFoundException if no converter found
 */
suspend fun DefaultClientWebSocketSession.sendSerialized(data: Any?, typeInfo: TypeInfo)

/**
 * Serialize data to WebSocket frame and send it (reified version)
 * Type information is automatically inferred
 * @param data Object to serialize and send
 * @throws WebsocketConverterNotFoundException if no converter found
 */
suspend inline fun <reified T> DefaultClientWebSocketSession.sendSerialized(data: T)

Usage Examples:

data class Message(val text: String, val timestamp: Long)
data class UserAction(val type: String, val payload: Map<String, Any>)

client.webSocket("ws://api.example.com") {
    // Send typed objects (type inferred)
    sendSerialized(Message("Hello!", System.currentTimeMillis()))
    sendSerialized(UserAction("login", mapOf("username" to "alice")))
    
    // Send with explicit type info
    val genericData: Any = listOf("item1", "item2", "item3")
    sendSerialized(genericData, typeInfo<List<String>>())
    
    // Send nullable data
    val optionalMessage: Message? = null
    sendSerialized(optionalMessage)
}

Receive Deserialized Data

Receive WebSocket frames and deserialize them to typed objects.

/**
 * Receive WebSocket frame and deserialize to specified type
 * May throw WebsocketDeserializeException if frame cannot be deserialized
 * @param typeInfo Type information for deserialization
 * @return Deserialized object of type T
 * @throws WebsocketConverterNotFoundException if no converter found
 * @throws WebsocketDeserializeException if deserialization fails
 * @throws ClosedReceiveChannelException if channel is closed
 */
suspend fun <T> DefaultClientWebSocketSession.receiveDeserialized(typeInfo: TypeInfo): T

/**
 * Receive WebSocket frame and deserialize to specified type (reified version)
 * Type information is automatically inferred
 * @return Deserialized object of type T
 * @throws WebsocketConverterNotFoundException if no converter found
 * @throws WebsocketDeserializeException if deserialization fails
 * @throws ClosedReceiveChannelException if channel is closed
 */
suspend inline fun <reified T> DefaultClientWebSocketSession.receiveDeserialized(): T

Usage Examples:

data class ServerResponse(val status: String, val data: Any?)
data class ChatMessage(val user: String, val message: String, val timestamp: Long)

client.webSocket("ws://chat.example.com") {
    // Receive typed objects
    while (true) {
        try {
            val response = receiveDeserialized<ServerResponse>()
            println("Server status: ${response.status}")
            
            // Handle different response types
            when (response.status) {
                "chat_message" -> {
                    val chatMsg = receiveDeserialized<ChatMessage>()
                    println("${chatMsg.user}: ${chatMsg.message}")
                }
                "user_list" -> {
                    val users = receiveDeserialized<List<String>>()
                    println("Online users: ${users.joinToString()}")
                }
            }
            
        } catch (e: WebsocketDeserializeException) {
            println("Failed to deserialize: ${e.message}")
            // Handle malformed frame
            val frame = e.frame
            if (frame is Frame.Text) {
                println("Raw content: ${frame.readText()}")
            }
        }
    }
}

Content Converter Access

Get the configured content converter for manual serialization operations.

/**
 * Get content converter from WebSocket plugin configuration
 * Returns null if no converter is configured
 */
val DefaultClientWebSocketSession.converter: WebsocketContentConverter?

Usage Examples:

client.webSocket("ws://api.example.com") {
    val converter = converter
    if (converter != null) {
        println("Using converter: ${converter::class.simpleName}")
        
        // Manual serialization
        val data = MyData("value")
        val frame = converter.serialize(
            Charsets.UTF_8,
            typeInfo<MyData>(),
            data
        )
        outgoing.send(frame)
        
    } else {
        println("No content converter configured")
        // Fall back to manual JSON or string serialization
    }
}

Content Converter Interface

Interface for implementing custom serialization converters.

/**
 * Interface for WebSocket content converters
 * Handles serialization/deserialization between objects and WebSocket frames
 */
interface WebsocketContentConverter {
    /**
     * Serialize object to WebSocket frame
     * @param charset Character encoding to use
     * @param typeInfo Type information for serialization
     * @param value Object to serialize
     * @return WebSocket frame containing serialized data
     */
    suspend fun serialize(
        charset: Charset,
        typeInfo: TypeInfo,
        value: Any?
    ): Frame
    
    /**
     * Deserialize WebSocket frame to object
     * @param charset Character encoding to use
     * @param typeInfo Type information for deserialization
     * @param content WebSocket frame to deserialize
     * @return Deserialized object
     */
    suspend fun deserialize(
        charset: Charset,
        typeInfo: TypeInfo,
        content: Frame
    ): Any?
}

Exception Types

Exceptions specific to WebSocket serialization operations.

/**
 * Thrown when no content converter is available for serialization
 * @param message Error description
 */
class WebsocketConverterNotFoundException(message: String) : Exception

/**
 * Thrown when frame deserialization fails
 * Contains the original frame for inspection
 * @param message Error description
 * @param frame Original frame that failed to deserialize
 */
class WebsocketDeserializeException(
    message: String,
    val frame: Frame
) : Exception

Serialization Examples

JSON Communication

// Configure client with JSON converter
val client = HttpClient {
    install(WebSockets) {
        contentConverter = GsonWebsocketContentConverter()
    }
}

data class ApiRequest(val action: String, val params: Map<String, Any>)
data class ApiResponse(val success: Boolean, val result: Any?, val error: String?)

client.webSocket("ws://api.example.com/json") {
    // Send JSON request
    sendSerialized(ApiRequest(
        action = "get_user",
        params = mapOf("id" to 123)
    ))
    
    // Receive JSON response
    val response = receiveDeserialized<ApiResponse>()
    if (response.success) {
        println("Result: ${response.result}")
    } else {
        println("Error: ${response.error}")
    }
}

Trading Data Stream

data class Trade(
    val symbol: String,
    val price: Double,
    val quantity: Double,
    val timestamp: Long
)

data class OrderBook(
    val symbol: String,
    val bids: List<Pair<Double, Double>>,
    val asks: List<Pair<Double, Double>>
)

client.webSocket("ws://trading.example.com") {
    // Subscribe to data streams
    sendSerialized(mapOf(
        "action" to "subscribe",
        "channels" to listOf("trades", "orderbook"),
        "symbols" to listOf("BTCUSD", "ETHUSD")
    ))
    
    while (true) {
        try {
            // Receive different message types
            val messageType = receiveDeserialized<Map<String, Any>>()
            
            when (messageType["type"]) {
                "trade" -> {
                    val trade = receiveDeserialized<Trade>()
                    handleTrade(trade)
                }
                
                "orderbook" -> {
                    val orderBook = receiveDeserialized<OrderBook>()
                    handleOrderBook(orderBook)
                }
                
                "error" -> {
                    val error = receiveDeserialized<Map<String, String>>()
                    println("Server error: ${error["message"]}")
                }
            }
            
        } catch (e: WebsocketDeserializeException) {
            println("Failed to parse message: ${e.message}")
            
            // Log raw frame content for debugging
            when (val frame = e.frame) {
                is Frame.Text -> println("Raw text: ${frame.readText()}")
                is Frame.Binary -> println("Raw binary: ${frame.data.size} bytes")
            }
        }
    }
}

Custom Protocol with Kotlinx Serialization

@Serializable
data class GameState(
    val players: List<Player>,
    val board: Array<Array<Int>>,
    val currentPlayer: String,
    val gameOver: Boolean
)

@Serializable
data class GameAction(
    val player: String,
    val action: String,
    val coordinates: Pair<Int, Int>?
)

// Configure client with Kotlinx Serialization
val client = HttpClient {
    install(WebSockets) {
        contentConverter = KotlinxSerializationWebsocketContentConverter(Json)
    }
}

client.webSocket("ws://game.example.com") {
    // Send game action
    sendSerialized(GameAction(
        player = "alice",
        action = "move",
        coordinates = Pair(2, 3)
    ))
    
    // Receive updated game state
    val newState = receiveDeserialized<GameState>()
    updateGameUI(newState)
}

Mixed Text and Binary Serialization

client.webSocket("ws://multimedia.example.com") {
    // Text-based metadata
    sendSerialized(mapOf(
        "type" to "image_upload",
        "filename" to "photo.jpg",
        "size" to 1024000
    ))
    
    // Binary image data (using custom converter that produces binary frames)
    val imageBytes = File("photo.jpg").readBytes()
    val converter = converter as? CustomBinaryConverter
    
    if (converter != null) {
        val binaryFrame = converter.serialize(
            Charsets.UTF_8,
            typeInfo<ByteArray>(),
            imageBytes
        ) as Frame.Binary
        
        outgoing.send(binaryFrame)
    }
    
    // Receive processing result
    val result = receiveDeserialized<Map<String, Any>>()
    println("Upload result: ${result["status"]}")
}

Error Handling and Fallbacks

client.webSocket("ws://api.example.com") {
    try {
        // Attempt to send typed object
        sendSerialized(MyComplexType(data))
        
    } catch (e: WebsocketConverterNotFoundException) {
        // Fallback to manual JSON serialization
        val json = Json.encodeToString(data)
        send(json)
    }
    
    // Receive with error handling
    while (true) {
        try {
            val response = receiveDeserialized<ApiResponse>()
            handleResponse(response)
            
        } catch (e: WebsocketDeserializeException) {
            // Try to handle as plain text
            val frame = e.frame
            if (frame is Frame.Text) {
                val plainText = frame.readText()
                println("Received plain text: $plainText")
                
                // Attempt manual parsing
                try {
                    val json = Json.parseToJsonElement(plainText)
                    handleJsonElement(json)
                } catch (e: Exception) {
                    println("Could not parse as JSON: $plainText")
                }
            }
            
        } catch (e: ClosedReceiveChannelException) {
            println("Connection closed")
            break
        }
    }
}

Batch Operations

client.webSocket("ws://batch.example.com") {
    // Send multiple objects in sequence
    val messages = listOf(
        ChatMessage("alice", "Hello everyone!"),
        ChatMessage("bob", "Hi Alice!"),
        ChatMessage("charlie", "Good morning!")
    )
    
    for (message in messages) {
        sendSerialized(message)
    }
    
    // Receive batch response
    val batchResult = receiveDeserialized<List<MessageStatus>>()
    batchResult.forEach { status ->
        println("Message ${status.id}: ${status.delivered}")
    }
}

Type-Safe Protocol Definition

sealed class ServerMessage {
    @Serializable
    data class UserJoined(val username: String) : ServerMessage()
    
    @Serializable
    data class UserLeft(val username: String) : ServerMessage()
    
    @Serializable
    data class ChatMessage(val user: String, val message: String) : ServerMessage()
    
    @Serializable
    data class SystemNotification(val level: String, val text: String) : ServerMessage()
}

client.webSocket("ws://chat.example.com") {
    // Type-safe message handling
    while (true) {
        val message = receiveDeserialized<ServerMessage>()
        
        when (message) {
            is ServerMessage.UserJoined -> 
                println("${message.username} joined the chat")
                
            is ServerMessage.UserLeft -> 
                println("${message.username} left the chat")
                
            is ServerMessage.ChatMessage -> 
                println("${message.user}: ${message.message}")
                
            is ServerMessage.SystemNotification -> 
                println("[${message.level}] ${message.text}")
        }
    }
}

Install with Tessl CLI

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

docs

frame-operations.md

index.md

plugin-configuration.md

serialization-support.md

session-management.md

websocket-connections.md

tile.json