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

protocol.mddocs/

0

# Protocol Implementation

1

2

Sans-I/O WebSocket protocol implementation providing the core WebSocket functionality independent of I/O handling. This enables custom integrations, advanced use cases, and provides the foundation for both asyncio and synchronous implementations.

3

4

## Capabilities

5

6

### Protocol Base Classes

7

8

Core protocol implementation that handles WebSocket frame processing, state management, and message assembly without performing any I/O operations.

9

10

```python { .api }

11

class Protocol:

12

"""

13

Base WebSocket protocol implementation (Sans-I/O).

14

15

Handles WebSocket frame processing, connection state management,

16

and message assembly/disassembly without performing I/O operations.

17

"""

18

19

def __init__(

20

self,

21

side: Side,

22

*,

23

logger: LoggerLike = None,

24

max_size: int = 2**20,

25

extensions: List[ExtensionFactory] = None

26

):

27

"""

28

Initialize protocol handler.

29

30

Parameters:

31

- side: Side.CLIENT or Side.SERVER

32

- logger: Logger instance for protocol logging

33

- max_size: Maximum message size (bytes)

34

- extensions: List of WebSocket extensions

35

"""

36

37

@property

38

def state(self) -> State:

39

"""Get current connection state."""

40

41

@property

42

def side(self) -> Side:

43

"""Get protocol side (CLIENT or SERVER)."""

44

45

@property

46

def close_code(self) -> int | None:

47

"""Get close code if connection is closed."""

48

49

@property

50

def close_reason(self) -> str | None:

51

"""Get close reason if connection is closed."""

52

53

def send_text(self, data: str) -> List[bytes]:

54

"""

55

Create frames for sending text message.

56

57

Parameters:

58

- data: Text message to send

59

60

Returns:

61

List[bytes]: List of frame bytes to transmit

62

63

Raises:

64

- ProtocolError: If connection state doesn't allow sending

65

"""

66

67

def send_binary(self, data: bytes) -> List[bytes]:

68

"""

69

Create frames for sending binary message.

70

71

Parameters:

72

- data: Binary message to send

73

74

Returns:

75

List[bytes]: List of frame bytes to transmit

76

77

Raises:

78

- ProtocolError: If connection state doesn't allow sending

79

"""

80

81

def send_ping(self, data: bytes = b"") -> List[bytes]:

82

"""

83

Create ping frame.

84

85

Parameters:

86

- data: Optional ping payload

87

88

Returns:

89

List[bytes]: List of frame bytes to transmit

90

91

Raises:

92

- ProtocolError: If connection state doesn't allow ping

93

"""

94

95

def send_pong(self, data: bytes = b"") -> List[bytes]:

96

"""

97

Create pong frame.

98

99

Parameters:

100

- data: Pong payload (should match received ping)

101

102

Returns:

103

List[bytes]: List of frame bytes to transmit

104

105

Raises:

106

- ProtocolError: If connection state doesn't allow pong

107

"""

108

109

def send_close(self, code: int = 1000, reason: str = "") -> List[bytes]:

110

"""

111

Create close frame and update connection state.

112

113

Parameters:

114

- code: Close code (1000 for normal closure)

115

- reason: Human-readable close reason

116

117

Returns:

118

List[bytes]: List of frame bytes to transmit

119

120

Raises:

121

- ProtocolError: If close code is invalid

122

"""

123

124

def receive_data(self, data: bytes) -> List[Event]:

125

"""

126

Process received data and return events.

127

128

Parameters:

129

- data: Raw bytes received from network

130

131

Returns:

132

List[Event]: List of protocol events (messages, pings, pongs, close)

133

134

Raises:

135

- ProtocolError: If data violates WebSocket protocol

136

"""

137

138

def receive_eof(self) -> List[Event]:

139

"""

140

Handle end-of-file condition.

141

142

Returns:

143

List[Event]: List of protocol events (typically ConnectionClosed)

144

"""

145

146

class ClientProtocol(Protocol):

147

"""

148

WebSocket client protocol implementation.

149

150

Handles client-specific protocol behavior including handshake validation

151

and client-side frame masking.

152

"""

153

154

def __init__(

155

self,

156

*,

157

logger: LoggerLike = None,

158

max_size: int = 2**20,

159

extensions: List[ClientExtensionFactory] = None

160

):

161

"""

162

Initialize client protocol.

163

164

Parameters:

165

- logger: Logger instance for protocol logging

166

- max_size: Maximum message size (bytes)

167

- extensions: List of client WebSocket extensions

168

"""

169

170

class ServerProtocol(Protocol):

171

"""

172

WebSocket server protocol implementation.

173

174

Handles server-specific protocol behavior including handshake processing

175

and server-side frame handling (no masking).

176

"""

177

178

def __init__(

179

self,

180

*,

181

logger: LoggerLike = None,

182

max_size: int = 2**20,

183

extensions: List[ServerExtensionFactory] = None

184

):

185

"""

186

Initialize server protocol.

187

188

Parameters:

189

- logger: Logger instance for protocol logging

190

- max_size: Maximum message size (bytes)

191

- extensions: List of server WebSocket extensions

192

"""

193

```

