or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

connection-management.mdevent-system.mdextensions.mdindex.mdlow-level-protocol.md

extensions.mddocs/

0

# Extensions

1

2

WebSocket extensions support including RFC 7692 permessage-deflate compression with configurable parameters and negotiation. The extension system is designed to be pluggable and allows for custom extensions while providing a complete implementation of the standard compression extension.

3

4

## Capabilities

5

6

### Base Extension Class

7

8

Abstract base class for implementing WebSocket extensions with the standard negotiation and processing lifecycle.

9

10

```python { .api }

11

class Extension:

12

"""

13

Base class for WebSocket extensions.

14

"""

15

name: str # Extension name for negotiation

16

17

def enabled(self) -> bool:

18

"""

19

Check if the extension is enabled.

20

21

Returns:

22

True if extension is enabled, False otherwise

23

"""

24

25

def offer(self) -> Union[bool, str]:

26

"""

27

Generate an extension offer for the client to send.

28

29

Returns:

30

Extension parameters as string, or boolean indicating simple offer

31

"""

32

33

def accept(self, offer: str) -> Optional[Union[bool, str]]:

34

"""

35

Accept an extension offer from the client (server-side).

36

37

Args:

38

offer: The extension offer string from client

39

40

Returns:

41

Extension response parameters, True for simple accept,

42

None to reject the offer

43

"""

44

45

def finalize(self, offer: str) -> None:

46

"""

47

Finalize extension negotiation (client-side).

48

49

Args:

50

offer: The accepted extension parameters from server

51

"""

52

53

def frame_inbound_header(

54

self,

55

proto: Union[FrameDecoder, FrameProtocol],

56

opcode: Opcode,

57

rsv: RsvBits,

58

payload_length: int,

59

) -> Union[CloseReason, RsvBits]:

60

"""

61

Process inbound frame header.

62

63

Args:

64

proto: The frame protocol instance

65

opcode: Frame opcode

66

rsv: Reserved bits from frame header

67

payload_length: Length of frame payload

68

69

Returns:

70

Modified RSV bits or CloseReason for error

71

"""

72

73

def frame_inbound_payload_data(

74

self, proto: Union[FrameDecoder, FrameProtocol], data: bytes

75

) -> Union[bytes, CloseReason]:

76

"""

77

Process inbound frame payload data.

78

79

Args:

80

proto: The frame protocol instance

81

data: Payload data chunk

82

83

Returns:

84

Processed payload data or CloseReason for error

85

"""

86

87

def frame_inbound_complete(

88

self, proto: Union[FrameDecoder, FrameProtocol], fin: bool

89

) -> Union[bytes, CloseReason, None]:

90

"""

91

Process inbound frame completion.

92

93

Args:

94

proto: The frame protocol instance

95

fin: Whether this completes the frame

96

97

Returns:

98

Additional payload data, CloseReason for error, or None

99

"""

100

101

def frame_outbound(

102

self,

103

proto: Union[FrameDecoder, FrameProtocol],

104

opcode: Opcode,

105

rsv: RsvBits,

106

data: bytes,

107

fin: bool,

108

) -> Tuple[RsvBits, bytes]:

109

"""

110

Process outbound frame.

111

112

Args:

113

proto: The frame protocol instance

114

opcode: Frame opcode

115

rsv: Reserved bits for frame

116

data: Frame payload data

117

fin: Whether this completes the frame

118

119

Returns:

120

Tuple of (modified_rsv_bits, processed_payload_data)

121

"""

122

```

123

124

### PerMessage-Deflate Extension

125

126

Complete implementation of RFC 7692 permessage-deflate compression extension with configurable compression parameters.

127

128

