or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

blueprints.mdconfiguration.mdcore-application.mdexceptions.mdindex.mdmiddleware-signals.mdrequest-response.mdserver-deployment.mdwebsockets.md

websockets.mddocs/

0

# WebSocket Support

1

2

Sanic provides native WebSocket support for real-time, bidirectional communication between client and server. This includes WebSocket routing, connection management, message handling, and integration with the broader Sanic ecosystem.

3

4

## Capabilities

5

6

### WebSocket Routes

7

8

Define WebSocket endpoints using decorators and programmatic registration.

9

10

```python { .api }

11

def websocket(

12

uri: str,

13

host: str = None,

14

strict_slashes: bool = None,

15

subprotocols: list = None,

16

name: str = None,

17

apply: list = None,

18

version: int = None,

19

version_prefix: str = "/v",

20

error_format: str = None,

21

):

22

"""

23

Decorator for WebSocket routes.

24

25

Parameters:

26

- uri: WebSocket endpoint path

27

- host: Host restriction pattern

28

- strict_slashes: Override global strict slash setting

29

- subprotocols: Supported WebSocket subprotocols

30

- name: Route name for url_for

31

- apply: List of decorators to apply

32

- version: API version number

33

- version_prefix: Version URL prefix

34

- error_format: Error response format

35

36

Usage:

37

@app.websocket('/ws')

38

async def websocket_handler(request, ws):

39

await ws.send("Hello WebSocket!")

40

msg = await ws.recv()

41

print(f"Received: {msg}")

42

"""

43

44

def add_websocket_route(

45

handler,

46

uri: str,

47

**kwargs

48

):

49

"""

50

Add WebSocket route programmatically.

51

52

Parameters:

53

- handler: WebSocket handler function

54

- uri: WebSocket endpoint path

55

- **kwargs: Additional route options

56

"""

57

```

58

59

### WebSocket Protocol Interface

60

61

The WebSocket connection interface for message handling and connection management.

62

63

```python { .api }

64

class Websocket:

65

"""WebSocket connection protocol interface."""

66

67

async def send(

68

self,

69

data,

70

encoding: str = "text"

71

):

72

"""

73

Send data to WebSocket client.

74

75

Parameters:

76

- data: Data to send (str, bytes, or dict for JSON)

77

- encoding: Data encoding type ("text", "bytes", or "json")

78

79

Raises:

80

- WebsocketClosed: If connection is closed

81

- ConnectionError: If send fails

82

"""

83

84

async def recv(self, timeout: float = None):

85

"""

86

Receive data from WebSocket client.

87

88

Parameters:

89

- timeout: Receive timeout in seconds

90

91

Returns:

92

- Received data (str or bytes)

93

94

Raises:

95

- WebsocketClosed: If connection is closed

96

- asyncio.TimeoutError: If timeout exceeded

97

"""

98

99

async def ping(self, data: bytes = b""):

100

"""

101

Send ping frame to client.

102

103

Parameters:

104

- data: Optional ping data

105

106

Returns:

107

- Pong waiter coroutine

108

"""

109

110

async def pong(self, data: bytes = b""):

111

"""

112

Send pong frame to client.

113

114

Parameters:

115

- data: Optional pong data

116

"""

117

118

async def close(

119

self,

120

code: int = 1000,

121

reason: str = ""

122

):

123

"""

124

Close WebSocket connection.

125

126

Parameters:

127

- code: Close status code

128

- reason: Close reason string

129

"""

130

131

async def wait_closed(self):

132

"""Wait for connection to be closed."""

133

134

@property

135

def closed(self) -> bool:

136

"""Whether the connection is closed."""

137

138

@property

139

def open(self) -> bool:

140

"""Whether the connection is open."""

141

142

@property

143

def client_state(self):

144

"""Client connection state."""

145

146

@property

147

def server_state(self):

148

"""Server connection state."""

149

150

@property

151

def subprotocol(self) -> str:

152

"""Negotiated subprotocol."""

153

```

154

155

### WebSocket Handler Function

156

157

The signature and structure for WebSocket handler functions.

158

159