194

195

### Protocol State Management

196

197

Enumerations and utilities for managing WebSocket connection state and side identification.

198

199

```python { .api }

200

class Side(Enum):

201

"""WebSocket connection side identification."""

202

CLIENT = "client"

203

SERVER = "server"

204

205

class State(Enum):

206

"""WebSocket connection states."""

207

CONNECTING = "connecting" # Initial state during handshake

208

OPEN = "open" # Connection established and ready

209

CLOSING = "closing" # Close frame sent, waiting for close response

210

CLOSED = "closed" # Connection fully closed

211

```

212

213

### Protocol Events

214

215

Event types returned by the protocol when processing received data.

216

217

```python { .api }

218

class Event:

219

"""Base class for protocol events."""

220

pass

221

222

class TextMessage(Event):

223

"""Text message received event."""

224

def __init__(self, data: str):

225

self.data = data

226

227

class BinaryMessage(Event):

228

"""Binary message received event."""

229

def __init__(self, data: bytes):

230

self.data = data

231

232

class Ping(Event):

233

"""Ping frame received event."""

234

def __init__(self, data: bytes):

235

self.data = data

236

237

class Pong(Event):

238

"""Pong frame received event."""

239

def __init__(self, data: bytes):

240

self.data = data

241

242

class Close(Event):

243

"""Close frame received event."""

244

def __init__(self, code: int, reason: str):

245

self.code = code

246

self.reason = reason

247

248

class ConnectionClosed(Event):

249

"""Connection closed event (EOF or error)."""

250

def __init__(self, exception: Exception = None):

251

self.exception = exception

252

```

253

254

## Usage Examples

255

256

### Basic Protocol Usage

257

258

```python

259

from websockets.protocol import ClientProtocol, Side, State

260

from websockets.frames import Frame, Opcode

261

262

def basic_protocol_example():

263

"""Demonstrate basic protocol usage."""

264

# Create client protocol

265

protocol = ClientProtocol(max_size=1024*1024) # 1MB max message

266

267

print(f"Initial state: {protocol.state}")

268

print(f"Protocol side: {protocol.side}")

269

270

# Send a text message

271

frames_to_send = protocol.send_text("Hello, WebSocket!")

272

print(f"Frames to send: {len(frames_to_send)}")

273

274

# Send a ping

275

ping_frames = protocol.send_ping(b"ping-data")

276

print(f"Ping frames: {len(ping_frames)}")

277

278

# Simulate receiving data (this would come from network)

279

# In real usage, you'd receive actual bytes from socket

280

received_data = b"..." # Raw frame bytes

281

# events = protocol.receive_data(received_data)

282

283

# Close connection

284

close_frames = protocol.send_close(1000, "Normal closure")

285

print(f"Close frames: {len(close_frames)}")

286

print(f"Final state: {protocol.state}")

287

288

basic_protocol_example()

289

```

