or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

content-serialization.mdindex.mdplugin-configuration.mdraw-websocket-operations.mdsession-operations.mdwebsocket-connections.md

raw-websocket-operations.mddocs/

0

# Raw WebSocket Operations (CIO Engine)

1

2

Ktor Client WebSockets provides raw WebSocket operations for the CIO engine that bypass automatic ping-pong processing and provide direct, low-level frame access.

3

4

**Note**: Raw WebSocket operations are only available with the CIO client engine and provide no automatic ping-pong or service message handling.

5

6

## Raw Connection Functions

7

8

### webSocketRawSession() - Create Raw Session

9

10

Creates a raw WebSocket session without automatic ping-pong processing:

11

12

```kotlin { .api }

13

suspend fun HttpClient.webSocketRawSession(

14

method: HttpMethod = HttpMethod.Get,

15

host: String? = null,

16

port: Int? = null,

17

path: String? = null,

18

block: HttpRequestBuilder.() -> Unit = {}

19

): ClientWebSocketSession

20

```

21

22

**Parameters:**

23

- **method**: HTTP method for WebSocket handshake (typically GET)

24

- **host**: Target host for WebSocket connection

25

- **port**: Target port (optional, defaults based on protocol)

26

- **path**: WebSocket endpoint path

27

- **block**: HTTP request configuration block

28

29

**Returns:**

30

- ClientWebSocketSession without automatic ping-pong handling

31

32

**Usage:**

33

```kotlin

34

import io.ktor.client.*

35

import io.ktor.client.engine.cio.*

36

import io.ktor.client.plugins.websocket.*

37

import io.ktor.client.plugins.websocket.cio.*

38

39

val client = HttpClient(CIO) {

40

install(WebSockets)

41

}

42

43

val rawSession = client.webSocketRawSession(

44

host = "echo.websocket.org",

45

port = 80,

46

path = "/"

47

)

48

49

try {

50

// Manual ping-pong control

51

rawSession.send(Frame.Ping("manual-ping".toByteArray()))

52

53

// Direct frame access

54

val frame = rawSession.incoming.receive()

55

println("Raw frame: $frame")

56

} finally {

57

rawSession.close()

58

}

59

```

60

61

### webSocketRaw() - Raw Session with Block

62

63

Establishes a raw WebSocket connection and executes a block with the session:

64

65

```kotlin { .api }

66

suspend fun HttpClient.webSocketRaw(

67

method: HttpMethod = HttpMethod.Get,

68

host: String? = null,

69

port: Int? = null,

70

path: String? = null,

71

request: HttpRequestBuilder.() -> Unit = {},

72

block: suspend ClientWebSocketSession.() -> Unit

73

)

74

```

75

76

**Usage:**

77

```kotlin

78

client.webSocketRaw(

79

host = "echo.websocket.org",

80

port = 80,

81

path = "/"

82

) {

83

// No automatic ping-pong - full control

84

send(Frame.Text("Raw WebSocket message"))

85

86

// Handle all frames manually

87

for (frame in incoming) {

88

when (frame) {

89

is Frame.Text -> println("Text: ${frame.readText()}")

90

is Frame.Binary -> println("Binary: ${frame.data.size} bytes")

91

is Frame.Ping -> {

92

println("Ping received - sending pong manually")

93

send(Frame.Pong(frame.data))

94

}

95

is Frame.Pong -> println("Pong received")

96

is Frame.Close -> {

97

println("Close: ${frame.readReason()}")

98

break

99

}

100

}

101

}

102

}

103

```

104

105

### wsRaw() - Short Alias for Raw WebSocket

106

107

Convenience alias for `webSocketRaw()`:

108

109

```kotlin { .api }

110

suspend fun HttpClient.wsRaw(

111

method: HttpMethod = HttpMethod.Get,

112

host: String? = null,

113

port: Int? = null,

114

path: String? = null,

115

request: HttpRequestBuilder.() -> Unit = {},

116

block: suspend ClientWebSocketSession.() -> Unit

117

)

118

```

119

120

**Usage:**

121

```kotlin

122

client.wsRaw("ws://echo.websocket.org/") {

123

send("Raw message via short alias")

124

val response = incoming.receive()

125

println("Raw response: $response")

126

}

127

```

128

129

### wssRaw() - Secure Raw WebSocket

130

131

Establishes secure raw WebSocket connections over TLS/SSL:

132

133

```kotlin { .api }

134

suspend fun HttpClient.wssRaw(

135

method: HttpMethod = HttpMethod.Get,

136

host: String? = null,

137

port: Int? = null,

138

path: String? = null,

139

request: HttpRequestBuilder.() -> Unit = {},

140

block: suspend ClientWebSocketSession.() -> Unit

141

)

142

```

143

144

**Usage:**

145

