0
# Interceptor System
1
2
Request/response processing pipeline with built-in interceptors for CORS, heartbeat, protocol handling, and custom processing logic. Interceptors provide a flexible way to process and modify requests and responses.
3
4
## Capabilities
5
6
### AtmosphereInterceptor Interface
7
8
Core interface for intercepting and processing Atmosphere requests with priority-based execution order.
9
10
```java { .api }
11
/**
12
* Request/response interception and modification
13
*/
14
public interface AtmosphereInterceptor {
15
/**
16
* Intercept incoming AtmosphereResource before processing
17
* @param resource AtmosphereResource to intercept
18
* @return Action indicating how to proceed
19
*/
20
public Action intercept(AtmosphereResource resource);
21
22
/**
23
* Post-process AtmosphereResource after main processing
24
* @param resource AtmosphereResource after processing
25
*/
26
public void postInspect(AtmosphereResource resource);
27
28
/**
29
* Configure interceptor with AtmosphereConfig
30
* @param config AtmosphereConfig instance
31
*/
32
public void configure(AtmosphereConfig config);
33
34
/**
35
* Get execution priority (lower numbers execute first)
36
* @return priority value
37
*/
38
public int priority();
39
40
/**
41
* Destroy interceptor and cleanup resources
42
*/
43
public void destroy();
44
45
/**
46
* Get supported protocols for this interceptor
47
* @return list of supported protocol names
48
*/
49
public List<String> supportedProtocols();
50
}
51
52
/**
53
* Action result from interceptor processing
54
*/
55
public enum Action {
56
CONTINUE, // Continue processing with next interceptor
57
SUSPEND, // Suspend processing at this point
58
RESUME, // Resume processing
59
CANCELLED, // Cancel request processing
60
SKIP_ATMOSPHEREHANDLER, // Skip AtmosphereHandler execution
61
CREATED // Resource was created by interceptor
62
}
63
```
64
65
**Usage Examples:**
66
67
```java
68
@AtmosphereInterceptorService
69
public class LoggingInterceptor implements AtmosphereInterceptor {
70
71
@Override
72
public Action intercept(AtmosphereResource resource) {
73
AtmosphereRequest request = resource.getRequest();
74
75
System.out.println("Intercepting request: " +
76
request.getMethod() + " " + request.getPathInfo());
77
78
// Log request headers
79
Enumeration<String> headers = request.getHeaderNames();
80
while (headers.hasMoreElements()) {
81
String headerName = headers.nextElement();
82
System.out.println("Header: " + headerName + " = " +
83
request.getHeader(headerName));
84
}
85
86
return Action.CONTINUE;
87
}
88
89
@Override
90
public void postInspect(AtmosphereResource resource) {
91
System.out.println("Post-processing request for: " +
92
resource.getRequest().getPathInfo());
93
}
94
95
@Override
96
public int priority() {
97
return 1000; // Low priority, execute after security interceptors
98
}
99
}
100
```
101
102
### Built-in Interceptors
103
104
Pre-built interceptors for common cross-cutting concerns and protocol handling.
105
106
```java { .api }
107
/**
108
* Handle Cross-Origin Resource Sharing (CORS)
109
*/
110
public class CorsInterceptor implements AtmosphereInterceptor {
111
/**
112
* Set allowed origins for CORS
113
* @param origins comma-separated list of allowed origins
114
* @return this interceptor
115
*/
116
public CorsInterceptor setAllowedOrigins(String origins);
117
118
/**
119
* Set allowed HTTP methods
120
* @param methods comma-separated list of allowed methods
121
* @return this interceptor
122
*/
123
public CorsInterceptor setAllowedMethods(String methods);
124
125
/**
126
* Set allowed headers
127
* @param headers comma-separated list of allowed headers
128
* @return this interceptor
129
*/
130
public CorsInterceptor setAllowedHeaders(String headers);
131
132
/**
133
* Enable credentials support
134
* @param allowCredentials true to allow credentials
135
* @return this interceptor
136
*/
137
public CorsInterceptor setAllowCredentials(boolean allowCredentials);
138
}
139
140
/**
141
* Implement heartbeat mechanism for connection monitoring
142
*/
143
public class HeartbeatInterceptor implements AtmosphereInterceptor {
144
/**
145
* Set heartbeat interval
146
* @param interval heartbeat interval in milliseconds
147
* @return this interceptor
148
*/
149
public HeartbeatInterceptor setHeartbeatInterval(long interval);
150
151
/**
152
* Set client heartbeat data
153
* @param heartbeatData data to send for heartbeat
154
* @return this interceptor
155
*/
156
public HeartbeatInterceptor setHeartbeatData(String heartbeatData);
157
}
158
159
/**
160
* Server-Sent Events protocol support
161
*/
162
public class SSEAtmosphereInterceptor implements AtmosphereInterceptor {
163
/**
164
* Set whether to pad SSE messages
165
* @param padding true to enable padding
166
* @return this interceptor
167
*/
168
public SSEAtmosphereInterceptor setPadding(boolean padding);
169
}
170
171
/**
172
* JSONP protocol support for cross-domain requests
173
*/
174
public class JSONPAtmosphereInterceptor implements AtmosphereInterceptor {
175
/**
176
* Set JSONP callback parameter name
177
* @param callbackName parameter name for callback
178
* @return this interceptor
179
*/
180
public JSONPAtmosphereInterceptor setCallbackName(String callbackName);
181
}
182
183
/**
184
* Set appropriate cache headers for responses
185
*/
186
public class CacheHeadersInterceptor implements AtmosphereInterceptor {
187
/**
188
* Set cache control directives
189
* @param cacheControl cache control header value
190
* @return this interceptor
191
*/
192
public CacheHeadersInterceptor setCacheControl(String cacheControl);
193
}
194
195
/**
196
* Handle idle resource cleanup
197
*/
198
public class IdleResourceInterceptor implements AtmosphereInterceptor {
199
/**
200
* Set idle timeout for resources
201
* @param idleTimeout timeout in milliseconds
202
* @return this interceptor
203
*/
204
public IdleResourceInterceptor setIdleTimeout(long idleTimeout);
205
}
206
207
/**
208
* Manage AtmosphereResource lifecycle events
209
*/
210
public class AtmosphereResourceLifecycleInterceptor implements AtmosphereInterceptor {
211
/**
212
* Enable lifecycle event broadcasting
213
* @param broadcastLifecycleEvents true to broadcast events
214
* @return this interceptor
215
*/
216
public AtmosphereResourceLifecycleInterceptor setBroadcastLifecycleEvents(boolean broadcastLifecycleEvents);
217
}
218
219
/**
220
* Handle client disconnection events
221
*/
222
public class OnDisconnectInterceptor implements AtmosphereInterceptor {
223
/**
224
* Set disconnection message
225
* @param disconnectMessage message to send on disconnect
226
* @return this interceptor
227
*/
228
public OnDisconnectInterceptor setDisconnectMessage(String disconnectMessage);
229
}
230
```
231
232
**Usage Examples:**
233
234
```java
235
// Configure CORS interceptor
236
CorsInterceptor corsInterceptor = new CorsInterceptor()
237
.setAllowedOrigins("http://localhost:3000,https://myapp.com")
238
.setAllowedMethods("GET,POST,PUT,DELETE")
239
.setAllowedHeaders("Content-Type,Authorization")
240
.setAllowCredentials(true);
241
242
// Configure heartbeat interceptor
243
HeartbeatInterceptor heartbeatInterceptor = new HeartbeatInterceptor()
244
.setHeartbeatInterval(30000) // 30 seconds
245
.setHeartbeatData("ping");
246
247
// Configure SSE interceptor
248
SSEAtmosphereInterceptor sseInterceptor = new SSEAtmosphereInterceptor()
249
.setPadding(true);
250
251
// Add interceptors to framework
252
AtmosphereFramework framework = new AtmosphereFramework();
253
framework.intercept(corsInterceptor)
254
.intercept(heartbeatInterceptor)
255
.intercept(sseInterceptor);
256
```
257
258
### Custom Interceptor Development
259
260
Base classes and utilities for developing custom interceptors.
261
262
```java { .api }
263
/**
264
* Adapter implementation providing default interceptor behavior
265
*/
266
public class AtmosphereInterceptorAdapter implements AtmosphereInterceptor {
267
/**
268
* Default intercept implementation (CONTINUE)
269
* @param resource AtmosphereResource
270
* @return Action.CONTINUE
271
*/
272
public Action intercept(AtmosphereResource resource) {
273
return Action.CONTINUE;
274
}
275
276
/**
277
* Default post-inspect implementation (no-op)
278
* @param resource AtmosphereResource
279
*/
280
public void postInspect(AtmosphereResource resource) {
281
// Default: do nothing
282
}
283
284
/**
285
* Default priority (medium priority)
286
* @return 1000
287
*/
288
public int priority() {
289
return 1000;
290
}
291
292
/**
293
* Default configuration (no-op)
294
* @param config AtmosphereConfig
295
*/
296
public void configure(AtmosphereConfig config) {
297
// Default: do nothing
298
}
299
300
/**
301
* Default destroy (no-op)
302
*/
303
public void destroy() {
304
// Default: do nothing
305
}
306
}
307
```
308
309
**Usage Examples:**
310
311
```java
312
// Authentication interceptor
313
@AtmosphereInterceptorService
314
public class AuthenticationInterceptor extends AtmosphereInterceptorAdapter {
315
316
@Override
317
public Action intercept(AtmosphereResource resource) {
318
AtmosphereRequest request = resource.getRequest();
319
320
// Check for authentication token
321
String authHeader = request.getHeader("Authorization");
322
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
323
// Send 401 Unauthorized
324
try {
325
resource.getResponse().setStatus(401);
326
resource.getResponse().getWriter().write("Unauthorized");
327
resource.getResponse().flushBuffer();
328
return Action.CANCELLED;
329
} catch (IOException e) {
330
return Action.CANCELLED;
331
}
332
}
333
334
// Validate token
335
String token = authHeader.substring(7);
336
if (!isValidToken(token)) {
337
try {
338
resource.getResponse().setStatus(403);
339
resource.getResponse().getWriter().write("Invalid token");
340
resource.getResponse().flushBuffer();
341
return Action.CANCELLED;
342
} catch (IOException e) {
343
return Action.CANCELLED;
344
}
345
}
346
347
// Add user info to request attributes
348
User user = getUserFromToken(token);
349
request.setAttribute("user", user);
350
351
return Action.CONTINUE;
352
}
353
354
@Override
355
public int priority() {
356
return 100; // High priority - execute early
357
}
358
359
private boolean isValidToken(String token) {
360
// Token validation logic
361
return token != null && token.length() > 10;
362
}
363
364
private User getUserFromToken(String token) {
365
// Extract user from token
366
return new User("user123", "John Doe");
367
}
368
}
369
370
// Rate limiting interceptor
371
@AtmosphereInterceptorService
372
public class RateLimitInterceptor extends AtmosphereInterceptorAdapter {
373
private final Map<String, TokenBucket> clientBuckets = new ConcurrentHashMap<>();
374
private final int maxRequestsPerMinute = 60;
375
376
@Override
377
public Action intercept(AtmosphereResource resource) {
378
String clientIp = resource.getRequest().getRemoteAddr();
379
380
// Get or create token bucket for client
381
TokenBucket bucket = clientBuckets.computeIfAbsent(clientIp,
382
k -> new TokenBucket(maxRequestsPerMinute, 1, TimeUnit.MINUTES));
383
384
// Check if request is allowed
385
if (!bucket.tryConsume()) {
386
try {
387
resource.getResponse().setStatus(429); // Too Many Requests
388
resource.getResponse().setHeader("Retry-After", "60");
389
resource.getResponse().getWriter().write("Rate limit exceeded");
390
resource.getResponse().flushBuffer();
391
return Action.CANCELLED;
392
} catch (IOException e) {
393
return Action.CANCELLED;
394
}
395
}
396
397
return Action.CONTINUE;
398
}
399
400
@Override
401
public int priority() {
402
return 200; // Execute after authentication but before main processing
403
}
404
}
405
406
// Request transformation interceptor
407
@AtmosphereInterceptorService
408
public class RequestTransformInterceptor extends AtmosphereInterceptorAdapter {
409
410
@Override
411
public Action intercept(AtmosphereResource resource) {
412
AtmosphereRequest request = resource.getRequest();
413
414
// Transform specific paths
415
String pathInfo = request.getPathInfo();
416
if (pathInfo != null && pathInfo.startsWith("/legacy/")) {
417
// Rewrite legacy paths to new format
418
String newPath = pathInfo.replace("/legacy/", "/api/v2/");
419
420
// Create new request with transformed path
421
AtmosphereRequest.Builder builder = new AtmosphereRequest.Builder();
422
builder.request(request).pathInfo(newPath);
423
424
// Replace request in resource
425
resource.setRequest(builder.build());
426
}
427
428
// Add custom headers
429
resource.getResponse().setHeader("X-Processed-By", "Atmosphere");
430
resource.getResponse().setHeader("X-Request-Id", UUID.randomUUID().toString());
431
432
return Action.CONTINUE;
433
}
434
435
@Override
436
public void postInspect(AtmosphereResource resource) {
437
// Add processing time header
438
long processingTime = System.currentTimeMillis() -
439
(Long) resource.getRequest().getAttribute("startTime");
440
resource.getResponse().setHeader("X-Processing-Time", String.valueOf(processingTime));
441
}
442
443
@Override
444
public int priority() {
445
return 500; // Medium priority
446
}
447
}
448
```
449
450
### Interceptor Chain Management
451
452
Utilities for managing and configuring interceptor execution chains.
453
454
```java { .api }
455
/**
456
* Interceptor chain configuration and management
457
*/
458
public class InterceptorManager {
459
/**
460
* Add interceptor to global chain
461
* @param interceptor AtmosphereInterceptor to add
462
*/
463
public void addInterceptor(AtmosphereInterceptor interceptor);
464
465
/**
466
* Remove interceptor from chain
467
* @param interceptor AtmosphereInterceptor to remove
468
*/
469
public void removeInterceptor(AtmosphereInterceptor interceptor);
470
471
/**
472
* Get all registered interceptors ordered by priority
473
* @return List of interceptors in execution order
474
*/
475
public List<AtmosphereInterceptor> getInterceptors();
476
477
/**
478
* Clear all interceptors
479
*/
480
public void clearInterceptors();
481
}
482
```
483
484
### Async I/O Interceptors
485
486
Special interceptors for handling asynchronous I/O operations.
487
488
```java { .api }
489
/**
490
* Intercept async I/O operations
491
*/
492
public interface AsyncIOInterceptor {
493
/**
494
* Intercept before payload is written
495
* @param resource AtmosphereResource
496
* @param data payload being written
497
* @param offset write offset
498
* @param length write length
499
* @return modified payload or original data
500
*/
501
public byte[] prePayload(AtmosphereResource resource, byte[] data, int offset, int length);
502
503
/**
504
* Intercept after payload is written
505
* @param resource AtmosphereResource
506
* @param data payload that was written
507
* @param offset write offset
508
* @param length write length
509
*/
510
public void postPayload(AtmosphereResource resource, byte[] data, int offset, int length);
511
512
/**
513
* Configure async I/O interceptor
514
* @param config AtmosphereConfig
515
*/
516
public void configure(AtmosphereConfig config);
517
}
518
519
/**
520
* Adapter for AsyncIOInterceptor with default implementations
521
*/
522
public class AsyncIOInterceptorAdapter implements AsyncIOInterceptor {
523
/**
524
* Default pre-payload processing (return original data)
525
*/
526
public byte[] prePayload(AtmosphereResource resource, byte[] data, int offset, int length) {
527
return data;
528
}
529
530
/**
531
* Default post-payload processing (no-op)
532
*/
533
public void postPayload(AtmosphereResource resource, byte[] data, int offset, int length) {
534
// Default: do nothing
535
}
536
}
537
```
538
539
**Usage Examples:**
540
541
```java
542
// Compression interceptor for async I/O
543
public class CompressionAsyncIOInterceptor extends AsyncIOInterceptorAdapter {
544
545
@Override
546
public byte[] prePayload(AtmosphereResource resource, byte[] data, int offset, int length) {
547
// Check if client supports compression
548
String acceptEncoding = resource.getRequest().getHeader("Accept-Encoding");
549
if (acceptEncoding != null && acceptEncoding.contains("gzip")) {
550
551
try {
552
// Compress data using GZIP
553
ByteArrayOutputStream baos = new ByteArrayOutputStream();
554
try (GZIPOutputStream gzipOut = new GZIPOutputStream(baos)) {
555
gzipOut.write(data, offset, length);
556
}
557
558
// Set compression headers
559
resource.getResponse().setHeader("Content-Encoding", "gzip");
560
resource.getResponse().setHeader("Vary", "Accept-Encoding");
561
562
byte[] compressed = baos.toByteArray();
563
System.out.println("Compressed " + length + " bytes to " + compressed.length + " bytes");
564
565
return compressed;
566
567
} catch (IOException e) {
568
System.err.println("Compression failed: " + e.getMessage());
569
return data; // Return original data on error
570
}
571
}
572
573
return data; // No compression
574
}
575
576
@Override
577
public void postPayload(AtmosphereResource resource, byte[] data, int offset, int length) {
578
// Log compression statistics
579
String contentEncoding = resource.getResponse().getHeader("Content-Encoding");
580
if ("gzip".equals(contentEncoding)) {
581
System.out.println("Sent compressed payload: " + length + " bytes");
582
}
583
}
584
}
585
```
586
587
### Complete Interceptor Example
588
589
Comprehensive example showing multiple interceptors working together in a real application.
590
591
```java
592
// Security interceptor chain
593
@AtmosphereInterceptorService
594
public class SecurityInterceptorChain extends AtmosphereInterceptorAdapter {
595
private final AuthenticationService authService;
596
private final AuditService auditService;
597
598
public SecurityInterceptorChain() {
599
this.authService = new AuthenticationService();
600
this.auditService = new AuditService();
601
}
602
603
@Override
604
public Action intercept(AtmosphereResource resource) {
605
AtmosphereRequest request = resource.getRequest();
606
String path = request.getPathInfo();
607
608
// Skip authentication for public endpoints
609
if (isPublicEndpoint(path)) {
610
return Action.CONTINUE;
611
}
612
613
// Authenticate request
614
String authResult = authenticateRequest(request);
615
if (authResult == null) {
616
sendUnauthorizedResponse(resource);
617
return Action.CANCELLED;
618
}
619
620
// Check authorization
621
if (!isAuthorized(authResult, path, request.getMethod())) {
622
sendForbiddenResponse(resource);
623
return Action.CANCELLED;
624
}
625
626
// Add security context to request
627
request.setAttribute("securityContext", createSecurityContext(authResult));
628
629
// Audit the request
630
auditService.logAccess(authResult, path, request.getMethod(),
631
request.getRemoteAddr());
632
633
return Action.CONTINUE;
634
}
635
636
@Override
637
public void postInspect(AtmosphereResource resource) {
638
// Audit the response
639
AtmosphereRequest request = resource.getRequest();
640
SecurityContext context = (SecurityContext) request.getAttribute("securityContext");
641
642
if (context != null) {
643
int statusCode = resource.getResponse().getStatus();
644
auditService.logResponse(context.getUserId(), request.getPathInfo(),
645
statusCode, System.currentTimeMillis());
646
}
647
}
648
649
@Override
650
public int priority() {
651
return 50; // Very high priority - execute first
652
}
653
654
private boolean isPublicEndpoint(String path) {
655
return path != null && (path.startsWith("/public/") ||
656
path.equals("/health") ||
657
path.equals("/status"));
658
}
659
660
private String authenticateRequest(AtmosphereRequest request) {
661
String authHeader = request.getHeader("Authorization");
662
if (authHeader != null && authHeader.startsWith("Bearer ")) {
663
String token = authHeader.substring(7);
664
return authService.validateToken(token);
665
}
666
return null;
667
}
668
669
private boolean isAuthorized(String userId, String path, String method) {
670
return authService.checkPermission(userId, path, method);
671
}
672
673
private void sendUnauthorizedResponse(AtmosphereResource resource) {
674
try {
675
resource.getResponse().setStatus(401);
676
resource.getResponse().setContentType("application/json");
677
resource.getResponse().getWriter()
678
.write("{\"error\":\"Unauthorized\",\"code\":401}");
679
resource.getResponse().flushBuffer();
680
} catch (IOException e) {
681
// Log error
682
}
683
}
684
685
private void sendForbiddenResponse(AtmosphereResource resource) {
686
try {
687
resource.getResponse().setStatus(403);
688
resource.getResponse().setContentType("application/json");
689
resource.getResponse().getWriter()
690
.write("{\"error\":\"Forbidden\",\"code\":403}");
691
resource.getResponse().flushBuffer();
692
} catch (IOException e) {
693
// Log error
694
}
695
}
696
697
private SecurityContext createSecurityContext(String userId) {
698
return new SecurityContext(userId, authService.getUserRoles(userId));
699
}
700
}
701
```