290

291

### Custom Protocol Integration

292

293

```python

294

import socket

295

from websockets.protocol import ServerProtocol

296

from websockets.exceptions import ProtocolError

297

298

class CustomWebSocketServer:

299

"""Example of custom WebSocket server using Sans-I/O protocol."""

300

301

def __init__(self, host: str, port: int):

302

self.host = host

303

self.port = port

304

self.socket = None

305

306

def start(self):

307

"""Start the custom server."""

308

self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

309

self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

310

self.socket.bind((self.host, self.port))

311

self.socket.listen(5)

312

313

print(f"Custom WebSocket server listening on {self.host}:{self.port}")

314

315

while True:

316

try:

317

client_socket, address = self.socket.accept()

318

print(f"Client connected: {address}")

319

320

# Handle client in separate method

321

self.handle_client(client_socket, address)

322

323

except KeyboardInterrupt:

324

break

325

except Exception as e:

326

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

327

328

self.socket.close()

329

330

def handle_client(self, client_socket: socket.socket, address):

331

"""Handle individual client connection."""

332

protocol = ServerProtocol(max_size=64*1024) # 64KB max message

333

334

try:

335

while True:

336

# Receive data from client

337

data = client_socket.recv(4096)

338

if not data:

339

# Client disconnected

340

events = protocol.receive_eof()

341

break

342

343

# Process data through protocol

344

try:

345

events = protocol.receive_data(data)

346

except ProtocolError as e:

347

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

348

break

349

350

# Handle protocol events

351

for event in events:

352

response_frames = self.process_event(event, protocol)

353

354

# Send response frames

355

for frame_bytes in response_frames:

356

client_socket.send(frame_bytes)

357

358

# Check if connection should close

359

if protocol.state == State.CLOSED:

360

break

361

362

except Exception as e:

363

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

364

finally:

365

client_socket.close()

366

print(f"Client disconnected: {address}")

367

368

def process_event(self, event, protocol):

369

"""Process protocol events and return response frames."""

370

from websockets.protocol import TextMessage, BinaryMessage, Ping, Close

371

372

if isinstance(event, TextMessage):

373

# Echo text messages

374

return protocol.send_text(f"Echo: {event.data}")

375

376

elif isinstance(event, BinaryMessage):

377

# Echo binary messages

378

return protocol.send_binary(b"Echo: " + event.data)

379

380

elif isinstance(event, Ping):

381

# Respond to pings with pongs

382

return protocol.send_pong(event.data)

383

384

elif isinstance(event, Close):

385

# Respond to close frames

386

return protocol.send_close(event.code, event.reason)

387

388

return []

389

390

# Example usage

391

def run_custom_server():

392

server = CustomWebSocketServer("localhost", 8765)

393

try:

394

server.start()

395

except KeyboardInterrupt:

396

print("\nServer stopped")

397

398

# Uncomment to run

399

# run_custom_server()

400

```

401

402

### Protocol State Machine

403

404