```kotlin

146

client.wssRaw(

147

host = "secure.websocket.example.com",

148

port = 443,

149

path = "/raw-endpoint"

150

) {

151

send("Secure raw WebSocket message")

152

153

// Manual ping-pong on secure connection

154

send(Frame.Ping("secure-ping".toByteArray()))

155

156

for (frame in incoming) {

157

when (frame) {

158

is Frame.Pong -> println("Secure pong received")

159

is Frame.Text -> println("Secure text: ${frame.readText()}")

160

is Frame.Close -> break

161

else -> println("Other frame: $frame")

162

}

163

}

164

}

165

```

166

167

## Raw WebSocket Characteristics

168

169

### No Automatic Ping-Pong

170

171

Raw WebSocket sessions do not send automatic ping frames or respond to ping frames:

172

173

```kotlin

174

client.webSocketRaw("ws://example.com/") {

175

// Must handle ping manually

176

launch {

177

while (isActive) {

178

send(Frame.Ping("manual-heartbeat".toByteArray()))

179

delay(30_000) // 30 second interval

180

}

181

}

182

183

// Must respond to pings manually

184

for (frame in incoming) {

185

when (frame) {

186

is Frame.Ping -> {

187

// Manual pong response required

188

send(Frame.Pong(frame.data))

189

}

190

is Frame.Text -> {

191

processMessage(frame.readText())

192

}

193

is Frame.Close -> break

194

else -> { /* Handle other frames */ }

195

}

196

}

197

}

198

```

199

200

### Direct Frame Control

201

202

Full control over all WebSocket frame types:

203

204

```kotlin

205

client.webSocketRaw("ws://example.com/raw") {

206

// Send custom ping with payload

207

send(Frame.Ping("custom-ping-data".toByteArray()))

208

209

// Send fragmented message

210

send(Frame.Text(fin = false, data = "Part 1 ".toByteArray()))

211

send(Frame.Text(fin = false, data = "Part 2 ".toByteArray()))

212

send(Frame.Text(fin = true, data = "Part 3".toByteArray()))

213

214

// Send binary frame with specific flags

215

val binaryData = byteArrayOf(1, 2, 3, 4, 5)

216

send(Frame.Binary(fin = true, data = binaryData))

217

218

// Custom close with specific code

219

send(Frame.Close(CloseReason(CloseReason.Codes.NORMAL, "Custom close")))

220

}

221

```

222

223

### Performance Benefits

224

225

Raw WebSocket operations can provide performance benefits for specific use cases:

226

227

```kotlin

228

client.webSocketRaw("ws://high-frequency.example.com/") {

229

// High-frequency trading or gaming scenarios

230

// No overhead from automatic ping-pong

231

val startTime = System.currentTimeMillis()

232

233

repeat(1000) { i ->

234

send(Frame.Text("Message $i"))

235

val response = incoming.receive()

236

// Process response immediately

237

}

238

239

val elapsed = System.currentTimeMillis() - startTime

240

println("1000 messages in ${elapsed}ms")

241

}

242

```

243

244

## Raw WebSocket Use Cases

245

246

### Custom Heartbeat Implementation

247

248

Implement custom heartbeat logic:

249

250

```kotlin

251

client.webSocketRaw("ws://example.com/custom-heartbeat") {

252

val heartbeatJob = launch {

253

var pingCounter = 0

254

while (isActive) {

255

val pingData = "ping-${++pingCounter}".toByteArray()

256

send(Frame.Ping(pingData))

257

258

// Wait for pong with timeout

259

withTimeoutOrNull(5000) {

260

while (true) {

261

val frame = incoming.receive()

262

if (frame is Frame.Pong && frame.data.contentEquals(pingData)) {

263

break // Pong received

264

}

265

// Handle other frames while waiting for pong

266

handleFrame(frame)

267

}

268

} ?: run {

269

println("Ping timeout - connection may be dead")

270

close(CloseReason(CloseReason.Codes.GOING_AWAY, "Ping timeout"))

271

return@launch

272

}

273

274

delay(10_000) // 10 second heartbeat

275

}

276

}

277

278

try {

279

// Main message processing

280

for (frame in incoming) {

281

when (frame) {

282

is Frame.Text -> processMessage(frame.readText())

283

is Frame.Close -> break

284

else -> { /* Handle in heartbeat logic */ }

285

}

286

}

287

} finally {

288

heartbeatJob.cancel()

289

}

290

}

291

```

292

293

### Protocol Implementation

294

295

Implement custom WebSocket-based protocols:

296

297

