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

frame-operations.mddocs/

Frame Operations

Low-level WebSocket frame handling for text, binary, and control frame processing with full control over WebSocket protocol features and frame-level operations.

Capabilities

Frame Base Class

Base sealed class for all WebSocket frame types with common properties and frame metadata.

/**
 * Base sealed class for WebSocket frames
 * Not reusable and not thread-safe
 * @param fin Final fragment flag - true for complete frames
 * @param frameType Type of the WebSocket frame
 * @param data Frame payload data
 * @param disposableHandle Handle for frame cleanup
 * @param rsv1 First extension bit
 * @param rsv2 Second extension bit  
 * @param rsv3 Third extension bit
 */
sealed class Frame private constructor(
    fin: Boolean,
    frameType: FrameType,
    data: ByteArray,
    disposableHandle: DisposableHandle = NonDisposableHandle,
    rsv1: Boolean = false,
    rsv2: Boolean = false,
    rsv3: Boolean = false
) {
    /** Final fragment flag - should be true for control frames */
    val fin: Boolean
    
    /** Frame type enumeration */
    val frameType: FrameType
    
    /** Frame payload data */
    val data: ByteArray
    
    /** Handle for frame disposal/cleanup */
    val disposableHandle: DisposableHandle
    
    /** First extension bit for WebSocket extensions */
    val rsv1: Boolean
    
    /** Second extension bit for WebSocket extensions */ 
    val rsv2: Boolean
    
    /** Third extension bit for WebSocket extensions */
    val rsv3: Boolean
}

Text Frame

Application-level text frame for UTF-8 encoded string messages.

/**
 * Text frame for UTF-8 encoded string messages
 * Can be fragmented in RAW WebSocket sessions (fin=false)
 */
class Frame.Text : Frame {
    /**
     * Create text frame with fragment control and extension bits
     * @param fin Final fragment flag
     * @param data UTF-8 encoded text data
     * @param rsv1 First extension bit
     * @param rsv2 Second extension bit
     * @param rsv3 Third extension bit
     */
    constructor(
        fin: Boolean,
        data: ByteArray,
        rsv1: Boolean = false,
        rsv2: Boolean = false, 
        rsv3: Boolean = false
    )
    
    /**
     * Create complete text frame from byte array
     * @param fin Final fragment flag
     * @param data UTF-8 encoded text data
     */
    constructor(fin: Boolean, data: ByteArray)
    
    /**
     * Create complete text frame from string
     * @param text Text content (automatically UTF-8 encoded)
     */
    constructor(text: String)
    
    /**
     * Create text frame from Source
     * @param fin Final fragment flag
     * @param packet Source containing UTF-8 text data
     */
    constructor(fin: Boolean, packet: Source)
}

Usage Examples:

// Create text frame from string
val textFrame = Frame.Text("Hello WebSocket!")

// Create fragmented text frame
val fragment1 = Frame.Text(false, "Hello ".toByteArray())
val fragment2 = Frame.Text(true, "World!".toByteArray())

// Send text frame
session.outgoing.send(Frame.Text("Message content"))

// Create text frame with extension bits
val compressedText = Frame.Text(
    fin = true,
    data = compressedData,
    rsv1 = true // Indicate compression extension
)

Binary Frame

Application-level binary frame for raw byte data transmission.

/**
 * Binary frame for raw byte data
 * Can be fragmented in RAW WebSocket sessions (fin=false)
 */
class Frame.Binary : Frame {
    /**
     * Create binary frame with fragment control and extension bits
     * @param fin Final fragment flag
     * @param data Binary payload data
     * @param rsv1 First extension bit
     * @param rsv2 Second extension bit
     * @param rsv3 Third extension bit
     */
    constructor(
        fin: Boolean,
        data: ByteArray,
        rsv1: Boolean = false,
        rsv2: Boolean = false,
        rsv3: Boolean = false
    )
    
    /**
     * Create complete binary frame from byte array
     * @param fin Final fragment flag
     * @param data Binary payload data
     */
    constructor(fin: Boolean, data: ByteArray)
    
    /**
     * Create binary frame from Source
     * @param fin Final fragment flag
     * @param packet Source containing binary data
     */
    constructor(fin: Boolean, packet: Source)
}

Usage Examples:

// Create binary frame from byte array
val imageData = loadImageBytes()
val binaryFrame = Frame.Binary(true, imageData)

