or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

client-support.mdconfiguration.mdcore-websocket-api.mdhandler-framework.mdindex.mdmessage-types.mdserver-integration.mdsockjs-support.mdstomp-messaging.md

handler-framework.mddocs/

0

# Handler Framework

1

2

WebSocket handler implementations and decorators for building robust WebSocket applications with cross-cutting concerns.

3

4

## Capabilities

5

6

### AbstractWebSocketHandler

7

8

Base implementation of WebSocketHandler with no-op defaults and type-specific message handling.

9

10

```java { .api }

11

/**

12

* Base implementation of WebSocketHandler providing no-op defaults

13

* and delegating message handling to type-specific methods.

14

*/

15

abstract class AbstractWebSocketHandler implements WebSocketHandler {

16

/**

17

* Called after WebSocket connection is established. No-op by default.

18

* @param session the WebSocket session

19

* @throws Exception if an error occurs

20

*/

21

public void afterConnectionEstablished(WebSocketSession session) throws Exception;

22

23

/**

24

* Handle incoming WebSocket message by delegating to type-specific methods.

25

* @param session the WebSocket session

26

* @param message the incoming message

27

* @throws Exception if an error occurs

28

*/

29

public final void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception;

30

31

/**

32

* Handle text messages. Override to process text messages.

33

* @param session the WebSocket session

34

* @param message the text message

35

* @throws Exception if an error occurs

36

*/

37

protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception;

38

39

/**

40

* Handle binary messages. Override to process binary messages.

41

* @param session the WebSocket session

42

* @param message the binary message

43

* @throws Exception if an error occurs

44

*/

45

protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception;

46

47

/**

48

* Handle pong messages. Override to process pong messages.

49

* @param session the WebSocket session

50

* @param message the pong message

51

* @throws Exception if an error occurs

52

*/

53

protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception;

54

55

/**

56

* Handle transport errors by logging them. Override for custom error handling.

57

* @param session the WebSocket session

58

* @param exception the transport error

59

* @throws Exception if an error occurs during error handling

60

*/

61

public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception;

62

63

/**

64

* Called after WebSocket connection is closed. No-op by default.

65

* @param session the WebSocket session

66

* @param closeStatus the close status

67

* @throws Exception if an error occurs

68

*/

69

public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception;

70

71

/**

72

* Whether partial messages are supported. Returns false by default.

73

* @return false

74

*/

75

public boolean supportsPartialMessages();

76

}

77

```

78

79

**Usage Example:**

80

81

```java

82

@Component

83

public class GameWebSocketHandler extends AbstractWebSocketHandler {

84

private final GameService gameService;

85

private final Set<WebSocketSession> players = ConcurrentHashMap.newKeySet();

86

87

public GameWebSocketHandler(GameService gameService) {

88

this.gameService = gameService;

89

}

90

91

@Override

92

public void afterConnectionEstablished(WebSocketSession session) throws Exception {

93

players.add(session);

94

String welcomeMsg = gameService.generateWelcomeMessage();

95

session.sendMessage(new TextMessage(welcomeMsg));

96

}

97

98

@Override

99

protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {

100

String command = message.getPayload();

101

GameAction action = parseGameCommand(command);

102

103

GameResult result = gameService.processAction(session.getId(), action);

104

105

// Send result to player

106

session.sendMessage(new TextMessage(result.toJson()));

107

108

// Broadcast updates to other players if needed

109

if (result.shouldBroadcast()) {

110

broadcastToOtherPlayers(session, result.getBroadcastMessage());

111

}

112

}

113

114

@Override

115

protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {

116

// Handle binary game data (e.g., player position updates)

117

ByteBuffer data = message.getPayload();

118

PlayerPosition position = PlayerPosition.fromByteBuffer(data);

119

120

gameService.updatePlayerPosition(session.getId(), position);

121

122

// Broadcast position to nearby players

123

broadcastPositionUpdate(session, position);

124

}

125

126

@Override

127

public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {

128

logger.error("Transport error for player {}: {}", session.getId(), exception.getMessage());

129

players.remove(session);

130

gameService.handlePlayerDisconnection(session.getId());

131

}

132

133

@Override

134

public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {

135

players.remove(session);

136

gameService.handlePlayerDisconnection(session.getId());

137

broadcastPlayerLeft(session);

138

}

139

140

private void broadcastToOtherPlayers(WebSocketSession excludeSession, String message) {

141

players.stream()

142

.filter(s -> !s.equals(excludeSession) && s.isOpen())

143

.forEach(s -> {

144

try {

145

s.sendMessage(new TextMessage(message));

146

} catch (IOException e) {

147

players.remove(s);

148

}

149

});

150

}

151

}

152

```

