Ktor client WebSockets plugin for Linux x64 platform - enables full-duplex communication between client and server over TCP connection
—
Content converter integration for automatic serialization/deserialization of objects to/from WebSocket frames, enabling seamless object-based communication over WebSocket connections.
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 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(): TUsage 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()}")
}
}
}
}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
}
}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?
}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// 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}")
}
}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")
}
}
}
}@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)
}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"]}")
}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
}
}
}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}")
}
}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