```python { .api }

160

async def websocket_handler(request, ws):

161

"""

162

WebSocket handler function signature.

163

164

Parameters:

165

- request: HTTP request object (for initial handshake)

166

- ws: WebSocket connection object

167

168

The handler should:

169

- Handle the WebSocket connection lifecycle

170

- Process incoming messages

171

- Send outgoing messages

172

- Handle connection errors and cleanup

173

- Manage connection state

174

"""

175

```

176

177

### WebSocket Configuration

178

179

Configuration options for WebSocket behavior and limits.

180

181

```python { .api }

182

# WebSocket timeout settings

183

WEBSOCKET_TIMEOUT: int = 10 # Connection timeout

184

WEBSOCKET_PING_INTERVAL: int = 20 # Ping interval in seconds

185

WEBSOCKET_PING_TIMEOUT: int = 20 # Ping timeout in seconds

186

187

# WebSocket message limits

188

WEBSOCKET_MAX_SIZE: int = 1048576 # Maximum message size (1MB)

189

WEBSOCKET_MAX_QUEUE: int = 32 # Maximum queued messages

190

191

# WebSocket compression

192

WEBSOCKET_COMPRESSION: str = None # Compression algorithm

193

```

194

195

### WebSocket Subprotocols

196

197

Support for WebSocket subprotocols for application-specific protocols.

198

199

```python { .api }

200

@app.websocket('/ws', subprotocols=['chat', 'notification'])

201

async def websocket_with_subprotocols(request, ws):

202

"""

203

WebSocket handler with subprotocol negotiation.

204

205

The client can request specific subprotocols during handshake.

206

The server selects the first supported subprotocol.

207

"""

208

209

selected_protocol = ws.subprotocol

210

211

if selected_protocol == 'chat':

212

await handle_chat_protocol(ws)

213

elif selected_protocol == 'notification':

214

await handle_notification_protocol(ws)

215

else:

216

await ws.close(code=1002, reason="Unsupported subprotocol")

217

```

218

219

### WebSocket Authentication

220

221

Authenticate WebSocket connections using request data and custom logic.

222

223

```python { .api }

224

@app.websocket('/ws/authenticated')

225

async def authenticated_websocket(request, ws):

226

"""

227

WebSocket handler with authentication.

228

229

Authentication is performed during the initial HTTP handshake

230

using headers, query parameters, or other request data.

231

"""

232

233

# Authenticate using request headers

234

auth_header = request.headers.get('Authorization')

235

if not auth_header:

236

await ws.close(code=1008, reason="Authentication required")

237

return

238

239

try:

240

user = await validate_websocket_token(auth_header)

241

request.ctx.user = user

242

except AuthenticationError:

243

await ws.close(code=1008, reason="Invalid authentication")

244

return

245

246

# Continue with authenticated WebSocket handling

247

await handle_authenticated_websocket(request, ws)

248

```

249

250

## Usage Examples

251

252

### Basic WebSocket Echo Server

253

254

```python

255

from sanic import Sanic

256

from sanic.response import html

257

258

app = Sanic("WebSocketApp")

259

260

@app.websocket('/ws')

261

async def websocket_echo(request, ws):

262

"""Simple echo WebSocket handler."""

263

264

await ws.send("Welcome to WebSocket echo server!")

265

266

async for msg in ws:

267

print(f"Received: {msg}")

268

await ws.send(f"Echo: {msg}")

269

270

@app.route('/')

271

async def index(request):

272

"""Serve WebSocket test page."""

273

return html('''

274

<!DOCTYPE html>

275

<html>

276

<head><title>WebSocket Test</title></head>

277

<body>

278

<div id="messages"></div>

279

<input type="text" id="messageInput" placeholder="Type a message...">

280

<button onclick="sendMessage()">Send</button>

281

282

<script>

283

const ws = new WebSocket('ws://localhost:8000/ws');

284

const messages = document.getElementById('messages');

285

286

ws.onmessage = function(event) {

287

const div = document.createElement('div');

288

div.textContent = event.data;

289

messages.appendChild(div);

290

};

291

292

function sendMessage() {

293

const input = document.getElementById('messageInput');

294

ws.send(input.value);

295

input.value = '';

296

}

297

</script>

298

</body>

299

</html>

300

''')

301

302

if __name__ == '__main__':

303

app.run(host='0.0.0.0', port=8000)

304

```