153

154

### Specialized Handler Classes

155

156

Pre-built handler implementations for common use cases.

157

158

```java { .api }

159

/**

160

* WebSocket handler that only accepts text messages.

161

* Rejects binary messages with POLICY_VIOLATION close status.

162

*/

163

class TextWebSocketHandler extends AbstractWebSocketHandler {

164

@Override

165

protected final void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception;

166

}

167

168

/**

169

* WebSocket handler that only accepts binary messages.

170

* Rejects text messages with POLICY_VIOLATION close status.

171

*/

172

class BinaryWebSocketHandler extends AbstractWebSocketHandler {

173

@Override

174

protected final void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception;

175

}

176

```

177

178

**Usage Examples:**

179

180

```java

181

// Text-only chat handler

182

@Component

183

public class ChatHandler extends TextWebSocketHandler {

184

@Override

185

protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {

186

String chatMessage = message.getPayload();

187

// Process chat message

188

broadcastChatMessage(session, chatMessage);

189

}

190

}

191

192

// Binary-only file transfer handler

193

@Component

194

public class FileTransferHandler extends BinaryWebSocketHandler {

195

@Override

196

protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {

197

ByteBuffer fileData = message.getPayload();

198

// Process binary file data

199

saveFileChunk(session, fileData, message.isLast());

200

}

201

}

202

```

203

204

### Per-Connection Handler Factory

205

206

Creates new handler instances for each WebSocket connection.

207

208

```java { .api }

209

/**

210

* WebSocket handler that creates a new handler instance per connection.

211

* Useful when handlers need to maintain per-connection state.

212

*/

213

class PerConnectionWebSocketHandler implements WebSocketHandler {

214

/**

215

* Create per-connection handler with handler class.

216

* @param handlerType the handler class to instantiate

217

*/

218

public PerConnectionWebSocketHandler(Class<? extends WebSocketHandler> handlerType);

219

220

/**

221

* Set the bean factory for creating handler instances.

222

* @param beanFactory Spring bean factory

223

*/

224

public void setBeanFactory(BeanFactory beanFactory);

225

}

226

```

227

228

**Usage Example:**

229

230

```java

231

@Configuration

232

@EnableWebSocket

233

public class PerConnectionConfig implements WebSocketConfigurer {

234

235

@Override

236

public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {

237

// Each connection gets its own StatefulChatHandler instance

238

registry.addHandler(

239

new PerConnectionWebSocketHandler(StatefulChatHandler.class),

240

"/stateful-chat"

241

);

242

}

243

}

244

245

// Handler that maintains per-connection state

246

@Component

247

@Scope("prototype") // Important: must be prototype scoped

248

public class StatefulChatHandler extends AbstractWebSocketHandler {

249

private String username;

250

private List<String> messageHistory = new ArrayList<>();

251

private long connectionTime;

252

253

@Override

254

public void afterConnectionEstablished(WebSocketSession session) throws Exception {

255

connectionTime = System.currentTimeMillis();

256

username = extractUsername(session);

257

session.sendMessage(new TextMessage("Welcome " + username + "!"));

258

}

259

260

@Override

261

protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {

262

String content = message.getPayload();

263

messageHistory.add(content);

264

265

// Process message with per-connection state

266

String response = String.format("[%s] %s (message #%d)",

267

username, content, messageHistory.size());

268

269

session.sendMessage(new TextMessage(response));

270

}

271

}

272

```

273

274

### Handler Decorators

275

276

Decorator pattern implementation for adding cross-cutting concerns to WebSocket handlers.

277

278

