or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

asyncio-client.mdasyncio-server.mddata-structures.mdexceptions.mdextensions.mdindex.mdprotocol.mdrouting.mdsync-client.mdsync-server.md

exceptions.mddocs/

0

# Exception Handling

1

2

Comprehensive exception hierarchy for precise error handling covering connection lifecycle, protocol violations, handshake failures, validation errors, and network issues in WebSocket operations.

3

4

## Capabilities

5

6

### Base Exception Classes

7

8

Foundation exception classes that serve as the root of the WebSocket exception hierarchy.

9

10

```python { .api }

11

class WebSocketException(Exception):

12

"""

13

Base class for all WebSocket-specific exceptions.

14

15

All other WebSocket exceptions inherit from this class,

16

allowing catch-all exception handling for WebSocket operations.

17

"""

18

19

def __init__(self, message: str = None):

20

"""

21

Initialize WebSocket exception.

22

23

Parameters:

24

- message: Human-readable error description

25

"""

26

super().__init__(message)

27

```

28

29

### Connection Lifecycle Exceptions

30

31

Exceptions related to WebSocket connection establishment, maintenance, and closure.

32

33

```python { .api }

34

class ConnectionClosed(WebSocketException):

35

"""

36

Base exception for closed WebSocket connections.

37

38

Raised when attempting operations on a closed connection

39

or when connection closure is detected.

40

"""

41

42

def __init__(self, rcvd: Close = None, sent: Close = None):

43

"""

44

Initialize connection closed exception.

45

46

Parameters:

47

- rcvd: Close frame received from peer (if any)

48

- sent: Close frame sent to peer (if any)

49

"""

50

self.rcvd = rcvd

51

self.sent = sent

52

53

if rcvd:

54

super().__init__(f"Connection closed: {rcvd.code} {rcvd.reason}")

55

else:

56

super().__init__("Connection closed")

57

58

@property

59

def code(self) -> int | None:

60

"""Get close code from received close frame."""

61

return self.rcvd.code if self.rcvd else None

62

63

@property

64

def reason(self) -> str | None:

65

"""Get close reason from received close frame."""

66

return self.rcvd.reason if self.rcvd else None

67

68

class ConnectionClosedOK(ConnectionClosed):

69

"""

70

Normal WebSocket connection closure (close code 1000).

71

72

Raised when connection is closed normally without errors,

73

typically when client or server explicitly closes connection.

74

"""

75

pass

76

77

class ConnectionClosedError(ConnectionClosed):

78

"""

79

Abnormal WebSocket connection closure (close code != 1000).

80

81

Raised when connection is closed due to errors, protocol violations,

82

or unexpected network conditions.

83

"""

84

pass

85

```

86

87

### Handshake Exceptions

88

89

Exceptions that occur during the WebSocket handshake process, including validation and negotiation failures.

90

91

