The Atmosphere Framework runtime providing comprehensive support for building real-time, event-driven web applications with transparent transport protocol support including WebSockets, Server Sent Events, Long-Polling, HTTP Streaming, and JSONP.
—
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.
Core interface for intercepting and processing Atmosphere requests with priority-based execution order.
/**
* Request/response interception and modification
*/
public interface AtmosphereInterceptor {
/**
* Intercept incoming AtmosphereResource before processing
* @param resource AtmosphereResource to intercept
* @return Action indicating how to proceed
*/
public Action intercept(AtmosphereResource resource);
/**
* Post-process AtmosphereResource after main processing
* @param resource AtmosphereResource after processing
*/
public void postInspect(AtmosphereResource resource);
/**
* Configure interceptor with AtmosphereConfig
* @param config AtmosphereConfig instance
*/
public void configure(AtmosphereConfig config);
/**
* Get execution priority (lower numbers execute first)
* @return priority value
*/
public int priority();
/**
* Destroy interceptor and cleanup resources
*/
public void destroy();
/**
* Get supported protocols for this interceptor
* @return list of supported protocol names
*/
public List<String> supportedProtocols();
}
/**
* Action result from interceptor processing
*/
public enum Action {
CONTINUE, // Continue processing with next interceptor
SUSPEND, // Suspend processing at this point
RESUME, // Resume processing
CANCELLED, // Cancel request processing
SKIP_ATMOSPHEREHANDLER, // Skip AtmosphereHandler execution
CREATED // Resource was created by interceptor
}Usage Examples:
@AtmosphereInterceptorService
public class LoggingInterceptor implements AtmosphereInterceptor {
@Override
public Action intercept(AtmosphereResource resource) {
AtmosphereRequest request = resource.getRequest();
System.out.println("Intercepting request: " +
request.getMethod() + " " + request.getPathInfo());
// Log request headers
Enumeration<String> headers = request.getHeaderNames();
while (headers.hasMoreElements()) {
String headerName = headers.nextElement();
System.out.println("Header: " + headerName + " = " +
request.getHeader(headerName));
}
return Action.CONTINUE;
}
@Override
public void postInspect(AtmosphereResource resource) {
System.out.println("Post-processing request for: " +
resource.getRequest().getPathInfo());
}
@Override
public int priority() {
return 1000; // Low priority, execute after security interceptors
}
}Pre-built interceptors for common cross-cutting concerns and protocol handling.
/**
* Handle Cross-Origin Resource Sharing (CORS)
*/
public class CorsInterceptor implements AtmosphereInterceptor {
/**
* Set allowed origins for CORS
* @param origins comma-separated list of allowed origins
* @return this interceptor
*/
public CorsInterceptor setAllowedOrigins(String origins);
/**
* Set allowed HTTP methods
* @param methods comma-separated list of allowed methods
* @return this interceptor
*/
public CorsInterceptor setAllowedMethods(String methods);
/**
* Set allowed headers
* @param headers comma-separated list of allowed headers
* @return this interceptor
*/
public CorsInterceptor setAllowedHeaders(String headers);
/**
* Enable credentials support
* @param allowCredentials true to allow credentials
* @return this interceptor
*/
public CorsInterceptor setAllowCredentials(boolean allowCredentials);
}
/**
* Implement heartbeat mechanism for connection monitoring
*/
public class HeartbeatInterceptor implements AtmosphereInterceptor {
/**
* Set heartbeat interval
* @param interval heartbeat interval in milliseconds
* @return this interceptor
*/
public HeartbeatInterceptor setHeartbeatInterval(long interval);
/**
* Set client heartbeat data
* @param heartbeatData data to send for heartbeat
* @return this interceptor
*/
public HeartbeatInterceptor setHeartbeatData(String heartbeatData);
}
/**
* Server-Sent Events protocol support
*/
public class SSEAtmosphereInterceptor implements AtmosphereInterceptor {
/**
* Set whether to pad SSE messages
* @param padding true to enable padding
* @return this interceptor
*/
public SSEAtmosphereInterceptor setPadding(boolean padding);
}
/**
* JSONP protocol support for cross-domain requests
*/
public class JSONPAtmosphereInterceptor implements AtmosphereInterceptor {
/**
* Set JSONP callback parameter name
* @param callbackName parameter name for callback
* @return this interceptor
*/
public JSONPAtmosphereInterceptor setCallbackName(String callbackName);
}
/**
* Set appropriate cache headers for responses
*/
public class CacheHeadersInterceptor implements AtmosphereInterceptor {
/**
* Set cache control directives
* @param cacheControl cache control header value
* @return this interceptor
*/
public CacheHeadersInterceptor setCacheControl(String cacheControl);
}
/**
* Handle idle resource cleanup
*/
public class IdleResourceInterceptor implements AtmosphereInterceptor {
/**
* Set idle timeout for resources
* @param idleTimeout timeout in milliseconds
* @return this interceptor
*/
public IdleResourceInterceptor setIdleTimeout(long idleTimeout);
}
/**
* Manage AtmosphereResource lifecycle events
*/
public class AtmosphereResourceLifecycleInterceptor implements AtmosphereInterceptor {
/**
* Enable lifecycle event broadcasting
* @param broadcastLifecycleEvents true to broadcast events
* @return this interceptor
*/
public AtmosphereResourceLifecycleInterceptor setBroadcastLifecycleEvents(boolean broadcastLifecycleEvents);
}
/**
* Handle client disconnection events
*/
public class OnDisconnectInterceptor implements AtmosphereInterceptor {
/**
* Set disconnection message
* @param disconnectMessage message to send on disconnect
* @return this interceptor
*/
public OnDisconnectInterceptor setDisconnectMessage(String disconnectMessage);
}Usage Examples:
// Configure CORS interceptor
CorsInterceptor corsInterceptor = new CorsInterceptor()
.setAllowedOrigins("http://localhost:3000,https://myapp.com")
.setAllowedMethods("GET,POST,PUT,DELETE")
.setAllowedHeaders("Content-Type,Authorization")
.setAllowCredentials(true);
// Configure heartbeat interceptor
HeartbeatInterceptor heartbeatInterceptor = new HeartbeatInterceptor()
.setHeartbeatInterval(30000) // 30 seconds
.setHeartbeatData("ping");
// Configure SSE interceptor
SSEAtmosphereInterceptor sseInterceptor = new SSEAtmosphereInterceptor()
.setPadding(true);
// Add interceptors to framework
AtmosphereFramework framework = new AtmosphereFramework();
framework.intercept(corsInterceptor)
.intercept(heartbeatInterceptor)
.intercept(sseInterceptor);Base classes and utilities for developing custom interceptors.
/**
* Adapter implementation providing default interceptor behavior
*/
public class AtmosphereInterceptorAdapter implements AtmosphereInterceptor {
/**
* Default intercept implementation (CONTINUE)
* @param resource AtmosphereResource
* @return Action.CONTINUE
*/
public Action intercept(AtmosphereResource resource) {
return Action.CONTINUE;
}
/**
* Default post-inspect implementation (no-op)
* @param resource AtmosphereResource
*/
public void postInspect(AtmosphereResource resource) {
// Default: do nothing
}
/**
* Default priority (medium priority)
* @return 1000
*/
public int priority() {
return 1000;
}
/**
* Default configuration (no-op)
* @param config AtmosphereConfig
*/
public void configure(AtmosphereConfig config) {
// Default: do nothing
}
/**
* Default destroy (no-op)
*/
public void destroy() {
// Default: do nothing
}
}Usage Examples:
// Authentication interceptor
@AtmosphereInterceptorService
public class AuthenticationInterceptor extends AtmosphereInterceptorAdapter {
@Override
public Action intercept(AtmosphereResource resource) {
AtmosphereRequest request = resource.getRequest();
// Check for authentication token
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
// Send 401 Unauthorized
try {
resource.getResponse().setStatus(401);
resource.getResponse().getWriter().write("Unauthorized");
resource.getResponse().flushBuffer();
return Action.CANCELLED;
} catch (IOException e) {
return Action.CANCELLED;
}
}
// Validate token
String token = authHeader.substring(7);
if (!isValidToken(token)) {
try {
resource.getResponse().setStatus(403);
resource.getResponse().getWriter().write("Invalid token");
resource.getResponse().flushBuffer();
return Action.CANCELLED;
} catch (IOException e) {
return Action.CANCELLED;
}
}
// Add user info to request attributes
User user = getUserFromToken(token);
request.setAttribute("user", user);
return Action.CONTINUE;
}
@Override
public int priority() {
return 100; // High priority - execute early
}
private boolean isValidToken(String token) {
// Token validation logic
return token != null && token.length() > 10;
}
private User getUserFromToken(String token) {
// Extract user from token
return new User("user123", "John Doe");
}
}
// Rate limiting interceptor
@AtmosphereInterceptorService
public class RateLimitInterceptor extends AtmosphereInterceptorAdapter {
private final Map<String, TokenBucket> clientBuckets = new ConcurrentHashMap<>();
private final int maxRequestsPerMinute = 60;
@Override
public Action intercept(AtmosphereResource resource) {
String clientIp = resource.getRequest().getRemoteAddr();
// Get or create token bucket for client
TokenBucket bucket = clientBuckets.computeIfAbsent(clientIp,
k -> new TokenBucket(maxRequestsPerMinute, 1, TimeUnit.MINUTES));
// Check if request is allowed
if (!bucket.tryConsume()) {
try {
resource.getResponse().setStatus(429); // Too Many Requests
resource.getResponse().setHeader("Retry-After", "60");
resource.getResponse().getWriter().write("Rate limit exceeded");
resource.getResponse().flushBuffer();
return Action.CANCELLED;
} catch (IOException e) {
return Action.CANCELLED;
}
}
return Action.CONTINUE;
}
@Override
public int priority() {
return 200; // Execute after authentication but before main processing
}
}
// Request transformation interceptor
@AtmosphereInterceptorService
public class RequestTransformInterceptor extends AtmosphereInterceptorAdapter {
@Override
public Action intercept(AtmosphereResource resource) {
AtmosphereRequest request = resource.getRequest();
// Transform specific paths
String pathInfo = request.getPathInfo();
if (pathInfo != null && pathInfo.startsWith("/legacy/")) {
// Rewrite legacy paths to new format
String newPath = pathInfo.replace("/legacy/", "/api/v2/");
// Create new request with transformed path
AtmosphereRequest.Builder builder = new AtmosphereRequest.Builder();
builder.request(request).pathInfo(newPath);
// Replace request in resource
resource.setRequest(builder.build());
}
// Add custom headers
resource.getResponse().setHeader("X-Processed-By", "Atmosphere");
resource.getResponse().setHeader("X-Request-Id", UUID.randomUUID().toString());
return Action.CONTINUE;
}
@Override
public void postInspect(AtmosphereResource resource) {
// Add processing time header
long processingTime = System.currentTimeMillis() -
(Long) resource.getRequest().getAttribute("startTime");
resource.getResponse().setHeader("X-Processing-Time", String.valueOf(processingTime));
}
@Override
public int priority() {
return 500; // Medium priority
}
}Utilities for managing and configuring interceptor execution chains.
/**
* Interceptor chain configuration and management
*/
public class InterceptorManager {
/**
* Add interceptor to global chain
* @param interceptor AtmosphereInterceptor to add
*/
public void addInterceptor(AtmosphereInterceptor interceptor);
/**
* Remove interceptor from chain
* @param interceptor AtmosphereInterceptor to remove
*/
public void removeInterceptor(AtmosphereInterceptor interceptor);
/**
* Get all registered interceptors ordered by priority
* @return List of interceptors in execution order
*/
public List<AtmosphereInterceptor> getInterceptors();
/**
* Clear all interceptors
*/
public void clearInterceptors();
}Special interceptors for handling asynchronous I/O operations.
/**
* Intercept async I/O operations
*/
public interface AsyncIOInterceptor {
/**
* Intercept before payload is written
* @param resource AtmosphereResource
* @param data payload being written
* @param offset write offset
* @param length write length
* @return modified payload or original data
*/
public byte[] prePayload(AtmosphereResource resource, byte[] data, int offset, int length);
/**
* Intercept after payload is written
* @param resource AtmosphereResource
* @param data payload that was written
* @param offset write offset
* @param length write length
*/
public void postPayload(AtmosphereResource resource, byte[] data, int offset, int length);
/**
* Configure async I/O interceptor
* @param config AtmosphereConfig
*/
public void configure(AtmosphereConfig config);
}
/**
* Adapter for AsyncIOInterceptor with default implementations
*/
public class AsyncIOInterceptorAdapter implements AsyncIOInterceptor {
/**
* Default pre-payload processing (return original data)
*/
public byte[] prePayload(AtmosphereResource resource, byte[] data, int offset, int length) {
return data;
}
/**
* Default post-payload processing (no-op)
*/
public void postPayload(AtmosphereResource resource, byte[] data, int offset, int length) {
// Default: do nothing
}
}Usage Examples:
// Compression interceptor for async I/O
public class CompressionAsyncIOInterceptor extends AsyncIOInterceptorAdapter {
@Override
public byte[] prePayload(AtmosphereResource resource, byte[] data, int offset, int length) {
// Check if client supports compression
String acceptEncoding = resource.getRequest().getHeader("Accept-Encoding");
if (acceptEncoding != null && acceptEncoding.contains("gzip")) {
try {
// Compress data using GZIP
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (GZIPOutputStream gzipOut = new GZIPOutputStream(baos)) {
gzipOut.write(data, offset, length);
}
// Set compression headers
resource.getResponse().setHeader("Content-Encoding", "gzip");
resource.getResponse().setHeader("Vary", "Accept-Encoding");
byte[] compressed = baos.toByteArray();
System.out.println("Compressed " + length + " bytes to " + compressed.length + " bytes");
return compressed;
} catch (IOException e) {
System.err.println("Compression failed: " + e.getMessage());
return data; // Return original data on error
}
}
return data; // No compression
}
@Override
public void postPayload(AtmosphereResource resource, byte[] data, int offset, int length) {
// Log compression statistics
String contentEncoding = resource.getResponse().getHeader("Content-Encoding");
if ("gzip".equals(contentEncoding)) {
System.out.println("Sent compressed payload: " + length + " bytes");
}
}
}Comprehensive example showing multiple interceptors working together in a real application.
// Security interceptor chain
@AtmosphereInterceptorService
public class SecurityInterceptorChain extends AtmosphereInterceptorAdapter {
private final AuthenticationService authService;
private final AuditService auditService;
public SecurityInterceptorChain() {
this.authService = new AuthenticationService();
this.auditService = new AuditService();
}
@Override
public Action intercept(AtmosphereResource resource) {
AtmosphereRequest request = resource.getRequest();
String path = request.getPathInfo();
// Skip authentication for public endpoints
if (isPublicEndpoint(path)) {
return Action.CONTINUE;
}
// Authenticate request
String authResult = authenticateRequest(request);
if (authResult == null) {
sendUnauthorizedResponse(resource);
return Action.CANCELLED;
}
// Check authorization
if (!isAuthorized(authResult, path, request.getMethod())) {
sendForbiddenResponse(resource);
return Action.CANCELLED;
}
// Add security context to request
request.setAttribute("securityContext", createSecurityContext(authResult));
// Audit the request
auditService.logAccess(authResult, path, request.getMethod(),
request.getRemoteAddr());
return Action.CONTINUE;
}
@Override
public void postInspect(AtmosphereResource resource) {
// Audit the response
AtmosphereRequest request = resource.getRequest();
SecurityContext context = (SecurityContext) request.getAttribute("securityContext");
if (context != null) {
int statusCode = resource.getResponse().getStatus();
auditService.logResponse(context.getUserId(), request.getPathInfo(),
statusCode, System.currentTimeMillis());
}
}
@Override
public int priority() {
return 50; // Very high priority - execute first
}
private boolean isPublicEndpoint(String path) {
return path != null && (path.startsWith("/public/") ||
path.equals("/health") ||
path.equals("/status"));
}
private String authenticateRequest(AtmosphereRequest request) {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
return authService.validateToken(token);
}
return null;
}
private boolean isAuthorized(String userId, String path, String method) {
return authService.checkPermission(userId, path, method);
}
private void sendUnauthorizedResponse(AtmosphereResource resource) {
try {
resource.getResponse().setStatus(401);
resource.getResponse().setContentType("application/json");
resource.getResponse().getWriter()
.write("{\"error\":\"Unauthorized\",\"code\":401}");
resource.getResponse().flushBuffer();
} catch (IOException e) {
// Log error
}
}
private void sendForbiddenResponse(AtmosphereResource resource) {
try {
resource.getResponse().setStatus(403);
resource.getResponse().setContentType("application/json");
resource.getResponse().getWriter()
.write("{\"error\":\"Forbidden\",\"code\":403}");
resource.getResponse().flushBuffer();
} catch (IOException e) {
// Log error
}
}
private SecurityContext createSecurityContext(String userId) {
return new SecurityContext(userId, authService.getUserRoles(userId));
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-atmosphere--atmosphere-runtime