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

event-system.mddocs/

0

# Event System

1

2

Comprehensive event-driven API covering all WebSocket operations including handshake, messages, control frames, and connection lifecycle. The event system provides a type-safe, structured approach to WebSocket protocol handling.

3

4

## Capabilities

5

6

### Base Event Class

7

8

All wsproto events inherit from the base Event class, providing a common interface for protocol operations.

9

10

```python { .api }

11

class Event(ABC):

12

"""

13

Base class for wsproto events.

14

"""

15

pass

16

```

17

18

### Handshake Events

19

20

Events related to WebSocket connection establishment and rejection.

21

22

```python { .api }

23

@dataclass(frozen=True)

24

class Request(Event):

25

"""

26

The beginning of a WebSocket connection, the HTTP Upgrade request.

27

28

This event is fired when a SERVER connection receives a WebSocket

29

handshake request (HTTP with upgrade header).

30

"""

31

host: str # Required hostname or host header value

32

target: str # Required request target (path and query string)

33

extensions: Union[Sequence[Extension], Sequence[str]] = field(default_factory=list) # Proposed extensions

34

extra_headers: Headers = field(default_factory=list) # Additional request headers

35

subprotocols: List[str] = field(default_factory=list) # Proposed subprotocols list

36

37

@dataclass(frozen=True)

38

class AcceptConnection(Event):

39

"""

40

The acceptance of a WebSocket upgrade request.

41

42

This event is fired when a CLIENT receives an acceptance response

43

from a server. It is also used to accept an upgrade request when

44

acting as a SERVER.

45

"""

46

subprotocol: Optional[str] = None # The accepted subprotocol to use

47

extensions: List[Extension] = field(default_factory=list) # Accepted extensions

48

extra_headers: Headers = field(default_factory=list) # Additional response headers

49

50

@dataclass(frozen=True)

51

class RejectConnection(Event):

52

"""

53

The rejection of a WebSocket upgrade request, the HTTP response.

54

55

The RejectConnection event sends appropriate HTTP headers to communicate

56

that the handshake has been rejected. You may send an HTTP body by setting

57

has_body to True and following with RejectData events.

58

"""

59

status_code: int = 400 # HTTP response status code

60

headers: Headers = field(default_factory=list) # Response headers

61

has_body: bool = False # Whether response has body (see RejectData)

62

63

@dataclass(frozen=True)

64

class RejectData(Event):

65

"""

66

The rejection HTTP response body.

67

68

The caller may send multiple RejectData events. The final event should

69

have the body_finished attribute set to True.

70

"""

71

data: bytes # Required raw body data

72

body_finished: bool = True # True if this is the final chunk of body data

73

```

74

75

### Message Events

76

77

Events for WebSocket data messages, supporting both text and binary data with fragmentation control.

78

79

```python { .api }

80

@dataclass(frozen=True)

81

class Message(Event, Generic[T]):

82

"""

83

The WebSocket data message base class.

84

"""

85

data: T # Required message data (str for text, bytes for binary)

86

frame_finished: bool = True # Whether frame is finished (for fragmentation)

87

message_finished: bool = True # True if this is the last frame of message

88

89

@dataclass(frozen=True)

90

class TextMessage(Message[str]):

91

"""

92

This event is fired when a data frame with TEXT payload is received.

93

94

The data represents a single chunk and may not be a complete WebSocket

95

message. You need to buffer and reassemble chunks using message_finished

96

to get the full message.

97

"""

98

data: str # Text message data as string

99

100

@dataclass(frozen=True)

101

class BytesMessage(Message[bytes]):

102

"""

103

This event is fired when a data frame with BINARY payload is received.

104

105

The data represents a single chunk and may not be a complete WebSocket

106

message. You need to buffer and reassemble chunks using message_finished

107

to get the full message.

108

"""

109

data: bytes # Binary message data as bytes

110

```

111

112

### Control Frame Events

113

114

Events for WebSocket control frames including connection close, ping, and pong.

115

116

```python { .api }

117

@dataclass(frozen=True)

118

class CloseConnection(Event):

119

"""

120

The end of a WebSocket connection, represents a closure frame.

121

122

wsproto does not automatically send a response to a close event. To comply

123

with the RFC you MUST send a close event back to the remote WebSocket if

124

you have not already sent one.

125

"""

126

code: int # Required integer close code indicating why connection closed

127

reason: Optional[str] = None # Optional additional reasoning for closure

128

129

def response(self) -> "CloseConnection":

130

"""

131

Generate an RFC-compliant close frame to send back to the peer.

132

133

Returns:

134

CloseConnection event with same code and reason

135

"""

136

137

@dataclass(frozen=True)

138

class Ping(Event):

139

"""

140

The Ping event can be sent to trigger a ping frame and is fired when received.

141

142

wsproto does not automatically send a pong response to a ping event. To comply

143

with the RFC you MUST send a pong event as soon as practical.

144

"""

145

payload: bytes = b"" # Optional payload to emit with the ping frame

146

147

def response(self) -> "Pong":

148

"""

149

Generate an RFC-compliant Pong response to this ping.

150

151

Returns:

152

Pong event with same payload

153

"""

154

155

@dataclass(frozen=True)

156

class Pong(Event):

157

"""

158

The Pong event is fired when a Pong is received.

159

"""

160

payload: bytes = b"" # Optional payload from the pong frame

161

```

162

163

## Usage Examples

164

165

### Handling Handshake Events

166

167