305

306

### Chat Room WebSocket Server

307

308

```python

309

import asyncio

310

from sanic import Sanic

311

from sanic.response import json

312

import json as json_lib

313

314

app = Sanic("ChatApp")

315

316

# Store active connections

317

connected_clients = set()

318

chat_rooms = {}

319

320

@app.websocket('/ws/chat/<room_id>')

321

async def chat_room(request, ws, room_id):

322

"""Multi-room chat WebSocket handler."""

323

324

# Initialize room if it doesn't exist

325

if room_id not in chat_rooms:

326

chat_rooms[room_id] = set()

327

328

# Add client to room

329

chat_rooms[room_id].add(ws)

330

connected_clients.add(ws)

331

332

try:

333

# Send welcome message

334

await ws.send(json_lib.dumps({

335

"type": "system",

336

"message": f"Welcome to chat room {room_id}",

337

"room": room_id

338

}))

339

340

# Broadcast user joined

341

await broadcast_to_room(room_id, {

342

"type": "user_joined",

343

"message": "A user joined the room",

344

"room": room_id

345

}, exclude=ws)

346

347

# Handle messages

348

async for message in ws:

349

try:

350

data = json_lib.loads(message)

351

352

if data.get("type") == "chat":

353

# Broadcast chat message to room

354

await broadcast_to_room(room_id, {

355

"type": "chat",

356

"message": data.get("message", ""),

357

"user": data.get("user", "Anonymous"),

358

"room": room_id

359

})

360

361

except json_lib.JSONDecodeError:

362

await ws.send(json_lib.dumps({

363

"type": "error",

364

"message": "Invalid JSON format"

365

}))

366

367

except Exception as e:

368

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

369

370

finally:

371

# Clean up when client disconnects

372

if ws in connected_clients:

373

connected_clients.remove(ws)

374

if room_id in chat_rooms and ws in chat_rooms[room_id]:

375

chat_rooms[room_id].remove(ws)

376

377

# Broadcast user left

378

await broadcast_to_room(room_id, {

379

"type": "user_left",

380

"message": "A user left the room",

381

"room": room_id

382

})

383

384

# Remove empty rooms

385

if not chat_rooms[room_id]:

386

del chat_rooms[room_id]

387

388

async def broadcast_to_room(room_id, message, exclude=None):

389

"""Broadcast message to all clients in a room."""

390

if room_id not in chat_rooms:

391

return

392

393

message_str = json_lib.dumps(message)

394

disconnected = set()

395

396

for client in chat_rooms[room_id]:

397

if client == exclude:

398

continue

399

400

try:

401

await client.send(message_str)

402

except Exception:

403

# Mark disconnected clients for removal

404

disconnected.add(client)

405

406

# Remove disconnected clients

407

chat_rooms[room_id] -= disconnected

408

409

@app.route('/api/rooms')

410

async def get_active_rooms(request):

411

"""Get list of active chat rooms."""

412

return json({

413

"rooms": list(chat_rooms.keys()),

414

"total_clients": len(connected_clients)

415

})

416

```

417

418

### Real-time Data Streaming

419

420

