diff --git a/build.gradle b/build.gradle
index 899f2bf8e9..da2f7e84be 100644
--- a/build.gradle
+++ b/build.gradle
@@ -507,7 +507,8 @@ project("spring-websocket") {
}
optional("org.eclipse.jetty.websocket:websocket-server:9.0.4.v20130625")
optional("org.eclipse.jetty.websocket:websocket-client:9.0.4.v20130625")
- optional("com.fasterxml.jackson.core:jackson-databind:2.2.0") // required for SockJS support currently
+ optional("com.fasterxml.jackson.core:jackson-databind:2.2.0")
+ optional("org.codehaus.jackson:jackson-mapper-asl:1.9.12")
}
repositories {
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractSockJsService.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractSockJsService.java
index 479226d31c..49593d538c 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractSockJsService.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractSockJsService.java
@@ -46,17 +46,19 @@ import org.springframework.util.StringUtils;
import org.springframework.web.socket.WebSocketHandler;
/**
- * An abstract class for {@link SockJsService} implementations. Provides configuration
- * support, SockJS path resolution, and processing for static SockJS requests (e.g.
- * "/info", "/iframe.html", etc). Sub-classes are responsible for handling transport
- * requests.
- *
- *
It is expected that this service is mapped correctly to one or more prefixes such as
- * "/echo" including all sub-URLs (e.g. "/echo/**"). A SockJS service itself is generally
- * unaware of request mapping details but nevertheless must be able to extract the SockJS
- * path, which is the portion of the request path following the prefix. In most cases,
- * this class can auto-detect the SockJS path but you can also explicitly configure the
- * list of valid prefixes with {@link #setValidSockJsPrefixes(String...)}.
+ * An abstract base class for {@link SockJsService} implementations that provides SockJS
+ * path resolution and handling of static SockJS requests (e.g. "/info", "/iframe.html",
+ * etc). Transport-specific requests are left as abstract methods.
+ *
+ * This service can be integrated into any HTTP request handling mechanism (e.g. plain
+ * Servlet, Spring MVC, or other). It is expected that it will be mapped correctly to a
+ * prefix (e.g. "/echo") and will also handle all sub-URLs (i.e. "/echo/**").
+ *
+ * The service itself is unaware of the underlying mapping mechanism but nevertheless must
+ * be able to extract the SockJS path, i.e. the portion of the request path following the
+ * prefix. In most cases, this class can auto-detect the SockJS path but it is also
+ * possible to configure explicitly the prefixes via
+ * {@link #setValidSockJsPrefixes(String...)}.
*
* @author Rossen Stoyanchev
* @since 4.0
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractSockJsSession.java
index c5e35a6f20..31d392b06e 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractSockJsSession.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractSockJsSession.java
@@ -73,13 +73,13 @@ public abstract class AbstractSockJsSession implements ConfigurableWebSocketSess
* @param config the sockJS configuration
* @param webSocketHandler the recipient of SockJS messages
*/
- public AbstractSockJsSession(String sessionId, SockJsConfiguration config,
- WebSocketHandler webSocketHandler) {
- Assert.notNull(sessionId, "sessionId must not be null");
- Assert.notNull(webSocketHandler, "webSocketHandler must not be null");
+ public AbstractSockJsSession(String sessionId, SockJsConfiguration config, WebSocketHandler webSocketHandler) {
+ Assert.notNull(sessionId, "sessionId is required");
+ Assert.notNull(config, "sockJsConfig is required");
+ Assert.notNull(webSocketHandler, "webSocketHandler is required");
this.id = sessionId;
- this.handler = webSocketHandler;
this.sockJsConfig = config;
+ this.handler = webSocketHandler;
}
@Override
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsConfiguration.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsConfiguration.java
index ceba86f242..f7cc5fd797 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsConfiguration.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsConfiguration.java
@@ -38,7 +38,7 @@ public interface SockJsConfiguration {
*
*
The default value is 128K (i.e. 128 * 1024).
*/
- public int getStreamBytesLimit();
+ int getStreamBytesLimit();
/**
* The amount of time in milliseconds when the server has not sent any
@@ -47,11 +47,17 @@ public interface SockJsConfiguration {
*
*
The default value is 25,000 (25 seconds).
*/
- public long getHeartbeatTime();
+ long getHeartbeatTime();
/**
* A scheduler instance to use for scheduling heart-beat messages.
*/
- public TaskScheduler getTaskScheduler();
+ TaskScheduler getTaskScheduler();
+
+ /**
+ * The codec to use for encoding and decoding SockJS messages.
+ * @exception IllegalStateException if no {@link SockJsMessageCodec} is available
+ */
+ SockJsMessageCodec getMessageCodecRequired();
}
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsFrame.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsFrame.java
index b7620d2348..4c1a374ea7 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsFrame.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsFrame.java
@@ -20,8 +20,6 @@ import java.nio.charset.Charset;
import org.springframework.util.Assert;
-import com.fasterxml.jackson.core.io.JsonStringEncoder;
-
/**
* Represents a SockJS frames. Provides methods for access to commonly used message
* frames.
@@ -31,42 +29,43 @@ import com.fasterxml.jackson.core.io.JsonStringEncoder;
*/
public class SockJsFrame {
- private static final SockJsFrame OPEN_FRAME = new SockJsFrame("o");
+ private static final SockJsFrame openFrame = new SockJsFrame("o");
- private static final SockJsFrame HEARTBEAT_FRAME = new SockJsFrame("h");
+ private static final SockJsFrame heartbeatFrame = new SockJsFrame("h");
- private static final SockJsFrame CLOSE_GO_AWAY_FRAME = closeFrame(3000, "Go away!");
+ private static final SockJsFrame closeGoAwayFrame = closeFrame(3000, "Go away!");
- private static final SockJsFrame CLOSE_ANOTHER_CONNECTION_OPEN = closeFrame(2010, "Another connection still open");
+ private static final SockJsFrame closeAnotherConnectionOpenFrame = closeFrame(2010, "Another connection still open");
private final String content;
private SockJsFrame(String content) {
- Assert.notNull("content must not be null");
+ Assert.notNull("content is required");
this.content = content;
}
public static SockJsFrame openFrame() {
- return OPEN_FRAME;
+ return openFrame;
}
public static SockJsFrame heartbeatFrame() {
- return HEARTBEAT_FRAME;
+ return heartbeatFrame;
}
- public static SockJsFrame messageFrame(String... messages) {
- return new MessageFrame(messages);
+ public static SockJsFrame messageFrame(SockJsMessageCodec codec, String... messages) {
+ String encoded = codec.encode(messages);
+ return new SockJsFrame(encoded);
}
public static SockJsFrame closeFrameGoAway() {
- return CLOSE_GO_AWAY_FRAME;
+ return closeGoAwayFrame;
}
public static SockJsFrame closeFrameAnotherConnectionOpen() {
- return CLOSE_ANOTHER_CONNECTION_OPEN;
+ return closeAnotherConnectionOpenFrame;
}
public static SockJsFrame closeFrame(int code, String reason) {
@@ -82,35 +81,6 @@ public class SockJsFrame {
return this.content.getBytes(Charset.forName("UTF-8"));
}
- /**
- * See "JSON Unicode Encoding" section of SockJS protocol.
- */
- public static String escapeCharacters(char[] characters) {
- StringBuilder result = new StringBuilder();
- for (char c : characters) {
- if (isSockJsEscapeCharacter(c)) {
- result.append('\\').append('u');
- String hex = Integer.toHexString(c).toLowerCase();
- for (int i = 0; i < (4 - hex.length()); i++) {
- result.append('0');
- }
- result.append(hex);
- }
- else {
- result.append(c);
- }
- }
- return result.toString();
- }
-
- // See `escapable_by_server` var in SockJS protocol (under "JSON Unicode Encoding")
-
- private static boolean isSockJsEscapeCharacter(char ch) {
- return (ch >= '\u0000' && ch <= '\u001F') || (ch >= '\u200C' && ch <= '\u200F')
- || (ch >= '\u2028' && ch <= '\u202F') || (ch >= '\u2060' && ch <= '\u206F')
- || (ch >= '\uFFF0' && ch <= '\uFFFF') || (ch >= '\uD800' && ch <= '\uDFFF');
- }
-
@Override
public String toString() {
String result = this.content;
@@ -137,31 +107,6 @@ public class SockJsFrame {
}
- private static class MessageFrame extends SockJsFrame {
-
- public MessageFrame(String... messages) {
- super(prepareContent(messages));
- }
-
- public static String prepareContent(String... messages) {
- Assert.notNull(messages, "messages must not be null");
- StringBuilder sb = new StringBuilder();
- sb.append("a[");
- for (int i=0; i < messages.length; i++) {
- sb.append('"');
- // TODO: dependency on Jackson
- char[] quotedChars = JsonStringEncoder.getInstance().quoteAsString(messages[i]);
- sb.append(escapeCharacters(quotedChars));
- sb.append('"');
- if (i < messages.length - 1) {
- sb.append(',');
- }
- }
- sb.append(']');
- return sb.toString();
- }
- }
-
public interface FrameFormat {
SockJsFrame format(SockJsFrame frame);
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsMessageCodec.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsMessageCodec.java
new file mode 100644
index 0000000000..a4fabe891a
--- /dev/null
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsMessageCodec.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.socket.sockjs;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+
+/**
+ * A contract for encoding and decoding of messages to and from a SockJS message frame,
+ * which is essentially an array of JSON-encoded messages. For example:
+ *
+ *
+ * a["message1","message2"]
+ *
+ *
+ * @author Rossen Stoyanchev
+ * @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).
+ *
+ * @param messages the messages to encode
+ * @return the content for a SockJS message frame, never {@code null}
+ */
+ String encode(String[] messages);
+
+ /**
+ * Decode the given SockJS message frame.
+ *
+ * @param content the SockJS message frame
+ * @return an array of messages or {@code null}
+ * @throws IOException if the content could not be parsed
+ */
+ String[] decode(String content) throws IOException;
+
+ /**
+ * Decode the given SockJS message frame.
+ *
+ * @param content the SockJS message frame
+ * @return an array of messages or {@code null}
+ * @throws IOException if the content could not be parsed
+ */
+ String[] decodeInputStream(InputStream content) throws IOException;
+
+}
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/TransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/TransportHandler.java
index b1c2e258ef..70449d59ff 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/TransportHandler.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/TransportHandler.java
@@ -36,6 +36,8 @@ public interface TransportHandler {
TransportType getTransportType();
+ void setSockJsConfiguration(SockJsConfiguration sockJsConfig);
+
void handleRequest(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler handler, AbstractSockJsSession session) throws TransportErrorException;
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/AbstractSockJsMessageCodec.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/AbstractSockJsMessageCodec.java
new file mode 100644
index 0000000000..893113a058
--- /dev/null
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/AbstractSockJsMessageCodec.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.socket.sockjs.support;
+
+import org.springframework.util.Assert;
+import org.springframework.web.socket.sockjs.SockJsMessageCodec;
+
+
+/**
+ * An base class for SockJS message codec that provides an implementation of
+ * {@link #encode(String[])}.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.0
+ */
+public abstract class AbstractSockJsMessageCodec implements SockJsMessageCodec {
+
+
+ @Override
+ public String encode(String[] messages) {
+ Assert.notNull(messages, "messages must not be null");
+ StringBuilder sb = new StringBuilder();
+ sb.append("a[");
+ for (int i=0; i < messages.length; i++) {
+ sb.append('"');
+ char[] quotedChars = applyJsonQuoting(messages[i]);
+ sb.append(escapeSockJsSpecialChars(quotedChars));
+ sb.append('"');
+ if (i < messages.length - 1) {
+ sb.append(',');
+ }
+ }
+ sb.append(']');
+ return sb.toString();
+ }
+
+ /**
+ * Apply standard JSON string quoting (see http://www.json.org/).
+ */
+ protected abstract char[] applyJsonQuoting(String content);
+
+ /**
+ * See "JSON Unicode Encoding" section of SockJS protocol.
+ */
+ private String escapeSockJsSpecialChars(char[] characters) {
+ StringBuilder result = new StringBuilder();
+ for (char c : characters) {
+ if (isSockJsSpecialChar(c)) {
+ result.append('\\').append('u');
+ String hex = Integer.toHexString(c).toLowerCase();
+ for (int i = 0; i < (4 - hex.length()); i++) {
+ result.append('0');
+ }
+ result.append(hex);
+ }
+ else {
+ result.append(c);
+ }
+ }
+ return result.toString();
+ }
+
+ /**
+ * See `escapable_by_server` variable in the SockJS protocol test suite.
+ */
+ private boolean isSockJsSpecialChar(char ch) {
+ return (ch >= '\u0000' && ch <= '\u001F') || (ch >= '\u200C' && ch <= '\u200F')
+ || (ch >= '\u2028' && ch <= '\u202F') || (ch >= '\u2060' && ch <= '\u206F')
+ || (ch >= '\uFFF0' && ch <= '\uFFFF') || (ch >= '\uD800' && ch <= '\uDFFF');
+ }
+
+}
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/Jackson2SockJsMessageCodec.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/Jackson2SockJsMessageCodec.java
new file mode 100644
index 0000000000..b9b5617cd3
--- /dev/null
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/Jackson2SockJsMessageCodec.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.socket.sockjs.support;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.springframework.util.Assert;
+
+import com.fasterxml.jackson.core.io.JsonStringEncoder;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+
+/**
+ * A Jackson 2 codec for encoding and decoding SockJS messages.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.0
+ */
+public class Jackson2SockJsMessageCodec extends AbstractSockJsMessageCodec {
+
+ private final ObjectMapper objectMapper;
+
+
+ public Jackson2SockJsMessageCodec() {
+ this.objectMapper = new ObjectMapper();
+ }
+
+ public Jackson2SockJsMessageCodec(ObjectMapper objectMapper) {
+ Assert.notNull(objectMapper, "objectMapper is required");
+ this.objectMapper = objectMapper;
+ }
+
+ @Override
+ public String[] decode(String content) throws IOException {
+ return this.objectMapper.readValue(content, String[].class);
+ }
+
+ @Override
+ public String[] decodeInputStream(InputStream content) throws IOException {
+ return this.objectMapper.readValue(content, String[].class);
+ }
+
+ @Override
+ protected char[] applyJsonQuoting(String content) {
+ return JsonStringEncoder.getInstance().quoteAsString(content);
+ }
+
+}
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/JacksonSockJsMessageCodec.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/JacksonSockJsMessageCodec.java
new file mode 100644
index 0000000000..34f37c5f2f
--- /dev/null
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/JacksonSockJsMessageCodec.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.socket.sockjs.support;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.codehaus.jackson.io.JsonStringEncoder;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.springframework.util.Assert;
+
+
+/**
+ * A Jackson 1.x codec for encoding and decoding SockJS messages.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.0
+ */
+public class JacksonSockJsMessageCodec extends AbstractSockJsMessageCodec {
+
+ private final ObjectMapper objectMapper;
+
+
+ public JacksonSockJsMessageCodec() {
+ this.objectMapper = new ObjectMapper();
+ }
+
+ public JacksonSockJsMessageCodec(ObjectMapper objectMapper) {
+ Assert.notNull(objectMapper, "objectMapper is required");
+ this.objectMapper = objectMapper;
+ }
+
+ @Override
+ public String[] decode(String content) throws IOException {
+ return this.objectMapper.readValue(content, String[].class);
+ }
+
+ @Override
+ public String[] decodeInputStream(InputStream content) throws IOException {
+ return this.objectMapper.readValue(content, String[].class);
+ }
+
+ @Override
+ protected char[] applyJsonQuoting(String content) {
+ return JsonStringEncoder.getInstance().quoteAsString(content);
+ }
+
+}
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/package-info.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/package-info.java
index 2b3cc8d734..2359960d30 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/package-info.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/package-info.java
@@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -15,10 +15,7 @@
*/
/**
- * Key server-side SockJS implementation classes including a
- * {@link org.springframework.web.socket.sockjs.support.DefaultSockJsService} implementation
- * as well as a Spring MVC HandlerMapping mapping SockJS services to incoming requests.
- *
+ * Support classes for the SockJS implementation.
*/
package org.springframework.web.socket.sockjs.support;
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpReceivingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpReceivingTransportHandler.java
index ce98eb55f2..8320969e15 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpReceivingTransportHandler.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpReceivingTransportHandler.java
@@ -20,8 +20,6 @@ import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Arrays;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
@@ -33,7 +31,6 @@ import org.springframework.web.socket.sockjs.TransportHandler;
import org.springframework.web.socket.support.ExceptionWebSocketHandlerDecorator;
import com.fasterxml.jackson.databind.JsonMappingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Base class for HTTP-based transports that read input messages from HTTP requests.
@@ -41,17 +38,9 @@ import com.fasterxml.jackson.databind.ObjectMapper;
* @author Rossen Stoyanchev
* @since 4.0
*/
-public abstract class AbstractHttpReceivingTransportHandler implements TransportHandler {
+public abstract class AbstractHttpReceivingTransportHandler
+ extends TransportHandlerSupport implements TransportHandler {
- protected final Log logger = LogFactory.getLog(this.getClass());
-
- // TODO: the JSON library used must be configurable
- private final ObjectMapper objectMapper = new ObjectMapper();
-
-
- public ObjectMapper getObjectMapper() {
- return this.objectMapper;
- }
@Override
public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response,
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpSendingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpSendingTransportHandler.java
index 0dac595b0f..8d58423714 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpSendingTransportHandler.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpSendingTransportHandler.java
@@ -18,19 +18,16 @@ package org.springframework.web.socket.sockjs.transport;
import java.io.IOException;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
-import org.springframework.web.socket.sockjs.AbstractSockJsSession;
-import org.springframework.web.socket.sockjs.ConfigurableTransportHandler;
-import org.springframework.web.socket.sockjs.SockJsConfiguration;
import org.springframework.web.socket.sockjs.SockJsFrame;
import org.springframework.web.socket.sockjs.SockJsFrame.FrameFormat;
+import org.springframework.web.socket.sockjs.AbstractSockJsSession;
import org.springframework.web.socket.sockjs.SockJsSessionFactory;
import org.springframework.web.socket.sockjs.TransportErrorException;
+import org.springframework.web.socket.sockjs.TransportHandler;
/**
* Base class for HTTP-based transports that send messages over HTTP.
@@ -38,22 +35,9 @@ import org.springframework.web.socket.sockjs.TransportErrorException;
* @author Rossen Stoyanchev
* @since 4.0
*/
-public abstract class AbstractHttpSendingTransportHandler
- implements ConfigurableTransportHandler, SockJsSessionFactory {
+public abstract class AbstractHttpSendingTransportHandler extends TransportHandlerSupport
+ implements TransportHandler, SockJsSessionFactory {
- protected final Log logger = LogFactory.getLog(this.getClass());
-
- private SockJsConfiguration sockJsConfig;
-
-
- @Override
- public void setSockJsConfiguration(SockJsConfiguration sockJsConfig) {
- this.sockJsConfig = sockJsConfig;
- }
-
- public SockJsConfiguration getSockJsConfig() {
- return this.sockJsConfig;
- }
@Override
public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response,
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/DefaultSockJsService.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/DefaultSockJsService.java
similarity index 87%
rename from spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/DefaultSockJsService.java
rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/DefaultSockJsService.java
index 57a3d34415..2be0f0f790 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/DefaultSockJsService.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/DefaultSockJsService.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.web.socket.sockjs.support;
+package org.springframework.web.socket.sockjs.transport;
import java.io.IOException;
import java.util.Arrays;
@@ -34,6 +34,8 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.scheduling.TaskScheduler;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.socket.WebSocketHandler;
@@ -42,20 +44,13 @@ import org.springframework.web.socket.server.HandshakeHandler;
import org.springframework.web.socket.server.support.ServerWebSocketSessionInitializer;
import org.springframework.web.socket.sockjs.AbstractSockJsService;
import org.springframework.web.socket.sockjs.AbstractSockJsSession;
-import org.springframework.web.socket.sockjs.ConfigurableTransportHandler;
+import org.springframework.web.socket.sockjs.SockJsMessageCodec;
import org.springframework.web.socket.sockjs.SockJsService;
import org.springframework.web.socket.sockjs.SockJsSessionFactory;
import org.springframework.web.socket.sockjs.TransportErrorException;
import org.springframework.web.socket.sockjs.TransportHandler;
import org.springframework.web.socket.sockjs.TransportType;
-import org.springframework.web.socket.sockjs.transport.EventSourceTransportHandler;
-import org.springframework.web.socket.sockjs.transport.HtmlFileTransportHandler;
-import org.springframework.web.socket.sockjs.transport.JsonpPollingTransportHandler;
-import org.springframework.web.socket.sockjs.transport.JsonpTransportHandler;
-import org.springframework.web.socket.sockjs.transport.WebSocketTransportHandler;
-import org.springframework.web.socket.sockjs.transport.XhrPollingTransportHandler;
-import org.springframework.web.socket.sockjs.transport.XhrStreamingTransportHandler;
-import org.springframework.web.socket.sockjs.transport.XhrTransportHandler;
+import org.springframework.web.socket.sockjs.support.Jackson2SockJsMessageCodec;
/**
@@ -68,8 +63,17 @@ import org.springframework.web.socket.sockjs.transport.XhrTransportHandler;
*/
public class DefaultSockJsService extends AbstractSockJsService {
+ private static final boolean jackson2Present = ClassUtils.isPresent(
+ "com.fasterxml.jackson.databind.ObjectMapper", DefaultSockJsService.class.getClassLoader());
+
+ private static final boolean jacksonPresent = ClassUtils.isPresent(
+ "org.codehaus.jackson.map.ObjectMapper", DefaultSockJsService.class.getClassLoader());
+
+
private final Map transportHandlers = new HashMap();
+ private SockJsMessageCodec messageCodec;
+
private final Map sessions = new ConcurrentHashMap();
private final ServerWebSocketSessionInitializer sessionInitializer = new ServerWebSocketSessionInitializer();
@@ -88,6 +92,16 @@ public class DefaultSockJsService extends AbstractSockJsService {
public DefaultSockJsService(TaskScheduler taskScheduler) {
super(taskScheduler);
addTransportHandlers(getDefaultTransportHandlers());
+ initMessageCodec();
+ }
+
+ protected void initMessageCodec() {
+ if (jackson2Present) {
+ this.messageCodec = new Jackson2SockJsMessageCodec();
+ }
+ else if (jacksonPresent) {
+ this.messageCodec = new Jackson2SockJsMessageCodec();
+ }
}
/**
@@ -108,6 +122,8 @@ public class DefaultSockJsService extends AbstractSockJsService {
super(taskScheduler);
+ initMessageCodec();
+
if (!CollectionUtils.isEmpty(transportHandlers)) {
addTransportHandlers(transportHandlers);
}
@@ -143,13 +159,27 @@ public class DefaultSockJsService extends AbstractSockJsService {
protected void addTransportHandlers(Collection handlers) {
for (TransportHandler handler : handlers) {
- if (handler instanceof ConfigurableTransportHandler) {
- ((ConfigurableTransportHandler) handler).setSockJsConfiguration(this);
- }
+ handler.setSockJsConfiguration(this);
this.transportHandlers.put(handler.getTransportType(), handler);
}
}
+
+ public void setMessageCodec(SockJsMessageCodec messageCodec) {
+ this.messageCodec = messageCodec;
+ }
+
+ public SockJsMessageCodec getMessageCodec() {
+ return this.messageCodec;
+ }
+
+ @Override
+ public SockJsMessageCodec getMessageCodecRequired() {
+ Assert.state(this.messageCodec != null, "A SockJsMessageCodec is required but not available."
+ + " Either add Jackson 2 or Jackson 1.x to the classpath, or configure a SockJsMessageCode");
+ return this.messageCodec;
+ }
+
public Map getTransportHandlers() {
return Collections.unmodifiableMap(this.transportHandlers);
}
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/EventSourceTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/EventSourceTransportHandler.java
index 39b5d83a9a..ea986bf55d 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/EventSourceTransportHandler.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/EventSourceTransportHandler.java
@@ -21,8 +21,8 @@ import java.nio.charset.Charset;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
-import org.springframework.util.Assert;
import org.springframework.web.socket.WebSocketHandler;
+import org.springframework.web.socket.sockjs.SockJsConfiguration;
import org.springframework.web.socket.sockjs.SockJsFrame.DefaultFrameFormat;
import org.springframework.web.socket.sockjs.SockJsFrame.FrameFormat;
import org.springframework.web.socket.sockjs.TransportType;
@@ -36,6 +36,7 @@ import org.springframework.web.socket.sockjs.TransportType;
*/
public class EventSourceTransportHandler extends AbstractHttpSendingTransportHandler {
+
@Override
public TransportType getTransportType() {
return TransportType.EVENT_SOURCE;
@@ -48,15 +49,7 @@ public class EventSourceTransportHandler extends AbstractHttpSendingTransportHan
@Override
public StreamingSockJsSession createSession(String sessionId, WebSocketHandler handler) {
- Assert.state(getSockJsConfig() != null, "This transport requires SockJsConfiguration");
- return new StreamingSockJsSession(sessionId, getSockJsConfig(), handler) {
- @Override
- protected void writePrelude() throws IOException {
- getResponse().getBody().write('\r');
- getResponse().getBody().write('\n');
- getResponse().flush();
- }
- };
+ return new EventSourceStreamingSockJsSession(sessionId, getSockJsConfig(), handler);
}
@Override
@@ -64,4 +57,19 @@ public class EventSourceTransportHandler extends AbstractHttpSendingTransportHan
return new DefaultFrameFormat("data: %s\r\n\r\n");
}
+
+ private final class EventSourceStreamingSockJsSession extends StreamingSockJsSession {
+
+ private EventSourceStreamingSockJsSession(String sessionId, SockJsConfiguration config, WebSocketHandler handler) {
+ super(sessionId, config, handler);
+ }
+
+ @Override
+ protected void writePrelude() throws IOException {
+ getResponse().getBody().write('\r');
+ getResponse().getBody().write('\n');
+ getResponse().flush();
+ }
+ }
+
}
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/HtmlFileTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/HtmlFileTransportHandler.java
index e3dfd012bb..ed76baf698 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/HtmlFileTransportHandler.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/HtmlFileTransportHandler.java
@@ -23,9 +23,9 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
-import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.socket.WebSocketHandler;
+import org.springframework.web.socket.sockjs.SockJsConfiguration;
import org.springframework.web.socket.sockjs.SockJsFrame.DefaultFrameFormat;
import org.springframework.web.socket.sockjs.SockJsFrame.FrameFormat;
import org.springframework.web.socket.sockjs.TransportErrorException;
@@ -85,20 +85,7 @@ public class HtmlFileTransportHandler extends AbstractHttpSendingTransportHandle
@Override
public StreamingSockJsSession createSession(String sessionId, WebSocketHandler handler) {
- Assert.state(getSockJsConfig() != null, "This transport requires SockJsConfiguration");
-
- return new StreamingSockJsSession(sessionId, getSockJsConfig(), handler) {
-
- @Override
- protected void writePrelude() throws IOException {
- // we already validated the parameter..
- String callback = getRequest().getQueryParams().getFirst("c");
-
- String html = String.format(PARTIAL_HTML_CONTENT, callback);
- getResponse().getBody().write(html.getBytes("UTF-8"));
- getResponse().flush();
- }
- };
+ return new HtmlFileStreamingSockJsSession(sessionId, getSockJsConfig(), handler);
}
@Override
@@ -116,7 +103,6 @@ public class HtmlFileTransportHandler extends AbstractHttpSendingTransportHandle
catch (Throwable t) {
throw new TransportErrorException("Failed to send error to client", t, session.getId());
}
-
super.handleRequestInternal(request, response, session);
}
@@ -130,4 +116,22 @@ public class HtmlFileTransportHandler extends AbstractHttpSendingTransportHandle
};
}
+
+ private final class HtmlFileStreamingSockJsSession extends StreamingSockJsSession {
+
+ private HtmlFileStreamingSockJsSession(String sessionId, SockJsConfiguration config, WebSocketHandler handler) {
+ super(sessionId, config, handler);
+ }
+
+ @Override
+ protected void writePrelude() throws IOException {
+ // we already validated the parameter..
+ String callback = getRequest().getQueryParams().getFirst("c");
+
+ String html = String.format(PARTIAL_HTML_CONTENT, callback);
+ getResponse().getBody().write(html.getBytes("UTF-8"));
+ getResponse().flush();
+ }
+ }
+
}
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/JsonpPollingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/JsonpPollingTransportHandler.java
index 4ebf1751fe..0b134f442c 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/JsonpPollingTransportHandler.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/JsonpPollingTransportHandler.java
@@ -22,7 +22,6 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
-import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.sockjs.SockJsFrame;
@@ -51,7 +50,6 @@ public class JsonpPollingTransportHandler extends AbstractHttpSendingTransportHa
@Override
public PollingSockJsSession createSession(String sessionId, WebSocketHandler handler) {
- Assert.state(getSockJsConfig() != null, "This transport requires SockJsConfiguration");
return new PollingSockJsSession(sessionId, getSockJsConfig(), handler);
}
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/JsonpTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/JsonpTransportHandler.java
index 585fcb2272..98777f3c6e 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/JsonpTransportHandler.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/JsonpTransportHandler.java
@@ -26,6 +26,7 @@ import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.socket.sockjs.AbstractSockJsSession;
+import org.springframework.web.socket.sockjs.SockJsMessageCodec;
import org.springframework.web.socket.sockjs.TransportErrorException;
import org.springframework.web.socket.sockjs.TransportHandler;
import org.springframework.web.socket.sockjs.TransportType;
@@ -61,14 +62,17 @@ public class JsonpTransportHandler extends AbstractHttpReceivingTransportHandler
@Override
protected String[] readMessages(ServerHttpRequest request) throws IOException {
+
+ SockJsMessageCodec messageCodec = getSockJsConfig().getMessageCodecRequired();
+
MediaType contentType = request.getHeaders().getContentType();
if ((contentType != null) && MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(contentType)) {
MultiValueMap map = this.formConverter.read(null, request);
String d = map.getFirst("d");
- return (StringUtils.hasText(d)) ? getObjectMapper().readValue(d, String[].class) : null;
+ return (StringUtils.hasText(d)) ? messageCodec.decode(d) : null;
}
else {
- return getObjectMapper().readValue(request.getBody(), String[].class);
+ return messageCodec.decodeInputStream(request.getBody());
}
}
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/PollingSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/PollingSockJsSession.java
index b35bd89d5e..13f04c822b 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/PollingSockJsSession.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/PollingSockJsSession.java
@@ -21,6 +21,7 @@ import java.io.IOException;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.sockjs.SockJsConfiguration;
import org.springframework.web.socket.sockjs.SockJsFrame;
+import org.springframework.web.socket.sockjs.SockJsMessageCodec;
/**
* A SockJS session for use with polling HTTP transports.
@@ -36,10 +37,14 @@ public class PollingSockJsSession extends AbstractHttpSockJsSession {
@Override
protected void flushCache() throws IOException {
+
cancelHeartbeat();
String[] messages = getMessageCache().toArray(new String[getMessageCache().size()]);
getMessageCache().clear();
- writeFrame(SockJsFrame.messageFrame(messages));
+
+ SockJsMessageCodec messageCodec = getSockJsConfig().getMessageCodecRequired();
+ SockJsFrame frame = SockJsFrame.messageFrame(messageCodec, messages);
+ writeFrame(frame);
}
@Override
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/StreamingSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/StreamingSockJsSession.java
index 33a9f23da7..3ce2ec9b2d 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/StreamingSockJsSession.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/StreamingSockJsSession.java
@@ -24,6 +24,7 @@ import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.sockjs.SockJsConfiguration;
import org.springframework.web.socket.sockjs.SockJsFrame;
import org.springframework.web.socket.sockjs.SockJsFrame.FrameFormat;
+import org.springframework.web.socket.sockjs.SockJsMessageCodec;
import org.springframework.web.socket.sockjs.TransportErrorException;
/**
@@ -60,7 +61,8 @@ public class StreamingSockJsSession extends AbstractHttpSockJsSession {
do {
String message = getMessageCache().poll();
- SockJsFrame frame = SockJsFrame.messageFrame(message);
+ SockJsMessageCodec messageCodec = getSockJsConfig().getMessageCodecRequired();
+ SockJsFrame frame = SockJsFrame.messageFrame(messageCodec, message);
writeFrame(frame);
this.byteCount += frame.getContentBytes().length + 1;
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/ConfigurableTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/TransportHandlerSupport.java
similarity index 53%
rename from spring-websocket/src/main/java/org/springframework/web/socket/sockjs/ConfigurableTransportHandler.java
rename to spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/TransportHandlerSupport.java
index f4b2fb51d1..3dfe2e6a06 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/ConfigurableTransportHandler.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/TransportHandlerSupport.java
@@ -14,16 +14,31 @@
* limitations under the License.
*/
-package org.springframework.web.socket.sockjs;
+package org.springframework.web.socket.sockjs.transport;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.web.socket.sockjs.SockJsConfiguration;
+
/**
- * A {@link TransportHandler} that requires access to SockJS configuration options.
- *
* @author Rossen Stoyanchev
- * @since 4.0
+ * @sicne 4.0
*/
-public interface ConfigurableTransportHandler extends TransportHandler {
+public abstract class TransportHandlerSupport {
+
+ protected final Log logger = LogFactory.getLog(this.getClass());
+
+ private SockJsConfiguration sockJsConfig;
+
+
+ public void setSockJsConfiguration(SockJsConfiguration sockJsConfig) {
+ this.sockJsConfig = sockJsConfig;
+ }
+
+ public SockJsConfiguration getSockJsConfig() {
+ return this.sockJsConfig;
+ }
- void setSockJsConfiguration(SockJsConfiguration sockJsConfig);
}
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/WebSocketServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/WebSocketServerSockJsSession.java
index 639bfbeba0..d1f873b433 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/WebSocketServerSockJsSession.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/WebSocketServerSockJsSession.java
@@ -26,8 +26,7 @@ import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.sockjs.AbstractSockJsSession;
import org.springframework.web.socket.sockjs.SockJsConfiguration;
import org.springframework.web.socket.sockjs.SockJsFrame;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.web.socket.sockjs.SockJsMessageCodec;
/**
* A SockJS session for use with the WebSocket transport.
@@ -39,9 +38,6 @@ public class WebSocketServerSockJsSession extends AbstractSockJsSession {
private WebSocketSession webSocketSession;
- // TODO: JSON library used must be configurable
- private final ObjectMapper objectMapper = new ObjectMapper();
-
public WebSocketServerSockJsSession(String sessionId, SockJsConfiguration config, WebSocketHandler handler) {
super(sessionId, config, handler);
@@ -89,7 +85,7 @@ public class WebSocketServerSockJsSession extends AbstractSockJsSession {
}
String[] messages;
try {
- messages = this.objectMapper.readValue(payload, String[].class);
+ messages = getSockJsConfig().getMessageCodecRequired().decode(payload);
}
catch (IOException ex) {
logger.error("Broken data received. Terminating WebSocket connection abruptly", ex);
@@ -102,7 +98,9 @@ public class WebSocketServerSockJsSession extends AbstractSockJsSession {
@Override
public void sendMessageInternal(String message) throws IOException {
cancelHeartbeat();
- writeFrame(SockJsFrame.messageFrame(message));
+ SockJsMessageCodec messageCodec = getSockJsConfig().getMessageCodecRequired();
+ SockJsFrame frame = SockJsFrame.messageFrame(messageCodec, message);
+ writeFrame(frame);
scheduleHeartbeat();
}
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/WebSocketTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/WebSocketTransportHandler.java
index 9bb00e4563..5b53f430ee 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/WebSocketTransportHandler.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/WebSocketTransportHandler.java
@@ -24,8 +24,6 @@ import org.springframework.util.Assert;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeHandler;
import org.springframework.web.socket.sockjs.AbstractSockJsSession;
-import org.springframework.web.socket.sockjs.ConfigurableTransportHandler;
-import org.springframework.web.socket.sockjs.SockJsConfiguration;
import org.springframework.web.socket.sockjs.SockJsSessionFactory;
import org.springframework.web.socket.sockjs.TransportErrorException;
import org.springframework.web.socket.sockjs.TransportHandler;
@@ -41,13 +39,11 @@ import org.springframework.web.socket.sockjs.TransportType;
* @author Rossen Stoyanchev
* @since 4.0
*/
-public class WebSocketTransportHandler implements ConfigurableTransportHandler,
- HandshakeHandler, SockJsSessionFactory {
+public class WebSocketTransportHandler extends TransportHandlerSupport
+ implements TransportHandler, SockJsSessionFactory, HandshakeHandler {
private final HandshakeHandler handshakeHandler;
- private SockJsConfiguration sockJsConfig;
-
public WebSocketTransportHandler(HandshakeHandler handshakeHandler) {
Assert.notNull(handshakeHandler, "handshakeHandler must not be null");
@@ -60,14 +56,9 @@ public class WebSocketTransportHandler implements ConfigurableTransportHandler,
return TransportType.WEBSOCKET;
}
- @Override
- public void setSockJsConfiguration(SockJsConfiguration sockJsConfig) {
- this.sockJsConfig = sockJsConfig;
- }
-
@Override
public AbstractSockJsSession createSession(String sessionId, WebSocketHandler webSocketHandler) {
- return new WebSocketServerSockJsSession(sessionId, this.sockJsConfig, webSocketHandler);
+ return new WebSocketServerSockJsSession(sessionId, getSockJsConfig(), webSocketHandler);
}
@Override
@@ -76,7 +67,7 @@ public class WebSocketTransportHandler implements ConfigurableTransportHandler,
try {
WebSocketServerSockJsSession wsSession = (WebSocketServerSockJsSession) session;
- WebSocketHandler sockJsWrapper = new SockJsWebSocketHandler(this.sockJsConfig, webSocketHandler, wsSession);
+ WebSocketHandler sockJsWrapper = new SockJsWebSocketHandler(getSockJsConfig(), webSocketHandler, wsSession);
this.handshakeHandler.doHandshake(request, response, sockJsWrapper);
}
catch (Throwable t) {
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrPollingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrPollingTransportHandler.java
index 8c784a2e83..3f26872a47 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrPollingTransportHandler.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrPollingTransportHandler.java
@@ -20,7 +20,6 @@ import java.nio.charset.Charset;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
-import org.springframework.util.Assert;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.sockjs.SockJsFrame.DefaultFrameFormat;
import org.springframework.web.socket.sockjs.SockJsFrame.FrameFormat;
@@ -52,7 +51,6 @@ public class XhrPollingTransportHandler extends AbstractHttpSendingTransportHand
@Override
public PollingSockJsSession createSession(String sessionId, WebSocketHandler handler) {
- Assert.state(getSockJsConfig() != null, "This transport requires SockJsConfiguration");
return new PollingSockJsSession(sessionId, getSockJsConfig(), handler);
}
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrStreamingTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrStreamingTransportHandler.java
index e0e7881168..2e38c136fe 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrStreamingTransportHandler.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrStreamingTransportHandler.java
@@ -21,8 +21,8 @@ import java.nio.charset.Charset;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
-import org.springframework.util.Assert;
import org.springframework.web.socket.WebSocketHandler;
+import org.springframework.web.socket.sockjs.SockJsConfiguration;
import org.springframework.web.socket.sockjs.SockJsFrame.DefaultFrameFormat;
import org.springframework.web.socket.sockjs.SockJsFrame.FrameFormat;
import org.springframework.web.socket.sockjs.TransportHandler;
@@ -36,6 +36,7 @@ import org.springframework.web.socket.sockjs.TransportType;
*/
public class XhrStreamingTransportHandler extends AbstractHttpSendingTransportHandler {
+
@Override
public TransportType getTransportType() {
return TransportType.XHR_STREAMING;
@@ -48,19 +49,7 @@ public class XhrStreamingTransportHandler extends AbstractHttpSendingTransportHa
@Override
public StreamingSockJsSession createSession(String sessionId, WebSocketHandler handler) {
- Assert.state(getSockJsConfig() != null, "This transport requires SockJsConfiguration");
-
- return new StreamingSockJsSession(sessionId, getSockJsConfig(), handler) {
-
- @Override
- protected void writePrelude() throws IOException {
- for (int i=0; i < 2048; i++) {
- getResponse().getBody().write('h');
- }
- getResponse().getBody().write('\n');
- getResponse().flush();
- }
- };
+ return new XhrStreamingSockJsSession(sessionId, getSockJsConfig(), handler);
}
@Override
@@ -68,4 +57,20 @@ public class XhrStreamingTransportHandler extends AbstractHttpSendingTransportHa
return new DefaultFrameFormat("%s\n");
}
+
+ private final class XhrStreamingSockJsSession extends StreamingSockJsSession {
+
+ private XhrStreamingSockJsSession(String sessionId, SockJsConfiguration config, WebSocketHandler handler) {
+ super(sessionId, config, handler);
+ }
+
+ @Override
+ protected void writePrelude() throws IOException {
+ for (int i=0; i < 2048; i++) {
+ getResponse().getBody().write('h');
+ }
+ getResponse().getBody().write('\n');
+ getResponse().flush();
+ }
+ }
}
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrTransportHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrTransportHandler.java
index eff26d2611..fdbc5f8893 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrTransportHandler.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/XhrTransportHandler.java
@@ -30,6 +30,7 @@ import org.springframework.web.socket.sockjs.TransportType;
*/
public class XhrTransportHandler extends AbstractHttpReceivingTransportHandler {
+
@Override
public TransportType getTransportType() {
return TransportType.XHR_SEND;
@@ -37,7 +38,7 @@ public class XhrTransportHandler extends AbstractHttpReceivingTransportHandler {
@Override
protected String[] readMessages(ServerHttpRequest request) throws IOException {
- return getObjectMapper().readValue(request.getBody(), String[].class);
+ return getSockJsConfig().getMessageCodecRequired().decodeInputStream(request.getBody());
}
@Override
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/package-info.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/package-info.java
index a9ea15cede..55c92a22eb 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/package-info.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/package-info.java
@@ -17,9 +17,10 @@
/**
* Server-side support for SockJS transports including
* {@link org.springframework.web.socket.sockjs.TransportHandler} implementations
- * for processing incoming requests and their
+ * for processing incoming requests, their
* {@link org.springframework.web.socket.sockjs.AbstractSockJsSession session} counterparts for
- * sending messages over the various transports.
+ * sending messages over the various transports, and
+ * {@link org.springframework.web.socket.sockjs.transport.DefaultSockJsService}.
*/
package org.springframework.web.socket.sockjs.transport;
diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/AbstractSockJsServiceTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/AbstractSockJsServiceTests.java
index 13cb2e87e4..d72b3934a0 100644
--- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/AbstractSockJsServiceTests.java
+++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/AbstractSockJsServiceTests.java
@@ -224,6 +224,11 @@ public class AbstractSockJsServiceTests extends AbstractHttpRequestTests {
super(scheduler);
}
+ @Override
+ public SockJsMessageCodec getMessageCodecRequired() {
+ throw new UnsupportedOperationException();
+ }
+
@Override
protected void handleRawWebSocketRequest(ServerHttpRequest request,
ServerHttpResponse response, WebSocketHandler handler) throws IOException {
diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/StubSockJsConfig.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/StubSockJsConfig.java
index 050c920540..095f1470b3 100644
--- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/StubSockJsConfig.java
+++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/StubSockJsConfig.java
@@ -18,6 +18,7 @@ package org.springframework.web.socket.sockjs;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.web.socket.sockjs.support.Jackson2SockJsMessageCodec;
/**
* @author Rossen Stoyanchev
@@ -30,6 +31,8 @@ public class StubSockJsConfig implements SockJsConfiguration {
private TaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
+ private SockJsMessageCodec messageCodec = new Jackson2SockJsMessageCodec();
+
@Override
public int getStreamBytesLimit() {
@@ -58,4 +61,17 @@ public class StubSockJsConfig implements SockJsConfiguration {
this.taskScheduler = taskScheduler;
}
+ @Override
+ public SockJsMessageCodec getMessageCodecRequired() {
+ return this.messageCodec;
+ }
+
+ public SockJsMessageCodec getMessageCodec() {
+ return messageCodec;
+ }
+
+ public void setMessageCodec(SockJsMessageCodec messageCodec) {
+ this.messageCodec = messageCodec;
+ }
+
}
diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/support/DefaultSockJsServiceTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/DefaultSockJsServiceTests.java
similarity index 96%
rename from spring-websocket/src/test/java/org/springframework/web/socket/sockjs/support/DefaultSockJsServiceTests.java
rename to spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/DefaultSockJsServiceTests.java
index 31fb40c6c1..2849f1f6ee 100644
--- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/support/DefaultSockJsServiceTests.java
+++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/DefaultSockJsServiceTests.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.web.socket.sockjs.support;
+package org.springframework.web.socket.sockjs.transport;
import java.util.Collections;
import java.util.HashSet;
@@ -30,6 +30,7 @@ import org.springframework.scheduling.TaskScheduler;
import org.springframework.web.socket.AbstractHttpRequestTests;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.sockjs.AbstractSockJsSession;
+import org.springframework.web.socket.sockjs.SockJsConfiguration;
import org.springframework.web.socket.sockjs.SockJsSessionFactory;
import org.springframework.web.socket.sockjs.StubSockJsConfig;
import org.springframework.web.socket.sockjs.TestSockJsSession;
@@ -164,6 +165,10 @@ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests {
return TransportType.XHR;
}
+ @Override
+ public void setSockJsConfiguration(SockJsConfiguration sockJsConfig) {
+ }
+
@Override
public void handleRequest(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler handler, AbstractSockJsSession session) throws TransportErrorException {
@@ -176,6 +181,7 @@ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests {
public AbstractSockJsSession createSession(String sessionId, WebSocketHandler webSocketHandler) {
return new TestSockJsSession(sessionId, new StubSockJsConfig(), webSocketHandler);
}
+
}
private static class StubXhrSendTransportHandler implements TransportHandler {
@@ -185,6 +191,10 @@ public class DefaultSockJsServiceTests extends AbstractHttpRequestTests {
return TransportType.XHR_SEND;
}
+ @Override
+ public void setSockJsConfiguration(SockJsConfiguration sockJsConfig) {
+ }
+
@Override
public void handleRequest(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler handler, AbstractSockJsSession session) throws TransportErrorException {
diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/HttpReceivingTransportHandlerTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/HttpReceivingTransportHandlerTests.java
index ee184d0fd6..816bda130a 100644
--- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/HttpReceivingTransportHandlerTests.java
+++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/transport/HttpReceivingTransportHandlerTests.java
@@ -105,16 +105,20 @@ public class HttpReceivingTransportHandlerTests extends AbstractHttpRequestTest
@Test
public void delegateMessageException() throws Exception {
+ StubSockJsConfig sockJsConfig = new StubSockJsConfig();
+
this.servletRequest.setContent("[\"x\"]".getBytes("UTF-8"));
WebSocketHandler webSocketHandler = mock(WebSocketHandler.class);
- TestSockJsSession session = new TestSockJsSession("1", new StubSockJsConfig(), webSocketHandler);
+ TestSockJsSession session = new TestSockJsSession("1", sockJsConfig, webSocketHandler);
session.delegateConnectionEstablished();
doThrow(new Exception()).when(webSocketHandler).handleMessage(session, new TextMessage("x"));
try {
- new XhrTransportHandler().handleRequest(this.request, this.response, webSocketHandler, session);
+ XhrTransportHandler transportHandler = new XhrTransportHandler();
+ transportHandler.setSockJsConfiguration(sockJsConfig);
+ transportHandler.handleRequest(this.request, this.response, webSocketHandler, session);
fail("Expected exception");
}
catch (TransportErrorException ex) {
@@ -129,6 +133,7 @@ public class HttpReceivingTransportHandlerTests extends AbstractHttpRequestTest
WebSocketHandler webSocketHandler = mock(WebSocketHandler.class);
AbstractSockJsSession session = new TestSockJsSession("1", new StubSockJsConfig(), webSocketHandler);
+ transportHandler.setSockJsConfiguration(new StubSockJsConfig());
transportHandler.handleRequest(this.request, this.response, webSocketHandler, session);
assertEquals("text/plain;charset=UTF-8", this.response.getHeaders().getContentType().toString());