SockJS protocol support providing WebSocket fallback transports for environments where WebSocket is unavailable or blocked. Includes HTTP streaming, polling, and Server-Sent Events transports for maximum compatibility.
Main entry point for processing SockJS HTTP requests.
/**
* Main SockJS service interface for processing HTTP requests according
* to the SockJS protocol. Handles transport selection, session management,
* and message routing.
*/
public interface SockJsService {
/**
* Process a SockJS HTTP request.
*
* @param request the HTTP request
* @param response the HTTP response
* @param sockJsPath the SockJS path (after the endpoint prefix)
* @param wsHandler the WebSocket handler for the session
* @throws SockJsException if processing fails
*/
void handleRequest(
ServerHttpRequest request,
ServerHttpResponse response,
String sockJsPath,
WebSocketHandler wsHandler) throws SockJsException;
}Enumeration of SockJS transport types.
/**
* Enumeration of SockJS transport types. Each transport provides a different
* mechanism for bi-directional communication when WebSocket is unavailable.
*/
public enum TransportType {
/**
* WebSocket transport - native WebSocket connection.
*/
WEBSOCKET,
/**
* XHR (XMLHttpRequest) polling - request-response polling.
*/
XHR,
/**
* XHR send - one-way message sending via POST.
*/
XHR_SEND,
/**
* XHR streaming - long-lived streaming connection.
*/
XHR_STREAMING,
/**
* Server-Sent Events (EventSource) - server push via SSE.
*/
EVENT_SOURCE,
/**
* HTML file - streaming via forever iframe.
*/
HTML_FILE;
/**
* Return the transport type identifier used in URLs.
*
* @return the transport type value
*/
public String value();
/**
* Return the HTTP method used by this transport.
*
* @return the HTTP method (GET or POST)
*/
public HttpMethod getHttpMethod();
/**
* Whether this transport sends no-cache headers.
*
* @return true if no-cache headers are sent
*/
public boolean sendsNoCacheInstruction();
/**
* Whether this transport requires session cookies.
*
* @return true if session cookies are needed
*/
public boolean sendsSessionCookie();
/**
* Whether this transport supports CORS.
*
* @return true if CORS is supported
*/
public boolean supportsCors();
/**
* Whether this transport supports the Origin header.
*
* @return true if Origin header is supported
*/
public boolean supportsOrigin();
/**
* Get TransportType from its string value.
*
* @param value the transport type value
* @return the TransportType, or null if not found
*/
public static TransportType fromValue(String value);
}Default implementation of SockJsService with pluggable transports.
/**
* Default SockJS service implementation supporting all standard transports.
* Auto-configures transport handlers based on the runtime environment.
*/
public class DefaultSockJsService extends TransportHandlingSockJsService {
/**
* Create a service with a task scheduler for heartbeats and timeouts.
*
* @param scheduler the task scheduler
*/
public DefaultSockJsService(TaskScheduler scheduler);
/**
* Create a service with custom transport handlers.
*
* @param scheduler the task scheduler
* @param handlers the transport handlers to use
*/
public DefaultSockJsService(
TaskScheduler scheduler,
Collection<TransportHandler> handlers);
}Base implementation of SockJsService with extensive configuration options.
/**
* Abstract base class for SockJS service implementations. Provides
* comprehensive configuration options for SockJS behavior.
*/
public abstract class AbstractSockJsService implements SockJsService {
/**
* Create a service with a task scheduler.
*
* @param scheduler the task scheduler for periodic tasks
*/
public AbstractSockJsService(TaskScheduler scheduler);
/**
* Set the name of the SockJS service (shown in info response).
* Default is "SockJS".
*
* @param name the service name
*/
public void setName(String name);
/**
* Get the service name.
*
* @return the name
*/
public String getName();
/**
* Set the URL to the SockJS client library.
* Used in iframe HTML responses.
*
* @param clientLibraryUrl the client library URL
*/
public void setClientLibraryUrl(String clientLibraryUrl);
/**
* Get the client library URL.
*
* @return the URL
*/
public String getClientLibraryUrl();
/**
* Set the minimum number of bytes that can be sent over a streaming
* transport before it's closed and reopened. This prevents browser memory
* issues with long-lived streams. Default is 128KB.
*
* @param streamBytesLimit the byte limit
*/
public void setStreamBytesLimit(int streamBytesLimit);
/**
* Get the stream bytes limit.
*
* @return the byte limit
*/
public int getStreamBytesLimit();
/**
* Whether the SockJS path requires a session cookie.
* Default is true.
*
* @param sessionCookieNeeded whether session cookie is needed
*/
public void setSessionCookieNeeded(boolean sessionCookieNeeded);
/**
* Whether session cookies are needed.
*
* @return true if needed
*/
public boolean isSessionCookieNeeded();
/**
* Set the heartbeat time in milliseconds. Heartbeats prevent proxies
* from closing idle connections. Default is 25 seconds.
*
* @param heartbeatTime the heartbeat interval
*/
public void setHeartbeatTime(long heartbeatTime);
/**
* Get the heartbeat time.
*
* @return the heartbeat interval in milliseconds
*/
public long getHeartbeatTime();
/**
* Set the disconnect delay in milliseconds. This is the time to wait
* before considering a session disconnected after connection is lost.
* Default is 5 seconds.
*
* @param disconnectDelay the disconnect delay
*/
public void setDisconnectDelay(long disconnectDelay);
/**
* Get the disconnect delay.
*
* @return the disconnect delay in milliseconds
*/
public long getDisconnectDelay();
/**
* Set the number of server-to-client messages to cache per session.
* Default is 100.
*
* @param httpMessageCacheSize the cache size
*/
public void setHttpMessageCacheSize(int httpMessageCacheSize);
/**
* Get the HTTP message cache size.
*
* @return the cache size
*/
public int getHttpMessageCacheSize();
/**
* Whether WebSocket transport is enabled. Default is true.
*
* @param webSocketEnabled whether WebSocket is enabled
*/
public void setWebSocketEnabled(boolean webSocketEnabled);
/**
* Whether WebSocket transport is enabled.
*
* @return true if enabled
*/
public boolean isWebSocketEnabled();
/**
* Set HandshakeInterceptors for the SockJS handshake.
*
* @param interceptors the interceptors
*/
public void setHandshakeInterceptors(List<HandshakeInterceptor> interceptors);
/**
* Get the handshake interceptors.
*
* @return the interceptors
*/
public List<HandshakeInterceptor> getHandshakeInterceptors();
/**
* Whether to suppress CORS handling. If true, CORS headers must be
* configured elsewhere. Default is false.
*
* @param suppressCors whether to suppress CORS
*/
public void setSuppressCors(boolean suppressCors);
/**
* Whether CORS handling is suppressed.
*
* @return true if suppressed
*/
public boolean shouldSuppressCors();
/**
* Set allowed origins for CORS. Use "*" to allow all origins.
*
* @param allowedOrigins the allowed origins
*/
public void setAllowedOrigins(Collection<String> allowedOrigins);
/**
* Set allowed origin patterns with wildcard support.
*
* @param allowedOriginPatterns the allowed origin patterns
*/
public void setAllowedOriginPatterns(Collection<String> allowedOriginPatterns);
/**
* Set the message codec for encoding/decoding SockJS messages.
*
* @param messageCodec the message codec
*/
public void setMessageCodec(SockJsMessageCodec messageCodec);
/**
* Get the message codec.
*
* @return the message codec
*/
public SockJsMessageCodec getMessageCodec();
@Override
public void handleRequest(
ServerHttpRequest request,
ServerHttpResponse response,
String sockJsPath,
WebSocketHandler wsHandler) throws SockJsException;
}Handler for a specific SockJS transport.
/**
* Handler for a specific SockJS transport type. Each transport handler
* implements the protocol for a particular transport mechanism.
*/
public interface TransportHandler {
/**
* Get the transport type handled by this handler.
*
* @return the transport type
*/
TransportType getTransportType();
/**
* Handle a transport request.
*
* @param request the HTTP request
* @param response the HTTP response
* @param wsHandler the WebSocket handler
* @param session the SockJS session
* @throws SockJsException if handling fails
*/
void handleRequest(
ServerHttpRequest request,
ServerHttpResponse response,
WebSocketHandler wsHandler,
SockJsSession session) throws SockJsException;
}Transport handler for native WebSocket connections.
/**
* TransportHandler for WebSocket transport. Upgrades the HTTP connection
* to a WebSocket connection when available.
*/
public class WebSocketTransportHandler implements TransportHandler {
/**
* Create a WebSocket transport handler with a handshake handler.
*
* @param handshakeHandler the handshake handler
*/
public WebSocketTransportHandler(HandshakeHandler handshakeHandler);
@Override
public TransportType getTransportType();
@Override
public void handleRequest(
ServerHttpRequest request,
ServerHttpResponse response,
WebSocketHandler wsHandler,
SockJsSession session) throws SockJsException;
}Transport handler for XHR polling.
/**
* TransportHandler for XHR polling transport. Implements request-response
* polling where each request returns immediately with pending messages or
* an empty response.
*/
public class XhrPollingTransportHandler extends AbstractHttpSendingTransportHandler {
/**
* Create an XHR polling transport handler.
*/
public XhrPollingTransportHandler();
@Override
public TransportType getTransportType();
}Transport handler for XHR streaming.
/**
* TransportHandler for XHR streaming transport. Establishes a long-lived
* HTTP connection and streams messages as they become available.
*/
public class XhrStreamingTransportHandler extends AbstractHttpSendingTransportHandler {
/**
* Create an XHR streaming transport handler.
*/
public XhrStreamingTransportHandler();
@Override
public TransportType getTransportType();
}Transport handler for Server-Sent Events.
/**
* TransportHandler for Server-Sent Events (EventSource) transport.
* Uses the EventSource browser API for server-to-client streaming.
*/
public class EventSourceTransportHandler extends AbstractHttpSendingTransportHandler {
/**
* Create an EventSource transport handler.
*/
public EventSourceTransportHandler();
@Override
public TransportType getTransportType();
}Transport handler for HTML file streaming.
/**
* TransportHandler for HTML file (forever iframe) transport.
* Streams messages through a hidden iframe for older browsers.
*/
public class HtmlFileTransportHandler extends AbstractHttpSendingTransportHandler {
/**
* Create an HTML file transport handler.
*/
public HtmlFileTransportHandler();
@Override
public TransportType getTransportType();
}Abstract base class representing a SockJS session.
/**
* Represents a SockJS session. A session may use different transports
* over its lifetime as connections are established and terminated.
*/
public abstract class SockJsSession {
/**
* Get the session ID.
*
* @return the session ID
*/
public abstract String getId();
/**
* Get the WebSocket handler for this session.
*
* @return the handler
*/
public WebSocketHandler getHandler();
/**
* Whether the session is currently active.
*
* @return true if active
*/
public abstract boolean isActive();
/**
* Send a message to the client.
*
* @param message the message to send
* @throws IOException if sending fails
*/
public abstract void sendMessage(WebSocketMessage<?> message) throws IOException;
/**
* Close the session with a status.
*
* @param status the close status
* @throws IOException if closing fails
*/
public abstract void close(CloseStatus status) throws IOException;
}SockJS frame encoding and decoding.
/**
* Codec for encoding and decoding SockJS message frames.
*/
public interface SockJsMessageCodec {
/**
* Encode messages into a SockJS frame.
*
* @param messages the messages to encode
* @return the encoded frame content
* @throws IOException if encoding fails
*/
String encode(String... messages) throws IOException;
/**
* Decode a SockJS frame into messages.
*
* @param content the frame content
* @return array of decoded messages
* @throws IOException if decoding fails
*/
String[] decode(String content) throws IOException;
}
/**
* SockJS message codec using Jackson 2 for JSON encoding/decoding.
*/
public class Jackson2SockJsMessageCodec extends AbstractSockJsMessageCodec {
/**
* Create a codec with default ObjectMapper.
*/
public Jackson2SockJsMessageCodec();
/**
* Create a codec with a custom ObjectMapper.
*
* @param objectMapper the ObjectMapper to use
*/
public Jackson2SockJsMessageCodec(ObjectMapper objectMapper);
@Override
public String encode(String... messages) throws IOException;
@Override
public String[] decode(String content) throws IOException;
}
/**
* Represents a SockJS frame with its type and data.
*/
public class SockJsFrame {
/**
* Create an open frame (session established).
*/
public static SockJsFrame openFrame();
/**
* Create a heartbeat frame (keep connection alive).
*/
public static SockJsFrame heartbeatFrame();
/**
* Create a message frame with encoded messages.
*
* @param codec the message codec
* @param messages the messages to encode
* @return the message frame
*/
public static SockJsFrame messageFrame(
SockJsMessageCodec codec,
String... messages);
/**
* Create a close frame (session closed).
*
* @param code the close code
* @param reason the close reason
* @return the close frame
*/
public static SockJsFrame closeFrame(int code, String reason);
/**
* Get the frame type.
*
* @return the frame type
*/
public SockJsFrameType getType();
/**
* Get the frame data content.
*
* @return the frame data
*/
public String getFrameData();
/**
* Get the message content (for message frames).
*
* @return the message content
*/
public String getMessageContent();
}
/**
* Enumeration of SockJS frame types.
*/
public enum SockJsFrameType {
OPEN,
HEARTBEAT,
MESSAGE,
CLOSE
}
/**
* Format for SockJS frames. Different transports may format frames differently.
*/
public interface SockJsFrameFormat {
/**
* Format a SockJS frame for transmission.
*
* @param frame the frame to format
* @return the formatted frame string
*/
String format(SockJsFrame frame);
}/**
* Base exception for SockJS errors.
*/
public class SockJsException extends NestedRuntimeException {
/**
* Create an exception with message and cause.
*
* @param message the error message
* @param cause the underlying cause
*/
public SockJsException(String message, Throwable cause);
/**
* Create an exception with message, session ID, and cause.
*
* @param message the error message
* @param sessionId the SockJS session ID
* @param cause the underlying cause
*/
public SockJsException(String message, String sessionId, Throwable cause);
/**
* Get the SockJS session ID associated with the error.
*
* @return the session ID, or null if not associated with a session
*/
public String getSockJsSessionId();
}
/**
* Exception thrown when message delivery fails.
*/
public class SockJsMessageDeliveryException extends SockJsException {
// Message delivery failure
}
/**
* Exception thrown when transport fails.
*/
public class SockJsTransportFailureException extends SockJsException {
// Transport failure
}import org.springframework.web.socket.config.annotation.*;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/ws")
.withSockJS(); // Enable SockJS fallback
}
@Bean
public WebSocketHandler myHandler() {
return new MyWebSocketHandler();
}
}@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/ws")
.withSockJS()
.setClientLibraryUrl("https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js")
.setStreamBytesLimit(512 * 1024) // 512KB
.setHeartbeatTime(20000) // 20 seconds
.setDisconnectDelay(10000) // 10 seconds
.setHttpMessageCacheSize(1000)
.setWebSocketEnabled(true)
.setSuppressCors(false);
}
}import org.springframework.web.socket.sockjs.transport.TransportHandler;
import org.springframework.web.socket.sockjs.transport.handler.*;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/ws")
.withSockJS()
.setTransportHandlers(customTransportHandlers());
}
private TransportHandler[] customTransportHandlers() {
return new TransportHandler[] {
new WebSocketTransportHandler(handshakeHandler()),
new XhrPollingTransportHandler(),
new XhrStreamingTransportHandler(),
new EventSourceTransportHandler()
// Omit HtmlFileTransportHandler if not needed
};
}
@Bean
public HandshakeHandler handshakeHandler() {
return new DefaultHandshakeHandler();
}
}import org.springframework.web.socket.server.HandshakeInterceptor;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/ws")
.addInterceptors(new HttpSessionHandshakeInterceptor())
.withSockJS()
.setInterceptors(customInterceptor());
}
private HandshakeInterceptor customInterceptor() {
return new HandshakeInterceptor() {
@Override
public boolean beforeHandshake(
ServerHttpRequest request,
ServerHttpResponse response,
WebSocketHandler wsHandler,
Map<String, Object> attributes) throws Exception {
// Validate SockJS specific requirements
String transport = extractTransport(request);
System.out.println("SockJS transport: " + transport);
return true;
}
@Override
public void afterHandshake(
ServerHttpRequest request,
ServerHttpResponse response,
WebSocketHandler wsHandler,
Exception exception) {
// Post-handshake logic
}
private String extractTransport(ServerHttpRequest request) {
String path = request.getURI().getPath();
String[] parts = path.split("/");
return parts.length > 3 ? parts[3] : "unknown";
}
};
}
}import org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
@Configuration
public class SockJsServiceConfig {
@Bean
public DefaultSockJsService sockJsService() {
DefaultSockJsService service = new DefaultSockJsService(taskScheduler());
service.setName("CustomSockJS");
service.setClientLibraryUrl("/js/sockjs.js");
service.setStreamBytesLimit(256 * 1024);
service.setSessionCookieNeeded(true);
service.setHeartbeatTime(25000);
service.setDisconnectDelay(5000);
service.setHttpMessageCacheSize(100);
service.setWebSocketEnabled(true);
// Configure CORS
service.setAllowedOrigins(Arrays.asList(
"https://example.com",
"https://app.example.com"
));
return service;
}
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(4);
scheduler.setThreadNamePrefix("sockjs-");
scheduler.initialize();
return scheduler;
}
}// Connect to SockJS endpoint
var socket = new SockJS('http://localhost:8080/ws');
socket.onopen = function() {
console.log('Connected to SockJS');
socket.send('Hello Server!');
};
socket.onmessage = function(event) {
console.log('Received:', event.data);
};
socket.onclose = function(event) {
console.log('Disconnected:', event.code, event.reason);
};
socket.onerror = function(error) {
console.error('Error:', error);
};
// Close connection
// socket.close();@Configuration
@EnableWebSocketMessageBroker
public class StompSockJsConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/stomp")
.setAllowedOrigins("*")
.withSockJS()
.setHeartbeatTime(20000)
.setDisconnectDelay(10000)
.setWebSocketEnabled(true)
.setClientLibraryUrl("/js/sockjs.min.js");
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic", "/queue");
registry.setApplicationDestinationPrefixes("/app");
}
}import org.springframework.web.socket.config.WebSocketMessageBrokerStats;
@Service
public class SockJsMonitoringService {
private final WebSocketMessageBrokerStats stats;
public SockJsMonitoringService(WebSocketMessageBrokerStats stats) {
this.stats = stats;
}
@Scheduled(fixedRate = 60000)
public void logStats() {
System.out.println("Total Sessions: " + stats.getWebSocketSessionStatsInfo());
System.out.println("Session Info: " + stats.getStompSubProtocolStatsInfo());
System.out.println("Broker Stats: " + stats.getStompBrokerRelayStatsInfo());
}
}import org.springframework.web.socket.sockjs.frame.SockJsMessageCodec;
public class CustomMessageCodec implements SockJsMessageCodec {
@Override
public String encode(String... messages) throws IOException {
// Custom encoding logic
JSONArray array = new JSONArray();
for (String message : messages) {
array.put(message);
}
return array.toString();
}
@Override
public String[] decode(String content) throws IOException {
// Custom decoding logic
JSONArray array = new JSONArray(content);
String[] messages = new String[array.length()];
for (int i = 0; i < array.length(); i++) {
messages[i] = array.getString(i);
}
return messages;
}
}
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/ws")
.withSockJS()
.setMessageCodec(new CustomMessageCodec());
}
}@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/ws")
.withSockJS()
// Only allow WebSocket and XHR polling
.setTransportHandlers(
new WebSocketTransportHandler(handshakeHandler()),
new XhrPollingTransportHandler()
);
}
}