```python { .api }

92

class InvalidHandshake(WebSocketException):

93

"""

94

Base exception for WebSocket handshake failures.

95

96

Raised when handshake process fails due to invalid headers,

97

unsupported protocols, or security policy violations.

98

"""

99

pass

100

101

class SecurityError(InvalidHandshake):

102

"""

103

Security policy violation during handshake.

104

105

Raised when connection attempt violates security policies

106

such as origin restrictions or certificate validation.

107

"""

108

pass

109

110

class InvalidMessage(InvalidHandshake):

111

"""

112

Malformed handshake message.

113

114

Raised when HTTP handshake message is malformed or

115

doesn't conform to WebSocket upgrade requirements.

116

"""

117

pass

118

119

class InvalidStatus(InvalidHandshake):

120

"""

121

WebSocket upgrade rejection.

122

123

Raised when server rejects WebSocket upgrade request

124

with non-101 HTTP status code.

125

"""

126

127

def __init__(self, status_code: int, headers: Headers = None):

128

"""

129

Initialize invalid status exception.

130

131

Parameters:

132

- status_code: HTTP status code received

133

- headers: HTTP response headers

134

"""

135

self.status_code = status_code

136

self.headers = headers or Headers()

137

super().__init__(f"HTTP {status_code}")

138

139

class InvalidHeader(InvalidHandshake):

140

"""

141

Invalid HTTP header in handshake.

142

143

Raised when handshake contains invalid or missing

144

required HTTP headers.

145

"""

146

147

def __init__(self, name: str, value: str = None):

148

"""

149

Initialize invalid header exception.

150

151

Parameters:

152

- name: Header name that is invalid

153

- value: Header value (if applicable)

154

"""

155

self.name = name

156

self.value = value

157

158

if value:

159

super().__init__(f"Invalid header {name}: {value}")

160

else:

161

super().__init__(f"Invalid header: {name}")

162

163

class InvalidHeaderFormat(InvalidHeader):

164

"""

165

Header parsing failure.

166

167

Raised when HTTP header cannot be parsed due to

168

incorrect format or encoding issues.

169

"""

170

pass

171

172

class InvalidHeaderValue(InvalidHeader):

173

"""

174

Semantically invalid header value.

175

176

Raised when header value is syntactically valid but

177

semantically incorrect for WebSocket handshake.

178

"""

179

pass

180

181

class InvalidOrigin(InvalidHandshake):

182

"""

183

Disallowed origin header.

184

185

Raised when Origin header doesn't match server's

186

allowed origins policy.

187

"""

188

189

def __init__(self, origin: str):

190

"""

191

Initialize invalid origin exception.

192

193

Parameters:

194

- origin: The disallowed origin value

195

"""

196

self.origin = origin

197

super().__init__(f"Invalid origin: {origin}")

198

199

class InvalidUpgrade(InvalidHandshake):

200

"""

201

Incorrect WebSocket upgrade headers.

202

203

Raised when required WebSocket upgrade headers

204

are missing or have incorrect values.

205

"""

206

pass

207

208

class NegotiationError(InvalidHandshake):

209

"""

210

Extension or subprotocol negotiation failure.

211

212

Raised when client and server cannot agree on

213

WebSocket extensions or subprotocols.

214

"""

215

pass

216

```

217

218

### Extension and Parameter Exceptions

219

220

Exceptions related to WebSocket extension processing and parameter validation.

221

222

```python { .api }

223

class DuplicateParameter(InvalidHandshake):

224

"""

225

Duplicate extension parameter.

226

227

Raised when WebSocket extension contains

228

duplicate parameter names.

229

"""

230

231

def __init__(self, name: str):

232

"""

233

Initialize duplicate parameter exception.

234

235

Parameters:

236

- name: Parameter name that is duplicated

237

"""

238

self.name = name

239

super().__init__(f"Duplicate parameter: {name}")

240

241

class InvalidParameterName(InvalidHandshake):

242

"""

243

Invalid extension parameter name.

244

245

Raised when extension parameter name doesn't

246

conform to specification requirements.

247

"""

248

249

def __init__(self, name: str):

250

"""

251

Initialize invalid parameter name exception.

252

253

Parameters:

254

- name: Invalid parameter name

255

"""

256

self.name = name

257

super().__init__(f"Invalid parameter name: {name}")

258

259

class InvalidParameterValue(InvalidHandshake):

260

"""

261

Invalid extension parameter value.

262

263

Raised when extension parameter value is

264

invalid for the specific parameter.

265

"""

266

267

def __init__(self, name: str, value: str):

268

"""

269

Initialize invalid parameter value exception.

270

271

Parameters:

272

- name: Parameter name

273

- value: Invalid parameter value

274

"""

275

self.name = name

276

self.value = value

277

super().__init__(f"Invalid parameter value {name}: {value}")

278

```

279

280

### Network and URI Exceptions

281

282

Exceptions related to network connectivity, proxy handling, and URI validation.

283

284