// Send binary frame
session.outgoing.send(Frame.Binary(true, fileBytes))

// Create fragmented binary frame
val chunk1 = Frame.Binary(false, data.sliceArray(0..1023))
val chunk2 = Frame.Binary(true, data.sliceArray(1024..data.size))

Control Frames

Control frames for WebSocket protocol management (Close, Ping, Pong).

/**
 * Close frame for connection termination
 * Must not be fragmented (fin always true)
 */
class Frame.Close : Frame

/**
 * Ping frame for connection keep-alive
 * Must not be fragmented (fin always true)
 */
class Frame.Ping : Frame

/**
 * Pong frame for ping response
 * Must not be fragmented (fin always true)
 */
class Frame.Pong : Frame

Usage Examples:

// Send ping with payload
session.outgoing.send(Frame.Ping(byteArrayOf(1, 2, 3)))

// Respond to ping with pong
when (val frame = session.incoming.receive()) {
    is Frame.Ping -> session.outgoing.send(Frame.Pong(frame.data))
}

// Close connection with reason
session.outgoing.send(Frame.Close())

Frame Type Enumeration

Enumeration defining WebSocket frame types with protocol opcodes.

/**
 * WebSocket frame type enumeration
 * @param controlFrame Whether this is a control frame type
 * @param opcode Frame type opcode for wire protocol
 */
enum class FrameType(val controlFrame: Boolean, val opcode: Int) {
    /** Application level text frame */
    TEXT(false, 1),
    
    /** Application level binary frame */
    BINARY(false, 2),
    
    /** Control close frame */
    CLOSE(true, 8),
    
    /** Control ping frame */
    PING(true, 9),
    
    /** Control pong frame */
    PONG(true, 0xa)
    
    companion object {
        /**
         * Find FrameType by numeric opcode
         * @param opcode Frame type opcode
         * @return FrameType instance or null if invalid
         */
        operator fun get(opcode: Int): FrameType?
    }
}

Frame Reading Functions

Utility functions for extracting data from frames.

/**
 * Read text content from text frame as UTF-8 string
 * @return Decoded text content
 */
fun Frame.Text.readText(): String

/**
 * Read raw bytes from any frame type
 * @return Frame payload data
 */
fun Frame.readBytes(): ByteArray

/**
 * Read close reason from close frame
 * @return CloseReason with code and message, or null if no reason
 */  
fun Frame.Close.readReason(): CloseReason?

Usage Examples:

when (val frame = session.incoming.receive()) {
    is Frame.Text -> {
        val message = frame.readText()
        println("Text message: $message")
    }
    
    is Frame.Binary -> {
        val bytes = frame.readBytes()
        println("Binary data: ${bytes.size} bytes")
    }
    
    is Frame.Close -> {
        val reason = frame.readReason()
        println("Connection closed: ${reason?.message}")
    }
}

Close Reason

Structure for WebSocket close frame reasons.

/**
 * WebSocket close reason with code and message
 * @param code Numeric close code (RFC 6455)
 * @param message Optional human-readable message
 */
data class CloseReason(val code: Short, val message: String) {
    
    /** Constructor using predefined codes */
    constructor(code: Codes, message: String)
    
    /** Get enum value for this code or null */
    val knownReason: Codes?

    /** Standard close reason codes */
    enum class Codes(val code: Short) {
        /** Normal closure (1000) */
        NORMAL(1000),
        
        /** Going away (1001) */
        GOING_AWAY(1001),
        
        /** Protocol error (1002) */
        PROTOCOL_ERROR(1002),
        
        /** Cannot accept data type (1003) */
        CANNOT_ACCEPT(1003),
        
        /** Not consistent UTF-8 (1007) */
        NOT_CONSISTENT(1007),
        
        /** Policy violation (1008) */
        VIOLATED_POLICY(1008),
        
        /** Message too big (1009) */
        TOO_BIG(1009),
        
        /** No extension (1010) */
        NO_EXTENSION(1010),
        
        /** Internal error (1011) */
        INTERNAL_ERROR(1011),
        
        /** Service restart (1012) */
        SERVICE_RESTART(1012),
        
        /** Try again later (1013) */
        TRY_AGAIN_LATER(1013);
        
        companion object {
            fun byCode(code: Short): Codes?
        }
    }
}

Frame Operation Examples

Basic Frame Handling

