SockJS frame handling provides classes and interfaces for creating, encoding, decoding, and formatting SockJS protocol frames. These components handle the low-level SockJS message framing, ensuring proper encoding of messages according to SockJS protocol specifications including JSON encoding and Unicode escaping.
Represents a SockJS frame and provides factory methods for creating different types of frames. SockJS frames are the basic unit of communication in the SockJS protocol, with different frame types for opening connections, sending messages, maintaining heartbeats, and closing connections.
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
/**
* Represents a SockJS frame. Provides factory methods to create SockJS frames.
* A frame consists of a frame type indicator and optional data content.
*
* Frame formats:
* - Open: "o"
* - Heartbeat: "h"
* - Message: "a[\"msg1\",\"msg2\"]" (array of JSON-encoded messages)
* - Close: "c[code,\"reason\"]"
*
* @since 4.0
*/
public class SockJsFrame {
/**
* The charset used by SockJS (UTF-8).
*/
public static final Charset CHARSET = StandardCharsets.UTF_8;
/**
* Create a new instance frame with the given frame content.
* The content must be a valid SockJS frame starting with a frame type
* indicator: 'o' (open), 'h' (heartbeat), 'a' or 'm' (message),
* or 'c' (close).
*
* @param content the content (must be a non-empty and represent a valid SockJS frame)
* @throws IllegalArgumentException if content is empty or has invalid frame type
*/
public SockJsFrame(String content);
/**
* Return the SockJS frame type.
*
* @return the frame type (OPEN, HEARTBEAT, MESSAGE, or CLOSE)
*/
public SockJsFrameType getType();
/**
* Return the SockJS frame content (never null). This is the complete
* frame string including the frame type indicator.
*
* @return the complete frame content
*/
public String getContent();
/**
* Return the SockJS frame content as a byte array using UTF-8 encoding.
*
* @return the frame content as bytes
*/
public byte[] getContentBytes();
/**
* Return data contained in a SockJS "message" and "close" frames.
* For SockJS "open" and "heartbeat" frames, which do not contain data,
* return null. This extracts the content after the frame type indicator.
*
* @return the frame data (without the frame type indicator), or null
* for OPEN and HEARTBEAT frames
*/
public String getFrameData();
/**
* Create an open frame. The open frame ("o") is sent when a SockJS
* session is first established to indicate successful connection.
*
* @return a SockJS open frame
*/
public static SockJsFrame openFrame();
/**
* Create a heartbeat frame. Heartbeat frames ("h") are sent periodically
* to keep the connection alive and prevent proxies from closing idle
* connections.
*
* @return a SockJS heartbeat frame
*/
public static SockJsFrame heartbeatFrame();
/**
* Create a message frame with the given messages. Messages are encoded
* into a JSON array using the provided codec, with proper JSON quoting
* and SockJS Unicode escaping applied.
*
* @param codec the message codec to use for encoding
* @param messages the messages to encode into the frame
* @return a SockJS message frame (format: "a[\"msg1\",\"msg2\"]")
*/
public static SockJsFrame messageFrame(SockJsMessageCodec codec, String... messages);
/**
* Create a close frame with code 3000 and reason "Go away!". This is
* used to reject connection attempts when a server wants to refuse
* new connections.
*
* @return a SockJS close frame with "Go away" message
*/
public static SockJsFrame closeFrameGoAway();
/**
* Create a close frame with code 2010 and reason "Another connection still open".
* This is used when a client attempts to open a new connection with a session ID
* that already has an active connection.
*
* @return a SockJS close frame for duplicate connection attempts
*/
public static SockJsFrame closeFrameAnotherConnectionOpen();
/**
* Create a close frame with the given code and reason. Close frames
* indicate that a SockJS session is being terminated.
*
* @param code the close code (e.g., 3000 for "Go away", 2010 for
* "Another connection still open")
* @param reason the close reason (may be null)
* @return a SockJS close frame (format: "c[code,\"reason\"]")
*/
public static SockJsFrame closeFrame(int code, String reason);
@Override
public boolean equals(Object other);
@Override
public int hashCode();
/**
* Return a string representation of the frame, truncating the content
* at 80 characters if necessary and escaping newlines.
*
* @return a string representation of the frame
*/
@Override
public String toString();
}Enumeration of the four SockJS frame types defined in the SockJS protocol.
/**
* SockJS frame types. Each frame type serves a specific purpose in the
* SockJS protocol lifecycle.
*
* @since 4.1
*/
public enum SockJsFrameType {
/**
* Open frame ("o") - sent when a session is first established.
*/
OPEN,
/**
* Heartbeat frame ("h") - sent periodically to keep connection alive.
*/
HEARTBEAT,
/**
* Message frame ("a[...]" or "m...") - contains application messages.
*/
MESSAGE,
/**
* Close frame ("c[...]") - indicates session closure with code and reason.
*/
CLOSE
}Applies a transport-specific format to SockJS frame content. Different transports may require different formatting - for example, XHR polling might append a newline, while JSONP wraps the frame in a JavaScript callback function.
/**
* Applies a transport-specific format to the content of a SockJS frame resulting
* in a content that can be written out. Primarily for use in HTTP server-side
* transports that push data.
*
* Formatting may vary from simply appending a new line character for XHR
* polling and streaming transports, to a jsonp-style callback function,
* surrounding script tags, and more.
*
* @since 4.0
*/
public interface SockJsFrameFormat {
/**
* Format a SockJS frame for transmission over a specific transport.
* The formatting applied depends on the transport requirements.
*
* @param frame the frame to format
* @return the formatted frame content ready for transmission
*/
String format(SockJsFrame frame);
}Default implementation of SockJsFrameFormat that uses Java's String formatting to apply a template to frame content.
/**
* A default implementation of SockJsFrameFormat that relies on
* String.format(String, Object...) to apply a format string to the
* frame content.
*
* For example, a format string of "%s\n" would append a newline to
* every frame, which is used by XHR polling and streaming transports.
*
* @since 4.0
*/
public class DefaultSockJsFrameFormat implements SockJsFrameFormat {
/**
* Create a new DefaultSockJsFrameFormat with the given format string.
* The format string should contain a single %s placeholder where the
* frame content will be inserted.
*
* @param format the format string (must not be null)
* @throws IllegalArgumentException if format is null
*/
public DefaultSockJsFrameFormat(String format);
/**
* Apply the format string to the frame content.
*
* @param frame the frame to format
* @return the formatted frame content
*/
@Override
public String format(SockJsFrame frame);
/**
* Pre-process the frame content before formatting. Subclasses can
* override this to modify the content before the format string is
* applied. The default implementation returns the content unchanged.
*
* @param content the frame content
* @return the pre-processed content
*/
protected String preProcessContent(String content);
}Codec for encoding and decoding messages to and from SockJS message frames. A SockJS message frame is essentially an array of JSON-encoded messages with additional Unicode escaping rules.
import java.io.IOException;
import java.io.InputStream;
/**
* Encode and decode messages to and from a SockJS message frame,
* essentially an array of JSON-encoded messages. For example:
*
* <pre>
* a["message1","message2"]
* </pre>
*
* The codec must apply standard JSON quoting to each message and
* additional JSON Unicode escaping rules as defined in the SockJS
* protocol specification.
*
* @since 4.0
*/
public interface SockJsMessageCodec {
/**
* Encode the given messages as a SockJS message frame. Aside from applying
* standard JSON quoting to each message, there are some additional JSON
* Unicode escaping rules. See the "JSON Unicode Encoding" section of
* SockJS protocol (i.e. the protocol test suite).
*
* The result is a complete message frame including the "a[" prefix and
* "]" suffix, with messages separated by commas.
*
* @param messages the messages to encode
* @return the content for a SockJS message frame (never null)
*/
String encode(String... messages);
/**
* Decode the given SockJS message frame into an array of messages.
* The input should be a JSON array of strings.
*
* @param content the SockJS message frame
* @return an array of decoded messages, or null if none
* @throws IOException if the content could not be parsed
*/
String[] decode(String content) throws IOException;
/**
* Decode the given SockJS message frame from an InputStream.
* This is useful for efficiently processing large messages without
* loading the entire content into memory as a String first.
*
* @param content the SockJS message frame as an InputStream
* @return an array of decoded messages, or null if none
* @throws IOException if the content could not be parsed
*/
String[] decodeInputStream(InputStream content) throws IOException;
}Base class for SockJS message codec implementations. Provides a complete implementation of the encode method with proper JSON quoting and SockJS Unicode escaping. Subclasses need only implement JSON quoting and decoding methods.
/**
* A base class for SockJS message codec that provides an implementation of
* encode(String[]) with proper SockJS Unicode escaping. Subclasses must
* implement the JSON quoting and decoding logic.
*
* The encode implementation creates a message frame in the format:
* a["message1","message2",...] where each message is JSON-quoted and
* special Unicode characters are escaped according to SockJS rules.
*
* @since 4.0
*/
public abstract class AbstractSockJsMessageCodec implements SockJsMessageCodec {
/**
* Encode messages into a SockJS message frame. Applies JSON quoting
* via applyJsonQuoting(String) and then SockJS-specific Unicode escaping.
*
* @param messages the messages to encode (must not be null)
* @return the encoded message frame (format: a["msg1","msg2"])
* @throws IllegalArgumentException if messages is null
*/
@Override
public String encode(String... messages);
/**
* Apply standard JSON string quoting to the given content. This should
* handle escaping of special characters like quotes, backslashes,
* newlines, etc., according to JSON specification.
*
* See <a href="https://www.json.org/">json.org</a> for JSON string rules.
*
* @param content the string content to quote
* @return the JSON-quoted content as a character array
*/
protected abstract char[] applyJsonQuoting(String content);
/**
* Decode a SockJS message frame. Must be implemented by subclasses
* to parse the JSON array and extract messages.
*
* @param content the message frame content
* @return array of decoded messages, or null if none
* @throws IOException if decoding fails
*/
@Override
public abstract String[] decode(String content) throws IOException;
/**
* Decode a SockJS message frame from an InputStream. Must be implemented
* by subclasses to parse the JSON array and extract messages.
*
* @param content the message frame content as an InputStream
* @return array of decoded messages, or null if none
* @throws IOException if decoding fails
*/
@Override
public abstract String[] decodeInputStream(InputStream content) throws IOException;
}A Jackson 2.x codec for encoding and decoding SockJS messages. Uses Jackson's ObjectMapper for JSON parsing and JsonStringEncoder for proper JSON string quoting.
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* A Jackson 2.x codec for encoding and decoding SockJS messages.
*
* It customizes Jackson's default properties with the following:
* <ul>
* <li>MapperFeature.DEFAULT_VIEW_INCLUSION is disabled</li>
* <li>DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES is disabled</li>
* </ul>
*
* @since 4.0
* @deprecated since 7.0 in favor of JacksonJsonSockJsMessageCodec
*/
@Deprecated(since = "7.0", forRemoval = true)
public class Jackson2SockJsMessageCodec extends AbstractSockJsMessageCodec {
/**
* Create a Jackson2SockJsMessageCodec with a default ObjectMapper.
* The ObjectMapper is configured with standard settings suitable for
* SockJS message encoding/decoding.
*/
public Jackson2SockJsMessageCodec();
/**
* Create a Jackson2SockJsMessageCodec with a custom ObjectMapper.
* This allows full control over Jackson configuration.
*
* @param objectMapper the ObjectMapper to use (must not be null)
* @throws IllegalArgumentException if objectMapper is null
*/
public Jackson2SockJsMessageCodec(ObjectMapper objectMapper);
/**
* Decode a SockJS message frame into an array of messages using
* Jackson's ObjectMapper.
*
* @param content the message frame content (JSON array of strings)
* @return array of decoded messages, or null if the array is empty
* @throws IOException if JSON parsing fails
*/
@Override
public String[] decode(String content) throws IOException;
/**
* Decode a SockJS message frame from an InputStream using Jackson's
* ObjectMapper. This is more efficient for large messages.
*
* @param content the message frame content as an InputStream
* @return array of decoded messages, or null if the array is empty
* @throws IOException if JSON parsing fails
*/
@Override
public String[] decodeInputStream(InputStream content) throws IOException;
/**
* Apply JSON quoting using Jackson's JsonStringEncoder. This handles
* all special characters defined in the JSON specification.
*
* @param content the string to quote
* @return the JSON-quoted string as a character array
*/
@Override
protected char[] applyJsonQuoting(String content);
}A Jackson 3.x codec for encoding and decoding SockJS messages. This is the recommended codec for Jackson 3.x and later, replacing the deprecated Jackson2SockJsMessageCodec.
import tools.jackson.databind.json.JsonMapper;
/**
* A Jackson 3.x codec for encoding and decoding SockJS messages.
* Uses Jackson 3.x (tools.jackson) for JSON processing.
*
* This is the recommended codec for applications using Jackson 3.x.
* It automatically discovers and registers Jackson modules found on
* the classpath.
*
* @since 7.0
*/
public class JacksonJsonSockJsMessageCodec extends AbstractSockJsMessageCodec {
/**
* Construct a new instance with a JsonMapper customized with the
* Jackson modules found by scanning the classpath. This is the
* recommended constructor for most use cases.
*/
public JacksonJsonSockJsMessageCodec();
/**
* Construct a new instance with the provided JsonMapper.Builder.
* The builder will be customized with Jackson modules found by
* scanning the classpath before creating the mapper.
*
* Use this constructor when you need to customize the JsonMapper
* configuration before module discovery.
*
* @param builder the JsonMapper.Builder (must not be null)
* @throws IllegalArgumentException if builder is null
*/
public JacksonJsonSockJsMessageCodec(JsonMapper.Builder builder);
/**
* Construct a new instance with the provided JsonMapper.
* Use this constructor when you need complete control over the
* JsonMapper configuration, including manual module registration.
*
* @param mapper the JsonMapper to use (must not be null)
* @throws IllegalArgumentException if mapper is null
*/
public JacksonJsonSockJsMessageCodec(JsonMapper mapper);
/**
* Decode a SockJS message frame into an array of messages using
* the configured JsonMapper.
*
* @param content the message frame content (JSON array of strings)
* @return array of decoded messages, or null if the array is empty
* @throws RuntimeException wrapping IOException if JSON parsing fails
*/
@Override
public String[] decode(String content);
/**
* Decode a SockJS message frame from an InputStream using the
* configured JsonMapper. This is more efficient for large messages.
*
* @param content the message frame content as an InputStream
* @return array of decoded messages, or null if the array is empty
* @throws RuntimeException wrapping IOException if JSON parsing fails
*/
@Override
public String[] decodeInputStream(InputStream content);
/**
* Apply JSON quoting using Jackson 3.x JsonStringEncoder. This handles
* all special characters defined in the JSON specification.
*
* @param content the string to quote
* @return the JSON-quoted string as a character array
*/
@Override
protected char[] applyJsonQuoting(String content);
}import org.springframework.web.socket.sockjs.frame.*;
// Create an open frame
SockJsFrame openFrame = SockJsFrame.openFrame();
System.out.println(openFrame.getContent()); // Output: "o"
// Create a heartbeat frame
SockJsFrame heartbeatFrame = SockJsFrame.heartbeatFrame();
System.out.println(heartbeatFrame.getContent()); // Output: "h"
// Create a message frame
SockJsMessageCodec codec = new JacksonJsonSockJsMessageCodec();
SockJsFrame messageFrame = SockJsFrame.messageFrame(
codec,
"Hello, World!",
"Second message"
);
System.out.println(messageFrame.getContent());
// Output: a["Hello, World!","Second message"]
// Create close frames
SockJsFrame closeFrame = SockJsFrame.closeFrame(3000, "Go away!");
System.out.println(closeFrame.getContent()); // Output: c[3000,"Go away!"]
SockJsFrame goAwayFrame = SockJsFrame.closeFrameGoAway();
SockJsFrame duplicateConnectionFrame = SockJsFrame.closeFrameAnotherConnectionOpen();import org.springframework.web.socket.sockjs.frame.*;
SockJsMessageCodec codec = new JacksonJsonSockJsMessageCodec();
// Create different frame types
SockJsFrame openFrame = SockJsFrame.openFrame();
SockJsFrame messageFrame = SockJsFrame.messageFrame(codec, "test");
SockJsFrame closeFrame = SockJsFrame.closeFrame(3000, "Done");
// Check frame types
System.out.println(openFrame.getType()); // OPEN
System.out.println(messageFrame.getType()); // MESSAGE
System.out.println(closeFrame.getType()); // CLOSE
// Get frame data (content after type indicator)
System.out.println(openFrame.getFrameData()); // null (no data)
System.out.println(messageFrame.getFrameData()); // ["test"]
System.out.println(closeFrame.getFrameData()); // [3000,"Done"]
// Get content as bytes for transmission
byte[] bytes = messageFrame.getContentBytes();
System.out.println(new String(bytes, SockJsFrame.CHARSET));import org.springframework.web.socket.sockjs.frame.*;
// Parse frame from string content
String openContent = "o";
SockJsFrame frame1 = new SockJsFrame(openContent);
System.out.println(frame1.getType()); // OPEN
String messageContent = "a[\"Hello\",\"World\"]";
SockJsFrame frame2 = new SockJsFrame(messageContent);
System.out.println(frame2.getType()); // MESSAGE
System.out.println(frame2.getFrameData()); // ["Hello","World"]
String closeContent = "c[3000,\"Goodbye\"]";
SockJsFrame frame3 = new SockJsFrame(closeContent);
System.out.println(frame3.getType()); // CLOSE
System.out.println(frame3.getFrameData()); // [3000,"Goodbye"]
// Invalid frame content throws exception
try {
new SockJsFrame("x[invalid]");
} catch (IllegalArgumentException e) {
System.out.println("Invalid frame type");
}import org.springframework.web.socket.sockjs.frame.*;
import java.io.IOException;
// Create codec
SockJsMessageCodec codec = new JacksonJsonSockJsMessageCodec();
// Encode messages
String encoded = codec.encode("Hello", "World", "Test");
System.out.println(encoded);
// Output: a["Hello","World","Test"]
// Encode with special characters
String encodedSpecial = codec.encode(
"Line 1\nLine 2",
"Quote: \"text\"",
"Unicode: \u2028"
);
System.out.println(encodedSpecial);
// Special characters are properly escaped
// Decode messages
try {
String[] messages = codec.decode("a[\"Hello\",\"World\"]");
for (String msg : messages) {
System.out.println(msg);
}
// Output: Hello
// World
} catch (IOException e) {
System.err.println("Decoding failed: " + e.getMessage());
}
// Decode from InputStream (efficient for large messages)
try (InputStream input = getMessageInputStream()) {
String[] messages = codec.decodeInputStream(input);
processMessages(messages);
} catch (IOException e) {
System.err.println("Decoding failed: " + e.getMessage());
}import org.springframework.web.socket.sockjs.frame.Jackson2SockJsMessageCodec;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
// Create codec with default ObjectMapper
@SuppressWarnings("removal")
Jackson2SockJsMessageCodec codec = new Jackson2SockJsMessageCodec();
// Or with custom ObjectMapper
ObjectMapper customMapper = new ObjectMapper();
customMapper.configure(/* custom settings */);
@SuppressWarnings("removal")
Jackson2SockJsMessageCodec customCodec =
new Jackson2SockJsMessageCodec(customMapper);
// Use the codec
String encoded = codec.encode("Message 1", "Message 2");
try {
String[] decoded = codec.decode(encoded);
System.out.println("Decoded " + decoded.length + " messages");
} catch (IOException e) {
System.err.println("Decoding error: " + e.getMessage());
}import org.springframework.web.socket.sockjs.frame.*;
// Create a message frame
SockJsMessageCodec codec = new JacksonJsonSockJsMessageCodec();
SockJsFrame frame = SockJsFrame.messageFrame(codec, "Hello");
// Format for XHR polling/streaming (append newline)
SockJsFrameFormat xhrFormat = new DefaultSockJsFrameFormat("%s\n");
String xhrFormatted = xhrFormat.format(frame);
System.out.println(xhrFormatted);
// Output: a["Hello"]\n
// Format for JSONP (wrap in callback)
SockJsFrameFormat jsonpFormat = new DefaultSockJsFrameFormat(
"callback(\"%s\");\r\n"
) {
@Override
protected String preProcessContent(String content) {
// Escape quotes and backslashes for JavaScript string
return content.replace("\\", "\\\\").replace("\"", "\\\"");
}
};
String jsonpFormatted = jsonpFormat.format(frame);
System.out.println(jsonpFormatted);
// Format for EventSource (SSE)
SockJsFrameFormat sseFormat = new DefaultSockJsFrameFormat("data: %s\r\n\r\n");
String sseFormatted = sseFormat.format(frame);
System.out.println(sseFormatted);
// Output: data: a["Hello"]\r\n\r\nimport org.springframework.web.socket.sockjs.frame.*;
// Custom format that adds timestamp and compression hint
public class TimestampedFrameFormat implements SockJsFrameFormat {
@Override
public String format(SockJsFrame frame) {
long timestamp = System.currentTimeMillis();
String content = frame.getContent();
// Add custom headers
return String.format(
"X-Timestamp: %d\r\n" +
"X-Frame-Type: %s\r\n" +
"X-Frame-Size: %d\r\n" +
"\r\n%s\r\n",
timestamp,
frame.getType(),
content.length(),
content
);
}
}
// Use custom format
SockJsFrameFormat format = new TimestampedFrameFormat();
SockJsFrame frame = SockJsFrame.openFrame();
String formatted = format.format(frame);
System.out.println(formatted);import org.springframework.web.socket.sockjs.frame.*;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
// Simple custom codec without Jackson dependency
public class SimpleJsonCodec extends AbstractSockJsMessageCodec {
@Override
protected char[] applyJsonQuoting(String content) {
// Basic JSON quoting (production code should handle more cases)
String quoted = content
.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\t", "\\t");
return quoted.toCharArray();
}
@Override
public String[] decode(String content) throws IOException {
// Simple JSON array parsing (production code should be more robust)
if (!content.startsWith("[") || !content.endsWith("]")) {
throw new IOException("Invalid JSON array");
}
String inner = content.substring(1, content.length() - 1).trim();
if (inner.isEmpty()) {
return new String[0];
}
// Split by comma (simplistic - doesn't handle escaped commas)
String[] parts = inner.split(",");
String[] messages = new String[parts.length];
for (int i = 0; i < parts.length; i++) {
String part = parts[i].trim();
// Remove quotes and unescape
if (part.startsWith("\"") && part.endsWith("\"")) {
part = part.substring(1, part.length() - 1);
part = part
.replace("\\n", "\n")
.replace("\\r", "\r")
.replace("\\t", "\t")
.replace("\\\"", "\"")
.replace("\\\\", "\\");
}
messages[i] = part;
}
return messages;
}
@Override
public String[] decodeInputStream(InputStream content) throws IOException {
byte[] bytes = content.readAllBytes();
String str = new String(bytes, StandardCharsets.UTF_8);
return decode(str);
}
}
// Use custom codec
SockJsMessageCodec codec = new SimpleJsonCodec();
String encoded = codec.encode("Test", "Messages");
System.out.println(encoded);
try {
String[] decoded = codec.decode(encoded);
System.out.println("Decoded: " + String.join(", ", decoded));
} catch (IOException e) {
System.err.println("Decode error: " + e.getMessage());
}import org.springframework.web.socket.sockjs.frame.*;
import java.io.IOException;
// SockJS requires special Unicode escaping for certain character ranges
SockJsMessageCodec codec = new JacksonJsonSockJsMessageCodec();
// These characters are automatically escaped by the codec
String[] specialMessages = {
"Control chars: \u0001\u0002\u0003", // \u0000-\u001F
"Line separator: \u2028", // Line separator
"Paragraph separator: \u2029", // Paragraph separator
"Zero width space: \u200B", // Zero width chars
"Surrogate pairs: \uD834\uDD1E", // Musical symbol
"Special ranges: \uFFF0\uFFF1" // Special use area
};
String encoded = codec.encode(specialMessages);
System.out.println("Encoded with Unicode escaping:");
System.out.println(encoded);
// Decoding properly handles the escapes
try {
String[] decoded = codec.decode(encoded);
System.out.println("\nDecoded messages:");
for (String msg : decoded) {
System.out.println(" " + msg);
}
} catch (IOException e) {
System.err.println("Error: " + e.getMessage());
}import org.springframework.web.socket.sockjs.frame.*;
import org.springframework.web.socket.sockjs.transport.handler.*;
// Configure SockJS service with custom codec
@Configuration
public class SockJsConfig {
@Bean
public DefaultSockJsService sockJsService(TaskScheduler scheduler) {
DefaultSockJsService service = new DefaultSockJsService(scheduler);
// Set custom message codec
service.setMessageCodec(customCodec());
return service;
}
@Bean
public SockJsMessageCodec customCodec() {
// Use Jackson 3.x codec (recommended)
return new JacksonJsonSockJsMessageCodec();
// Or use custom JsonMapper configuration
// JsonMapper.Builder builder = JsonMapper.builder();
// builder.configure(/* custom settings */);
// return new JacksonJsonSockJsMessageCodec(builder);
}
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(4);
scheduler.setThreadNamePrefix("sockjs-");
scheduler.initialize();
return scheduler;
}
}import org.springframework.web.socket.sockjs.frame.*;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class SockJsFrameTest {
@Test
public void testFrameCreation() {
SockJsFrame openFrame = SockJsFrame.openFrame();
assertEquals(SockJsFrameType.OPEN, openFrame.getType());
assertEquals("o", openFrame.getContent());
assertNull(openFrame.getFrameData());
SockJsFrame closeFrame = SockJsFrame.closeFrame(3000, "Done");
assertEquals(SockJsFrameType.CLOSE, closeFrame.getType());
assertEquals("c[3000,\"Done\"]", closeFrame.getContent());
assertEquals("[3000,\"Done\"]", closeFrame.getFrameData());
}
@Test
public void testMessageEncoding() throws Exception {
SockJsMessageCodec codec = new JacksonJsonSockJsMessageCodec();
// Test basic encoding
String encoded = codec.encode("test");
assertEquals("a[\"test\"]", encoded);
// Test multiple messages
encoded = codec.encode("msg1", "msg2", "msg3");
assertEquals("a[\"msg1\",\"msg2\",\"msg3\"]", encoded);
// Test decoding
String[] decoded = codec.decode("a[\"hello\",\"world\"]");
assertArrayEquals(new String[]{"hello", "world"}, decoded);
}
@Test
public void testFrameFormatting() {
SockJsFrameFormat format = new DefaultSockJsFrameFormat("%s\n");
SockJsFrame frame = SockJsFrame.heartbeatFrame();
String formatted = format.format(frame);
assertEquals("h\n", formatted);
}
@Test
public void testInvalidFrameContent() {
assertThrows(IllegalArgumentException.class, () -> {
new SockJsFrame("");
});
assertThrows(IllegalArgumentException.class, () -> {
new SockJsFrame("x[invalid]");
});
}
}