0
# Server Integration
1
2
Server-side WebSocket support with handshake handling and upgrade strategies for different server implementations.
3
4
## Capabilities
5
6
### Handshake Handler Interface
7
8
Contract for processing WebSocket handshake requests and performing the protocol upgrade.
9
10
```java { .api }
11
/**
12
* Contract for processing WebSocket handshake requests.
13
* Implementations handle the HTTP to WebSocket protocol upgrade.
14
*/
15
interface HandshakeHandler {
16
/**
17
* Perform the WebSocket handshake.
18
* @param request HTTP request for the handshake
19
* @param response HTTP response for the handshake
20
* @param wsHandler WebSocket handler for the connection
21
* @param attributes attributes to associate with the session
22
* @return true if handshake was successful
23
* @throws HandshakeFailureException if handshake fails
24
*/
25
boolean doHandshake(
26
ServerHttpRequest request,
27
ServerHttpResponse response,
28
WebSocketHandler wsHandler,
29
Map<String, Object> attributes
30
) throws HandshakeFailureException;
31
}
32
```
33
34
**Usage Example:**
35
36
```java
37
@Component
38
public class CustomHandshakeHandler implements HandshakeHandler {
39
40
@Override
41
public boolean doHandshake(
42
ServerHttpRequest request,
43
ServerHttpResponse response,
44
WebSocketHandler wsHandler,
45
Map<String, Object> attributes) throws HandshakeFailureException {
46
47
// Extract user information from request
48
String userId = extractUserId(request);
49
if (userId == null) {
50
throw new HandshakeFailureException("User ID required");
51
}
52
53
// Validate user permissions
54
if (!hasWebSocketPermission(userId)) {
55
throw new HandshakeFailureException("Insufficient permissions");
56
}
57
58
// Add user info to session attributes
59
attributes.put("userId", userId);
60
attributes.put("connectTime", System.currentTimeMillis());
61
62
// Set custom response headers
63
response.getHeaders().add("X-WebSocket-Server", "Spring");
64
65
return true; // Proceed with handshake
66
}
67
68
private String extractUserId(ServerHttpRequest request) {
69
// Extract from query parameter
70
String userId = request.getURI().getQuery();
71
if (userId != null && userId.startsWith("userId=")) {
72
return userId.substring("userId=".length());
73
}
74
75
// Extract from header
76
List<String> authHeaders = request.getHeaders().get("Authorization");
77
if (authHeaders != null && !authHeaders.isEmpty()) {
78
return parseUserIdFromToken(authHeaders.get(0));
79
}
80
81
return null;
82
}
83
}
84
```
85
86
### Handshake Interceptor Interface
87
88
Contract for intercepting WebSocket handshake requests to add custom logic before and after handshake.
89
90
```java { .api }
91
/**
92
* Contract for intercepting WebSocket handshake requests.
93
* Allows adding custom logic before and after the handshake process.
94
*/
95
interface HandshakeInterceptor {
96
/**
97
* Called before the handshake is processed.
98
* @param request HTTP request
99
* @param response HTTP response
100
* @param wsHandler WebSocket handler
101
* @param attributes session attributes
102
* @return true to proceed with handshake, false to abort
103
* @throws Exception if an error occurs
104
*/
105
boolean beforeHandshake(
106
ServerHttpRequest request,
107
ServerHttpResponse response,
108
WebSocketHandler wsHandler,
109
Map<String, Object> attributes
110
) throws Exception;
111
112
/**
113
* Called after the handshake is processed.
114
* @param request HTTP request
115
* @param response HTTP response
116
* @param wsHandler WebSocket handler
117
* @param exception exception that occurred during handshake (null if successful)
118
*/
119
void afterHandshake(
120
ServerHttpRequest request,
121
ServerHttpResponse response,
122
WebSocketHandler wsHandler,
123
Exception exception
124
);
125
}
126
```
127
128
**Usage Example:**
129
130
```java
131
@Component
132
public class AuthenticationInterceptor implements HandshakeInterceptor {
133
134
private final AuthenticationService authService;
135
136
public AuthenticationInterceptor(AuthenticationService authService) {
137
this.authService = authService;
138
}
139
140
@Override
141
public boolean beforeHandshake(
142
ServerHttpRequest request,
143
ServerHttpResponse response,
144
WebSocketHandler wsHandler,
145
Map<String, Object> attributes) throws Exception {
146
147
// Check authentication token
148
String token = extractToken(request);
149
if (token == null) {
150
response.setStatusCode(HttpStatus.UNAUTHORIZED);
151
return false;
152
}
153
154
// Validate token and get user
155
User user = authService.validateToken(token);
156
if (user == null) {
157
response.setStatusCode(HttpStatus.UNAUTHORIZED);
158
return false;
159
}
160
161
// Store user in session attributes
162
attributes.put("user", user);
163
attributes.put("roles", user.getRoles());
164
165
// Log successful authentication
166
logger.info("WebSocket authentication successful for user: {}", user.getUsername());
167
168
return true;
169
}
170
171
@Override
172
public void afterHandshake(
173
ServerHttpRequest request,
174
ServerHttpResponse response,
175
WebSocketHandler wsHandler,
176
Exception exception) {
177
178
if (exception != null) {
179
logger.error("WebSocket handshake failed: {}", exception.getMessage());
180
} else {
181
logger.info("WebSocket handshake completed successfully");
182
}
183
}
184
185
private String extractToken(ServerHttpRequest request) {
186
// Try Authorization header first
187
List<String> authHeaders = request.getHeaders().get("Authorization");
188
if (authHeaders != null && !authHeaders.isEmpty()) {
189
String auth = authHeaders.get(0);
190
if (auth.startsWith("Bearer ")) {
191
return auth.substring(7);
192
}
193
}
194
195
// Try query parameter
196
String query = request.getURI().getQuery();
197
if (query != null) {
198
String[] params = query.split("&");
199
for (String param : params) {
200
if (param.startsWith("token=")) {
201
return param.substring(6);
202
}
203
}
204
}
205
206
return null;
207
}
208
}
209
210
@Component
211
public class RateLimitInterceptor implements HandshakeInterceptor {
212
213
private final RateLimitService rateLimitService;
214
215
public RateLimitInterceptor(RateLimitService rateLimitService) {
216
this.rateLimitService = rateLimitService;
217
}
218
219
@Override
220
public boolean beforeHandshake(
221
ServerHttpRequest request,
222
ServerHttpResponse response,
223
WebSocketHandler wsHandler,
224
Map<String, Object> attributes) throws Exception {
225
226
String clientIp = getClientIp(request);
227
228
if (!rateLimitService.isAllowed(clientIp)) {
229
response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
230
response.getHeaders().add("X-RateLimit-Retry-After", "60");
231
return false;
232
}
233
234
rateLimitService.recordRequest(clientIp);
235
return true;
236
}
237
238
@Override
239
public void afterHandshake(
240
ServerHttpRequest request,
241
ServerHttpResponse response,
242
WebSocketHandler wsHandler,
243
Exception exception) {
244
// No action needed after handshake
245
}
246
}
247
```
248
249
### Request Upgrade Strategy Interface
250
251
Strategy interface for upgrading HTTP requests to WebSocket protocol for different server implementations.
252
253
```java { .api }
254
/**
255
* Strategy for upgrading HTTP requests to WebSocket protocol.
256
* Implementations handle server-specific WebSocket upgrade procedures.
257
*/
258
interface RequestUpgradeStrategy {
259
/**
260
* Get supported WebSocket versions.
261
* @return array of supported version strings
262
*/
263
String[] getSupportedVersions();
264
265
/**
266
* Get supported WebSocket extensions for the request.
267
* @param request HTTP request
268
* @return list of supported extensions
269
*/
270
List<WebSocketExtension> getSupportedExtensions(ServerHttpRequest request);
271
272
/**
273
* Perform the protocol upgrade from HTTP to WebSocket.
274
* @param request HTTP request
275
* @param response HTTP response
276
* @param selectedProtocol negotiated sub-protocol
277
* @param selectedExtensions negotiated extensions
278
* @param user authenticated user principal
279
* @param wsHandler WebSocket handler
280
* @param attrs session attributes
281
* @throws HandshakeFailureException if upgrade fails
282
*/
283
void upgrade(
284
ServerHttpRequest request,
285
ServerHttpResponse response,
286
String selectedProtocol,
287
List<WebSocketExtension> selectedExtensions,
288
Principal user,
289
WebSocketHandler wsHandler,
290
Map<String, Object> attrs
291
) throws HandshakeFailureException;
292
}
293
```
294
295
### Built-in Handshake Handlers
296
297
Pre-built handshake handler implementations for common scenarios.
298
299
```java { .api }
300
/**
301
* Base implementation of HandshakeHandler with protocol negotiation support.
302
* Provides common handshake logic and delegates upgrade to RequestUpgradeStrategy.
303
*/
304
abstract class AbstractHandshakeHandler implements HandshakeHandler {
305
/**
306
* Set supported sub-protocols.
307
* @param protocols array of protocol names
308
*/
309
public void setSupportedProtocols(String... protocols);
310
311
/**
312
* Get supported sub-protocols.
313
* @return array of supported protocols
314
*/
315
public String[] getSupportedProtocols();
316
317
/**
318
* Set the request upgrade strategy.
319
* @param requestUpgradeStrategy upgrade strategy implementation
320
*/
321
public void setRequestUpgradeStrategy(RequestUpgradeStrategy requestUpgradeStrategy);
322
323
/**
324
* Get the request upgrade strategy.
325
* @return upgrade strategy implementation
326
*/
327
public RequestUpgradeStrategy getRequestUpgradeStrategy();
328
329
/**
330
* Determine the user principal for the WebSocket session.
331
* @param request HTTP request
332
* @param wsHandler WebSocket handler
333
* @param attributes session attributes
334
* @return user principal or null
335
*/
336
protected abstract Principal determineUser(
337
ServerHttpRequest request,
338
WebSocketHandler wsHandler,
339
Map<String, Object> attributes
340
);
341
}
342
343
/**
344
* Default implementation of HandshakeHandler.
345
* Uses request principal as WebSocket session user.
346
*/
347
class DefaultHandshakeHandler extends AbstractHandshakeHandler {
348
/**
349
* Create default handshake handler.
350
*/
351
public DefaultHandshakeHandler();
352
353
/**
354
* Create default handshake handler with upgrade strategy.
355
* @param upgradeStrategy request upgrade strategy
356
*/
357
public DefaultHandshakeHandler(RequestUpgradeStrategy upgradeStrategy);
358
359
/**
360
* Uses the request principal as the WebSocket session user.
361
* @param request HTTP request
362
* @param wsHandler WebSocket handler
363
* @param attributes session attributes
364
* @return request principal
365
*/
366
@Override
367
protected Principal determineUser(
368
ServerHttpRequest request,
369
WebSocketHandler wsHandler,
370
Map<String, Object> attributes
371
);
372
}
373
```
374
375
**Usage Example:**
376
377
```java
378
@Configuration
379
@EnableWebSocket
380
public class WebSocketHandshakeConfig implements WebSocketConfigurer {
381
382
@Override
383
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
384
// Using default handshake handler
385
registry.addHandler(new EchoWebSocketHandler(), "/echo")
386
.setHandshakeHandler(new DefaultHandshakeHandler())
387
.setAllowedOrigins("*");
388
389
// Using custom handshake handler with specific protocols
390
DefaultHandshakeHandler chatHandler = new DefaultHandshakeHandler();
391
chatHandler.setSupportedProtocols("chat-v1", "chat-v2");
392
393
registry.addHandler(new ChatWebSocketHandler(), "/chat")
394
.setHandshakeHandler(chatHandler)
395
.addInterceptors(new AuthenticationInterceptor(authService));
396
397
// Using custom upgrade strategy for specific server
398
DefaultHandshakeHandler customHandler = new DefaultHandshakeHandler(
399
new TomcatRequestUpgradeStrategy()
400
);
401
402
registry.addHandler(new GameWebSocketHandler(), "/game")
403
.setHandshakeHandler(customHandler);
404
}
405
406
@Bean
407
public AuthenticationService authService() {
408
return new JwtAuthenticationService();
409
}
410
}
411
412
// Custom handshake handler with role-based user determination
413
public class RoleBasedHandshakeHandler extends AbstractHandshakeHandler {
414
415
@Override
416
protected Principal determineUser(
417
ServerHttpRequest request,
418
WebSocketHandler wsHandler,
419
Map<String, Object> attributes) {
420
421
// Get authenticated user from security context
422
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
423
if (auth != null && auth.isAuthenticated()) {
424
return auth;
425
}
426
427
// Fallback to request principal
428
return request.getPrincipal();
429
}
430
431
@Override
432
public boolean doHandshake(
433
ServerHttpRequest request,
434
ServerHttpResponse response,
435
WebSocketHandler wsHandler,
436
Map<String, Object> attributes) throws HandshakeFailureException {
437
438
// Check if user has required role
439
Principal user = determineUser(request, wsHandler, attributes);
440
if (user instanceof Authentication auth) {
441
boolean hasRole = auth.getAuthorities().stream()
442
.anyMatch(authority -> authority.getAuthority().equals("ROLE_WEBSOCKET"));
443
444
if (!hasRole) {
445
throw new HandshakeFailureException("Insufficient role for WebSocket access");
446
}
447
}
448
449
return super.doHandshake(request, response, wsHandler, attributes);
450
}
451
}
452
```
453
454
### Built-in Handshake Interceptors
455
456
Pre-built interceptor implementations for common use cases.
457
458
```java { .api }
459
/**
460
* Interceptor that copies HTTP session attributes to WebSocket session.
461
* Useful for maintaining session state across the protocol upgrade.
462
*/
463
class HttpSessionHandshakeInterceptor implements HandshakeInterceptor {
464
/**
465
* Create interceptor that copies all HTTP session attributes.
466
*/
467
public HttpSessionHandshakeInterceptor();
468
469
/**
470
* Create interceptor that copies specific HTTP session attributes.
471
* @param attributeNames names of attributes to copy
472
*/
473
public HttpSessionHandshakeInterceptor(Collection<String> attributeNames);
474
475
/**
476
* Set specific attribute names to copy.
477
* @param attributeNames attribute names
478
*/
479
public void setAttributeNames(Collection<String> attributeNames);
480
481
/**
482
* Get attribute names to copy.
483
* @return collection of attribute names
484
*/
485
public Collection<String> getAttributeNames();
486
487
/**
488
* Set whether to copy all HTTP session attributes.
489
* @param copyAllAttributes true to copy all attributes
490
*/
491
public void setCopyAllAttributes(boolean copyAllAttributes);
492
493
/**
494
* Check if copying all attributes.
495
* @return true if copying all attributes
496
*/
497
public boolean isCopyAllAttributes();
498
499
/**
500
* Set whether to copy HTTP session ID.
501
* @param copyHttpSessionId true to copy session ID
502
*/
503
public void setCopyHttpSessionId(boolean copyHttpSessionId);
504
505
/**
506
* Check if copying HTTP session ID.
507
* @return true if copying session ID
508
*/
509
public boolean isCopyHttpSessionId();
510
}
511
512
/**
513
* Interceptor that checks request origin against allowed origins.
514
* Provides CORS-like origin validation for WebSocket handshakes.
515
*/
516
class OriginHandshakeInterceptor implements HandshakeInterceptor {
517
/**
518
* Create interceptor with no origin restrictions.
519
*/
520
public OriginHandshakeInterceptor();
521
522
/**
523
* Create interceptor with allowed origins.
524
* @param allowedOrigins collection of allowed origin patterns
525
*/
526
public OriginHandshakeInterceptor(Collection<String> allowedOrigins);
527
528
/**
529
* Set allowed origins.
530
* @param allowedOrigins collection of allowed origin patterns
531
*/
532
public void setAllowedOrigins(Collection<String> allowedOrigins);
533
534
/**
535
* Get allowed origins.
536
* @return collection of allowed origin patterns
537
*/
538
public Collection<String> getAllowedOrigins();
539
}
540
```
541
542
**Usage Example:**
543
544
```java
545
@Configuration
546
@EnableWebSocket
547
public class InterceptorConfig implements WebSocketConfigurer {
548
549
@Override
550
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
551
// Using HTTP session interceptor
552
HttpSessionHandshakeInterceptor sessionInterceptor =
553
new HttpSessionHandshakeInterceptor(Arrays.asList("user", "preferences"));
554
sessionInterceptor.setCopyHttpSessionId(true);
555
556
// Using origin interceptor
557
OriginHandshakeInterceptor originInterceptor =
558
new OriginHandshakeInterceptor(Arrays.asList(
559
"https://app.example.com",
560
"https://*.example.com",
561
"http://localhost:*"
562
));
563
564
registry.addHandler(new ChatWebSocketHandler(), "/chat")
565
.addInterceptors(sessionInterceptor, originInterceptor)
566
.setAllowedOrigins("*");
567
}
568
}
569
570
// Custom session attribute interceptor
571
public class UserSessionInterceptor implements HandshakeInterceptor {
572
573
@Override
574
public boolean beforeHandshake(
575
ServerHttpRequest request,
576
ServerHttpResponse response,
577
WebSocketHandler wsHandler,
578
Map<String, Object> attributes) throws Exception {
579
580
// Extract user session from HTTP session
581
if (request instanceof ServletServerHttpRequest servletRequest) {
582
HttpSession httpSession = servletRequest.getServletRequest().getSession(false);
583
if (httpSession != null) {
584
// Copy specific user attributes
585
copyAttribute(httpSession, attributes, "userId");
586
copyAttribute(httpSession, attributes, "username");
587
copyAttribute(httpSession, attributes, "roles");
588
copyAttribute(httpSession, attributes, "preferences");
589
590
// Add session metadata
591
attributes.put("httpSessionId", httpSession.getId());
592
attributes.put("sessionCreationTime", httpSession.getCreationTime());
593
attributes.put("lastAccessedTime", httpSession.getLastAccessedTime());
594
}
595
}
596
597
return true;
598
}
599
600
@Override
601
public void afterHandshake(
602
ServerHttpRequest request,
603
ServerHttpResponse response,
604
WebSocketHandler wsHandler,
605
Exception exception) {
606
// No action needed
607
}
608
609
private void copyAttribute(HttpSession httpSession, Map<String, Object> attributes, String name) {
610
Object value = httpSession.getAttribute(name);
611
if (value != null) {
612
attributes.put(name, value);
613
}
614
}
615
}
616
```
617
618
### Server-Specific Upgrade Strategies
619
620
Upgrade strategy implementations for different WebSocket server implementations.
621
622
```java { .api }
623
/**
624
* Base upgrade strategy for JSR-356 standard WebSocket implementations.
625
*/
626
abstract class AbstractStandardUpgradeStrategy implements RequestUpgradeStrategy {
627
/**
628
* Get the WebSocket container.
629
* @return JSR-356 WebSocket container
630
*/
631
protected abstract WebSocketContainer getContainer();
632
}
633
634
/**
635
* Upgrade strategy for Apache Tomcat WebSocket implementation.
636
*/
637
class TomcatRequestUpgradeStrategy extends AbstractStandardUpgradeStrategy {
638
public TomcatRequestUpgradeStrategy();
639
}
640
641
/**
642
* Upgrade strategy for JBoss Undertow WebSocket implementation.
643
*/
644
class UndertowRequestUpgradeStrategy implements RequestUpgradeStrategy {
645
public UndertowRequestUpgradeStrategy();
646
}
647
648
/**
649
* Upgrade strategy for Eclipse Jetty WebSocket implementation.
650
*/
651
class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy {
652
public JettyRequestUpgradeStrategy();
653
}
654
655
/**
656
* Upgrade strategy for GlassFish WebSocket implementation.
657
*/
658
class GlassFishRequestUpgradeStrategy extends AbstractStandardUpgradeStrategy {
659
public GlassFishRequestUpgradeStrategy();
660
}
661
662
/**
663
* Upgrade strategy for Oracle WebLogic WebSocket implementation.
664
*/
665
class WebLogicRequestUpgradeStrategy extends AbstractStandardUpgradeStrategy {
666
public WebLogicRequestUpgradeStrategy();
667
}
668
669
/**
670
* Upgrade strategy for IBM WebSphere WebSocket implementation.
671
*/
672
class WebSphereRequestUpgradeStrategy implements RequestUpgradeStrategy {
673
public WebSphereRequestUpgradeStrategy();
674
}
675
```
676
677
### Exception Handling
678
679
Exception classes for WebSocket handshake failures.
680
681
```java { .api }
682
/**
683
* Exception thrown when WebSocket handshake fails.
684
*/
685
class HandshakeFailureException extends Exception {
686
/**
687
* Create exception with message.
688
* @param message error message
689
*/
690
public HandshakeFailureException(String message);
691
692
/**
693
* Create exception with message and cause.
694
* @param message error message
695
* @param cause underlying cause
696
*/
697
public HandshakeFailureException(String message, Throwable cause);
698
}
699
```