Add SockJS path detection
This commit is contained in:
parent
97d225ba75
commit
7845ebc428
|
@ -16,6 +16,7 @@
|
|||
package org.springframework.http.server;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* TODO..
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
// ---------------------------------------------------------------------
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
* <p>
|
||||
* 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<String> sockJsPrefixes = new ArrayList<String>();
|
||||
|
||||
private final Set<String> sockJsPathCache = new CopyOnWriteArraySet<String>();
|
||||
|
||||
|
||||
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).
|
||||
* <p>
|
||||
* 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<String>() {
|
||||
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
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -217,7 +217,7 @@ public abstract class AbstractSockJsSession implements ConfigurableWebSocketSess
|
|||
* <p>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
|
|||
* <p>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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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<String> headerHints;
|
||||
|
||||
private static final Map<String, TransportType> transportTypes = new HashMap<String, TransportType>();
|
||||
|
||||
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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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("<!DOCTYPE html>\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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"));
|
||||
}
|
||||
|
||||
}
|
|
@ -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<TransportType, TransportHandler> handlers = sockJsService.getTransportHandlers();
|
||||
Map<TransportType, TransportHandler> 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());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE log4j:configuration PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "log4j.dtd">
|
||||
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
|
||||
|
||||
<!-- Appenders -->
|
||||
<appender name="console" class="org.apache.log4j.ConsoleAppender">
|
||||
<param name="Target" value="System.out" />
|
||||
<layout class="org.apache.log4j.PatternLayout">
|
||||
<param name="ConversionPattern" value="%d{HH:mm:ss} [%t] %c{1} - %m%n" />
|
||||
</layout>
|
||||
</appender>
|
||||
|
||||
<logger name="org.springframework.samples">
|
||||
<level value="debug" />
|
||||
</logger>
|
||||
|
||||
<logger name="org.springframework.web">
|
||||
<level value="debug" />
|
||||
</logger>
|
||||
|
||||
<logger name="org.springframework.web.socket">
|
||||
<level value="trace" />
|
||||
</logger>
|
||||
|
||||
<root>
|
||||
<priority value="warn" />
|
||||
<appender-ref ref="console" />
|
||||
</root>
|
||||
|
||||
</log4j:configuration>
|
Loading…
Reference in New Issue