client.webSocket("ws://echo.websocket.org") {
    // Send different frame types
    outgoing.send(Frame.Text("Hello WebSocket!"))
    outgoing.send(Frame.Binary(true, byteArrayOf(1, 2, 3, 4)))
    
    // Process incoming frames
    for (frame in incoming) {
        when (frame) {
            is Frame.Text -> {
                val text = frame.readText()
                println("Received text: $text")
            }
            
            is Frame.Binary -> {
                val bytes = frame.readBytes()
                println("Received ${bytes.size} bytes")
            }
            
            is Frame.Close -> {
                val reason = frame.readReason()
                println("Connection closed: ${reason?.code} - ${reason?.message}")
                break
            }
            
            is Frame.Ping -> {
                // Respond to ping
                outgoing.send(Frame.Pong(frame.data))
                println("Responded to ping")
            }
            
            is Frame.Pong -> {
                println("Received pong response")
            }
        }
    }
}

File Transfer Example

client.webSocket("ws://fileserver.example.com") {
    // Send file as binary frames
    val fileBytes = File("large-file.dat").readBytes()
    val chunkSize = 64 * 1024 // 64KB chunks
    
    for (i in fileBytes.indices step chunkSize) {
        val chunk = fileBytes.sliceArray(i until minOf(i + chunkSize, fileBytes.size))
        val isLast = i + chunkSize >= fileBytes.size
        
        outgoing.send(Frame.Binary(isLast, chunk))
    }
    
    // Wait for acknowledgment
    val ack = incoming.receive()
    if (ack is Frame.Text && ack.readText() == "ACK") {
        println("File transfer completed")
    }
}

JSON Message Protocol

client.webSocket("ws://api.example.com/json") {
    // Send JSON as text frames
    val request = JsonObject(mapOf(
        "action" to JsonPrimitive("subscribe"),
        "channel" to JsonPrimitive("trades")
    ))
    
    outgoing.send(Frame.Text(request.toString()))
    
    // Process JSON responses
    for (frame in incoming) {
        when (frame) {
            is Frame.Text -> {
                val json = Json.parseToJsonElement(frame.readText())
                handleJsonMessage(json)
            }
            
            is Frame.Close -> break
        }
    }
}

Extension Bit Usage

client.webSocket("ws://compressed.example.com") {
    // Send compressed frame (using rsv1 for compression flag)
    val originalData = "Large text content...".toByteArray()
    val compressedData = compress(originalData)
    
    outgoing.send(Frame.Text(
        fin = true,
        data = compressedData,
        rsv1 = true // Indicate compression
    ))
    
    // Handle compressed incoming frames
    for (frame in incoming) {
        when (frame) {
            is Frame.Text -> {
                val data = if (frame.rsv1) {
                    decompress(frame.data)
                } else {
                    frame.data
                }
                
                val text = String(data, Charsets.UTF_8)
                println("Message: $text")
            }
        }
    }
}

Custom Protocol Implementation

client.webSocket("ws://custom.example.com") {
    // Custom protocol: first byte indicates message type
    fun sendCustomMessage(type: Byte, payload: String) {
        val data = byteArrayOf(type) + payload.toByteArray()
        outgoing.send(Frame.Binary(true, data))
    }
    
    // Send different message types
    sendCustomMessage(0x01, "User message")
    sendCustomMessage(0x02, "System command")
    
    // Process custom protocol messages
    for (frame in incoming) {
        when (frame) {
            is Frame.Binary -> {
                val data = frame.readBytes()
                if (data.isNotEmpty()) {
                    val type = data[0]
                    val payload = String(data, 1, data.size - 1)
                    
                    when (type.toInt()) {
                        0x01 -> handleUserMessage(payload)
                        0x02 -> handleSystemCommand(payload)
                        0x03 -> handleErrorMessage(payload)
                    }
                }
            }
            
            is Frame.Close -> break
        }
    }
}

Graceful Connection Closure

client.webSocket("ws://api.example.com") {
    try {
        // Normal operation
        for (frame in incoming) {
            when (frame) {
                is Frame.Text -> processMessage(frame.readText())
                is Frame.Close -> {
                    val reason = frame.readReason()
                    println("Server closed connection: ${reason?.message}")
                    break
                }
            }
        }
    } finally {
        // Send close frame before terminating
        try {
            outgoing.send(Frame.Close())
        } catch (e: Exception) {
            // Channel might be already closed
        }
    }
}

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