```python

405

from websockets.protocol import ClientProtocol, State

406

from websockets.exceptions import ProtocolError

407

408

def protocol_state_machine_example():

409

"""Demonstrate protocol state transitions."""

410

protocol = ClientProtocol()

411

412

def print_state():

413

print(f"Current state: {protocol.state}")

414

415

print("=== Protocol State Machine Example ===")

416

417

# Initial state

418

print_state() # Should be CONNECTING initially

419

420

# Simulate successful handshake (this would be done by connection layer)

421

# protocol._state = State.OPEN # Private attribute, don't do this in real code

422

423

try:

424

# Try to send message in OPEN state

425

frames = protocol.send_text("Hello")

426

print(f"Sent text message: {len(frames)} frames")

427

print_state()

428

429

# Send ping

430

ping_frames = protocol.send_ping(b"test")

431

print(f"Sent ping: {len(ping_frames)} frames")

432

print_state()

433

434

# Start closing

435

close_frames = protocol.send_close(1000, "Normal closure")

436

print(f"Sent close frame: {len(close_frames)} frames")

437

print_state() # Should be CLOSING

438

439

# Try to send after close (should fail)

440

try:

441

protocol.send_text("This should fail")

442

except ProtocolError as e:

443

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

444

445

# Simulate receiving close response

446

# In real usage, this would come from receive_data()

447

# protocol._state = State.CLOSED

448

print_state()

449

450

except ProtocolError as e:

451

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

452

453

protocol_state_machine_example()

454

```

455

456

### Frame Processing Example

457

458

```python

459

from websockets.protocol import ServerProtocol

460

from websockets.frames import Frame, Opcode

461

import struct

462

463

def frame_processing_example():

464

"""Demonstrate low-level frame processing."""

465

protocol = ServerProtocol()

466

467

# Create a simple text frame manually (for demonstration)

468

message = "Hello, WebSocket!"

469

message_bytes = message.encode('utf-8')

470

471

# WebSocket frame format (simplified)

472

frame_data = bytearray()

473

474

# First byte: FIN=1, RSV=000, Opcode=TEXT(1)

475

frame_data.append(0x81) # 10000001

476

477

# Payload length

478

if len(message_bytes) < 126:

479

frame_data.append(len(message_bytes))

480

elif len(message_bytes) < 65536:

481

frame_data.append(126)

482

frame_data.extend(struct.pack('!H', len(message_bytes)))

483

else:

484

frame_data.append(127)

485

frame_data.extend(struct.pack('!Q', len(message_bytes)))

486

487

# Payload data

488

frame_data.extend(message_bytes)

489

490

print(f"Created frame: {len(frame_data)} bytes")

491

print(f"Frame hex: {frame_data.hex()}")

492

493

# Process frame through protocol

494

try:

495

events = protocol.receive_data(bytes(frame_data))

496

print(f"Generated {len(events)} events")

497

498

for event in events:

499

if hasattr(event, 'data'):

500

print(f"Event data: {event.data}")

501

502

except Exception as e:

503

print(f"Frame processing error: {e}")

504

505

frame_processing_example()

506

```

507

508

### Extension Integration

509

510

```python

511

from websockets.protocol import ClientProtocol

512

from websockets.extensions.base import Extension

513

514

class SimpleExtension(Extension):

515

"""Example custom extension."""

516

517

def __init__(self, name: str):

518

super().__init__()

519

self.name = name

520

521

def encode(self, frame):

522

"""Process outgoing frames."""

523

# Simple example: add prefix to text frames

524

if frame.opcode == 1: # TEXT frame

525

modified_data = f"[{self.name}] ".encode() + frame.data

526

return frame._replace(data=modified_data)

527

return frame

528

529

def decode(self, frame):

530

"""Process incoming frames."""

531

# Simple example: remove prefix from text frames

532

if frame.opcode == 1: # TEXT frame

533

prefix = f"[{self.name}] ".encode()

534

if frame.data.startswith(prefix):

535

modified_data = frame.data[len(prefix):]

536

return frame._replace(data=modified_data)

537

return frame

538

539

def extension_example():

540

"""Demonstrate protocol with custom extensions."""

541

# Create extension

542

extension = SimpleExtension("MyExt")

543

544

# Create protocol with extension

545

# Note: Real extension integration is more complex

546

protocol = ClientProtocol()

547

548

print("Extension integration example")

549

print(f"Extension name: {extension.name}")

550

551

# In a real implementation, extensions would be negotiated

552

# during handshake and integrated into the protocol

553

554

extension_example()

555

```