```python { .api }

285

class InvalidURI(WebSocketException):

286

"""

287

Invalid WebSocket URI format.

288

289

Raised when WebSocket URI is malformed or

290

uses unsupported scheme or format.

291

"""

292

293

def __init__(self, uri: str, reason: str = None):

294

"""

295

Initialize invalid URI exception.

296

297

Parameters:

298

- uri: The invalid URI

299

- reason: Specific reason for invalidity

300

"""

301

self.uri = uri

302

self.reason = reason

303

304

if reason:

305

super().__init__(f"Invalid URI {uri}: {reason}")

306

else:

307

super().__init__(f"Invalid URI: {uri}")

308

309

class ProxyError(InvalidHandshake):

310

"""

311

Proxy connection failure.

312

313

Raised when connection through HTTP/SOCKS proxy

314

fails or proxy rejects the request.

315

"""

316

pass

317

318

class InvalidProxy(ProxyError):

319

"""

320

Invalid proxy configuration.

321

322

Raised when proxy settings are malformed or

323

proxy server information is invalid.

324

"""

325

pass

326

327

class InvalidProxyMessage(ProxyError):

328

"""

329

Malformed proxy response.

330

331

Raised when proxy server returns malformed

332

HTTP response during connection establishment.

333

"""

334

pass

335

336

class InvalidProxyStatus(ProxyError):

337

"""

338

Proxy rejection or error status.

339

340

Raised when proxy server rejects connection

341

request with error status code.

342

"""

343

344

def __init__(self, status_code: int, reason: str = None):

345

"""

346

Initialize invalid proxy status exception.

347

348

Parameters:

349

- status_code: HTTP status code from proxy

350

- reason: Optional reason phrase

351

"""

352

self.status_code = status_code

353

self.reason = reason

354

355

if reason:

356

super().__init__(f"Proxy error {status_code}: {reason}")

357

else:

358

super().__init__(f"Proxy error: {status_code}")

359

```

360

361

### Protocol Violations

362

363

Exceptions for WebSocket protocol violations and operational errors.

364

365

```python { .api }

366

class ProtocolError(WebSocketException):

367

"""

368

WebSocket protocol violation.

369

370

Raised when received data violates WebSocket protocol

371

specification or when invalid operations are attempted.

372

"""

373

pass

374

375

class PayloadTooBig(ProtocolError):

376

"""

377

Message exceeds size limits.

378

379

Raised when received message or frame exceeds

380

configured maximum size limits.

381

"""

382

383

def __init__(self, size: int, max_size: int):

384

"""

385

Initialize payload too big exception.

386

387

Parameters:

388

- size: Actual payload size

389

- max_size: Maximum allowed size

390

"""

391

self.size = size

392

self.max_size = max_size

393

super().__init__(f"Payload too big: {size} > {max_size}")

394

395

class InvalidState(ProtocolError):

396

"""

397

Invalid operation for current connection state.

398

399

Raised when attempting operations that are not

400

valid for the current WebSocket connection state.

401

"""

402

403

def __init__(self, operation: str, state: str):

404

"""

405

Initialize invalid state exception.

406

407

Parameters:

408

- operation: Operation that was attempted

409

- state: Current connection state

410

"""

411

self.operation = operation

412

self.state = state

413

super().__init__(f"Cannot {operation} in state {state}")

414

415

class ConcurrencyError(ProtocolError):

416

"""

417

Concurrent read/write operations detected.

418

419

Raised when multiple coroutines attempt to read from

420

or write to the same WebSocket connection simultaneously.

421

"""

422

423

def __init__(self, operation: str):

424

"""

425

Initialize concurrency error exception.

426

427

Parameters:

428

- operation: Operation that detected concurrency issue

429

"""

430

self.operation = operation

431

super().__init__(f"Concurrent {operation} operations")

432

```

433

434

## Usage Examples

435

436

### Basic Exception Handling

437

438

```python

439

import asyncio

440

from websockets import connect, ConnectionClosed, InvalidURI, ProtocolError

441

442

async def basic_exception_handling():

443

"""Demonstrate basic WebSocket exception handling."""

444

try:

445

async with connect("ws://localhost:8765") as websocket:

446

# Send and receive messages

447

await websocket.send("Hello, Server!")

448

response = await websocket.recv()

449

print(f"Received: {response}")

450

451

except ConnectionClosed as e:

452

print(f"Connection closed: code={e.code}, reason={e.reason}")

453

454

# Check if closure was normal or error

455

if isinstance(e, ConnectionClosedOK):

456

print("Connection closed normally")

457

elif isinstance(e, ConnectionClosedError):

458

print("Connection closed with error")

459

460

except InvalidURI as e:

461

print(f"Invalid WebSocket URI: {e.uri}")

462

if e.reason:

463

print(f"Reason: {e.reason}")

464

465

except ProtocolError as e:

466

print(f"Protocol error: {e}")

467

468

except Exception as e:

469

print(f"Unexpected error: {e}")

470

471

asyncio.run(basic_exception_handling())

472

```

473

474

### Handshake Exception Handling

475

476