```kotlin

298

// Custom protocol with raw frames

299

client.webSocketRaw("ws://protocol.example.com/v1") {

300

// Send protocol version negotiation

301

send(Frame.Binary(true, byteArrayOf(0x01, 0x00))) // Version 1.0

302

303

// Wait for version response

304

val versionFrame = incoming.receive()

305

if (versionFrame is Frame.Binary && versionFrame.data[0] == 0x01.toByte()) {

306

println("Protocol v1.0 negotiated")

307

}

308

309

// Custom frame format: [type:1][length:4][payload:length]

310

fun sendCustomFrame(type: Byte, payload: ByteArray) {

311

val frame = ByteArray(5 + payload.size)

312

frame[0] = type

313

// Write length (big-endian)

314

frame[1] = (payload.size shr 24).toByte()

315

frame[2] = (payload.size shr 16).toByte()

316

frame[3] = (payload.size shr 8).toByte()

317

frame[4] = payload.size.toByte()

318

// Copy payload

319

payload.copyInto(frame, 5)

320

321

send(Frame.Binary(true, frame))

322

}

323

324

// Send custom message

325

sendCustomFrame(0x10, "Hello Custom Protocol".toByteArray())

326

}

327

```

328

329

### Low-Level Debugging

330

331

Debug WebSocket protocol issues:

332

333

```kotlin

334

client.webSocketRaw("ws://debug.example.com/") {

335

// Log all frames

336

launch {

337

for (frame in incoming) {

338

when (frame) {

339

is Frame.Text -> {

340

println("RX Text: ${frame.readText()}")

341

println("RX Text fin=${frame.fin}, len=${frame.data.size}")

342

}

343

is Frame.Binary -> {

344

println("RX Binary: fin=${frame.fin}, len=${frame.data.size}")

345

println("RX Binary data: ${frame.data.joinToString(" ") { "%02x".format(it) }}")

346

}

347

is Frame.Ping -> {

348

println("RX Ping: ${frame.data.joinToString(" ") { "%02x".format(it) }}")

349

// Send pong manually for debugging

350

send(Frame.Pong(frame.data))

351

println("TX Pong: ${frame.data.joinToString(" ") { "%02x".format(it) }}")

352

}

353

is Frame.Pong -> {

354

println("RX Pong: ${frame.data.joinToString(" ") { "%02x".format(it) }}")

355

}

356

is Frame.Close -> {

357

val reason = frame.readReason()

358

println("RX Close: code=${reason?.code}, message='${reason?.message}'")

359

break

360

}

361

}

362

}

363

}

364

365

// Send test frames

366

println("TX Text: Hello")

367

send(Frame.Text("Hello"))

368

369

println("TX Binary: [1,2,3,4]")

370

send(Frame.Binary(true, byteArrayOf(1, 2, 3, 4)))

371

372

println("TX Ping: ping-data")

373

send(Frame.Ping("ping-data".toByteArray()))

374

375

delay(5000) // Let frames exchange

376

377

println("TX Close: Normal")

378

send(Frame.Close(CloseReason(CloseReason.Codes.NORMAL, "Debug complete")))

379

}

380

```

381

382

## Engine Requirements

383

384

Raw WebSocket operations are CIO engine specific:

385

386

```kotlin

387

// Correct - CIO engine supports raw operations

388

val cioClient = HttpClient(CIO) {

389

install(WebSockets)

390

}

391

cioClient.webSocketRaw("ws://example.com/") { /* Works */ }

392

393

// Incorrect - Other engines don't support raw operations

394

val jsClient = HttpClient(Js) {

395

install(WebSockets)

396

}

397

// jsClient.webSocketRaw() // Compilation error - function not available

398

```

399

400

**Engine compatibility:**

401

- **CIO**: Full raw WebSocket support

402

- **JavaScript**: No raw operations available

403

- **Native engines**: No raw operations available

404

- **Java**: No raw operations available (use CIO instead)

405

406

## Error Handling in Raw Mode

407

408

Handle errors without automatic ping-pong safety net:

409

410

```kotlin

411

client.webSocketRaw("ws://unreliable.example.com/") {

412

try {

413

// Manual connection health monitoring

414

var lastFrameTime = System.currentTimeMillis()

415

416

withTimeout(60_000) { // 60 second total timeout

417

for (frame in incoming) {

418

lastFrameTime = System.currentTimeMillis()

419

420

when (frame) {

421

is Frame.Text -> processMessage(frame.readText())

422

is Frame.Ping -> send(Frame.Pong(frame.data))

423

is Frame.Close -> {

424

val reason = frame.readReason()

425

println("Server closed: ${reason?.code} - ${reason?.message}")

426

break

427

}

428

else -> { /* Handle other frames */ }

429

}

430

431

// Check for connection timeout

432

if (System.currentTimeMillis() - lastFrameTime > 30_000) {

433

println("No frames received for 30 seconds")

434

close(CloseReason(CloseReason.Codes.GOING_AWAY, "Timeout"))

435

break

436

}

437

}

438

}

439

} catch (e: TimeoutCancellationException) {

440

println("Raw WebSocket operation timed out")

441

close(CloseReason(CloseReason.Codes.GOING_AWAY, "Client timeout"))

442

} catch (e: Exception) {

443

println("Raw WebSocket error: ${e.message}")

444

close(CloseReason(CloseReason.Codes.INTERNAL_ERROR, "Client error"))

445

}

446

}

447

```