Polish SockJS exception handling and javadoc
See javadoc in SockJsService for details. Also remove ReadOnlyMultiValueMap, CollectionUtils has a method for that already.
This commit is contained in:
parent
a03517fa35
commit
15a2f03459
|
@ -1,150 +0,0 @@
|
|||
/*
|
||||
* 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.util;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
/**
|
||||
* Wraps a {@link MultiValueMap} to make it immutable.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public class ReadOnlyMultiValueMap<K, V> implements MultiValueMap<K, V> {
|
||||
|
||||
private final MultiValueMap<K, V> targetMap;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new ReadOnlyMultiValueMap that wraps the given target map.
|
||||
*/
|
||||
public ReadOnlyMultiValueMap(MultiValueMap<K, V> targetMap) {
|
||||
this.targetMap = targetMap;
|
||||
}
|
||||
|
||||
// MultiValueMap implementation
|
||||
|
||||
@Override
|
||||
public void add(K key, V value) {
|
||||
throw new UnsupportedOperationException("This map is immutable.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public V getFirst(K key) {
|
||||
return this.targetMap.getFirst(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(K key, V value) {
|
||||
throw new UnsupportedOperationException("This map is immutable.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAll(Map<K, V> values) {
|
||||
throw new UnsupportedOperationException("This map is immutable.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<K, V> toSingleValueMap() {
|
||||
return Collections.unmodifiableMap(this.targetMap.toSingleValueMap());
|
||||
}
|
||||
|
||||
|
||||
// Map implementation
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return this.targetMap.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return this.targetMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return this.targetMap.containsKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
return this.targetMap.containsValue(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<V> get(Object key) {
|
||||
return this.targetMap.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<V> put(K key, List<V> value) {
|
||||
throw new UnsupportedOperationException("This map is immutable.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<V> remove(Object key) {
|
||||
throw new UnsupportedOperationException("This map is immutable.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends K, ? extends List<V>> m) {
|
||||
this.targetMap.putAll(m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
throw new UnsupportedOperationException("This map is immutable.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<K> keySet() {
|
||||
return Collections.unmodifiableSet(this.targetMap.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<List<V>> values() {
|
||||
return Collections.unmodifiableCollection(this.targetMap.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<K, List<V>>> entrySet() {
|
||||
return Collections.unmodifiableSet(this.targetMap.entrySet());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return this.targetMap.equals(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.targetMap.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.targetMap.toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -44,9 +44,9 @@ import org.springframework.http.HttpHeaders;
|
|||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.ReadOnlyMultiValueMap;
|
||||
|
||||
/**
|
||||
* {@link ServerHttpRequest} implementation that is based on a {@link HttpServletRequest}.
|
||||
|
@ -184,7 +184,7 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
|
|||
}
|
||||
}
|
||||
}
|
||||
this.queryParams = new ReadOnlyMultiValueMap<String, String>(result);
|
||||
this.queryParams = CollectionUtils.unmodifiableMultiValueMap(result);
|
||||
}
|
||||
return this.queryParams;
|
||||
}
|
||||
|
|
|
@ -19,19 +19,19 @@ package org.springframework.web.socket.sockjs;
|
|||
import org.springframework.core.NestedRuntimeException;
|
||||
|
||||
/**
|
||||
* Raised when SockJS request handling fails.
|
||||
* Base class for exceptions raised while processing SockJS HTTP requests.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class SockJsProcessingException extends NestedRuntimeException {
|
||||
public class SockJsException extends NestedRuntimeException {
|
||||
|
||||
private final String sessionId;
|
||||
|
||||
|
||||
public SockJsProcessingException(String msg, Throwable cause, String sessionId) {
|
||||
super(msg, cause);
|
||||
public SockJsException(String message, String sessionId, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
|
@ -39,9 +39,4 @@ public class SockJsProcessingException extends NestedRuntimeException {
|
|||
return this.sessionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return "Transport error for SockJS session id=" + this.sessionId + ", " + super.getMessage();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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.List;
|
||||
|
||||
|
||||
/**
|
||||
* An exception thrown when a message frame was successfully received over an HTTP POST
|
||||
* and parsed but one or more of the messages it contained could not be delivered to the
|
||||
* WebSocketHandler either because the handler failed or because the connection got
|
||||
* closed.
|
||||
* <p>
|
||||
* The SockJS session is not automatically closed after this exception.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class SockJsMessageDeliveryException extends SockJsException {
|
||||
|
||||
private final List<String> undeliveredMessages;
|
||||
|
||||
|
||||
public SockJsMessageDeliveryException(String sessionId, List<String> undeliveredMessages, Throwable cause) {
|
||||
super("Failed to deliver message(s) " + undeliveredMessages + " for session " + sessionId, sessionId, cause);
|
||||
this.undeliveredMessages = undeliveredMessages;
|
||||
}
|
||||
|
||||
public List<String> getUndeliveredMessages() {
|
||||
return this.undeliveredMessages;
|
||||
}
|
||||
|
||||
}
|
|
@ -16,14 +16,24 @@
|
|||
|
||||
package org.springframework.web.socket.sockjs;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.support.ExceptionWebSocketHandlerDecorator;
|
||||
|
||||
/**
|
||||
* A service for processing SockJS HTTP requests.
|
||||
* The main entry point for processing HTTP requests from SockJS clients.
|
||||
* <p>
|
||||
* In a Servlet 3+ container, {@link SockJsHttpRequestHandler} can be used to invoke this
|
||||
* service. The processing servlet, as well as all filters involved, must have
|
||||
* asynchronous support enabled through the ServletContext API or by adding an
|
||||
* {@code <async-support>true</async-support>} element to servlet and filter declarations
|
||||
* in web.xml.
|
||||
* <p>
|
||||
* The service can be integrated into any HTTP request handling mechanism (e.g. plain
|
||||
* Servlet, Spring MVC, or other). It is expected that it will be mapped, as expected
|
||||
* by the SockJS protocol, to a specific prefix (e.g. "/echo") including all sub-URLs
|
||||
* (i.e. Ant path pattern "/echo/**").
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
|
@ -34,16 +44,27 @@ public interface SockJsService {
|
|||
|
||||
|
||||
/**
|
||||
* Process a SockJS request.
|
||||
* Process a SockJS HTTP request.
|
||||
* <p>
|
||||
* See the "Base URL", "Static URLs", and "Session URLs" sections of the <a
|
||||
* href="http://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html">SockJS
|
||||
* protocol</a> for details on the types of URLs expected.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param response the current response
|
||||
* @param handler the handler to process messages with
|
||||
* @param handler the handler that will exchange messages with the SockJS client
|
||||
*
|
||||
* @throws IOException raised if writing the to response of the current request fails
|
||||
* @throws SockJsProcessingException
|
||||
* @throws SockJsException raised when request processing fails; generally, failed
|
||||
* attempts to send messages to clients automatically close the SockJS session
|
||||
* and raise {@link SockJsTransportFailureException}; failed attempts to read
|
||||
* messages from clients do not automatically close the session and may result
|
||||
* in {@link SockJsMessageDeliveryException} or {@link SockJsException};
|
||||
* exceptions from the WebSocketHandler can be handled internally or through
|
||||
* {@link ExceptionWebSocketHandlerDecorator} or some alternative decorator.
|
||||
* The former is automatically added when using
|
||||
* {@link SockJsHttpRequestHandler}.
|
||||
*/
|
||||
void handleRequest(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler)
|
||||
throws IOException, SockJsProcessingException;
|
||||
throws SockJsException;
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
|
||||
/**
|
||||
* Indicates a serious failure that occurred in the SockJS implementation as opposed to in
|
||||
* user code (e.g. IOException while writing to the response). When this exception is
|
||||
* raised, the SockJS session is typically closed.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class SockJsTransportFailureException extends SockJsException {
|
||||
|
||||
|
||||
public SockJsTransportFailureException(String message, String sessionId, Throwable cause) {
|
||||
super(message, sessionId, cause);
|
||||
}
|
||||
|
||||
}
|
|
@ -45,23 +45,21 @@ import org.springframework.util.DigestUtils;
|
|||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.sockjs.SockJsProcessingException;
|
||||
import org.springframework.web.socket.sockjs.SockJsException;
|
||||
import org.springframework.web.socket.sockjs.SockJsService;
|
||||
|
||||
/**
|
||||
* An abstract base class for {@link SockJsService} implementations that provides SockJS
|
||||
* path resolution and handling of static SockJS requests (e.g. "/info", "/iframe.html",
|
||||
* etc). Transport-specific requests are left as abstract methods.
|
||||
* etc). Sub-classes must handle session URLs (i.e. transport-specific requests).
|
||||
* <p>
|
||||
* This service can be integrated into any HTTP request handling mechanism (e.g. plain
|
||||
* Servlet, Spring MVC, or other). It is expected that it will be mapped correctly to a
|
||||
* prefix (e.g. "/echo") and will also handle all sub-URLs (i.e. "/echo/**").
|
||||
* <p>
|
||||
* The service itself is unaware of the underlying mapping mechanism but nevertheless must
|
||||
* be able to extract the SockJS path, i.e. the portion of the request path following the
|
||||
* prefix. In most cases, this class can auto-detect the SockJS path but it is also
|
||||
* possible to configure explicitly the prefixes via
|
||||
* {@link #setValidSockJsPrefixes(String...)}.
|
||||
* This service is unaware of the underlying HTTP request processing mechanism and URL
|
||||
* mappings but nevertheless needs to know the "SockJS path" for a given request, i.e. the
|
||||
* portion of the URL path that follows the SockJS prefix. In most cases, this can be
|
||||
* auto-detected since the <a href="https://github.com/sockjs/sockjs-client">SockJS
|
||||
* client</a> sends a "greeting URL" first. However it is recommended to configure
|
||||
* explicitly the expected SockJS prefixes via {@link #setValidSockJsPrefixes(String...)}
|
||||
* to eliminate any potential issues.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
|
@ -265,11 +263,14 @@ public abstract class AbstractSockJsService implements SockJsService {
|
|||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* This method determines the SockJS path and handles SockJS static URLs. Session URLs
|
||||
* and raw WebSocket requests are delegated to abstract methods.
|
||||
*/
|
||||
@Override
|
||||
public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler)
|
||||
throws IOException, SockJsProcessingException {
|
||||
throws SockJsException {
|
||||
|
||||
String sockJsPath = getSockJsPath(request);
|
||||
if (sockJsPath == null) {
|
||||
|
@ -292,42 +293,39 @@ public abstract class AbstractSockJsService implements SockJsService {
|
|||
if (sockJsPath.equals("") || sockJsPath.equals("/")) {
|
||||
response.getHeaders().setContentType(new MediaType("text", "plain", Charset.forName("UTF-8")));
|
||||
response.getBody().write("Welcome to SockJS!\n".getBytes("UTF-8"));
|
||||
return;
|
||||
}
|
||||
else if (sockJsPath.equals("/info")) {
|
||||
this.infoHandler.handle(request, response);
|
||||
return;
|
||||
}
|
||||
else if (sockJsPath.matches("/iframe[0-9-.a-z_]*.html")) {
|
||||
this.iframeHandler.handle(request, response);
|
||||
return;
|
||||
}
|
||||
else if (sockJsPath.equals("/websocket")) {
|
||||
handleRawWebSocketRequest(request, response, handler);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
String[] pathSegments = StringUtils.tokenizeToStringArray(sockJsPath.substring(1), "/");
|
||||
if (pathSegments.length != 3) {
|
||||
logger.warn("Expected \"/{server}/{session}/{transport}\" but got \"" + sockJsPath + "\"");
|
||||
response.setStatusCode(HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
String serverId = pathSegments[0];
|
||||
String sessionId = pathSegments[1];
|
||||
String transport = pathSegments[2];
|
||||
|
||||
if (!validateRequest(serverId, sessionId, transport)) {
|
||||
response.setStatusCode(HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
handleTransportRequest(request, response, handler, sessionId, transport);
|
||||
}
|
||||
|
||||
String[] pathSegments = StringUtils.tokenizeToStringArray(sockJsPath.substring(1), "/");
|
||||
if (pathSegments.length != 3) {
|
||||
logger.warn("Expected \"/{server}/{session}/{transport}\" but got \"" + sockJsPath + "\"");
|
||||
response.setStatusCode(HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
String serverId = pathSegments[0];
|
||||
String sessionId = pathSegments[1];
|
||||
String transport = pathSegments[2];
|
||||
|
||||
if (!validateRequest(serverId, sessionId, transport)) {
|
||||
response.setStatusCode(HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
handleTransportRequest(request, response, handler, sessionId, transport);
|
||||
}
|
||||
finally {
|
||||
response.flush();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new SockJsException("Failed to write to the response", null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -385,14 +383,23 @@ public abstract class AbstractSockJsService implements SockJsService {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate whether the given transport String extracted from the URL is a valid
|
||||
* SockJS transport type (regardless of whether a transport handler is configured).
|
||||
*/
|
||||
protected abstract boolean isValidTransportType(String transportType);
|
||||
|
||||
/**
|
||||
* Handle request for raw WebSocket communication, i.e. without any SockJS message framing.
|
||||
*/
|
||||
protected abstract void handleRawWebSocketRequest(ServerHttpRequest request,
|
||||
ServerHttpResponse response, WebSocketHandler webSocketHandler) throws IOException;
|
||||
|
||||
/**
|
||||
* Handle a SockJS session URL (i.e. transport-specific request).
|
||||
*/
|
||||
protected abstract void handleTransportRequest(ServerHttpRequest request, ServerHttpResponse response,
|
||||
WebSocketHandler webSocketHandler, String sessionId, String transport)
|
||||
throws IOException, SockJsProcessingException;
|
||||
WebSocketHandler webSocketHandler, String sessionId, String transport) throws SockJsException;
|
||||
|
||||
|
||||
protected boolean validateRequest(String serverId, String sessionId, String transport) {
|
||||
|
@ -446,7 +453,7 @@ public abstract class AbstractSockJsService implements SockJsService {
|
|||
response.getHeaders().setCacheControl("no-store, no-cache, must-revalidate, max-age=0");
|
||||
}
|
||||
|
||||
protected void sendMethodNotAllowed(ServerHttpResponse response, List<HttpMethod> httpMethods) throws IOException {
|
||||
protected void sendMethodNotAllowed(ServerHttpResponse response, List<HttpMethod> httpMethods) {
|
||||
logger.debug("Sending Method Not Allowed (405)");
|
||||
response.setStatusCode(HttpStatus.METHOD_NOT_ALLOWED);
|
||||
response.getHeaders().setAllow(new HashSet<HttpMethod>(httpMethods));
|
||||
|
|
|
@ -21,8 +21,7 @@ import java.nio.charset.Charset;
|
|||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Represents a SockJS frames. Provides methods for access to commonly used message
|
||||
* frames.
|
||||
* Represents a SockJS frame and provides factory methods for creating SockJS frames.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
|
|
|
@ -21,8 +21,8 @@ import java.io.InputStream;
|
|||
|
||||
|
||||
/**
|
||||
* A contract for encoding and decoding of messages to and from a SockJS message frame,
|
||||
* which is essentially an array of JSON-encoded messages. For example:
|
||||
* Encode and decode messages to and from a SockJS message frame, essentially an array of
|
||||
* JSON-encoded messages. For example:
|
||||
*
|
||||
* <pre>
|
||||
* a["message1","message2"]
|
||||
|
|
|
@ -20,19 +20,36 @@ import org.springframework.http.server.ServerHttpRequest;
|
|||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.sockjs.SockJsProcessingException;
|
||||
import org.springframework.web.socket.sockjs.SockJsException;
|
||||
import org.springframework.web.socket.sockjs.SockJsService;
|
||||
|
||||
/**
|
||||
* Handles SockJS session URLs.
|
||||
* Handle a SockJS session URL, i.e. transport-specific request.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public interface TransportHandler {
|
||||
|
||||
|
||||
/**
|
||||
* @return the transport type supported by this handler
|
||||
*/
|
||||
TransportType getTransportType();
|
||||
|
||||
/**
|
||||
* Handle the given request and delegate messages to the provided
|
||||
* {@link WebSocketHandler}.
|
||||
*
|
||||
* @param request the current request
|
||||
* @param response the current response
|
||||
* @param handler the target WebSocketHandler, never {@code null}
|
||||
* @param session the SockJS session, never {@code null}
|
||||
*
|
||||
* @throws SockJsException raised when request processing fails as explained in
|
||||
* {@link SockJsService}
|
||||
*/
|
||||
void handleRequest(ServerHttpRequest request, ServerHttpResponse response,
|
||||
WebSocketHandler handler, WebSocketSession session) throws SockJsProcessingException;
|
||||
WebSocketHandler handler, WebSocketSession session) throws SockJsException;
|
||||
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ import java.util.Map;
|
|||
import org.springframework.http.HttpMethod;
|
||||
|
||||
/**
|
||||
* Defines SockJS transport types.
|
||||
* SockJS transport types.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
|
|
|
@ -25,17 +25,16 @@ import org.springframework.http.MediaType;
|
|||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.sockjs.SockJsProcessingException;
|
||||
import org.springframework.web.socket.sockjs.SockJsException;
|
||||
import org.springframework.web.socket.sockjs.transport.TransportHandler;
|
||||
import org.springframework.web.socket.support.ExceptionWebSocketHandlerDecorator;
|
||||
import org.springframework.web.socket.sockjs.transport.session.AbstractHttpSockJsSession;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
|
||||
/**
|
||||
* Base class for HTTP-based transports that read input messages from HTTP requests.
|
||||
* Base class for HTTP transport handlers that receive messages via HTTP POST.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
|
@ -46,17 +45,19 @@ public abstract class AbstractHttpReceivingTransportHandler
|
|||
|
||||
@Override
|
||||
public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response,
|
||||
WebSocketHandler wsHandler, WebSocketSession wsSession) throws SockJsProcessingException {
|
||||
WebSocketHandler wsHandler, WebSocketSession wsSession) throws SockJsException {
|
||||
|
||||
// TODO: check "Sec-WebSocket-Protocol" header
|
||||
// https://github.com/sockjs/sockjs-client/issues/130
|
||||
|
||||
Assert.notNull(wsSession, "No session");
|
||||
handleRequestInternal(request, response, wsHandler, wsSession);
|
||||
AbstractHttpSockJsSession sockJsSession = (AbstractHttpSockJsSession) wsSession;
|
||||
|
||||
handleRequestInternal(request, response, wsHandler, sockJsSession);
|
||||
}
|
||||
|
||||
protected void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response,
|
||||
WebSocketHandler wsHandler, WebSocketSession wsSession) throws SockJsProcessingException {
|
||||
WebSocketHandler wsHandler, AbstractHttpSockJsSession sockJsSession) throws SockJsException {
|
||||
|
||||
String[] messages = null;
|
||||
try {
|
||||
|
@ -64,22 +65,22 @@ public abstract class AbstractHttpReceivingTransportHandler
|
|||
}
|
||||
catch (JsonMappingException ex) {
|
||||
logger.error("Failed to read message: " + ex.getMessage());
|
||||
sendInternalServerError(response, "Payload expected.", wsSession.getId());
|
||||
handleReadError(response, "Payload expected.", sockJsSession.getId());
|
||||
return;
|
||||
}
|
||||
catch (IOException ex) {
|
||||
logger.error("Failed to read message: " + ex.getMessage());
|
||||
sendInternalServerError(response, "Broken JSON encoding.", wsSession.getId());
|
||||
handleReadError(response, "Broken JSON encoding.", sockJsSession.getId());
|
||||
return;
|
||||
}
|
||||
catch (Throwable t) {
|
||||
logger.error("Failed to read message: " + t.getMessage());
|
||||
sendInternalServerError(response, "Failed to process messages", wsSession.getId());
|
||||
handleReadError(response, "Failed to read message(s)", sockJsSession.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
if (messages == null) {
|
||||
sendInternalServerError(response, "Payload expected.", wsSession.getId());
|
||||
handleReadError(response, "Payload expected.", sockJsSession.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -90,26 +91,16 @@ public abstract class AbstractHttpReceivingTransportHandler
|
|||
response.setStatusCode(getResponseStatus());
|
||||
response.getHeaders().setContentType(new MediaType("text", "plain", Charset.forName("UTF-8")));
|
||||
|
||||
try {
|
||||
for (String message : messages) {
|
||||
wsHandler.handleMessage(wsSession, new TextMessage(message));
|
||||
}
|
||||
}
|
||||
catch (Throwable t) {
|
||||
ExceptionWebSocketHandlerDecorator.tryCloseWithError(wsSession, t, logger);
|
||||
throw new SockJsProcessingException("Unhandled WebSocketHandler error in " + this, t, wsSession.getId());
|
||||
}
|
||||
sockJsSession.delegateMessages(messages);
|
||||
}
|
||||
|
||||
protected void sendInternalServerError(ServerHttpResponse response, String error,
|
||||
String sessionId) throws SockJsProcessingException {
|
||||
|
||||
private void handleReadError(ServerHttpResponse resp, String error, String sessionId) {
|
||||
try {
|
||||
response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
response.getBody().write(error.getBytes("UTF-8"));
|
||||
resp.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
resp.getBody().write(error.getBytes("UTF-8"));
|
||||
}
|
||||
catch (Throwable t) {
|
||||
throw new SockJsProcessingException("Failed to send error message to client", t, sessionId);
|
||||
catch (IOException ex) {
|
||||
throw new SockJsException("Failed to send error: " + error, sessionId, ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,14 +23,14 @@ import org.springframework.http.server.ServerHttpRequest;
|
|||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.sockjs.SockJsProcessingException;
|
||||
import org.springframework.web.socket.sockjs.SockJsException;
|
||||
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame;
|
||||
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.FrameFormat;
|
||||
import org.springframework.web.socket.sockjs.transport.TransportHandler;
|
||||
import org.springframework.web.socket.sockjs.transport.session.AbstractHttpSockJsSession;
|
||||
|
||||
/**
|
||||
* Base class for HTTP-based transports that send messages over HTTP.
|
||||
* Base class for HTTP transport handlers that push messages to connected clients.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
|
@ -41,17 +41,17 @@ public abstract class AbstractHttpSendingTransportHandler extends TransportHandl
|
|||
|
||||
@Override
|
||||
public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response,
|
||||
WebSocketHandler webSocketHandler, WebSocketSession session) throws SockJsProcessingException {
|
||||
WebSocketHandler wsHandler, WebSocketSession wsSession) throws SockJsException {
|
||||
|
||||
// Set content type before writing
|
||||
response.getHeaders().setContentType(getContentType());
|
||||
|
||||
AbstractHttpSockJsSession sockJsSession = (AbstractHttpSockJsSession) session;
|
||||
AbstractHttpSockJsSession sockJsSession = (AbstractHttpSockJsSession) wsSession;
|
||||
handleRequestInternal(request, response, sockJsSession);
|
||||
}
|
||||
|
||||
protected void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response,
|
||||
AbstractHttpSockJsSession sockJsSession) throws SockJsProcessingException {
|
||||
AbstractHttpSockJsSession sockJsSession) throws SockJsException {
|
||||
|
||||
if (sockJsSession.isNew()) {
|
||||
logger.debug("Opening " + getTransportType() + " connection");
|
||||
|
@ -62,13 +62,13 @@ public abstract class AbstractHttpSendingTransportHandler extends TransportHandl
|
|||
sockJsSession.setLongPollingRequest(request, response, getFrameFormat(request));
|
||||
}
|
||||
else {
|
||||
logger.debug("another " + getTransportType() + " connection still open: " + sockJsSession);
|
||||
SockJsFrame frame = getFrameFormat(request).format(SockJsFrame.closeFrameAnotherConnectionOpen());
|
||||
try {
|
||||
logger.debug("another " + getTransportType() + " connection still open: " + sockJsSession);
|
||||
SockJsFrame closeFrame = SockJsFrame.closeFrameAnotherConnectionOpen();
|
||||
response.getBody().write(getFrameFormat(request).format(closeFrame).getContentBytes());
|
||||
response.getBody().write(frame.getContentBytes());
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new SockJsProcessingException("Failed to send SockJS close frame", e, sockJsSession.getId());
|
||||
catch (IOException ex) {
|
||||
throw new SockJsException("Failed to send " + frame, sockJsSession.getId(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ import org.springframework.web.socket.WebSocketSession;
|
|||
import org.springframework.web.socket.server.DefaultHandshakeHandler;
|
||||
import org.springframework.web.socket.server.HandshakeHandler;
|
||||
import org.springframework.web.socket.server.support.ServerWebSocketSessionInitializer;
|
||||
import org.springframework.web.socket.sockjs.SockJsProcessingException;
|
||||
import org.springframework.web.socket.sockjs.SockJsException;
|
||||
import org.springframework.web.socket.sockjs.SockJsService;
|
||||
import org.springframework.web.socket.sockjs.support.AbstractSockJsService;
|
||||
import org.springframework.web.socket.sockjs.support.frame.Jackson2SockJsMessageCodec;
|
||||
|
@ -203,8 +203,7 @@ public class DefaultSockJsService extends AbstractSockJsService {
|
|||
|
||||
@Override
|
||||
protected void handleTransportRequest(ServerHttpRequest request, ServerHttpResponse response,
|
||||
WebSocketHandler wsHandler, String sessionId, String transport)
|
||||
throws IOException, SockJsProcessingException {
|
||||
WebSocketHandler wsHandler, String sessionId, String transport) throws SockJsException {
|
||||
|
||||
TransportType transportType = TransportType.fromValue(transport);
|
||||
if (transportType == null) {
|
||||
|
|
|
@ -24,7 +24,9 @@ import org.springframework.http.MediaType;
|
|||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.sockjs.SockJsTransportFailureException;
|
||||
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.DefaultFrameFormat;
|
||||
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.FrameFormat;
|
||||
import org.springframework.web.socket.sockjs.transport.TransportHandler;
|
||||
|
@ -32,7 +34,6 @@ import org.springframework.web.socket.sockjs.transport.TransportType;
|
|||
import org.springframework.web.socket.sockjs.transport.session.AbstractHttpSockJsSession;
|
||||
import org.springframework.web.socket.sockjs.transport.session.SockJsServiceConfig;
|
||||
import org.springframework.web.socket.sockjs.transport.session.StreamingSockJsSession;
|
||||
import org.springframework.web.socket.sockjs.SockJsProcessingException;
|
||||
import org.springframework.web.util.JavaScriptUtils;
|
||||
|
||||
/**
|
||||
|
@ -92,20 +93,22 @@ public class HtmlFileTransportHandler extends AbstractHttpSendingTransportHandle
|
|||
|
||||
@Override
|
||||
public void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response,
|
||||
AbstractHttpSockJsSession session) throws SockJsProcessingException {
|
||||
AbstractHttpSockJsSession sockJsSession) {
|
||||
|
||||
try {
|
||||
String callback = request.getQueryParams().getFirst("c");
|
||||
if (! StringUtils.hasText(callback)) {
|
||||
response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
response.getBody().write("\"callback\" parameter required".getBytes("UTF-8"));
|
||||
try {
|
||||
response.getBody().write("\"callback\" parameter required".getBytes("UTF-8"));
|
||||
}
|
||||
catch (IOException t) {
|
||||
sockJsSession.tryCloseWithSockJsTransportError(t, CloseStatus.SERVER_ERROR);
|
||||
throw new SockJsTransportFailureException("Failed to write to response", sockJsSession.getId(), t);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Throwable t) {
|
||||
throw new SockJsProcessingException("Failed to send error to client", t, session.getId());
|
||||
}
|
||||
super.handleRequestInternal(request, response, session);
|
||||
|
||||
super.handleRequestInternal(request, response, sockJsSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -127,7 +130,8 @@ public class HtmlFileTransportHandler extends AbstractHttpSendingTransportHandle
|
|||
|
||||
@Override
|
||||
protected void writePrelude() throws IOException {
|
||||
// we already validated the parameter..
|
||||
|
||||
// we already validated the parameter above..
|
||||
String callback = getRequest().getQueryParams().getFirst("c");
|
||||
|
||||
String html = String.format(PARTIAL_HTML_CONTENT, callback);
|
||||
|
|
|
@ -23,13 +23,15 @@ import org.springframework.http.MediaType;
|
|||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.sockjs.SockJsException;
|
||||
import org.springframework.web.socket.sockjs.SockJsTransportFailureException;
|
||||
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame;
|
||||
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.FrameFormat;
|
||||
import org.springframework.web.socket.sockjs.transport.TransportType;
|
||||
import org.springframework.web.socket.sockjs.transport.session.AbstractHttpSockJsSession;
|
||||
import org.springframework.web.socket.sockjs.transport.session.PollingSockJsSession;
|
||||
import org.springframework.web.socket.sockjs.SockJsProcessingException;
|
||||
import org.springframework.web.util.JavaScriptUtils;
|
||||
|
||||
/**
|
||||
|
@ -57,7 +59,7 @@ public class JsonpPollingTransportHandler extends AbstractHttpSendingTransportHa
|
|||
|
||||
@Override
|
||||
public void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response,
|
||||
AbstractHttpSockJsSession session) throws SockJsProcessingException {
|
||||
AbstractHttpSockJsSession sockJsSession) throws SockJsException {
|
||||
|
||||
try {
|
||||
String callback = request.getQueryParams().getFirst("c");
|
||||
|
@ -68,16 +70,17 @@ public class JsonpPollingTransportHandler extends AbstractHttpSendingTransportHa
|
|||
}
|
||||
}
|
||||
catch (Throwable t) {
|
||||
throw new SockJsProcessingException("Failed to send error to client", t, session.getId());
|
||||
sockJsSession.tryCloseWithSockJsTransportError(t, CloseStatus.SERVER_ERROR);
|
||||
throw new SockJsTransportFailureException("Failed to send error", sockJsSession.getId(), t);
|
||||
}
|
||||
|
||||
super.handleRequestInternal(request, response, session);
|
||||
super.handleRequestInternal(request, response, sockJsSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FrameFormat getFrameFormat(ServerHttpRequest request) {
|
||||
|
||||
// we already validated the parameter..
|
||||
// we already validated the parameter above..
|
||||
String callback = request.getQueryParams().getFirst("c");
|
||||
|
||||
return new SockJsFrame.DefaultFrameFormat(callback + "(\"%s\");\r\n") {
|
||||
|
|
|
@ -26,11 +26,11 @@ import org.springframework.http.server.ServerHttpResponse;
|
|||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.sockjs.SockJsProcessingException;
|
||||
import org.springframework.web.socket.sockjs.SockJsException;
|
||||
import org.springframework.web.socket.sockjs.support.frame.SockJsMessageCodec;
|
||||
import org.springframework.web.socket.sockjs.transport.TransportHandler;
|
||||
import org.springframework.web.socket.sockjs.transport.TransportType;
|
||||
import org.springframework.web.socket.sockjs.transport.session.AbstractHttpSockJsSession;
|
||||
|
||||
/**
|
||||
* A {@link TransportHandler} that receives messages over HTTP.
|
||||
|
@ -49,15 +49,14 @@ public class JsonpTransportHandler extends AbstractHttpReceivingTransportHandler
|
|||
|
||||
@Override
|
||||
public void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response,
|
||||
WebSocketHandler webSocketHandler, WebSocketSession webSocketSession) throws SockJsProcessingException {
|
||||
|
||||
super.handleRequestInternal(request, response, webSocketHandler, webSocketSession);
|
||||
WebSocketHandler wsHandler, AbstractHttpSockJsSession sockJsSession) throws SockJsException {
|
||||
|
||||
super.handleRequestInternal(request, response, wsHandler, sockJsSession);
|
||||
try {
|
||||
response.getBody().write("ok".getBytes("UTF-8"));
|
||||
}
|
||||
catch (Throwable t) {
|
||||
throw new SockJsProcessingException("Failed to write response body", t, webSocketSession.getId());
|
||||
catch(IOException ex) {
|
||||
throw new SockJsException("Failed to write to the response body", sockJsSession.getId(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -46,20 +46,20 @@ public class SockJsWebSocketHandler extends TextWebSocketHandlerAdapter {
|
|||
|
||||
private final SockJsServiceConfig sockJsServiceConfig;
|
||||
|
||||
private final WebSocketServerSockJsSession session;
|
||||
private final WebSocketServerSockJsSession sockJsSession;
|
||||
|
||||
private final AtomicInteger sessionCount = new AtomicInteger(0);
|
||||
|
||||
|
||||
public SockJsWebSocketHandler(SockJsServiceConfig sockJsServiceConfig,
|
||||
WebSocketHandler webSocketHandler, WebSocketServerSockJsSession session) {
|
||||
public SockJsWebSocketHandler(SockJsServiceConfig serviceConfig,
|
||||
WebSocketHandler webSocketHandler, WebSocketServerSockJsSession sockJsSession) {
|
||||
|
||||
Assert.notNull(sockJsServiceConfig, "sockJsServiceConfig must not be null");
|
||||
Assert.notNull(serviceConfig, "serviceConfig must not be null");
|
||||
Assert.notNull(webSocketHandler, "webSocketHandler must not be null");
|
||||
Assert.notNull(session, "session must not be null");
|
||||
Assert.notNull(sockJsSession, "session must not be null");
|
||||
|
||||
this.sockJsServiceConfig = sockJsServiceConfig;
|
||||
this.session = session;
|
||||
this.sockJsServiceConfig = serviceConfig;
|
||||
this.sockJsSession = sockJsSession;
|
||||
}
|
||||
|
||||
protected SockJsServiceConfig getSockJsConfig() {
|
||||
|
@ -69,22 +69,22 @@ public class SockJsWebSocketHandler extends TextWebSocketHandlerAdapter {
|
|||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession wsSession) throws Exception {
|
||||
Assert.isTrue(this.sessionCount.compareAndSet(0, 1), "Unexpected connection");
|
||||
this.session.initWebSocketSession(wsSession);
|
||||
this.sockJsSession.initWebSocketSession(wsSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleTextMessage(WebSocketSession wsSession, TextMessage message) throws Exception {
|
||||
this.session.handleMessage(message, wsSession);
|
||||
this.sockJsSession.handleMessage(message, wsSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterConnectionClosed(WebSocketSession wsSession, CloseStatus status) throws Exception {
|
||||
this.session.delegateConnectionClosed(status);
|
||||
this.sockJsSession.delegateConnectionClosed(status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleTransportError(WebSocketSession webSocketSession, Throwable exception) throws Exception {
|
||||
this.session.delegateError(exception);
|
||||
this.sockJsSession.delegateError(exception);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,10 +21,12 @@ import java.io.IOException;
|
|||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.server.HandshakeHandler;
|
||||
import org.springframework.web.socket.sockjs.SockJsProcessingException;
|
||||
import org.springframework.web.socket.sockjs.SockJsException;
|
||||
import org.springframework.web.socket.sockjs.SockJsTransportFailureException;
|
||||
import org.springframework.web.socket.sockjs.transport.TransportHandler;
|
||||
import org.springframework.web.socket.sockjs.transport.TransportType;
|
||||
import org.springframework.web.socket.sockjs.transport.session.AbstractSockJsSession;
|
||||
|
@ -64,15 +66,16 @@ public class WebSocketTransportHandler extends TransportHandlerSupport
|
|||
|
||||
@Override
|
||||
public void handleRequest(ServerHttpRequest request, ServerHttpResponse response,
|
||||
WebSocketHandler wsHandler, WebSocketSession wsSession) throws SockJsProcessingException {
|
||||
WebSocketHandler wsHandler, WebSocketSession wsSession) throws SockJsException {
|
||||
|
||||
WebSocketServerSockJsSession sockJsSession = (WebSocketServerSockJsSession) wsSession;
|
||||
try {
|
||||
WebSocketServerSockJsSession sockJsSession = (WebSocketServerSockJsSession) wsSession;
|
||||
WebSocketHandler sockJsHandler = new SockJsWebSocketHandler(getSockJsServiceConfig(), wsHandler, sockJsSession);
|
||||
this.handshakeHandler.doHandshake(request, response, sockJsHandler);
|
||||
wsHandler = new SockJsWebSocketHandler(getSockJsServiceConfig(), wsHandler, sockJsSession);
|
||||
this.handshakeHandler.doHandshake(request, response, wsHandler);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
throw new SockJsProcessingException("Failed to start handshake request", t, wsSession.getId());
|
||||
sockJsSession.tryCloseWithSockJsTransportError(t, CloseStatus.SERVER_ERROR);
|
||||
throw new SockJsTransportFailureException("WebSocket handshake failure", wsSession.getId(), t);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,10 +27,10 @@ import org.springframework.http.server.ServletServerHttpAsyncRequestControl;
|
|||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.sockjs.SockJsProcessingException;
|
||||
import org.springframework.web.socket.sockjs.SockJsException;
|
||||
import org.springframework.web.socket.sockjs.SockJsTransportFailureException;
|
||||
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame;
|
||||
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.FrameFormat;
|
||||
import org.springframework.web.socket.support.ExceptionWebSocketHandlerDecorator;
|
||||
|
||||
/**
|
||||
* An abstract base class for use with HTTP transport based SockJS sessions.
|
||||
|
@ -77,22 +77,22 @@ public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession {
|
|||
}
|
||||
|
||||
public synchronized void setInitialRequest(ServerHttpRequest request, ServerHttpResponse response,
|
||||
FrameFormat frameFormat) throws SockJsProcessingException {
|
||||
FrameFormat frameFormat) throws SockJsException {
|
||||
|
||||
udpateRequest(request, response, frameFormat);
|
||||
try {
|
||||
udpateRequest(request, response, frameFormat);
|
||||
writePrelude();
|
||||
writeFrame(SockJsFrame.openFrame());
|
||||
}
|
||||
catch (Throwable t) {
|
||||
tryCloseWithSockJsTransportError(t, null);
|
||||
throw new SockJsProcessingException("Failed open SockJS session", t, getId());
|
||||
tryCloseWithSockJsTransportError(t, CloseStatus.SERVER_ERROR);
|
||||
throw new SockJsTransportFailureException("Failed to send \"open\" frame", getId(), t);
|
||||
}
|
||||
try {
|
||||
delegateConnectionEstablished();
|
||||
}
|
||||
catch (Throwable t) {
|
||||
ExceptionWebSocketHandlerDecorator.tryCloseWithError(this, t, logger);
|
||||
throw new SockJsException("Unhandled exception from WebSocketHandler", getId(), t);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,30 +100,22 @@ public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession {
|
|||
}
|
||||
|
||||
public synchronized void setLongPollingRequest(ServerHttpRequest request, ServerHttpResponse response,
|
||||
FrameFormat frameFormat) throws SockJsProcessingException {
|
||||
FrameFormat frameFormat) throws SockJsException {
|
||||
|
||||
udpateRequest(request, response, frameFormat);
|
||||
if (isClosed()) {
|
||||
logger.debug("Connection already closed (but not removed yet)");
|
||||
writeFrame(SockJsFrame.closeFrameGoAway());
|
||||
return;
|
||||
}
|
||||
try {
|
||||
udpateRequest(request, response, frameFormat);
|
||||
|
||||
if (isClosed()) {
|
||||
logger.debug("connection already closed");
|
||||
try {
|
||||
writeFrame(SockJsFrame.closeFrameGoAway());
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new SockJsProcessingException("Failed to send SockJS close frame", ex, getId());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.asyncControl.start(-1);
|
||||
|
||||
scheduleHeartbeat();
|
||||
tryFlushCache();
|
||||
}
|
||||
catch (Throwable t) {
|
||||
tryCloseWithSockJsTransportError(t, null);
|
||||
throw new SockJsProcessingException("Failed to start long running request and flush messages", t, getId());
|
||||
tryCloseWithSockJsTransportError(t, CloseStatus.SERVER_ERROR);
|
||||
throw new SockJsTransportFailureException("Failed to flush messages", getId(), t);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,12 +148,12 @@ public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected final synchronized void sendMessageInternal(String message) throws IOException {
|
||||
protected final synchronized void sendMessageInternal(String message) throws SockJsTransportFailureException {
|
||||
this.messageCache.add(message);
|
||||
tryFlushCache();
|
||||
}
|
||||
|
||||
private void tryFlushCache() throws IOException {
|
||||
private void tryFlushCache() throws SockJsTransportFailureException {
|
||||
if (isActive() && !getMessageCache().isEmpty()) {
|
||||
logger.trace("Flushing messages");
|
||||
flushCache();
|
||||
|
@ -171,7 +163,7 @@ public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession {
|
|||
/**
|
||||
* Only called if the connection is currently active
|
||||
*/
|
||||
protected abstract void flushCache() throws IOException;
|
||||
protected abstract void flushCache() throws SockJsTransportFailureException;
|
||||
|
||||
@Override
|
||||
protected void disconnect(CloseStatus status) {
|
||||
|
@ -182,11 +174,11 @@ public abstract class AbstractHttpSockJsSession extends AbstractSockJsSession {
|
|||
updateLastActiveTime();
|
||||
if (isActive() && this.asyncControl.hasStarted()) {
|
||||
try {
|
||||
logger.debug("Completing async request");
|
||||
logger.debug("Completing asynchronous request");
|
||||
this.asyncControl.complete();
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
logger.error("Failed to complete async request: " + ex.getMessage());
|
||||
logger.error("Failed to complete request: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
this.request = null;
|
||||
|
|
|
@ -21,7 +21,10 @@ import java.io.IOException;
|
|||
import java.net.SocketException;
|
||||
import java.net.URI;
|
||||
import java.security.Principal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
|
@ -33,7 +36,8 @@ import org.springframework.web.socket.WebSocketHandler;
|
|||
import org.springframework.web.socket.WebSocketMessage;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.adapter.ConfigurableWebSocketSession;
|
||||
import org.springframework.web.socket.sockjs.SockJsProcessingException;
|
||||
import org.springframework.web.socket.sockjs.SockJsMessageDeliveryException;
|
||||
import org.springframework.web.socket.sockjs.SockJsTransportFailureException;
|
||||
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame;
|
||||
|
||||
/**
|
||||
|
@ -46,7 +50,6 @@ public abstract class AbstractSockJsSession implements ConfigurableWebSocketSess
|
|||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
|
||||
private final String id;
|
||||
|
||||
private URI uri;
|
||||
|
@ -185,9 +188,21 @@ public abstract class AbstractSockJsSession implements ConfigurableWebSocketSess
|
|||
this.handler.afterConnectionEstablished(this);
|
||||
}
|
||||
|
||||
public void delegateMessages(String[] messages) throws Exception {
|
||||
public void delegateMessages(String[] messages) throws SockJsMessageDeliveryException {
|
||||
List<String> undelivered = new ArrayList<String>(Arrays.asList(messages));
|
||||
for (String message : messages) {
|
||||
this.handler.handleMessage(this, new TextMessage(message));
|
||||
try {
|
||||
if (isClosed()) {
|
||||
throw new SockJsMessageDeliveryException(this.id, undelivered, null);
|
||||
}
|
||||
else {
|
||||
this.handler.handleMessage(this, new TextMessage(message));
|
||||
undelivered.remove(0);
|
||||
}
|
||||
}
|
||||
catch (Throwable t) {
|
||||
throw new SockJsMessageDeliveryException(this.id, undelivered, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -278,55 +293,53 @@ public abstract class AbstractSockJsSession implements ConfigurableWebSocketSess
|
|||
/**
|
||||
* Close due to error arising from SockJS transport handling.
|
||||
*/
|
||||
protected void tryCloseWithSockJsTransportError(Throwable ex, CloseStatus closeStatus) {
|
||||
logger.error("Closing due to transport error for " + this, ex);
|
||||
public void tryCloseWithSockJsTransportError(Throwable ex, CloseStatus closeStatus) {
|
||||
logger.error("Closing due to transport error for " + this);
|
||||
try {
|
||||
delegateError(ex);
|
||||
}
|
||||
catch (Throwable delegateEx) {
|
||||
logger.error("Unhandled error for " + this, delegateEx);
|
||||
try {
|
||||
close(closeStatus);
|
||||
}
|
||||
catch (Throwable closeEx) {
|
||||
logger.error("Unhandled error for " + this, closeEx);
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For internal use within a TransportHandler and the (TransportHandler-specific)
|
||||
* session sub-class.
|
||||
* session class.
|
||||
*/
|
||||
protected void writeFrame(SockJsFrame frame) throws IOException {
|
||||
protected void writeFrame(SockJsFrame frame) throws SockJsTransportFailureException {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Preparing to write " + frame);
|
||||
}
|
||||
try {
|
||||
writeFrameInternal(frame);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
catch (Throwable ex) {
|
||||
if (ex instanceof EOFException || ex instanceof SocketException) {
|
||||
logger.warn("Client went away. Terminating connection");
|
||||
}
|
||||
else {
|
||||
logger.warn("Terminating connection due to failure to send message: " + ex.getMessage());
|
||||
logger.warn("Terminating connection after failure to send message: " + ex.getMessage());
|
||||
}
|
||||
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());
|
||||
disconnect(CloseStatus.SERVER_ERROR);
|
||||
close(CloseStatus.SERVER_ERROR);
|
||||
throw new SockJsProcessingException("Failed to write " + frame, ex, this.getId());
|
||||
try {
|
||||
disconnect(CloseStatus.SERVER_ERROR);
|
||||
close(CloseStatus.SERVER_ERROR);
|
||||
}
|
||||
catch (Throwable ex2) {
|
||||
// ignore
|
||||
}
|
||||
throw new SockJsTransportFailureException("Failed to write " + frame, this.getId(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void writeFrameInternal(SockJsFrame frame) throws Exception;
|
||||
protected abstract void writeFrameInternal(SockJsFrame frame) throws IOException;
|
||||
|
||||
public synchronized void sendHeartbeat() throws Exception {
|
||||
public synchronized void sendHeartbeat() throws SockJsTransportFailureException {
|
||||
if (isActive()) {
|
||||
writeFrame(SockJsFrame.heartbeatFrame());
|
||||
scheduleHeartbeat();
|
||||
|
|
|
@ -16,9 +16,8 @@
|
|||
|
||||
package org.springframework.web.socket.sockjs.transport.session;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.sockjs.SockJsTransportFailureException;
|
||||
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame;
|
||||
import org.springframework.web.socket.sockjs.support.frame.SockJsMessageCodec;
|
||||
|
||||
|
@ -35,7 +34,7 @@ public class PollingSockJsSession extends AbstractHttpSockJsSession {
|
|||
|
||||
|
||||
@Override
|
||||
protected void flushCache() throws IOException {
|
||||
protected void flushCache() throws SockJsTransportFailureException {
|
||||
|
||||
cancelHeartbeat();
|
||||
String[] messages = getMessageCache().toArray(new String[getMessageCache().size()]);
|
||||
|
@ -47,7 +46,7 @@ public class PollingSockJsSession extends AbstractHttpSockJsSession {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void writeFrame(SockJsFrame frame) throws IOException {
|
||||
protected void writeFrame(SockJsFrame frame) throws SockJsTransportFailureException {
|
||||
super.writeFrame(frame);
|
||||
resetRequest();
|
||||
}
|
||||
|
|
|
@ -21,10 +21,11 @@ import java.io.IOException;
|
|||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.sockjs.SockJsException;
|
||||
import org.springframework.web.socket.sockjs.SockJsTransportFailureException;
|
||||
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame;
|
||||
import org.springframework.web.socket.sockjs.support.frame.SockJsMessageCodec;
|
||||
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame.FrameFormat;
|
||||
import org.springframework.web.socket.sockjs.SockJsProcessingException;
|
||||
import org.springframework.web.socket.sockjs.support.frame.SockJsMessageCodec;
|
||||
|
||||
/**
|
||||
* A SockJS session for use with streaming HTTP transports.
|
||||
|
@ -43,7 +44,7 @@ public class StreamingSockJsSession extends AbstractHttpSockJsSession {
|
|||
|
||||
@Override
|
||||
public synchronized void setInitialRequest(ServerHttpRequest request, ServerHttpResponse response,
|
||||
FrameFormat frameFormat) throws SockJsProcessingException {
|
||||
FrameFormat frameFormat) throws SockJsException {
|
||||
|
||||
super.setInitialRequest(request, response, frameFormat);
|
||||
|
||||
|
@ -54,7 +55,7 @@ public class StreamingSockJsSession extends AbstractHttpSockJsSession {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void flushCache() throws IOException {
|
||||
protected void flushCache() throws SockJsTransportFailureException {
|
||||
|
||||
cancelHeartbeat();
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.springframework.web.socket.CloseStatus;
|
|||
import org.springframework.web.socket.TextMessage;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.sockjs.SockJsTransportFailureException;
|
||||
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame;
|
||||
import org.springframework.web.socket.sockjs.support.frame.SockJsMessageCodec;
|
||||
|
||||
|
@ -63,7 +64,7 @@ public class WebSocketServerSockJsSession extends AbstractSockJsSession {
|
|||
this.webSocketSession.sendMessage(message);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
tryCloseWithSockJsTransportError(ex, null);
|
||||
tryCloseWithSockJsTransportError(ex, CloseStatus.SERVER_ERROR);
|
||||
return;
|
||||
}
|
||||
scheduleHeartbeat();
|
||||
|
@ -94,7 +95,7 @@ public class WebSocketServerSockJsSession extends AbstractSockJsSession {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void sendMessageInternal(String message) throws IOException {
|
||||
public void sendMessageInternal(String message) throws SockJsTransportFailureException {
|
||||
cancelHeartbeat();
|
||||
SockJsMessageCodec messageCodec = getSockJsServiceConfig().getMessageCodec();
|
||||
SockJsFrame frame = SockJsFrame.messageFrame(messageCodec, message);
|
||||
|
@ -113,7 +114,9 @@ public class WebSocketServerSockJsSession extends AbstractSockJsSession {
|
|||
|
||||
@Override
|
||||
protected void disconnect(CloseStatus status) throws IOException {
|
||||
this.webSocketSession.close(status);
|
||||
if (this.webSocketSession != null) {
|
||||
this.webSocketSession.close(status);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -58,6 +58,9 @@ public class LoggingWebSocketHandlerDecorator extends WebSocketHandlerDecorator
|
|||
@Override
|
||||
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Transport error for " + session + ": " + exception);
|
||||
}
|
||||
else if (logger.isTraceEnabled()) {
|
||||
logger.debug("Transport error for " + session, exception);
|
||||
}
|
||||
super.handleTransportError(session, exception);
|
||||
|
|
|
@ -27,7 +27,7 @@ import org.springframework.scheduling.TaskScheduler;
|
|||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import org.springframework.web.socket.AbstractHttpRequestTests;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.sockjs.SockJsProcessingException;
|
||||
import org.springframework.web.socket.sockjs.SockJsException;
|
||||
import org.springframework.web.socket.sockjs.transport.TransportType;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
@ -246,7 +246,7 @@ public class AbstractSockJsServiceTests extends AbstractHttpRequestTests {
|
|||
|
||||
@Override
|
||||
protected void handleTransportRequest(ServerHttpRequest req, ServerHttpResponse res, WebSocketHandler handler,
|
||||
String sessionId, String transport) throws IOException, SockJsProcessingException {
|
||||
String sessionId, String transport) throws IOException, SockJsException {
|
||||
|
||||
this.sessionId = sessionId;
|
||||
this.transport = transport;
|
||||
|
|
|
@ -20,13 +20,12 @@ import org.junit.Before;
|
|||
import org.junit.Test;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.socket.AbstractHttpRequestTests;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.sockjs.SockJsProcessingException;
|
||||
import org.springframework.web.socket.sockjs.SockJsMessageDeliveryException;
|
||||
import org.springframework.web.socket.sockjs.transport.session.AbstractSockJsSession;
|
||||
import org.springframework.web.socket.sockjs.transport.session.StubSockJsServiceConfig;
|
||||
import org.springframework.web.socket.sockjs.transport.session.TestSockJsSession;
|
||||
import org.springframework.web.socket.sockjs.transport.session.TestHttpSockJsSession;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
@ -107,48 +106,47 @@ public class HttpReceivingTransportHandlerTests extends AbstractHttpRequestTest
|
|||
|
||||
this.servletRequest.setContent("[\"x\"]".getBytes("UTF-8"));
|
||||
|
||||
WebSocketHandler webSocketHandler = mock(WebSocketHandler.class);
|
||||
TestSockJsSession session = new TestSockJsSession("1", sockJsConfig, webSocketHandler);
|
||||
WebSocketHandler wsHandler = mock(WebSocketHandler.class);
|
||||
TestHttpSockJsSession session = new TestHttpSockJsSession("1", sockJsConfig, wsHandler);
|
||||
session.delegateConnectionEstablished();
|
||||
|
||||
doThrow(new Exception()).when(webSocketHandler).handleMessage(session, new TextMessage("x"));
|
||||
doThrow(new Exception()).when(wsHandler).handleMessage(session, new TextMessage("x"));
|
||||
|
||||
try {
|
||||
XhrTransportHandler transportHandler = new XhrTransportHandler();
|
||||
transportHandler.setSockJsServiceConfiguration(sockJsConfig);
|
||||
transportHandler.handleRequest(this.request, this.response, webSocketHandler, session);
|
||||
transportHandler.handleRequest(this.request, this.response, wsHandler, session);
|
||||
fail("Expected exception");
|
||||
}
|
||||
catch (SockJsProcessingException ex) {
|
||||
assertEquals(CloseStatus.SERVER_ERROR, session.getStatus());
|
||||
catch (SockJsMessageDeliveryException ex) {
|
||||
assertNull(session.getCloseStatus());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void handleRequest(AbstractHttpReceivingTransportHandler transportHandler)
|
||||
throws Exception {
|
||||
private void handleRequest(AbstractHttpReceivingTransportHandler transportHandler) throws Exception {
|
||||
|
||||
WebSocketHandler webSocketHandler = mock(WebSocketHandler.class);
|
||||
AbstractSockJsSession session = new TestSockJsSession("1", new StubSockJsServiceConfig(), webSocketHandler);
|
||||
WebSocketHandler wsHandler = mock(WebSocketHandler.class);
|
||||
AbstractSockJsSession session = new TestHttpSockJsSession("1", new StubSockJsServiceConfig(), wsHandler);
|
||||
|
||||
transportHandler.setSockJsServiceConfiguration(new StubSockJsServiceConfig());
|
||||
transportHandler.handleRequest(this.request, this.response, webSocketHandler, session);
|
||||
transportHandler.handleRequest(this.request, this.response, wsHandler, session);
|
||||
|
||||
assertEquals("text/plain;charset=UTF-8", this.response.getHeaders().getContentType().toString());
|
||||
verify(webSocketHandler).handleMessage(session, new TextMessage("x"));
|
||||
verify(wsHandler).handleMessage(session, new TextMessage("x"));
|
||||
}
|
||||
|
||||
private void handleRequestAndExpectFailure() throws Exception {
|
||||
|
||||
resetResponse();
|
||||
|
||||
WebSocketHandler webSocketHandler = mock(WebSocketHandler.class);
|
||||
AbstractSockJsSession session = new TestSockJsSession("1", new StubSockJsServiceConfig(), webSocketHandler);
|
||||
WebSocketHandler wsHandler = mock(WebSocketHandler.class);
|
||||
AbstractSockJsSession session = new TestHttpSockJsSession("1", new StubSockJsServiceConfig(), wsHandler);
|
||||
|
||||
new XhrTransportHandler().handleRequest(this.request, this.response, webSocketHandler, session);
|
||||
new XhrTransportHandler().handleRequest(this.request, this.response, wsHandler, session);
|
||||
|
||||
assertEquals(500, this.servletResponse.getStatus());
|
||||
verifyNoMoreInteractions(webSocketHandler);
|
||||
verifyNoMoreInteractions(wsHandler);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -151,7 +151,7 @@ public class AbstractHttpSockJsSessionTests extends BaseAbstractSockJsSessionTes
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void flushCache() throws IOException {
|
||||
protected void flushCache() {
|
||||
this.cacheFlushed = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,15 +18,18 @@ package org.springframework.web.socket.sockjs.transport.session;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.sql.Date;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
import org.springframework.web.socket.sockjs.SockJsProcessingException;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.sockjs.SockJsMessageDeliveryException;
|
||||
import org.springframework.web.socket.sockjs.SockJsTransportFailureException;
|
||||
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame;
|
||||
import org.springframework.web.socket.sockjs.transport.session.AbstractSockJsSession;
|
||||
import org.springframework.web.socket.support.ExceptionWebSocketHandlerDecorator;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Matchers.*;
|
||||
|
@ -95,6 +98,33 @@ public class AbstractSockJsSessionTests extends BaseAbstractSockJsSessionTests<T
|
|||
verifyNoMoreInteractions(this.webSocketHandler);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void delegateMessagesWithErrorAndConnectionClosing() throws Exception {
|
||||
|
||||
WebSocketHandler wsHandler = new ExceptionWebSocketHandlerDecorator(this.webSocketHandler);
|
||||
TestSockJsSession sockJsSession = new TestSockJsSession("1", this.sockJsConfig, wsHandler);
|
||||
|
||||
String msg1 = "message 1";
|
||||
String msg2 = "message 2";
|
||||
String msg3 = "message 3";
|
||||
|
||||
doThrow(new IOException()).when(this.webSocketHandler).handleMessage(sockJsSession, new TextMessage(msg2));
|
||||
|
||||
sockJsSession.delegateConnectionEstablished();
|
||||
try {
|
||||
sockJsSession.delegateMessages(new String[] { msg1, msg2, msg3 });
|
||||
fail("expected exception");
|
||||
}
|
||||
catch (SockJsMessageDeliveryException ex) {
|
||||
assertEquals(Arrays.asList(msg3), ex.getUndeliveredMessages());
|
||||
verify(this.webSocketHandler).afterConnectionEstablished(sockJsSession);
|
||||
verify(this.webSocketHandler).handleMessage(sockJsSession, new TextMessage(msg1));
|
||||
verify(this.webSocketHandler).handleMessage(sockJsSession, new TextMessage(msg2));
|
||||
verify(this.webSocketHandler).afterConnectionClosed(sockJsSession, CloseStatus.SERVER_ERROR);
|
||||
verifyNoMoreInteractions(this.webSocketHandler);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void delegateConnectionClosed() throws Exception {
|
||||
this.session.delegateConnectionEstablished();
|
||||
|
@ -112,17 +142,17 @@ public class AbstractSockJsSessionTests extends BaseAbstractSockJsSessionTests<T
|
|||
assertNew();
|
||||
|
||||
this.session.close();
|
||||
assertNull("Close not ignored for a new session", this.session.getStatus());
|
||||
assertNull("Close not ignored for a new session", this.session.getCloseStatus());
|
||||
|
||||
this.session.delegateConnectionEstablished();
|
||||
assertOpen();
|
||||
|
||||
this.session.close();
|
||||
assertClosed();
|
||||
assertEquals(3000, this.session.getStatus().getCode());
|
||||
assertEquals(3000, this.session.getCloseStatus().getCode());
|
||||
|
||||
this.session.close(CloseStatus.SERVER_ERROR);
|
||||
assertEquals("Close should be ignored if already closed", 3000, this.session.getStatus().getCode());
|
||||
assertEquals("Close should be ignored if already closed", 3000, this.session.getCloseStatus().getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -152,7 +182,7 @@ public class AbstractSockJsSessionTests extends BaseAbstractSockJsSessionTests<T
|
|||
assertEquals(1, this.session.getNumberOfLastActiveTimeUpdates());
|
||||
assertTrue(this.session.didCancelHeartbeat());
|
||||
|
||||
assertEquals(new CloseStatus(3000, "Go away!"), this.session.getStatus());
|
||||
assertEquals(new CloseStatus(3000, "Go away!"), this.session.getCloseStatus());
|
||||
assertClosed();
|
||||
verify(this.webSocketHandler).afterConnectionClosed(this.session, new CloseStatus(3000, "Go away!"));
|
||||
}
|
||||
|
@ -160,13 +190,13 @@ public class AbstractSockJsSessionTests extends BaseAbstractSockJsSessionTests<T
|
|||
@Test
|
||||
public void closeWithWriteFrameExceptions() throws Exception {
|
||||
|
||||
this.session.setExceptionOnWriteFrame(new IOException());
|
||||
this.session.setExceptionOnWrite(new IOException());
|
||||
|
||||
this.session.delegateConnectionEstablished();
|
||||
this.session.setActive(true);
|
||||
this.session.close();
|
||||
|
||||
assertEquals(new CloseStatus(3000, "Go away!"), this.session.getStatus());
|
||||
assertEquals(new CloseStatus(3000, "Go away!"), this.session.getCloseStatus());
|
||||
assertClosed();
|
||||
}
|
||||
|
||||
|
@ -179,7 +209,7 @@ public class AbstractSockJsSessionTests extends BaseAbstractSockJsSessionTests<T
|
|||
this.session.setActive(true);
|
||||
this.session.close(CloseStatus.NORMAL);
|
||||
|
||||
assertEquals(CloseStatus.NORMAL, this.session.getStatus());
|
||||
assertEquals(CloseStatus.NORMAL, this.session.getCloseStatus());
|
||||
assertClosed();
|
||||
}
|
||||
|
||||
|
@ -193,28 +223,14 @@ public class AbstractSockJsSessionTests extends BaseAbstractSockJsSessionTests<T
|
|||
|
||||
@Test
|
||||
public void writeFrameIoException() throws Exception {
|
||||
this.session.setExceptionOnWriteFrame(new IOException());
|
||||
this.session.setExceptionOnWrite(new IOException());
|
||||
this.session.delegateConnectionEstablished();
|
||||
try {
|
||||
this.session.writeFrame(SockJsFrame.openFrame());
|
||||
fail("expected exception");
|
||||
}
|
||||
catch (IOException ex) {
|
||||
assertEquals(CloseStatus.SERVER_ERROR, this.session.getStatus());
|
||||
verify(this.webSocketHandler).afterConnectionClosed(this.session, CloseStatus.SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeFrameThrowable() throws Exception {
|
||||
this.session.setExceptionOnWriteFrame(new NullPointerException());
|
||||
this.session.delegateConnectionEstablished();
|
||||
try {
|
||||
this.session.writeFrame(SockJsFrame.openFrame());
|
||||
fail("expected exception");
|
||||
}
|
||||
catch (SockJsProcessingException ex) {
|
||||
assertEquals(CloseStatus.SERVER_ERROR, this.session.getStatus());
|
||||
catch (SockJsTransportFailureException ex) {
|
||||
assertEquals(CloseStatus.SERVER_ERROR, this.session.getCloseStatus());
|
||||
verify(this.webSocketHandler).afterConnectionClosed(this.session, CloseStatus.SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* 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.transport.session;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.sockjs.SockJsTransportFailureException;
|
||||
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame;
|
||||
|
||||
/**
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class TestHttpSockJsSession extends AbstractHttpSockJsSession {
|
||||
|
||||
private boolean active;
|
||||
|
||||
private final List<SockJsFrame> sockJsFrames = new ArrayList<>();
|
||||
|
||||
private CloseStatus closeStatus;
|
||||
|
||||
private IOException exceptionOnWrite;
|
||||
|
||||
private int numberOfLastActiveTimeUpdates;
|
||||
|
||||
private boolean cancelledHeartbeat;
|
||||
|
||||
private String subProtocol;
|
||||
|
||||
|
||||
public TestHttpSockJsSession(String sessionId, SockJsServiceConfig config, WebSocketHandler handler) {
|
||||
super(sessionId, config, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAcceptedProtocol() {
|
||||
return this.subProtocol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAcceptedProtocol(String protocol) {
|
||||
this.subProtocol = protocol;
|
||||
}
|
||||
|
||||
public CloseStatus getCloseStatus() {
|
||||
return this.closeStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
public void setActive(boolean active) {
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
public List<SockJsFrame> getSockJsFramesWritten() {
|
||||
return this.sockJsFrames;
|
||||
}
|
||||
|
||||
public void setExceptionOnWrite(IOException exceptionOnWrite) {
|
||||
this.exceptionOnWrite = exceptionOnWrite;
|
||||
}
|
||||
|
||||
public int getNumberOfLastActiveTimeUpdates() {
|
||||
return this.numberOfLastActiveTimeUpdates;
|
||||
}
|
||||
|
||||
public boolean didCancelHeartbeat() {
|
||||
return this.cancelledHeartbeat;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateLastActiveTime() {
|
||||
this.numberOfLastActiveTimeUpdates++;
|
||||
super.updateLastActiveTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cancelHeartbeat() {
|
||||
this.cancelledHeartbeat = true;
|
||||
super.cancelHeartbeat();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeFrameInternal(SockJsFrame frame) throws IOException {
|
||||
this.sockJsFrames.add(frame);
|
||||
if (this.exceptionOnWrite != null) {
|
||||
throw this.exceptionOnWrite;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disconnect(CloseStatus status) {
|
||||
this.closeStatus = status;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void flushCache() throws SockJsTransportFailureException {
|
||||
}
|
||||
|
||||
}
|
|
@ -23,7 +23,6 @@ import java.util.List;
|
|||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.sockjs.support.frame.SockJsFrame;
|
||||
import org.springframework.web.socket.sockjs.transport.session.AbstractSockJsSession;
|
||||
|
||||
/**
|
||||
* @author Rossen Stoyanchev
|
||||
|
@ -32,11 +31,11 @@ public class TestSockJsSession extends AbstractSockJsSession {
|
|||
|
||||
private boolean active;
|
||||
|
||||
private final List<SockJsFrame> sockJsFramesWritten = new ArrayList<>();
|
||||
private final List<SockJsFrame> sockJsFrames = new ArrayList<>();
|
||||
|
||||
private CloseStatus status;
|
||||
private CloseStatus closeStatus;
|
||||
|
||||
private Exception exceptionOnWriteFrame;
|
||||
private IOException exceptionOnWrite;
|
||||
|
||||
private int numberOfLastActiveTimeUpdates;
|
||||
|
||||
|
@ -59,8 +58,8 @@ public class TestSockJsSession extends AbstractSockJsSession {
|
|||
this.subProtocol = protocol;
|
||||
}
|
||||
|
||||
public CloseStatus getStatus() {
|
||||
return this.status;
|
||||
public CloseStatus getCloseStatus() {
|
||||
return this.closeStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -73,11 +72,11 @@ public class TestSockJsSession extends AbstractSockJsSession {
|
|||
}
|
||||
|
||||
public List<SockJsFrame> getSockJsFramesWritten() {
|
||||
return this.sockJsFramesWritten;
|
||||
return this.sockJsFrames;
|
||||
}
|
||||
|
||||
public void setExceptionOnWriteFrame(Exception exceptionOnWriteFrame) {
|
||||
this.exceptionOnWriteFrame = exceptionOnWriteFrame;
|
||||
public void setExceptionOnWrite(IOException exceptionOnWrite) {
|
||||
this.exceptionOnWrite = exceptionOnWrite;
|
||||
}
|
||||
|
||||
public int getNumberOfLastActiveTimeUpdates() {
|
||||
|
@ -101,20 +100,20 @@ public class TestSockJsSession extends AbstractSockJsSession {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void sendMessageInternal(String message) throws IOException {
|
||||
protected void sendMessageInternal(String message) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeFrameInternal(SockJsFrame frame) throws Exception {
|
||||
this.sockJsFramesWritten.add(frame);
|
||||
if (this.exceptionOnWriteFrame != null) {
|
||||
throw this.exceptionOnWriteFrame;
|
||||
protected void writeFrameInternal(SockJsFrame frame) throws IOException {
|
||||
this.sockJsFrames.add(frame);
|
||||
if (this.exceptionOnWrite != null) {
|
||||
throw this.exceptionOnWrite;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void disconnect(CloseStatus status) throws IOException {
|
||||
this.status = status;
|
||||
this.closeStatus = status;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue