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

server-integration.mddocs/

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

```