Add SockJS path detection
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user