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
```