```python

421

import asyncio

422

import random

423

from datetime import datetime

424

425

app = Sanic("DataStreamApp")

426

427

@app.websocket('/ws/data-stream')

428

async def data_stream(request, ws):

429

"""Stream real-time data to WebSocket clients."""

430

431

# Send initial connection confirmation

432

await ws.send(json_lib.dumps({

433

"type": "connected",

434

"message": "Data stream connected",

435

"timestamp": datetime.utcnow().isoformat()

436

}))

437

438

# Create background task for data streaming

439

stream_task = asyncio.create_task(stream_data(ws))

440

receive_task = asyncio.create_task(handle_client_messages(ws))

441

442

try:

443

# Wait for either task to complete

444

done, pending = await asyncio.wait(

445

[stream_task, receive_task],

446

return_when=asyncio.FIRST_COMPLETED

447

)

448

449

# Cancel pending tasks

450

for task in pending:

451

task.cancel()

452

453

except Exception as e:

454

print(f"WebSocket streaming error: {e}")

455

456

finally:

457

if not ws.closed:

458

await ws.close()

459

460

async def stream_data(ws):

461

"""Stream data to WebSocket client."""

462

while not ws.closed:

463

try:

464

# Generate sample data

465

data = {

466

"type": "data",

467

"timestamp": datetime.utcnow().isoformat(),

468

"value": random.randint(1, 100),

469

"sensor_id": "sensor_001",

470

"temperature": round(random.uniform(20.0, 30.0), 2),

471

"humidity": round(random.uniform(40.0, 80.0), 2)

472

}

473

474

await ws.send(json_lib.dumps(data))

475

await asyncio.sleep(1) # Send data every second

476

477

except Exception as e:

478

print(f"Data streaming error: {e}")

479

break

480

481

async def handle_client_messages(ws):

482

"""Handle messages from WebSocket client."""

483

async for message in ws:

484

try:

485

data = json_lib.loads(message)

486

487

if data.get("type") == "ping":

488

await ws.send(json_lib.dumps({

489

"type": "pong",

490

"timestamp": datetime.utcnow().isoformat()

491

}))

492

493

elif data.get("type") == "subscribe":

494

# Handle subscription requests

495

sensor_id = data.get("sensor_id")

496

await ws.send(json_lib.dumps({

497

"type": "subscribed",

498

"sensor_id": sensor_id,

499

"message": f"Subscribed to {sensor_id}"

500

}))

501

502

except json_lib.JSONDecodeError:

503

await ws.send(json_lib.dumps({

504

"type": "error",

505

"message": "Invalid message format"

506

}))

507

```

508

509

### WebSocket with Authentication and Rate Limiting

510

511

```python

512

import time

513

from collections import defaultdict

514

515

app = Sanic("SecureWebSocketApp")

516

517

# Rate limiting storage

518

client_requests = defaultdict(list)

519

RATE_LIMIT = 10 # 10 messages per minute

520

RATE_WINDOW = 60 # 1 minute window

521

522

@app.websocket('/ws/secure')

523

async def secure_websocket(request, ws):

524

"""WebSocket with authentication and rate limiting."""

525

526

# Authenticate connection

527

token = request.args.get('token') or request.headers.get('Authorization')

528

if not token:

529

await ws.close(code=1008, reason="Authentication token required")

530

return

531

532

try:

533

user = await validate_token(token)

534

client_id = user['id']

535

except Exception:

536

await ws.close(code=1008, reason="Invalid authentication token")

537

return

538

539

await ws.send(json_lib.dumps({

540

"type": "authenticated",

541

"user": user['username'],

542

"message": "Authentication successful"

543

}))

544

545

try:

546

async for message in ws:

547

# Rate limiting check

548

if not check_rate_limit(client_id):

549

await ws.send(json_lib.dumps({

550

"type": "error",

551

"message": "Rate limit exceeded. Please slow down."

552

}))

553

continue

554

555

# Process message

556

try:

557

data = json_lib.loads(message)

558

559

# Echo message back with user info

560

response = {

561

"type": "message",

562

"user": user['username'],

563

"message": data.get("message", ""),

564

"timestamp": datetime.utcnow().isoformat()

565

}

566

567

await ws.send(json_lib.dumps(response))

568

569

except json_lib.JSONDecodeError:

570

await ws.send(json_lib.dumps({

571

"type": "error",

572

"message": "Invalid JSON format"

573

}))

574

575

except Exception as e:

576

print(f"Secure WebSocket error: {e}")

577

578

finally:

579

# Clean up rate limiting data

580

if client_id in client_requests:

581

del client_requests[client_id]

582

583

def check_rate_limit(client_id):

584

"""Check if client is within rate limits."""

585

now = time.time()

586

587

# Clean old requests

588

client_requests[client_id] = [

589

req_time for req_time in client_requests[client_id]

590

if now - req_time < RATE_WINDOW

591

]

592

593

# Check rate limit

594

if len(client_requests[client_id]) >= RATE_LIMIT:

595

return False

596

597

# Record this request

598

client_requests[client_id].append(now)

599

return True

600

601

async def validate_token(token):

602

"""Validate authentication token."""

603

# Implement your token validation logic

604

if token == "valid_token":

605

return {"id": "user123", "username": "testuser"}

606

raise ValueError("Invalid token")

607

```