```python

477

import asyncio

478

from websockets import connect

479

from websockets import (

480

InvalidHandshake, SecurityError, InvalidStatus, InvalidOrigin,

481

NegotiationError, InvalidHeader

482

)

483

484

async def handshake_exception_handling():

485

"""Handle various handshake failures."""

486

test_cases = [

487

("ws://localhost:8765", None), # Normal case

488

("ws://invalid-server:9999", "Connection failed"),

489

("wss://expired-cert.example.com", "Certificate error"),

490

]

491

492

for uri, expected_error in test_cases:

493

try:

494

print(f"Connecting to {uri}...")

495

496

async with connect(

497

uri,

498

additional_headers={"Origin": "https://myapp.com"},

499

subprotocols=["chat", "notifications"],

500

open_timeout=5

501

) as websocket:

502

await websocket.send("Connection successful")

503

response = await websocket.recv()

504

print(f"Success: {response}")

505

506

except SecurityError as e:

507

print(f"Security error: {e}")

508

509

except InvalidStatus as e:

510

print(f"Server rejected connection: HTTP {e.status_code}")

511

print(f"Response headers: {dict(e.headers)}")

512

513

except InvalidOrigin as e:

514

print(f"Origin rejected: {e.origin}")

515

516

except InvalidHeader as e:

517

print(f"Invalid header: {e.name} = {e.value}")

518

519

except NegotiationError as e:

520

print(f"Failed to negotiate extensions/subprotocols: {e}")

521

522

except InvalidHandshake as e:

523

print(f"Handshake failed: {e}")

524

525

except Exception as e:

526

print(f"Other error: {e}")

527

528

print()

529

530

asyncio.run(handshake_exception_handling())

531

```

532

533

### Connection State Exception Handling

534

535

```python

536

import asyncio

537

from websockets import connect, InvalidState, ConcurrencyError, PayloadTooBig

538

539

async def state_exception_handling():

540

"""Handle connection state and operational errors."""

541

try:

542

async with connect(

543

"ws://localhost:8765",

544

max_size=1024 # 1KB max message size

545

) as websocket:

546

547

# Test large message (should raise PayloadTooBig)

548

try:

549

large_message = "x" * 2048 # 2KB message

550

await websocket.send(large_message)

551

except PayloadTooBig as e:

552

print(f"Message too large: {e.size} > {e.max_size}")

553

554

# Test concurrent operations

555

try:

556

# This would cause concurrency error if attempted

557

# await asyncio.gather(

558

# websocket.recv(),

559

# websocket.recv() # Concurrent recv operations

560

# )

561

pass

562

except ConcurrencyError as e:

563

print(f"Concurrency error: {e.operation}")

564

565

# Normal operation

566

await websocket.send("Hello")

567

response = await websocket.recv()

568

print(f"Normal operation: {response}")

569

570

# Test operation after close

571

await websocket.close()

572

573

try:

574

await websocket.send("This should fail")

575

except InvalidState as e:

576

print(f"Invalid state: {e.operation} in {e.state}")

577

578

except Exception as e:

579

print(f"Connection error: {e}")

580

581

asyncio.run(state_exception_handling())

582

```

583

584

### Robust Error Recovery

585

586

```python

587

import asyncio

588

import logging

589

from websockets import connect, WebSocketException, ConnectionClosed

590

591

# Set up logging

592

logging.basicConfig(level=logging.INFO)

593

logger = logging.getLogger(__name__)

594

595

async def robust_client_with_recovery():

596

"""Client with comprehensive error handling and recovery."""

597

uri = "ws://localhost:8765"

598

max_retries = 3

599

retry_delay = 1

600

601

for attempt in range(max_retries):

602

try:

603

logger.info(f"Connection attempt {attempt + 1}")

604

605

async with connect(uri, ping_interval=20, ping_timeout=10) as websocket:

606

logger.info("Connected successfully")

607

608

# Main message loop with error recovery

609

message_count = 0

610

error_count = 0

611

612

while error_count < 5: # Max 5 consecutive errors

613

try:

614

# Send periodic messages

615

message = f"Message {message_count}"

616

await websocket.send(message)

617

message_count += 1

618

619

# Wait for response with timeout

620

try:

621

response = await asyncio.wait_for(

622

websocket.recv(),

623

timeout=30

624

)

625

logger.info(f"Received: {response}")

626

error_count = 0 # Reset error count on success

627

628

except asyncio.TimeoutError:

629

logger.warning("Response timeout")

630

error_count += 1

631

continue

632

633

# Wait before next message

634

await asyncio.sleep(5)

635

636

except ConnectionClosed as e:

637

logger.warning(f"Connection closed: {e.code} {e.reason}")

638

break

639

640

except WebSocketException as e:

641

logger.error(f"WebSocket error: {e}")

642

error_count += 1

643

644

if error_count < 5:

645

await asyncio.sleep(1) # Brief pause before retry

646

647

except Exception as e:

648

logger.error(f"Unexpected error: {e}")

649

error_count += 1

650

651

if error_count >= 5:

652

logger.error("Too many consecutive errors, giving up")

653

break

654

655

except WebSocketException as e:

656

logger.error(f"WebSocket connection failed: {e}")

657

658

except Exception as e:

659

logger.error(f"Unexpected connection error: {e}")

660

661

# Retry logic

662

if attempt < max_retries - 1:

663

logger.info(f"Retrying in {retry_delay} seconds...")

664

await asyncio.sleep(retry_delay)

665

retry_delay *= 2 # Exponential backoff

666

else:

667

logger.error("Max retries exceeded")

668

669

asyncio.run(robust_client_with_recovery())

670

```