```python

168

from wsproto import WSConnection, ConnectionType

169

from wsproto.events import Request, AcceptConnection, RejectConnection

170

171

# Server handling handshake

172

ws = WSConnection(ConnectionType.SERVER)

173

ws.receive_data(handshake_data)

174

175

for event in ws.events():

176

if isinstance(event, Request):

177

print(f"WebSocket request for {event.target} from {event.host}")

178

print(f"Subprotocols: {event.subprotocols}")

179

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

180

181

# Accept the connection

182

if event.target == '/chat':

183

accept_data = ws.send(AcceptConnection(

184

subprotocol='chat.v1' if 'chat.v1' in event.subprotocols else None

185

))

186

else:

187

# Reject the connection

188

reject_data = ws.send(RejectConnection(

189

status_code=404,

190

headers=[(b'content-type', b'text/plain')],

191

has_body=True

192

))

193

reject_body = ws.send(RejectData(

194

data=b'Path not found',

195

body_finished=True

196

))

197

```

198

199

### Handling Message Events

200

201

```python

202

from wsproto.events import TextMessage, BytesMessage

203

import json

204

205

# Process different message types

206

for event in ws.events():

207

if isinstance(event, TextMessage):

208

print(f"Received text: {event.data}")

209

210

# Handle JSON messages

211

try:

212

json_data = json.loads(event.data)

213

print(f"JSON payload: {json_data}")

214

except json.JSONDecodeError:

215

print("Not valid JSON")

216

217

# Handle fragmented messages

218

if not event.message_finished:

219

# Buffer this chunk and wait for more

220

text_buffer += event.data

221

else:

222

# Complete message received

223

complete_message = text_buffer + event.data

224

text_buffer = ""

225

226

elif isinstance(event, BytesMessage):

227

print(f"Received {len(event.data)} bytes")

228

229

# Handle binary data

230

if event.data.startswith(b'\x89PNG'):

231

print("Received PNG image")

232

233

# Handle fragmented binary messages

234

if not event.message_finished:

235

binary_buffer += event.data

236

else:

237

complete_binary = binary_buffer + event.data

238

binary_buffer = b""

239

```

240

241

### Handling Control Frame Events

242

243

```python

244

from wsproto.events import Ping, Pong, CloseConnection

245

from wsproto.frame_protocol import CloseReason

246

247

# Handle control frames

248

for event in ws.events():

249

if isinstance(event, Ping):

250

print(f"Received ping with payload: {event.payload}")

251

# Must respond with pong

252

pong_data = ws.send(event.response())

253

socket.send(pong_data)

254

255

elif isinstance(event, Pong):

256

print(f"Received pong with payload: {event.payload}")

257

# Handle pong response (e.g., measure round-trip time)

258

259

elif isinstance(event, CloseConnection):

260

print(f"Connection closing: code={event.code}, reason={event.reason}")

261

262

# Must respond to close frame

263

if ws.state != ConnectionState.LOCAL_CLOSING:

264

close_response = ws.send(event.response())

265

socket.send(close_response)

266

267

# Handle different close codes

268

if event.code == CloseReason.NORMAL_CLOSURE:

269

print("Normal closure")

270

elif event.code == CloseReason.GOING_AWAY:

271

print("Server going away")

272

elif event.code == CloseReason.PROTOCOL_ERROR:

273

print("Protocol error occurred")

274

```

275

276

### Sending Events

277

278

```python

279

from wsproto.events import TextMessage, BytesMessage, CloseConnection, Ping

280

281

# Send text message

282

text_data = ws.send(TextMessage(data="Hello, WebSocket!"))

283

socket.send(text_data)

284

285

# Send binary message

286

binary_data = ws.send(BytesMessage(data=b"Binary payload"))

287

socket.send(binary_data)

288

289

# Send fragmented message

290

fragment1 = ws.send(TextMessage(data="Start of ", message_finished=False))

291

fragment2 = ws.send(TextMessage(data="long message", message_finished=True))

292

socket.send(fragment1 + fragment2)

293

294

# Send ping

295

ping_data = ws.send(Ping(payload=b"ping-payload"))

296

socket.send(ping_data)

297

298

# Close connection

299

close_data = ws.send(CloseConnection(code=1000, reason="Goodbye"))

300

socket.send(close_data)

301

```

302

303

### Event-Driven WebSocket Echo Server

304

305

```python

306

import socket

307

from wsproto import WSConnection, ConnectionType

308

from wsproto.events import (

309

Request, AcceptConnection, TextMessage, BytesMessage,

310

CloseConnection, Ping

311

)

312

313

def handle_client(client_socket):

314

ws = WSConnection(ConnectionType.SERVER)

315

316

while True:

317

try:

318

data = client_socket.recv(4096)

319

if not data:

320

break

321

322

ws.receive_data(data)

323

324

for event in ws.events():

325

if isinstance(event, Request):

326

# Accept all connections

327

response = ws.send(AcceptConnection())

328

client_socket.send(response)

329

330

elif isinstance(event, TextMessage):

331

# Echo text messages

332

echo_data = ws.send(TextMessage(data=f"Echo: {event.data}"))

333

client_socket.send(echo_data)

334

335

elif isinstance(event, BytesMessage):

336

# Echo binary messages

337

echo_data = ws.send(BytesMessage(data=b"Echo: " + event.data))

338

client_socket.send(echo_data)

339

340

elif isinstance(event, Ping):

341

# Respond to pings

342

pong_data = ws.send(event.response())

343

client_socket.send(pong_data)

344

345

elif isinstance(event, CloseConnection):

346

# Respond to close

347

close_data = ws.send(event.response())

348

client_socket.send(close_data)

349

return

350

351

except Exception as e:

352

print(f"Error: {e}")

353

break

354

355

client_socket.close()

356

357

# Server setup

358

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

359

server.bind(('localhost', 8765))

360

server.listen(1)

361

362

while True:

363

client, addr = server.accept()

364

handle_client(client)

365

```