```python { .api }

129

class PerMessageDeflate(Extension):

130

"""

131

RFC 7692 permessage-deflate WebSocket compression extension.

132

"""

133

name = "permessage-deflate" # Standard extension name

134

135

DEFAULT_CLIENT_MAX_WINDOW_BITS = 15 # Default client compression window size

136

DEFAULT_SERVER_MAX_WINDOW_BITS = 15 # Default server compression window size

137

138

def __init__(

139

self,

140

client_no_context_takeover: bool = False,

141

client_max_window_bits: Optional[int] = None,

142

server_no_context_takeover: bool = False,

143

server_max_window_bits: Optional[int] = None,

144

) -> None:

145

"""

146

Initialize permessage-deflate extension.

147

148

Args:

149

client_no_context_takeover: Client resets compression context each message

150

client_max_window_bits: Client compression window size (9-15)

151

server_no_context_takeover: Server resets compression context each message

152

server_max_window_bits: Server compression window size (9-15)

153

"""

154

155

@property

156

def client_max_window_bits(self) -> int:

157

"""

158

Get client maximum window bits.

159

160

Returns:

161

Window size between 9 and 15 inclusive

162

"""

163

164

@client_max_window_bits.setter

165

def client_max_window_bits(self, value: int) -> None:

166

"""

167

Set client maximum window bits.

168

169

Args:

170

value: Window size between 9 and 15 inclusive

171

172

Raises:

173

ValueError: If value is not between 9 and 15

174

"""

175

176

@property

177

def server_max_window_bits(self) -> int:

178

"""

179

Get server maximum window bits.

180

181

Returns:

182

Window size between 9 and 15 inclusive

183

"""

184

185

@server_max_window_bits.setter

186

def server_max_window_bits(self, value: int) -> None:

187

"""

188

Set server maximum window bits.

189

190

Args:

191

value: Window size between 9 and 15 inclusive

192

193

Raises:

194

ValueError: If value is not between 9 and 15

195

"""

196

```

197

198

### Extension Support Constants

199

200

```python { .api }

201

# Dictionary mapping all supported extension names to their class

202

SUPPORTED_EXTENSIONS = {PerMessageDeflate.name: PerMessageDeflate}

203

```

204

205

## Usage Examples

206

207

### Basic Compression Setup

208

209

```python

210

from wsproto import WSConnection, ConnectionType

211

from wsproto.extensions import PerMessageDeflate

212

from wsproto.events import Request, AcceptConnection

213

214

# Client with compression

215

compression = PerMessageDeflate()

216

ws = WSConnection(ConnectionType.CLIENT)

217

218

# Send request with compression offered

219

request_data = ws.send(Request(

220

host='example.com',

221

target='/ws',

222

extensions=[compression]

223

))

224

225

# Server accepting compression

226

ws_server = WSConnection(ConnectionType.SERVER)

227

ws_server.receive_data(request_data)

228

229

for event in ws_server.events():

230

if isinstance(event, Request):

231

# Accept with compression

232

server_compression = PerMessageDeflate()

233

response_data = ws_server.send(AcceptConnection(

234

extensions=[server_compression]

235

))

236

```

237

238

### Advanced Compression Configuration

239

240

```python

241

from wsproto.extensions import PerMessageDeflate

242

243

# Configure compression parameters

244

compression = PerMessageDeflate(

245

client_no_context_takeover=True, # Reset client context each message

246

client_max_window_bits=12, # Smaller window for less memory

247

server_no_context_takeover=False, # Keep server context between messages

248

server_max_window_bits=15, # Maximum compression for server

249

)

250

251

print(f"Client window bits: {compression.client_max_window_bits}")

252

print(f"Server window bits: {compression.server_max_window_bits}")

253

print(f"Extension enabled: {compression.enabled()}")

254

255

# Use in connection

256

ws = WSConnection(ConnectionType.CLIENT)

257

request_data = ws.send(Request(

258

host='example.com',

259

target='/ws',

260

extensions=[compression]

261

))

262

```

263

264

### Server-Side Extension Negotiation

265

266

```python

267

from wsproto import WSConnection, ConnectionType

268

from wsproto.extensions import PerMessageDeflate, SUPPORTED_EXTENSIONS

269

from wsproto.events import Request, AcceptConnection

270

271

def handle_extensions(requested_extensions):

272

"""Handle extension negotiation on server side."""

273

accepted_extensions = []

274

275

for ext_offer in requested_extensions:

276

ext_name = ext_offer.split(';')[0].strip()

277

278

if ext_name in SUPPORTED_EXTENSIONS:

279

ext_class = SUPPORTED_EXTENSIONS[ext_name]

280

extension = ext_class()

281

282

# Try to accept the offer

283

result = extension.accept(ext_offer)

284

if result is not None:

285

extension.finalize(ext_offer)

286

accepted_extensions.append(extension)

287

print(f"Accepted extension: {ext_name}")

288

else:

289

print(f"Rejected extension: {ext_name}")

290

else:

291

print(f"Unsupported extension: {ext_name}")

292

293

return accepted_extensions

294

295

# Server handling extensions

296

ws = WSConnection(ConnectionType.SERVER)

297

ws.receive_data(handshake_data)

298

299

for event in ws.events():

300

if isinstance(event, Request):

301

print(f"Requested extensions: {event.extensions}")

302

303

# Negotiate extensions

304

accepted_extensions = handle_extensions(event.extensions)

305

306

# Accept connection with negotiated extensions

307

response_data = ws.send(AcceptConnection(

308

extensions=accepted_extensions

309

))

310

```

