WebSocket plugin for Ktor HTTP client enabling full-duplex real-time communication
—
Client-specific WebSocket session interfaces and message serialization capabilities that integrate with Ktor's HTTP client call lifecycle and provide automatic content conversion.
Core interface for client-side WebSocket sessions that provides access to the associated HTTP client call.
/**
* Client specific WebSocketSession with access to HTTP call context
*/
interface ClientWebSocketSession : WebSocketSession {
/**
* HttpClientCall associated with this WebSocket session
*/
val call: HttpClientCall
}Default implementation of client WebSocket session that delegates to DefaultWebSocketSession while providing HTTP call access.
/**
* Default implementation of ClientWebSocketSession
*/
class DefaultClientWebSocketSession(
override val call: HttpClientCall,
delegate: DefaultWebSocketSession
) : ClientWebSocketSession, DefaultWebSocketSession by delegateUsage Examples:
import io.ktor.client.*
import io.ktor.client.plugins.websocket.*
import io.ktor.websocket.*
val client = HttpClient {
install(WebSockets)
}
client.webSocket("ws://localhost:8080/websocket") { session ->
// session is DefaultClientWebSocketSession
// Access HTTP call information
val requestUrl = session.call.request.url
val userAgent = session.call.request.headers["User-Agent"]
println("Connected to: $requestUrl")
println("User-Agent: $userAgent")
// Use standard WebSocket operations
session.send("Hello from ${userAgent}")
for (frame in session.incoming) {
when (frame) {
is Frame.Text -> println("Received: ${frame.readText()}")
is Frame.Close -> break
else -> {}
}
}
}Access to the WebSocket content converter configured in the plugin for automatic serialization/deserialization.
/**
* Extension property to access the content converter from WebSockets plugin
*/
val DefaultClientWebSocketSession.converter: WebsocketContentConverter?Usage Examples:
client.webSocket("ws://localhost:8080/api") {
val converter = this.converter
if (converter != null) {
println("Content converter available: ${converter::class.simpleName}")
} else {
println("No content converter configured")
}
}Send serialized data using the configured content converter.
/**
* Serializes data to a frame and enqueues it using TypeInfo
*/
suspend fun DefaultClientWebSocketSession.sendSerialized(
data: Any?,
typeInfo: TypeInfo
)
/**
* Serializes data to a frame and enqueues it using reified type
*/
suspend inline fun <reified T> DefaultClientWebSocketSession.sendSerialized(data: T)Usage Examples:
import io.ktor.util.reflect.*
data class Message(val text: String, val timestamp: Long)
data class User(val id: Int, val name: String)
val client = HttpClient {
install(WebSockets) {
contentConverter = JsonWebsocketContentConverter()
}
}
client.webSocket("ws://localhost:8080/chat") {
// Send with reified type (recommended)
sendSerialized(Message("Hello", System.currentTimeMillis()))
// Send with explicit TypeInfo
val user = User(123, "Alice")
sendSerialized(user, typeInfo<User>())
// Handle responses...
}Receive and deserialize frames using the configured content converter.
/**
* Dequeues a frame and deserializes it using TypeInfo
*/
suspend fun <T> DefaultClientWebSocketSession.receiveDeserialized(typeInfo: TypeInfo): T
/**
* Dequeues a frame and deserializes it using reified type
*/
suspend inline fun <reified T> DefaultClientWebSocketSession.receiveDeserialized(): TUsage Examples:
client.webSocket("ws://localhost:8080/api") {
// Send a request
sendSerialized(ApiRequest("getUserList"))
// Receive typed response (recommended)
val userList: List<User> = receiveDeserialized()
println("Received ${userList.size} users")
// Receive with explicit TypeInfo
val response: ApiResponse = receiveDeserialized(typeInfo<ApiResponse>())
println("API response: ${response.status}")
}Serialization and deserialization operations can throw specific exceptions.
/**
* Base exception for WebSocket content conversion errors
*/
open class WebsocketContentConvertException(
message: String,
cause: Throwable? = null
) : ContentConvertException(message, cause)
/**
* Exception thrown when no content converter is found
*/
class WebsocketConverterNotFoundException(
message: String,
cause: Throwable? = null
) : WebsocketContentConvertException(message, cause)
/**
* Exception thrown when deserialization fails
*/
class WebsocketDeserializeException(
message: String,
cause: Throwable? = null,
val frame: Frame
) : WebsocketContentConvertException(message, cause)Usage Examples:
client.webSocket("ws://localhost:8080/api") {
try {
// This will throw if no converter is configured
sendSerialized(MyData("test"))
val response: MyResponse = receiveDeserialized()
} catch (e: WebsocketConverterNotFoundException) {
println("No content converter configured: ${e.message}")
} catch (e: WebsocketDeserializeException) {
println("Failed to deserialize message: ${e.message}")
e.frame?.let { frame ->
println("Problematic frame type: ${frame::class.simpleName}")
}
} catch (e: ClosedReceiveChannelException) {
println("WebSocket connection closed")
}
}Direct access to WebSocket frames alongside serialization capabilities.
Usage Examples:
client.webSocket("ws://localhost:8080/mixed") {
// Send serialized data
sendSerialized(MyMessage("Hello"))
// Send raw frame
send("Raw text message")
// Handle mixed message types
for (frame in incoming) {
when (frame) {
is Frame.Text -> {
val text = frame.readText()
if (text.startsWith("{")) {
// Assume JSON, deserialize manually
try {
val data: MyMessage = converter?.deserialize(
frame,
typeInfo<MyMessage>()
) ?: error("No converter")
println("Structured: $data")
} catch (e: Exception) {
println("Raw text: $text")
}
} else {
println("Raw text: $text")
}
}
is Frame.Binary -> {
// Handle binary frames
val bytes = frame.readBytes()
println("Binary data: ${bytes.size} bytes")
}
is Frame.Close -> break
else -> {}
}
}
}Leverage HTTP call context for WebSocket session management.
Usage Examples:
client.webSocket("ws://localhost:8080/websocket") {
// Access request information
val originalUrl = call.request.url
val requestHeaders = call.request.headers
// Access response information (from upgrade response)
val responseStatus = call.response.status
val responseHeaders = call.response.headers
println("Upgraded from: $originalUrl")
println("Response status: $responseStatus")
// Check if specific headers were set during upgrade
val serverProtocol = responseHeaders["Sec-WebSocket-Protocol"]
if (serverProtocol != null) {
println("Server selected protocol: $serverProtocol")
}
// Use this information for session logic
send("Connected with protocol: ${serverProtocol ?: "default"}")
}Install with Tessl CLI
npx tessl i tessl/maven-io-ktor--ktor-client-websockets-macosarm64