```java { .api }

279

/**

280

* Base decorator for WebSocketHandler implementations.

281

* Delegates all calls to the wrapped handler.

282

*/

283

class WebSocketHandlerDecorator implements WebSocketHandler {

284

/**

285

* Create decorator with delegate handler.

286

* @param delegate the handler to decorate

287

*/

288

public WebSocketHandlerDecorator(WebSocketHandler delegate);

289

290

/**

291

* Get the decorated handler.

292

* @return delegate handler

293

*/

294

public WebSocketHandler getDelegate();

295

296

/**

297

* Get the last handler in the decoration chain.

298

* @return the innermost handler

299

*/

300

public WebSocketHandler getLastHandler();

301

}

302

303

/**

304

* Factory interface for creating WebSocket handler decorators.

305

* Used to apply cross-cutting concerns to handlers.

306

*/

307

interface WebSocketHandlerDecoratorFactory {

308

/**

309

* Decorate a WebSocket handler.

310

* @param handler the handler to decorate

311

* @return decorated handler

312

*/

313

WebSocketHandler decorate(WebSocketHandler handler);

314

}

315

316

/**

317

* Decorator that handles exceptions from WebSocketHandler methods.

318

* Prevents exceptions from propagating and potentially closing connections.

319

*/

320

class ExceptionWebSocketHandlerDecorator extends WebSocketHandlerDecorator {

321

public ExceptionWebSocketHandlerDecorator(WebSocketHandler delegate);

322

}

323

324

/**

325

* Decorator that adds logging to WebSocketHandler method calls.

326

* Logs method entry, exit, and exceptions for debugging.

327

*/

328

class LoggingWebSocketHandlerDecorator extends WebSocketHandlerDecorator {

329

public LoggingWebSocketHandlerDecorator(WebSocketHandler delegate);

330

}

331

```

332

333

**Usage Examples:**

334

335

```java

336

// Custom security decorator

337

public class SecurityWebSocketHandlerDecorator extends WebSocketHandlerDecorator {

338

private final SecurityService securityService;

339

340

public SecurityWebSocketHandlerDecorator(WebSocketHandler delegate, SecurityService securityService) {

341

super(delegate);

342

this.securityService = securityService;

343

}

344

345

@Override

346

public void afterConnectionEstablished(WebSocketSession session) throws Exception {

347

if (!securityService.isAuthorized(session)) {

348

session.close(CloseStatus.POLICY_VIOLATION.withReason("Unauthorized"));

349

return;

350

}

351

super.afterConnectionEstablished(session);

352

}

353

354

@Override

355

public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {

356

if (!securityService.canSendMessage(session, message)) {

357

session.close(CloseStatus.POLICY_VIOLATION.withReason("Message not allowed"));

358

return;

359

}

360

super.handleMessage(session, message);

361

}

362

}

363

364

// Custom rate limiting decorator

365

public class RateLimitingDecoratorFactory implements WebSocketHandlerDecoratorFactory {

366

private final RateLimitService rateLimitService;

367

368

public RateLimitingDecoratorFactory(RateLimitService rateLimitService) {

369

this.rateLimitService = rateLimitService;

370

}

371

372

@Override

373

public WebSocketHandler decorate(WebSocketHandler handler) {

374

return new RateLimitingWebSocketHandlerDecorator(handler, rateLimitService);

375

}

376

}

377

378

// Configuration with decorators

379

@Configuration

380

@EnableWebSocket

381

public class DecoratedWebSocketConfig implements WebSocketConfigurer {

382

383

@Autowired

384

private SecurityService securityService;

385

386

@Autowired

387

private RateLimitService rateLimitService;

388

389

@Override

390

public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {

391

WebSocketHandler chatHandler = new ChatWebSocketHandler();

392

393

// Apply multiple decorators

394

chatHandler = new LoggingWebSocketHandlerDecorator(chatHandler);

395

chatHandler = new ExceptionWebSocketHandlerDecorator(chatHandler);

396

chatHandler = new SecurityWebSocketHandlerDecorator(chatHandler, securityService);

397

398

registry.addHandler(chatHandler, "/secure-chat");

399

}

400

401

// Alternative: using decorator factories in transport configuration

402

@Bean

403

public WebSocketTransportRegistration webSocketTransportRegistration() {

404

return new WebSocketTransportRegistration()

405

.addDecoratorFactory(new RateLimitingDecoratorFactory(rateLimitService))

406

.addDecoratorFactory(handler -> new MetricsWebSocketHandlerDecorator(handler));

407

}

408

}

409

```

