diff --git a/spring-web/src/main/java/org/springframework/http/server/AsyncServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/AsyncServerHttpRequest.java
index 0253ac66e1..bbf973d5d8 100644
--- a/spring-web/src/main/java/org/springframework/http/server/AsyncServerHttpRequest.java
+++ b/spring-web/src/main/java/org/springframework/http/server/AsyncServerHttpRequest.java
@@ -16,6 +16,7 @@
package org.springframework.http.server;
+
/**
* TODO..
*/
diff --git a/spring-web/src/main/java/org/springframework/http/server/AsyncServletServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/AsyncServletServerHttpRequest.java
index 1e53fc7abf..996a7d3fdc 100644
--- a/spring-web/src/main/java/org/springframework/http/server/AsyncServletServerHttpRequest.java
+++ b/spring-web/src/main/java/org/springframework/http/server/AsyncServletServerHttpRequest.java
@@ -100,11 +100,6 @@ public class AsyncServletServerHttpRequest extends ServletServerHttpRequest
}
}
- public void dispatch() {
- Assert.notNull(this.asyncContext, "Cannot dispatch without an AsyncContext");
- this.asyncContext.dispatch();
- }
-
public void completeAsync() {
Assert.notNull(this.asyncContext, "Cannot dispatch without an AsyncContext");
if (isAsyncStarted() && !isAsyncCompleted()) {
@@ -112,6 +107,7 @@ public class AsyncServletServerHttpRequest extends ServletServerHttpRequest
}
}
+
// ---------------------------------------------------------------------
// Implementation of AsyncListener methods
// ---------------------------------------------------------------------
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/StandardWebSocketClient.java b/spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/StandardWebSocketClient.java
index 0734f7c085..21678e12ef 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/StandardWebSocketClient.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/client/endpoint/StandardWebSocketClient.java
@@ -75,17 +75,9 @@ public class StandardWebSocketClient implements WebSocketClient {
public WebSocketSession doHandshake(WebSocketHandler webSocketHandler,
final HttpHeaders httpHeaders, URI uri) throws WebSocketConnectFailureException {
- return doHandshake(webSocketHandler, httpHeaders, UriComponentsBuilder.fromUri(uri).build());
- }
-
- public WebSocketSession doHandshake(WebSocketHandler webSocketHandler,
- final HttpHeaders httpHeaders, UriComponents uriComponents) throws WebSocketConnectFailureException {
-
- URI uri = uriComponents.toUri();
-
StandardWebSocketSessionAdapter session = new StandardWebSocketSessionAdapter();
session.setUri(uri);
- session.setRemoteHostName(uriComponents.getHost());
+ session.setRemoteHostName(uri.getHost());
Endpoint endpoint = new StandardEndpointAdapter(webSocketHandler, session);
ClientEndpointConfig.Builder configBuidler = ClientEndpointConfig.Builder.create();
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/client/jetty/JettyWebSocketClient.java b/spring-websocket/src/main/java/org/springframework/web/socket/client/jetty/JettyWebSocketClient.java
index 6cc19bf561..d5bb04ddfd 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/client/jetty/JettyWebSocketClient.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/client/jetty/JettyWebSocketClient.java
@@ -133,19 +133,11 @@ public class JettyWebSocketClient implements WebSocketClient, SmartLifecycle {
public WebSocketSession doHandshake(WebSocketHandler webSocketHandler, HttpHeaders headers, URI uri)
throws WebSocketConnectFailureException {
- return doHandshake(webSocketHandler, headers, UriComponentsBuilder.fromUri(uri).build());
- }
-
- public WebSocketSession doHandshake(WebSocketHandler webSocketHandler, HttpHeaders headers, UriComponents uriComponents)
- throws WebSocketConnectFailureException {
-
// TODO: populate headers
- URI uri = uriComponents.toUri();
-
JettyWebSocketSessionAdapter session = new JettyWebSocketSessionAdapter();
session.setUri(uri);
- session.setRemoteHostName(uriComponents.getHost());
+ session.setRemoteHostName(uri.getHost());
JettyWebSocketListenerAdapter listener = new JettyWebSocketListenerAdapter(webSocketHandler, session);
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/DefaultHandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/DefaultHandshakeHandler.java
index 8390c3079e..f3948b795e 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/server/DefaultHandshakeHandler.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/DefaultHandshakeHandler.java
@@ -149,7 +149,7 @@ public class DefaultHandshakeHandler implements HandshakeHandler {
protected void handleInvalidUpgradeHeader(ServerHttpRequest request, ServerHttpResponse response) throws IOException {
logger.debug("Invalid Upgrade header " + request.getHeaders().getUpgrade());
response.setStatusCode(HttpStatus.BAD_REQUEST);
- response.getBody().write("Can \"Upgrade\" only to \"websocket\".".getBytes("UTF-8"));
+ response.getBody().write("Can \"Upgrade\" only to \"WebSocket\".".getBytes("UTF-8"));
}
protected void handleInvalidConnectHeader(ServerHttpRequest request, ServerHttpResponse response) throws IOException {
@@ -227,13 +227,13 @@ public class DefaultHandshakeHandler implements HandshakeHandler {
private RequestUpgradeStrategy create() {
String className;
if (tomcatWebSocketPresent) {
- className = "org.springframework.websocket.server.support.TomcatRequestUpgradeStrategy";
+ className = "org.springframework.web.socket.server.support.TomcatRequestUpgradeStrategy";
}
else if (glassFishWebSocketPresent) {
- className = "org.springframework.websocket.server.support.GlassFishRequestUpgradeStrategy";
+ className = "org.springframework.web.socket.server.support.GlassFishRequestUpgradeStrategy";
}
else if (jettyWebSocketPresent) {
- className = "org.springframework.websocket.server.support.JettyRequestUpgradeStrategy";
+ className = "org.springframework.web.socket.server.support.JettyRequestUpgradeStrategy";
}
else {
throw new IllegalStateException("No suitable " + RequestUpgradeStrategy.class.getSimpleName());
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/package-info.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/package-info.java
index f73eb69625..5b5a29efbe 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/package-info.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/endpoint/package-info.java
@@ -20,9 +20,7 @@
* {@link org.springframework.web.socket.server.endpoint.EndpointExporter} for
* registering type-based endpoints,
* {@link org.springframework.web.socket.server.endpoint.SpringConfigurator} for
- * instantiating annotated endpoints through Spring, and
- * {@link org.springframework.websocket.server.support.EndpointHandshakeHandler}
- * for integrating endpoints into HTTP request processing.
+ * instantiating annotated endpoints through Spring.
*/
package org.springframework.web.socket.server.endpoint;
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractServerSockJsSession.java
index 725d3c3ac6..710e5e7d33 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractServerSockJsSession.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/AbstractServerSockJsSession.java
@@ -83,7 +83,6 @@ public abstract class AbstractServerSockJsSession extends AbstractSockJsSession
disconnect(status);
}
- // TODO: close status/reason
protected abstract void disconnect(CloseStatus status) throws IOException;
/**
@@ -104,12 +103,14 @@ public abstract class AbstractServerSockJsSession extends AbstractSockJsSession
else {
logger.warn("Terminating connection due to failure to send message: " + ex.getMessage());
}
- close();
+ disconnect(CloseStatus.SERVER_ERROR);
+ close(CloseStatus.SERVER_ERROR);
throw ex;
}
catch (Throwable ex) {
logger.warn("Terminating connection due to failure to send message: " + ex.getMessage());
- close();
+ disconnect(CloseStatus.SERVER_ERROR);
+ close(CloseStatus.SERVER_ERROR);
throw new SockJsRuntimeException("Failed to write " + frame, ex);
}
}
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 31f15781ed..0abf1cc523 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
@@ -17,11 +17,16 @@ package org.springframework.web.socket.sockjs;
import java.io.IOException;
import java.nio.charset.Charset;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -39,7 +44,18 @@ import org.springframework.util.StringUtils;
import org.springframework.web.socket.WebSocketHandler;
/**
- * Provides support for SockJS configuration options and serves the static SockJS URLs.
+ * 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...)}.
*
* @author Rossen Stoyanchev
* @since 4.0
@@ -51,7 +67,7 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf
private static final int ONE_YEAR = 365 * 24 * 60 * 60;
- private String name = "SockJS Service " + ObjectUtils.getIdentityHexString(this);
+ private String name = "SockJSService@" + ObjectUtils.getIdentityHexString(this);
private String clientLibraryUrl = "https://d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.4.min.js";
@@ -67,6 +83,9 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf
private final TaskScheduler taskScheduler;
+ private final List sockJsPrefixes = new ArrayList();
+
+ private final Set sockJsPathCache = new CopyOnWriteArraySet();
public AbstractSockJsService(TaskScheduler scheduler) {
@@ -85,6 +104,38 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf
return this.name;
}
+ /**
+ * Use this property to configure one or more prefixes that this SockJS service is
+ * allowed to serve. The prefix (e.g. "/echo") is needed to extract the SockJS
+ * specific portion of the URL (e.g. "${prefix}/info", "${prefix}/iframe.html", etc).
+ *
+ * This property is not strictly required. In most cases, the SockJS path can be
+ * auto-detected since the initial request from the SockJS client is of the form
+ * "{prefix}/info". Assuming the SockJS service is mapped correctly (e.g. using
+ * Ant-style pattern "/echo/**") this should work fine. This property can be used
+ * to configure explicitly the prefixes this service is allowed to service.
+ *
+ * @param prefixes the prefixes to use; prefixes do not need to include the portions
+ * of the path that represent Servlet container context or Servlet path.
+ */
+ public void setValidSockJsPrefixes(String... prefixes) {
+
+ this.sockJsPrefixes.clear();
+ for (String prefix : prefixes) {
+ if (prefix.endsWith("/") && (prefix.length() > 1)) {
+ prefix = prefix.substring(0, prefix.length() - 1);
+ }
+ this.sockJsPrefixes.add(prefix);
+ }
+
+ // sort with longest prefix at the top
+ Collections.sort(this.sockJsPrefixes, Collections.reverseOrder(new Comparator() {
+ public int compare(String o1, String o2) {
+ return new Integer(o1.length()).compareTo(new Integer(o2.length()));
+ }
+ }));
+ }
+
/**
* Transports which don't support cross-domain communication natively (e.g.
* "eventsource", "htmlfile") rely on serving a simple page (using the
@@ -198,10 +249,18 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf
*
* @throws Exception
*/
- public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response,
- String sockJsPath, WebSocketHandler webSocketHandler) throws IOException, TransportErrorException {
+ public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler)
+ throws IOException, TransportErrorException {
- logger.debug(request.getMethod() + " [" + sockJsPath + "]");
+ String sockJsPath = getSockJsPath(request);
+ if (sockJsPath == null) {
+ logger.warn("Could not determine SockJS path for URL \"" + request.getURI().getPath() +
+ ". Consider setting validSockJsPrefixes.");
+ response.setStatusCode(HttpStatus.NOT_FOUND);
+ return;
+ }
+
+ logger.debug(request.getMethod() + " with SockJS path [" + sockJsPath + "]");
try {
request.getHeaders();
@@ -225,13 +284,13 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf
return;
}
else if (sockJsPath.equals("/websocket")) {
- handleRawWebSocketRequest(request, response, webSocketHandler);
+ handleRawWebSocketRequest(request, response, handler);
return;
}
String[] pathSegments = StringUtils.tokenizeToStringArray(sockJsPath.substring(1), "/");
if (pathSegments.length != 3) {
- logger.debug("Expected /{server}/{session}/{transport} but got " + sockJsPath);
+ logger.warn("Expected \"/{server}/{session}/{transport}\" but got \"" + sockJsPath + "\"");
response.setStatusCode(HttpStatus.NOT_FOUND);
return;
}
@@ -245,13 +304,62 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf
return;
}
- handleTransportRequest(request, response, sessionId, TransportType.fromValue(transport), webSocketHandler);
+ handleTransportRequest(request, response, sessionId, TransportType.fromValue(transport), handler);
}
finally {
response.flush();
}
}
+ /**
+ * Return the SockJS path or null if the path could not be determined.
+ */
+ private String getSockJsPath(ServerHttpRequest request) {
+
+ String path = request.getURI().getPath();
+
+ // SockJS prefix hints?
+ if (!this.sockJsPrefixes.isEmpty()) {
+ for (String prefix : this.sockJsPrefixes) {
+ int index = path.indexOf(prefix);
+ if (index != -1) {
+ this.sockJsPathCache.add(path.substring(0, index + prefix.length()));
+ return path.substring(index + prefix.length());
+ }
+ }
+ }
+
+ // SockJS info request?
+ if (path.endsWith("/info")) {
+ this.sockJsPathCache.add(path.substring(0, path.length() - 6));
+ return "/info";
+ }
+
+ // Have we seen this prefix before (following the initial /info request)?
+ String match = null;
+ for (String sockJsPath : this.sockJsPathCache) {
+ if (path.startsWith(sockJsPath)) {
+ if ((match == null) || (match.length() < sockJsPath.length())) {
+ match = sockJsPath;
+ }
+ }
+ }
+ if (match != null) {
+ return path.substring(match.length());
+ }
+
+ // SockJS greeting?
+ String pathNoSlash = path.endsWith("/") ? path.substring(0, path.length() - 1) : path;
+ String lastSegment = pathNoSlash.substring(pathNoSlash.lastIndexOf('/') + 1);
+
+ if ((TransportType.fromValue(lastSegment) == null) && !lastSegment.startsWith("iframe")) {
+ this.sockJsPathCache.add(path);
+ return "";
+ }
+
+ return null;
+ }
+
protected abstract void handleRawWebSocketRequest(ServerHttpRequest request,
ServerHttpResponse response, WebSocketHandler webSocketHandler) throws IOException;
@@ -263,18 +371,18 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf
protected boolean validateRequest(String serverId, String sessionId, String transport) {
if (!StringUtils.hasText(serverId) || !StringUtils.hasText(sessionId) || !StringUtils.hasText(transport)) {
- logger.debug("Empty server, session, or transport value");
+ logger.warn("Empty server, session, or transport value");
return false;
}
// Server and session id's must not contain "."
if (serverId.contains(".") || sessionId.contains(".")) {
- logger.debug("Server or session contain a \".\"");
+ logger.warn("Server or session contain a \".\"");
return false;
}
if (!isWebSocketEnabled() && transport.equals(TransportType.WEBSOCKET.value())) {
- logger.debug("Websocket transport is disabled");
+ logger.warn("Websocket transport is disabled");
return false;
}
@@ -346,7 +454,7 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf
response.setStatusCode(HttpStatus.NO_CONTENT);
- addCorsHeaders(request, response, HttpMethod.GET, HttpMethod.OPTIONS);
+ addCorsHeaders(request, response, HttpMethod.OPTIONS, HttpMethod.GET);
addCacheHeaders(response);
}
else {
@@ -404,4 +512,5 @@ public abstract class AbstractSockJsService implements SockJsService, SockJsConf
}
};
+
}
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 c7009b9c96..eb461db072 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
@@ -217,7 +217,7 @@ public abstract class AbstractSockJsSession implements ConfigurableWebSocketSess
* Performs cleanup and notifies the {@link SockJsHandler}.
*/
public final void close() throws IOException {
- close(CloseStatus.NORMAL);
+ close(new CloseStatus(3000, "Go away!"));
}
/**
@@ -225,7 +225,7 @@ public abstract class AbstractSockJsSession implements ConfigurableWebSocketSess
*
Performs cleanup and notifies the {@link SockJsHandler}.
*/
public final void close(CloseStatus status) throws IOException {
- if (!isClosed()) {
+ if (isOpen()) {
if (logger.isDebugEnabled()) {
logger.debug("Closing " + this + ", " + status);
}
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 436e31f79b..17233f7e0e 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
@@ -78,6 +78,30 @@ public class SockJsFrame {
return this.content.getBytes(Charset.forName("UTF-8"));
}
+ public static String escapeCharacters(char[] chars) {
+ StringBuilder result = new StringBuilder();
+ for (char ch : chars) {
+ if (isSockJsEscapeCharacter(ch)) {
+ result.append('\\').append('u');
+ String hex = Integer.toHexString(ch).toLowerCase();
+ for (int i = 0; i < (4 - hex.length()); i++) {
+ result.append('0');
+ }
+ result.append(hex);
+ }
+ else {
+ result.append(ch);
+ }
+ }
+ return result.toString();
+ }
+
+ 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');
+ }
+
public String toString() {
String result = this.content;
if (result.length() > 80) {
@@ -101,7 +125,7 @@ public class SockJsFrame {
sb.append('"');
// TODO: dependency on Jackson
char[] quotedChars = JsonStringEncoder.getInstance().quoteAsString(messages[i]);
- sb.append(escapeSockJsCharacters(quotedChars));
+ sb.append(escapeCharacters(quotedChars));
sb.append('"');
if (i < messages.length - 1) {
sb.append(',');
@@ -110,30 +134,6 @@ public class SockJsFrame {
sb.append(']');
return sb.toString();
}
-
- private static String escapeSockJsCharacters(char[] chars) {
- StringBuilder result = new StringBuilder();
- for (char ch : chars) {
- if (isSockJsEscapeCharacter(ch)) {
- result.append('\\').append('u');
- String hex = Integer.toHexString(ch).toLowerCase();
- for (int i = 0; i < (4 - hex.length()); i++) {
- result.append('0');
- }
- result.append(hex);
- }
- else {
- result.append(ch);
- }
- }
- return result.toString();
- }
-
- 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');
- }
}
public interface FrameFormat {
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsService.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsService.java
index 915ab840fb..45e34d4c69 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsService.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/SockJsService.java
@@ -23,13 +23,14 @@ import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
/**
+ *
* @author Rossen Stoyanchev
* @since 4.0
*/
public interface SockJsService {
- void handleRequest(ServerHttpRequest request, ServerHttpResponse response,
- String sockJsPath, WebSocketHandler webSocketHandler) throws IOException, TransportErrorException;
+ void handleRequest(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler)
+ throws IOException, TransportErrorException;
}
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/TransportType.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/TransportType.java
index 05f89bac38..1c2c5814d7 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/TransportType.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/TransportType.java
@@ -17,7 +17,9 @@
package org.springframework.web.socket.sockjs;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import org.springframework.http.HttpMethod;
@@ -50,6 +52,14 @@ public enum TransportType {
private final List headerHints;
+ private static final Map transportTypes = new HashMap();
+
+ static {
+ for (TransportType type : values()) {
+ transportTypes.put(type.value, type);
+ }
+ }
+
private TransportType(String value, HttpMethod httpMethod, String... headerHints) {
this.value = value;
@@ -57,6 +67,7 @@ public enum TransportType {
this.headerHints = Arrays.asList(headerHints);
}
+
public String value() {
return this.value;
}
@@ -80,13 +91,8 @@ public enum TransportType {
return this.headerHints.contains("jsessionid");
}
- public static TransportType fromValue(String transportValue) {
- for (TransportType type : values()) {
- if (type.value().equals(transportValue)) {
- return type;
- }
- }
- throw new IllegalArgumentException("No matching constant for [" + transportValue + "]");
+ public static TransportType fromValue(String value) {
+ return transportTypes.get(value);
}
@Override
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/support/DefaultSockJsService.java
index c0fec3e6bb..6f8e497678 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/support/DefaultSockJsService.java
@@ -177,7 +177,7 @@ public class DefaultSockJsService extends AbstractSockJsService {
if (!supportedMethod.equals(request.getMethod())) {
if (HttpMethod.OPTIONS.equals(request.getMethod()) && transportType.supportsCors()) {
response.setStatusCode(HttpStatus.NO_CONTENT);
- addCorsHeaders(request, response, supportedMethod, HttpMethod.OPTIONS);
+ addCorsHeaders(request, response, HttpMethod.OPTIONS, supportedMethod);
addCacheHeaders(response);
}
else {
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/SockJsHttpRequestHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/SockJsHttpRequestHandler.java
index 336df01ca0..8a335a76d2 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/SockJsHttpRequestHandler.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/SockJsHttpRequestHandler.java
@@ -32,8 +32,6 @@ import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.sockjs.SockJsService;
import org.springframework.web.socket.support.ExceptionWebSocketHandlerDecorator;
import org.springframework.web.socket.support.LoggingWebSocketHandlerDecorator;
-import org.springframework.web.util.NestedServletException;
-import org.springframework.web.util.UrlPathHelper;
/**
* @author Rossen Stoyanchev
@@ -41,29 +39,17 @@ import org.springframework.web.util.UrlPathHelper;
*/
public class SockJsHttpRequestHandler implements HttpRequestHandler {
- private final String prefix;
-
private final SockJsService sockJsService;
private final WebSocketHandler webSocketHandler;
- private final UrlPathHelper urlPathHelper = new UrlPathHelper();
-
/**
* Class constructor with {@link SockJsHandler} instance ...
- *
- * @param prefix the path prefix for the SockJS service. All requests with a path
- * that begins with the specified prefix will be handled by this service. In a
- * Servlet container this is the path within the current servlet mapping.
*/
- public SockJsHttpRequestHandler(String prefix, SockJsService sockJsService, WebSocketHandler webSocketHandler) {
-
- Assert.hasText(prefix, "prefix is required");
+ public SockJsHttpRequestHandler(SockJsService sockJsService, WebSocketHandler webSocketHandler) {
Assert.notNull(sockJsService, "sockJsService is required");
Assert.notNull(webSocketHandler, "webSocketHandler is required");
-
- this.prefix = prefix;
this.sockJsService = sockJsService;
this.webSocketHandler = decorateWebSocketHandler(webSocketHandler);
}
@@ -79,35 +65,14 @@ public class SockJsHttpRequestHandler implements HttpRequestHandler {
return new LoggingWebSocketHandlerDecorator(handler);
}
- public String getPrefix() {
- return this.prefix;
- }
-
- public String getPattern() {
- return this.prefix + "/**";
- }
-
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
- String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
-
- Assert.isTrue(lookupPath.startsWith(this.prefix),
- "Request path does not match the prefix of the SockJsService " + this.prefix);
-
- String sockJsPath = lookupPath.substring(prefix.length());
-
ServerHttpRequest httpRequest = new AsyncServletServerHttpRequest(request, response);
ServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
- try {
- this.sockJsService.handleRequest(httpRequest, httpResponse, sockJsPath, this.webSocketHandler);
- }
- catch (Exception ex) {
- // TODO
- throw new NestedServletException("SockJS service failure", ex);
- }
+ this.sockJsService.handleRequest(httpRequest, httpResponse, this.webSocketHandler);
}
}
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 313794ea1c..237343c9ac 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
@@ -28,6 +28,7 @@ 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.SockJsFrame;
import org.springframework.web.socket.sockjs.SockJsRuntimeException;
import org.springframework.web.socket.sockjs.TransportErrorException;
import org.springframework.web.socket.sockjs.TransportHandler;
@@ -61,6 +62,7 @@ public abstract class AbstractHttpReceivingTransportHandler implements Transport
if (session == null) {
response.setStatusCode(HttpStatus.NOT_FOUND);
+ logger.warn("Session not found");
return;
}
@@ -75,22 +77,28 @@ public abstract class AbstractHttpReceivingTransportHandler implements Transport
messages = readMessages(request);
}
catch (JsonMappingException ex) {
+ logger.error("Failed to read message: ", ex);
sendInternalServerError(response, "Payload expected.", session.getId());
return;
}
catch (IOException ex) {
+ logger.error("Failed to read message: ", ex);
sendInternalServerError(response, "Broken JSON encoding.", session.getId());
return;
}
catch (Throwable t) {
+ logger.error("Failed to read message: ", t);
sendInternalServerError(response, "Failed to process messages", session.getId());
return;
}
if (logger.isTraceEnabled()) {
- logger.trace("Received messages: " + Arrays.asList(messages));
+ logger.trace("Received message(s): " + Arrays.asList(messages));
}
+ response.setStatusCode(getResponseStatus());
+ response.getHeaders().setContentType(new MediaType("text", "plain", Charset.forName("UTF-8")));
+
try {
session.delegateMessages(messages);
}
@@ -98,9 +106,6 @@ public abstract class AbstractHttpReceivingTransportHandler implements Transport
ExceptionWebSocketHandlerDecorator.tryCloseWithError(session, t, logger);
throw new SockJsRuntimeException("Unhandled WebSocketHandler error in " + this, t);
}
-
- response.setStatusCode(getResponseStatus());
- response.getHeaders().setContentType(new MediaType("text", "plain", Charset.forName("UTF-8")));
}
protected void sendInternalServerError(ServerHttpResponse response, String error,
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpServerSockJsSession.java
index c04e4c54ee..be38656c67 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpServerSockJsSession.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/AbstractHttpServerSockJsSession.java
@@ -157,12 +157,13 @@ public abstract class AbstractHttpServerSockJsSession extends AbstractServerSock
protected synchronized void resetRequest() {
updateLastActiveTime();
- if (isActive()) {
+ if (isActive() && this.asyncRequest.isAsyncStarted()) {
try {
+ logger.debug("Completing async request");
this.asyncRequest.completeAsync();
}
catch (Throwable ex) {
- logger.warn("Failed to complete async request: " + ex.getMessage());
+ logger.error("Failed to complete async request: " + ex.getMessage());
}
}
this.asyncRequest = null;
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/StreamingServerSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/StreamingServerSockJsSession.java
index f01bb94c99..fe37568a61 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/StreamingServerSockJsSession.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/StreamingServerSockJsSession.java
@@ -41,7 +41,11 @@ public class StreamingServerSockJsSession extends AbstractHttpServerSockJsSessio
FrameFormat frameFormat) throws TransportErrorException {
super.setInitialRequest(request, response, frameFormat);
- super.setLongPollingRequest(request, response, frameFormat);
+
+ // the WebSocketHandler delegate may have closed the session
+ if (!isClosed()) {
+ super.setLongPollingRequest(request, response, frameFormat);
+ }
}
protected void flushCache() throws IOException {
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 fb91266a2b..48bd272d7e 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
@@ -62,7 +62,7 @@ public class WebSocketServerSockJsSession extends AbstractServerSockJsSession {
@Override
public boolean isActive() {
- return this.webSocketSession.isOpen();
+ return ((this.webSocketSession != null) && this.webSocketSession.isOpen());
}
public void handleMessage(TextMessage message, WebSocketSession wsSession) throws Exception {
diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/AbstractHttpRequestTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/AbstractHttpRequestTests.java
new file mode 100644
index 0000000000..7ef451307e
--- /dev/null
+++ b/spring-websocket/src/test/java/org/springframework/web/socket/AbstractHttpRequestTests.java
@@ -0,0 +1,61 @@
+/*
+ * 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;
+
+import org.junit.Before;
+import org.springframework.http.server.AsyncServletServerHttpRequest;
+import org.springframework.http.server.ServerHttpRequest;
+import org.springframework.http.server.ServerHttpResponse;
+import org.springframework.http.server.ServletServerHttpResponse;
+import org.springframework.mock.web.test.MockHttpServletRequest;
+import org.springframework.mock.web.test.MockHttpServletResponse;
+
+
+/**
+ * @author Rossen Stoyanchev
+ */
+public class AbstractHttpRequestTests {
+
+ protected ServerHttpRequest request;
+
+ protected ServerHttpResponse response;
+
+ protected MockHttpServletRequest servletRequest;
+
+ protected MockHttpServletResponse servletResponse;
+
+
+ @Before
+ public void setUp() {
+ this.servletRequest = new MockHttpServletRequest();
+ this.servletResponse = new MockHttpServletResponse();
+ this.request = new AsyncServletServerHttpRequest(this.servletRequest, this.servletResponse);
+ this.response = new ServletServerHttpResponse(this.servletResponse);
+ }
+
+
+ protected void setRequest(String method, String requestUri) {
+ this.servletRequest.setMethod(method);
+ this.servletRequest.setRequestURI(requestUri);
+ }
+
+ protected void resetResponse() {
+ this.servletResponse = new MockHttpServletResponse();
+ this.response = new ServletServerHttpResponse(this.servletResponse);
+ }
+
+}
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
new file mode 100644
index 0000000000..fcc5325127
--- /dev/null
+++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/AbstractSockJsServiceTests.java
@@ -0,0 +1,145 @@
+/*
+ * 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 org.junit.Before;
+import org.junit.Test;
+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.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.web.socket.AbstractHttpRequestTests;
+import org.springframework.web.socket.WebSocketHandler;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Rossen Stoyanchev
+ */
+public class AbstractSockJsServiceTests extends AbstractHttpRequestTests {
+
+ private TestSockJsService service;
+
+ private WebSocketHandler handler;
+
+
+ @Before
+ public void setUp() {
+ super.setUp();
+ this.service = new TestSockJsService(new ThreadPoolTaskScheduler());
+ }
+
+ @Test
+ public void getSockJsPath() throws Exception {
+
+ handleRequest("/echo", HttpStatus.OK);
+ assertEquals("Welcome to SockJS!\n", this.servletResponse.getContentAsString());
+
+ handleRequest("/echo/info", HttpStatus.OK);
+ assertTrue(this.servletResponse.getContentAsString().startsWith("{\"entropy\":"));
+
+ handleRequest("/echo/", HttpStatus.OK);
+ assertEquals("Welcome to SockJS!\n", this.servletResponse.getContentAsString());
+
+ handleRequest("/echo/iframe.html", HttpStatus.OK);
+ assertTrue(this.servletResponse.getContentAsString().startsWith("\n"));
+
+ handleRequest("/echo/websocket", HttpStatus.OK);
+ assertNull(this.service.sessionId);
+ assertSame(this.handler, this.service.handler);
+
+ handleRequest("/echo/server1/session2/xhr", HttpStatus.OK);
+ assertEquals("session2", this.service.sessionId);
+ assertEquals(TransportType.XHR, this.service.transportType);
+ assertSame(this.handler, this.service.handler);
+
+ handleRequest("/echo/other", HttpStatus.NOT_FOUND);
+ handleRequest("/echo//", HttpStatus.NOT_FOUND);
+ handleRequest("/echo///", HttpStatus.NOT_FOUND);
+ }
+
+
+ @Test
+ public void getSockJsPathGreetingRequest() throws Exception {
+ handleRequest("/echo", HttpStatus.OK);
+ assertEquals("Welcome to SockJS!\n", this.servletResponse.getContentAsString());
+ }
+
+ @Test
+ public void getSockJsPathInfoRequest() throws Exception {
+ handleRequest("/echo/info", HttpStatus.OK);
+ assertTrue(this.servletResponse.getContentAsString().startsWith("{\"entropy\":"));
+ }
+
+ @Test
+ public void getSockJsPathWithConfiguredPrefix() throws Exception {
+ this.service.setValidSockJsPrefixes("/echo");
+ handleRequest("/echo/s1/s2/xhr", HttpStatus.OK);
+ }
+
+ @Test
+ public void getInfoOptions() throws Exception {
+ setRequest("OPTIONS", "/echo/info");
+ this.service.handleRequest(this.request, this.response, this.handler);
+
+ assertEquals(204, servletResponse.getStatus());
+ }
+
+
+ private void handleRequest(String uri, HttpStatus httpStatus) throws IOException {
+ resetResponse();
+ setRequest("GET", uri);
+ this.service.handleRequest(this.request, this.response, this.handler);
+
+ assertEquals(httpStatus.value(), this.servletResponse.getStatus());
+ }
+
+ private static class TestSockJsService extends AbstractSockJsService {
+
+ private String sessionId;
+
+ private TransportType transportType;
+
+ private WebSocketHandler handler;
+
+ public TestSockJsService(TaskScheduler scheduler) {
+ super(scheduler);
+ }
+
+ @Override
+ protected void handleRawWebSocketRequest(ServerHttpRequest request,
+ ServerHttpResponse response, WebSocketHandler handler) throws IOException {
+
+ this.handler = handler;
+ }
+
+ @Override
+ protected void handleTransportRequest(ServerHttpRequest request,
+ ServerHttpResponse response, String sessionId,
+ TransportType transportType, WebSocketHandler handler)
+ throws IOException, TransportErrorException {
+
+ this.sessionId = sessionId;
+ this.transportType = transportType;
+ this.handler = handler;
+ }
+ }
+
+}
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
new file mode 100644
index 0000000000..0f2a4c4f05
--- /dev/null
+++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/StubSockJsConfig.java
@@ -0,0 +1,59 @@
+/*
+ * 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 org.springframework.scheduling.TaskScheduler;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+
+
+/**
+ * @author Rossen Stoyanchev
+ */
+public class StubSockJsConfig implements SockJsConfiguration {
+
+ private int streamBytesLimit = 128 * 1024;
+
+ private long heartbeatTime = 25 * 1000;
+
+ private TaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
+
+
+ public int getStreamBytesLimit() {
+ return streamBytesLimit;
+ }
+
+ public void setStreamBytesLimit(int streamBytesLimit) {
+ this.streamBytesLimit = streamBytesLimit;
+ }
+
+ public long getHeartbeatTime() {
+ return heartbeatTime;
+ }
+
+ public void setHeartbeatTime(long heartbeatTime) {
+ this.heartbeatTime = heartbeatTime;
+ }
+
+ public TaskScheduler getTaskScheduler() {
+ return taskScheduler;
+ }
+
+ public void setTaskScheduler(TaskScheduler taskScheduler) {
+ this.taskScheduler = taskScheduler;
+ }
+
+}
diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/StubTaskScheduler.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/StubTaskScheduler.java
new file mode 100644
index 0000000000..57849d1ba1
--- /dev/null
+++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/StubTaskScheduler.java
@@ -0,0 +1,87 @@
+/*
+ * 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.util.Date;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Delayed;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.springframework.scheduling.TaskScheduler;
+import org.springframework.scheduling.Trigger;
+
+
+/**
+ * @author Rossen Stoyanchev
+ */
+public class StubTaskScheduler implements TaskScheduler {
+
+ @Override
+ public ScheduledFuture schedule(Runnable task, Trigger trigger) {
+ return new StubScheduledFuture();
+ }
+
+ @Override
+ public ScheduledFuture schedule(Runnable task, Date startTime) {
+ return new StubScheduledFuture();
+ }
+
+ @Override
+ public ScheduledFuture scheduleAtFixedRate(Runnable task, Date startTime, long period) {
+ return new StubScheduledFuture();
+ }
+
+ @Override
+ public ScheduledFuture scheduleAtFixedRate(Runnable task, long period) {
+ return new StubScheduledFuture();
+ }
+
+ @Override
+ public ScheduledFuture scheduleWithFixedDelay(Runnable task, Date startTime, long delay) {
+ return new StubScheduledFuture();
+ }
+
+ @Override
+ public ScheduledFuture scheduleWithFixedDelay(Runnable task, long delay) {
+ return new StubScheduledFuture();
+ }
+
+
+ private static class StubScheduledFuture extends FutureTask implements ScheduledFuture {
+
+ @SuppressWarnings("unchecked")
+ public StubScheduledFuture() {
+ super(new Callable() {
+ public Object call() throws Exception {
+ return null;
+ }
+ });
+ }
+
+ @Override
+ public long getDelay(TimeUnit unit) {
+ return 0;
+ }
+
+ @Override
+ public int compareTo(Delayed o) {
+ return 0;
+ }
+ }
+}
diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/TransportTypeTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/TransportTypeTests.java
new file mode 100644
index 0000000000..9f4b34c17f
--- /dev/null
+++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/TransportTypeTests.java
@@ -0,0 +1,42 @@
+/*
+ * 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 org.junit.Test;
+
+import static org.junit.Assert.*;
+
+
+/**
+ * @author Rossen Stoyanchev
+ */
+public class TransportTypeTests {
+
+
+ @Test
+ public void testFromValue() {
+ assertEquals(TransportType.WEBSOCKET, TransportType.fromValue("websocket"));
+ assertEquals(TransportType.XHR, TransportType.fromValue("xhr"));
+ assertEquals(TransportType.XHR_SEND, TransportType.fromValue("xhr_send"));
+ assertEquals(TransportType.JSONP, TransportType.fromValue("jsonp"));
+ assertEquals(TransportType.JSONP_SEND, TransportType.fromValue("jsonp_send"));
+ assertEquals(TransportType.XHR_STREAMING, TransportType.fromValue("xhr_streaming"));
+ assertEquals(TransportType.EVENT_SOURCE, TransportType.fromValue("eventsource"));
+ assertEquals(TransportType.HTML_FILE, TransportType.fromValue("htmlfile"));
+ }
+
+}
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/support/DefaultSockJsServiceTests.java
index a135e51a40..a8630d5319 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/support/DefaultSockJsServiceTests.java
@@ -18,11 +18,13 @@ package org.springframework.web.socket.sockjs.support;
import java.util.Map;
+import org.junit.Before;
import org.junit.Test;
-import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.web.socket.AbstractHttpRequestTests;
+import org.springframework.web.socket.adapter.TextWebSocketHandlerAdapter;
+import org.springframework.web.socket.sockjs.StubTaskScheduler;
import org.springframework.web.socket.sockjs.TransportHandler;
import org.springframework.web.socket.sockjs.TransportType;
-import org.springframework.web.socket.sockjs.support.DefaultSockJsService;
import static org.junit.Assert.*;
@@ -32,14 +34,22 @@ import static org.junit.Assert.*;
*
* @author Rossen Stoyanchev
*/
-public class DefaultSockJsServiceTests {
+public class DefaultSockJsServiceTests extends AbstractHttpRequestTests {
+ private DefaultSockJsService service;
+
+
+ @Before
+ public void setUp() {
+ super.setUp();
+ this.service = new DefaultSockJsService(new StubTaskScheduler());
+ this.service.setValidSockJsPrefixes("/echo");
+ }
@Test
- public void testDefaultTransportHandlers() {
+ public void defaultTransportHandlers() {
- DefaultSockJsService sockJsService = new DefaultSockJsService(new ThreadPoolTaskScheduler());
- Map handlers = sockJsService.getTransportHandlers();
+ Map handlers = service.getTransportHandlers();
assertEquals(8, handlers.size());
assertNotNull(handlers.get(TransportType.WEBSOCKET));
@@ -52,5 +62,21 @@ public class DefaultSockJsServiceTests {
assertNotNull(handlers.get(TransportType.EVENT_SOURCE));
}
+ @Test
+ public void xhrSend() throws Exception {
+
+ setRequest("POST", "/echo/000/c5839f69/xhr");
+ this.service.handleRequest(this.request, this.response, new TextWebSocketHandlerAdapter());
+
+ resetResponse();
+ setRequest("POST", "/echo/000/c5839f69/xhr_send");
+ this.servletRequest.setContent("[\"x\"]".getBytes("UTF-8"));
+
+ this.service.handleRequest(this.request, this.response, new TextWebSocketHandlerAdapter());
+
+ assertEquals(204, this.servletResponse.getStatus());
+ assertEquals("text/plain;charset=UTF-8", this.servletResponse.getContentType());
+ }
+
}
diff --git a/spring-websocket/src/test/resources/log4j.xml b/spring-websocket/src/test/resources/log4j.xml
new file mode 100644
index 0000000000..8fa59bf2f3
--- /dev/null
+++ b/spring-websocket/src/test/resources/log4j.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+