671

672

### Exception Classification and Logging

673

674

```python

675

import asyncio

676

import logging

677

from websockets import connect

678

from websockets import (

679

WebSocketException, ConnectionClosed, InvalidHandshake, ProtocolError,

680

SecurityError, InvalidURI, ProxyError

681

)

682

683

class WebSocketErrorHandler:

684

"""Centralized WebSocket error handling and logging."""

685

686

def __init__(self):

687

self.logger = logging.getLogger(self.__class__.__name__)

688

689

def classify_error(self, exception: Exception) -> str:

690

"""Classify exception into error categories."""

691

if isinstance(exception, ConnectionClosed):

692

return "connection_closed"

693

elif isinstance(exception, SecurityError):

694

return "security_error"

695

elif isinstance(exception, InvalidHandshake):

696

return "handshake_error"

697

elif isinstance(exception, ProtocolError):

698

return "protocol_error"

699

elif isinstance(exception, ProxyError):

700

return "proxy_error"

701

elif isinstance(exception, InvalidURI):

702

return "uri_error"

703

elif isinstance(exception, WebSocketException):

704

return "websocket_error"

705

else:

706

return "unknown_error"

707

708

def should_retry(self, exception: Exception) -> bool:

709

"""Determine if operation should be retried."""

710

category = self.classify_error(exception)

711

712

# Don't retry on these error types

713

no_retry_categories = {

714

"security_error", "uri_error", "handshake_error"

715

}

716

717

return category not in no_retry_categories

718

719

def log_error(self, exception: Exception, context: str = ""):

720

"""Log error with appropriate level and details."""

721

category = self.classify_error(exception)

722

723

if category == "connection_closed":

724

if isinstance(exception, ConnectionClosedOK):

725

self.logger.info(f"{context}: Connection closed normally")

726

else:

727

self.logger.warning(f"{context}: {exception}")

728

729

elif category in ["security_error", "protocol_error"]:

730

self.logger.error(f"{context}: {category}: {exception}")

731

732

elif category in ["handshake_error", "proxy_error"]:

733

self.logger.warning(f"{context}: {category}: {exception}")

734

735

else:

736

self.logger.error(f"{context}: {category}: {exception}")

737

738

async def error_handling_example():

739

"""Demonstrate centralized error handling."""

740

error_handler = WebSocketErrorHandler()

741

742

test_uris = [

743

"ws://localhost:8765",

744

"ws://invalid-host:9999",

745

"wss://expired-cert.example.com",

746

"invalid-uri",

747

]

748

749

for uri in test_uris:

750

try:

751

context = f"Connecting to {uri}"

752

753

async with connect(uri, open_timeout=5) as websocket:

754

await websocket.send("Test message")

755

response = await websocket.recv()

756

print(f"Success: {response}")

757

758

except Exception as e:

759

error_handler.log_error(e, context)

760

761

if error_handler.should_retry(e):

762

print(f"Error is retryable: {error_handler.classify_error(e)}")

763

else:

764

print(f"Error is not retryable: {error_handler.classify_error(e)}")

765

766

# Set up logging

767

logging.basicConfig(

768

level=logging.INFO,

769

format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'

770

)

771

772

asyncio.run(error_handling_example())

773

```