311

312

### Custom Extension Implementation

313

314

```python

315

from wsproto.extensions import Extension

316

from wsproto.frame_protocol import Opcode, RsvBits, CloseReason

317

318

class SimpleLoggingExtension(Extension):

319

"""Example custom extension that logs frame information."""

320

321

name = "simple-logging"

322

323

def __init__(self):

324

self._enabled = False

325

326

def enabled(self) -> bool:

327

return self._enabled

328

329

def offer(self) -> Union[bool, str]:

330

return True # Simple offer with no parameters

331

332

def accept(self, offer: str) -> Optional[Union[bool, str]]:

333

self._enabled = True

334

return True # Accept the offer

335

336

def finalize(self, offer: str) -> None:

337

self._enabled = True

338

339

def frame_inbound_header(self, proto, opcode, rsv, payload_length):

340

print(f"Inbound frame: opcode={opcode}, length={payload_length}")

341

return RsvBits(False, False, False) # Don't modify RSV bits

342

343

def frame_inbound_payload_data(self, proto, data):

344

print(f"Inbound payload: {len(data)} bytes")

345

return data # Pass through unchanged

346

347

def frame_outbound(self, proto, opcode, rsv, data, fin):

348

print(f"Outbound frame: opcode={opcode}, length={len(data)}, fin={fin}")

349

return (rsv, data) # Pass through unchanged

350

351

# Use custom extension

352

custom_ext = SimpleLoggingExtension()

353

ws = WSConnection(ConnectionType.CLIENT)

354

request_data = ws.send(Request(

355

host='example.com',

356

target='/ws',

357

extensions=[custom_ext]

358

))

359

```

360

361

### Compression with Different Window Sizes

362

363

```python

364

from wsproto.extensions import PerMessageDeflate

365

366

# Memory-conscious compression (smaller windows)

367

low_memory_compression = PerMessageDeflate(

368

client_max_window_bits=9, # Minimum window size

369

server_max_window_bits=9,

370

client_no_context_takeover=True, # Reset context to save memory

371

server_no_context_takeover=True,

372

)

373

374

# High-compression setup (larger windows)

375

high_compression = PerMessageDeflate(

376

client_max_window_bits=15, # Maximum window size

377

server_max_window_bits=15,

378

client_no_context_takeover=False, # Keep context for better compression

379

server_no_context_takeover=False,

380

)

381

382

# Test different configurations

383

extensions_to_test = [

384

("Low Memory", low_memory_compression),

385

("High Compression", high_compression),

386

]

387

388

for name, ext in extensions_to_test:

389

ws = WSConnection(ConnectionType.CLIENT)

390

print(f"Testing {name} configuration:")

391

print(f" Client window: {ext.client_max_window_bits}")

392

print(f" Server window: {ext.server_max_window_bits}")

393

394

request_data = ws.send(Request(

395

host='example.com',

396

target='/ws',

397

extensions=[ext]

398

))

399

```

400

401

### Extension Error Handling

402

403

```python

404

from wsproto.extensions import PerMessageDeflate

405

406

try:

407

# Invalid window size

408

bad_compression = PerMessageDeflate(client_max_window_bits=16)

409

except ValueError as e:

410

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

411

412

try:

413

# Another invalid configuration

414

bad_compression = PerMessageDeflate(server_max_window_bits=8)

415

except ValueError as e:

416

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

417

418

# Valid configuration

419

good_compression = PerMessageDeflate(

420

client_max_window_bits=12,

421

server_max_window_bits=14,

422

)

423

print(f"Valid compression created: {good_compression}")

424

```