410

411

### Session Decorators

412

413

Decorators for WebSocket sessions to add functionality like thread safety and flow control.

414

415

```java { .api }

416

/**

417

* Base decorator for WebSocketSession implementations.

418

* Delegates all calls to the wrapped session.

419

*/

420

class WebSocketSessionDecorator implements WebSocketSession {

421

/**

422

* Create decorator with delegate session.

423

* @param delegate the session to decorate

424

*/

425

public WebSocketSessionDecorator(WebSocketSession delegate);

426

427

/**

428

* Get the decorated session.

429

* @return delegate session

430

*/

431

public WebSocketSession getDelegate();

432

}

433

434

/**

435

* Session decorator that provides thread-safe message sending with flow control.

436

* Queues messages and prevents buffer overflow.

437

*/

438

class ConcurrentWebSocketSessionDecorator extends WebSocketSessionDecorator {

439

/**

440

* Create concurrent session decorator with limits.

441

* @param delegate the session to decorate

442

* @param sendTimeLimit max time to wait for send operation (ms)

443

* @param bufferSizeLimit max buffer size for queued messages

444

*/

445

public ConcurrentWebSocketSessionDecorator(WebSocketSession delegate, int sendTimeLimit, int bufferSizeLimit);

446

447

/**

448

* Get the send time limit.

449

* @return send time limit in milliseconds

450

*/

451

public int getSendTimeLimit();

452

453

/**

454

* Get the buffer size limit.

455

* @return buffer size limit in bytes

456

*/

457

public int getBufferSizeLimit();

458

459

/**

460

* Thread-safe send implementation with flow control.

461

* @param message the message to send

462

* @throws IOException if sending fails

463

* @throws SessionLimitExceededException if limits are exceeded

464

*/

465

@Override

466

public void sendMessage(WebSocketMessage<?> message) throws IOException;

467

}

468

469

/**

470

* Exception thrown when session limits are exceeded.

471

*/

472

class SessionLimitExceededException extends IOException {

473

public SessionLimitExceededException(String message);

474

}

475

```

476

477

**Usage Example:**

478

479

```java

480

@Component

481

public class HighThroughputHandler extends AbstractWebSocketHandler {

482

483

@Override

484

public void afterConnectionEstablished(WebSocketSession session) throws Exception {

485

// Wrap session with concurrent decorator for thread safety

486

WebSocketSession concurrentSession = new ConcurrentWebSocketSessionDecorator(

487

session,

488

10000, // 10 second send timeout

489

64 * 1024 // 64KB buffer limit

490

);

491

492

// Store decorated session for use in other threads

493

session.getAttributes().put("concurrent.session", concurrentSession);

494

}

495

496

@Override

497

protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {

498

// Get concurrent session for thread-safe operations

499

WebSocketSession concurrentSession = (WebSocketSession)

500

session.getAttributes().get("concurrent.session");

501

502

// Process message in background thread

503

CompletableFuture.runAsync(() -> {

504

try {

505

String result = processLongRunningTask(message.getPayload());

506

concurrentSession.sendMessage(new TextMessage(result));

507

} catch (IOException e) {

508

handleSendError(session, e);

509

}

510

});

511

512

// Send immediate acknowledgment

513

session.sendMessage(new TextMessage("Processing..."));

514

}

515

516

private void handleSendError(WebSocketSession session, IOException e) {

517

if (e instanceof SessionLimitExceededException) {

518

logger.warn("Session buffer limit exceeded for {}", session.getId());

519

try {

520

session.close(CloseStatus.POLICY_VIOLATION.withReason("Send buffer overflow"));

521

} catch (IOException closeEx) {

522

logger.error("Failed to close session", closeEx);

523

}

524

}

525

}

526

}

527

```