0
# Frame Operations
1
2
Low-level WebSocket frame handling for text, binary, and control frame processing with full control over WebSocket protocol features and frame-level operations.
3
4
## Capabilities
5
6
### Frame Base Class
7
8
Base sealed class for all WebSocket frame types with common properties and frame metadata.
9
10
```kotlin { .api }
11
/**
12
* Base sealed class for WebSocket frames
13
* Not reusable and not thread-safe
14
* @param fin Final fragment flag - true for complete frames
15
* @param frameType Type of the WebSocket frame
16
* @param data Frame payload data
17
* @param disposableHandle Handle for frame cleanup
18
* @param rsv1 First extension bit
19
* @param rsv2 Second extension bit
20
* @param rsv3 Third extension bit
21
*/
22
sealed class Frame private constructor(
23
fin: Boolean,
24
frameType: FrameType,
25
data: ByteArray,
26
disposableHandle: DisposableHandle = NonDisposableHandle,
27
rsv1: Boolean = false,
28
rsv2: Boolean = false,
29
rsv3: Boolean = false
30
) {
31
/** Final fragment flag - should be true for control frames */
32
val fin: Boolean
33
34
/** Frame type enumeration */
35
val frameType: FrameType
36
37
/** Frame payload data */
38
val data: ByteArray
39
40
/** Handle for frame disposal/cleanup */
41
val disposableHandle: DisposableHandle
42
43
/** First extension bit for WebSocket extensions */
44
val rsv1: Boolean
45
46
/** Second extension bit for WebSocket extensions */
47
val rsv2: Boolean
48
49
/** Third extension bit for WebSocket extensions */
50
val rsv3: Boolean
51
}
52
```
53
54
### Text Frame
55
56
Application-level text frame for UTF-8 encoded string messages.
57
58
```kotlin { .api }
59
/**
60
* Text frame for UTF-8 encoded string messages
61
* Can be fragmented in RAW WebSocket sessions (fin=false)
62
*/
63
class Frame.Text : Frame {
64
/**
65
* Create text frame with fragment control and extension bits
66
* @param fin Final fragment flag
67
* @param data UTF-8 encoded text data
68
* @param rsv1 First extension bit
69
* @param rsv2 Second extension bit
70
* @param rsv3 Third extension bit
71
*/
72
constructor(
73
fin: Boolean,
74
data: ByteArray,
75
rsv1: Boolean = false,
76
rsv2: Boolean = false,
77
rsv3: Boolean = false
78
)
79
80
/**
81
* Create complete text frame from byte array
82
* @param fin Final fragment flag
83
* @param data UTF-8 encoded text data
84
*/
85
constructor(fin: Boolean, data: ByteArray)
86
87
/**
88
* Create complete text frame from string
89
* @param text Text content (automatically UTF-8 encoded)
90
*/
91
constructor(text: String)
92
93
/**
94
* Create text frame from Source
95
* @param fin Final fragment flag
96
* @param packet Source containing UTF-8 text data
97
*/
98
constructor(fin: Boolean, packet: Source)
99
}
100
```
101
102
**Usage Examples:**
103
104
```kotlin
105
// Create text frame from string
106
val textFrame = Frame.Text("Hello WebSocket!")
107
108
// Create fragmented text frame
109
val fragment1 = Frame.Text(false, "Hello ".toByteArray())
110
val fragment2 = Frame.Text(true, "World!".toByteArray())
111
112
// Send text frame
113
session.outgoing.send(Frame.Text("Message content"))
114
115
// Create text frame with extension bits
116
val compressedText = Frame.Text(
117
fin = true,
118
data = compressedData,
119
rsv1 = true // Indicate compression extension
120
)
121
```
122
123
### Binary Frame
124
125
Application-level binary frame for raw byte data transmission.
126
127
```kotlin { .api }
128
/**
129
* Binary frame for raw byte data
130
* Can be fragmented in RAW WebSocket sessions (fin=false)
131
*/
132
class Frame.Binary : Frame {
133
/**
134
* Create binary frame with fragment control and extension bits
135
* @param fin Final fragment flag
136
* @param data Binary payload data
137
* @param rsv1 First extension bit
138
* @param rsv2 Second extension bit
139
* @param rsv3 Third extension bit
140
*/
141
constructor(
142
fin: Boolean,
143
data: ByteArray,
144
rsv1: Boolean = false,
145
rsv2: Boolean = false,
146
rsv3: Boolean = false
147
)
148
149
/**
150
* Create complete binary frame from byte array
151
* @param fin Final fragment flag
152
* @param data Binary payload data
153
*/
154
constructor(fin: Boolean, data: ByteArray)
155
156
/**
157
* Create binary frame from Source
158
* @param fin Final fragment flag
159
* @param packet Source containing binary data
160
*/
161
constructor(fin: Boolean, packet: Source)
162
}
163
```
164
165
**Usage Examples:**
166
167
```kotlin
168
// Create binary frame from byte array
169
val imageData = loadImageBytes()
170
val binaryFrame = Frame.Binary(true, imageData)
171
172
// Send binary frame
173
session.outgoing.send(Frame.Binary(true, fileBytes))
174
175
// Create fragmented binary frame
176
val chunk1 = Frame.Binary(false, data.sliceArray(0..1023))
177
val chunk2 = Frame.Binary(true, data.sliceArray(1024..data.size))
178
```
179
180
### Control Frames
181
182
Control frames for WebSocket protocol management (Close, Ping, Pong).
183
184
```kotlin { .api }
185
/**
186
* Close frame for connection termination
187
* Must not be fragmented (fin always true)
188
*/
189
class Frame.Close : Frame
190
191
/**
192
* Ping frame for connection keep-alive
193
* Must not be fragmented (fin always true)
194
*/
195
class Frame.Ping : Frame
196
197
/**
198
* Pong frame for ping response
199
* Must not be fragmented (fin always true)
200
*/
201
class Frame.Pong : Frame
202
```
203
204
**Usage Examples:**
205
206
```kotlin
207
// Send ping with payload
208
session.outgoing.send(Frame.Ping(byteArrayOf(1, 2, 3)))
209
210
// Respond to ping with pong
211
when (val frame = session.incoming.receive()) {
212
is Frame.Ping -> session.outgoing.send(Frame.Pong(frame.data))
213
}
214
215
// Close connection with reason
216
session.outgoing.send(Frame.Close())
217
```
218
219
### Frame Type Enumeration
220
221
Enumeration defining WebSocket frame types with protocol opcodes.
222
223
```kotlin { .api }
224
/**
225
* WebSocket frame type enumeration
226
* @param controlFrame Whether this is a control frame type
227
* @param opcode Frame type opcode for wire protocol
228
*/
229
enum class FrameType(val controlFrame: Boolean, val opcode: Int) {
230
/** Application level text frame */
231
TEXT(false, 1),
232
233
/** Application level binary frame */
234
BINARY(false, 2),
235
236
/** Control close frame */
237
CLOSE(true, 8),
238
239
/** Control ping frame */
240
PING(true, 9),
241
242
/** Control pong frame */
243
PONG(true, 0xa)
244
245
companion object {
246
/**
247
* Find FrameType by numeric opcode
248
* @param opcode Frame type opcode
249
* @return FrameType instance or null if invalid
250
*/
251
operator fun get(opcode: Int): FrameType?
252
}
253
}
254
```
255
256
### Frame Reading Functions
257
258
Utility functions for extracting data from frames.
259
260
```kotlin { .api }
261
/**
262
* Read text content from text frame as UTF-8 string
263
* @return Decoded text content
264
*/
265
fun Frame.Text.readText(): String
266
267
/**
268
* Read raw bytes from any frame type
269
* @return Frame payload data
270
*/
271
fun Frame.readBytes(): ByteArray
272
273
/**
274
* Read close reason from close frame
275
* @return CloseReason with code and message, or null if no reason
276
*/
277
fun Frame.Close.readReason(): CloseReason?
278
```
279
280
**Usage Examples:**
281
282
```kotlin
283
when (val frame = session.incoming.receive()) {
284
is Frame.Text -> {
285
val message = frame.readText()
286
println("Text message: $message")
287
}
288
289
is Frame.Binary -> {
290
val bytes = frame.readBytes()
291
println("Binary data: ${bytes.size} bytes")
292
}
293
294
is Frame.Close -> {
295
val reason = frame.readReason()
296
println("Connection closed: ${reason?.message}")
297
}
298
}
299
```
300
301
### Close Reason
302
303
Structure for WebSocket close frame reasons.
304
305
```kotlin { .api }
306
/**
307
* WebSocket close reason with code and message
308
* @param code Numeric close code (RFC 6455)
309
* @param message Optional human-readable message
310
*/
311
data class CloseReason(val code: Short, val message: String) {
312
313
/** Constructor using predefined codes */
314
constructor(code: Codes, message: String)
315
316
/** Get enum value for this code or null */
317
val knownReason: Codes?
318
319
/** Standard close reason codes */
320
enum class Codes(val code: Short) {
321
/** Normal closure (1000) */
322
NORMAL(1000),
323
324
/** Going away (1001) */
325
GOING_AWAY(1001),
326
327
/** Protocol error (1002) */
328
PROTOCOL_ERROR(1002),
329
330
/** Cannot accept data type (1003) */
331
CANNOT_ACCEPT(1003),
332
333
/** Not consistent UTF-8 (1007) */
334
NOT_CONSISTENT(1007),
335
336
/** Policy violation (1008) */
337
VIOLATED_POLICY(1008),
338
339
/** Message too big (1009) */
340
TOO_BIG(1009),
341
342
/** No extension (1010) */
343
NO_EXTENSION(1010),
344
345
/** Internal error (1011) */
346
INTERNAL_ERROR(1011),
347
348
/** Service restart (1012) */
349
SERVICE_RESTART(1012),
350
351
/** Try again later (1013) */
352
TRY_AGAIN_LATER(1013);
353
354
companion object {
355
fun byCode(code: Short): Codes?
356
}
357
}
358
}
359
```
360
361
## Frame Operation Examples
362
363
### Basic Frame Handling
364
365
```kotlin
366
client.webSocket("ws://echo.websocket.org") {
367
// Send different frame types
368
outgoing.send(Frame.Text("Hello WebSocket!"))
369
outgoing.send(Frame.Binary(true, byteArrayOf(1, 2, 3, 4)))
370
371
// Process incoming frames
372
for (frame in incoming) {
373
when (frame) {
374
is Frame.Text -> {
375
val text = frame.readText()
376
println("Received text: $text")
377
}
378
379
is Frame.Binary -> {
380
val bytes = frame.readBytes()
381
println("Received ${bytes.size} bytes")
382
}
383
384
is Frame.Close -> {
385
val reason = frame.readReason()
386
println("Connection closed: ${reason?.code} - ${reason?.message}")
387
break
388
}
389
390
is Frame.Ping -> {
391
// Respond to ping
392
outgoing.send(Frame.Pong(frame.data))
393
println("Responded to ping")
394
}
395
396
is Frame.Pong -> {
397
println("Received pong response")
398
}
399
}
400
}
401
}
402
```
403
404
### File Transfer Example
405
406
```kotlin
407
client.webSocket("ws://fileserver.example.com") {
408
// Send file as binary frames
409
val fileBytes = File("large-file.dat").readBytes()
410
val chunkSize = 64 * 1024 // 64KB chunks
411
412
for (i in fileBytes.indices step chunkSize) {
413
val chunk = fileBytes.sliceArray(i until minOf(i + chunkSize, fileBytes.size))
414
val isLast = i + chunkSize >= fileBytes.size
415
416
outgoing.send(Frame.Binary(isLast, chunk))
417
}
418
419
// Wait for acknowledgment
420
val ack = incoming.receive()
421
if (ack is Frame.Text && ack.readText() == "ACK") {
422
println("File transfer completed")
423
}
424
}
425
```
426
427
### JSON Message Protocol
428
429
```kotlin
430
client.webSocket("ws://api.example.com/json") {
431
// Send JSON as text frames
432
val request = JsonObject(mapOf(
433
"action" to JsonPrimitive("subscribe"),
434
"channel" to JsonPrimitive("trades")
435
))
436
437
outgoing.send(Frame.Text(request.toString()))
438
439
// Process JSON responses
440
for (frame in incoming) {
441
when (frame) {
442
is Frame.Text -> {
443
val json = Json.parseToJsonElement(frame.readText())
444
handleJsonMessage(json)
445
}
446
447
is Frame.Close -> break
448
}
449
}
450
}
451
```
452
453
### Extension Bit Usage
454
455
```kotlin
456
client.webSocket("ws://compressed.example.com") {
457
// Send compressed frame (using rsv1 for compression flag)
458
val originalData = "Large text content...".toByteArray()
459
val compressedData = compress(originalData)
460
461
outgoing.send(Frame.Text(
462
fin = true,
463
data = compressedData,
464
rsv1 = true // Indicate compression
465
))
466
467
// Handle compressed incoming frames
468
for (frame in incoming) {
469
when (frame) {
470
is Frame.Text -> {
471
val data = if (frame.rsv1) {
472
decompress(frame.data)
473
} else {
474
frame.data
475
}
476
477
val text = String(data, Charsets.UTF_8)
478
println("Message: $text")
479
}
480
}
481
}
482
}
483
```
484
485
### Custom Protocol Implementation
486
487
```kotlin
488
client.webSocket("ws://custom.example.com") {
489
// Custom protocol: first byte indicates message type
490
fun sendCustomMessage(type: Byte, payload: String) {
491
val data = byteArrayOf(type) + payload.toByteArray()
492
outgoing.send(Frame.Binary(true, data))
493
}
494
495
// Send different message types
496
sendCustomMessage(0x01, "User message")
497
sendCustomMessage(0x02, "System command")
498
499
// Process custom protocol messages
500
for (frame in incoming) {
501
when (frame) {
502
is Frame.Binary -> {
503
val data = frame.readBytes()
504
if (data.isNotEmpty()) {
505
val type = data[0]
506
val payload = String(data, 1, data.size - 1)
507
508
when (type.toInt()) {
509
0x01 -> handleUserMessage(payload)
510
0x02 -> handleSystemCommand(payload)
511
0x03 -> handleErrorMessage(payload)
512
}
513
}
514
}
515
516
is Frame.Close -> break
517
}
518
}
519
}
520
```
521
522
### Graceful Connection Closure
523
524
```kotlin
525
client.webSocket("ws://api.example.com") {
526
try {
527
// Normal operation
528
for (frame in incoming) {
529
when (frame) {
530
is Frame.Text -> processMessage(frame.readText())
531
is Frame.Close -> {
532
val reason = frame.readReason()
533
println("Server closed connection: ${reason?.message}")
534
break
535
}
536
}
537
}
538
} finally {
539
// Send close frame before terminating
540
try {
541
outgoing.send(Frame.Close())
542
} catch (e: Exception) {
543
// Channel might be already closed
544
}
545
}
546
}
547
```