Deprecate ListenableFuture in favor of CompletableFuture
This commit deprecates ListenableFuture in favor of CompletableFuture. ListenableFuture was introduced in Spring Framework 4.0, when CompletableFuture was not yet available. Spring now requires JDK 17, so having our own type no longer seems necessary. Major changes in this commit include: - Deprecation of ListenableFuture and related types (ListenableFutureCallback, SettableListenableFuture, etc.) - Deprecation of AsyncListenableTaskExecutor in favor of default methods in AsyncTaskExecutor (submitCompletable). - AsyncHandlerMethodReturnValueHandler now has toCompletableFuture instead of toListenableFuture. - WebSocketClient now has execute methods, which do the same as doHandshake, but return CompletableFutures (cf. the reactive WebSocketClient). All other changes - add an overloaded method that takes a CompletableFuture parameter instead of ListenableFuture, and/or - add a method with a 'Async' suffix that returns a CompletableFuture instead of a ListenableFuture (connectAsync, sendAsync). Closes gh-27780
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -22,6 +22,7 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -63,16 +64,16 @@ public abstract class AbstractWebSocketClient implements WebSocketClient {
|
||||
|
||||
|
||||
@Override
|
||||
public ListenableFuture<WebSocketSession> doHandshake(WebSocketHandler webSocketHandler,
|
||||
public CompletableFuture<WebSocketSession> execute(WebSocketHandler webSocketHandler,
|
||||
String uriTemplate, Object... uriVars) {
|
||||
|
||||
Assert.notNull(uriTemplate, "'uriTemplate' must not be null");
|
||||
URI uri = UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand(uriVars).encode().toUri();
|
||||
return doHandshake(webSocketHandler, null, uri);
|
||||
return execute(webSocketHandler, null, uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ListenableFuture<WebSocketSession> doHandshake(WebSocketHandler webSocketHandler,
|
||||
public final CompletableFuture<WebSocketSession> execute(WebSocketHandler webSocketHandler,
|
||||
@Nullable WebSocketHttpHeaders headers, URI uri) {
|
||||
|
||||
Assert.notNull(webSocketHandler, "WebSocketHandler must not be null");
|
||||
@@ -96,7 +97,7 @@ public abstract class AbstractWebSocketClient implements WebSocketClient {
|
||||
List<WebSocketExtension> extensions =
|
||||
(headers != null ? headers.getSecWebSocketExtensions() : Collections.emptyList());
|
||||
|
||||
return doHandshakeInternal(webSocketHandler, headersToUse, uri, subProtocols, extensions,
|
||||
return executeInternal(webSocketHandler, headersToUse, uri, subProtocols, extensions,
|
||||
Collections.emptyMap());
|
||||
}
|
||||
|
||||
@@ -119,8 +120,28 @@ public abstract class AbstractWebSocketClient implements WebSocketClient {
|
||||
* @param attributes the attributes to associate with the WebSocketSession, i.e. via
|
||||
* {@link WebSocketSession#getAttributes()}; currently always an empty map.
|
||||
* @return the established WebSocket session wrapped in a ListenableFuture.
|
||||
* @deprecated as of 6.0, in favor of {@link #executeInternal(WebSocketHandler, HttpHeaders, URI, List, List, Map)}
|
||||
*/
|
||||
protected abstract ListenableFuture<WebSocketSession> doHandshakeInternal(WebSocketHandler webSocketHandler,
|
||||
@Deprecated
|
||||
protected ListenableFuture<WebSocketSession> doHandshakeInternal(WebSocketHandler webSocketHandler,
|
||||
HttpHeaders headers, URI uri, List<String> subProtocols, List<WebSocketExtension> extensions,
|
||||
Map<String, Object> attributes) {
|
||||
throw new UnsupportedOperationException("doHandshakeInternal is deprecated in favor of executeInternal");
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the actual handshake to establish a connection to the server.
|
||||
* @param webSocketHandler the client-side handler for WebSocket messages
|
||||
* @param headers the HTTP headers to use for the handshake, with unwanted (forbidden)
|
||||
* headers filtered out (never {@code null})
|
||||
* @param uri the target URI for the handshake (never {@code null})
|
||||
* @param subProtocols requested sub-protocols, or an empty list
|
||||
* @param extensions requested WebSocket extensions, or an empty list
|
||||
* @param attributes the attributes to associate with the WebSocketSession, i.e. via
|
||||
* {@link WebSocketSession#getAttributes()}; currently always an empty map.
|
||||
* @return the established WebSocket session wrapped in a ListenableFuture.
|
||||
*/
|
||||
protected abstract CompletableFuture<WebSocketSession> executeInternal(WebSocketHandler webSocketHandler,
|
||||
HttpHeaders headers, URI uri, List<String> subProtocols, List<WebSocketExtension> extensions,
|
||||
Map<String, Object> attributes);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -17,8 +17,10 @@
|
||||
package org.springframework.web.socket.client;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.concurrent.CompletableToListenableFutureAdapter;
|
||||
import org.springframework.util.concurrent.ListenableFuture;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.WebSocketHttpHeaders;
|
||||
@@ -35,10 +37,56 @@ import org.springframework.web.socket.WebSocketSession;
|
||||
*/
|
||||
public interface WebSocketClient {
|
||||
|
||||
ListenableFuture<WebSocketSession> doHandshake(WebSocketHandler webSocketHandler,
|
||||
String uriTemplate, Object... uriVariables);
|
||||
/**
|
||||
* Execute a handshake request to the given url and handle the resulting
|
||||
* WebSocket session with the given handler.
|
||||
* @param webSocketHandler the session handler
|
||||
* @param uriTemplate the url template
|
||||
* @param uriVariables the variables to expand the template
|
||||
* @return a future that completes when the session is available
|
||||
* @deprecated as of 6.0, in favor of {@link #execute(WebSocketHandler, String, Object...)}
|
||||
*/
|
||||
@Deprecated
|
||||
default ListenableFuture<WebSocketSession> doHandshake(WebSocketHandler webSocketHandler,
|
||||
String uriTemplate, Object... uriVariables) {
|
||||
return new CompletableToListenableFutureAdapter<>(execute(webSocketHandler, uriTemplate, uriVariables));
|
||||
}
|
||||
|
||||
ListenableFuture<WebSocketSession> doHandshake(WebSocketHandler webSocketHandler,
|
||||
/**
|
||||
* Execute a handshake request to the given url and handle the resulting
|
||||
* WebSocket session with the given handler.
|
||||
* @param webSocketHandler the session handler
|
||||
* @param uriTemplate the url template
|
||||
* @param uriVariables the variables to expand the template
|
||||
* @return a future that completes when the session is available
|
||||
* @since 6.0
|
||||
*/
|
||||
CompletableFuture<WebSocketSession> execute(WebSocketHandler webSocketHandler,
|
||||
String uriTemplate, Object... uriVariables);
|
||||
|
||||
/**
|
||||
* Execute a handshake request to the given url and handle the resulting
|
||||
* WebSocket session with the given handler.
|
||||
* @param webSocketHandler the session handler
|
||||
* @param uri the url
|
||||
* @return a future that completes when the session is available
|
||||
* @deprecated as of 6.0, in favor of {@link #execute(WebSocketHandler, WebSocketHttpHeaders, URI)}
|
||||
*/
|
||||
@Deprecated
|
||||
default ListenableFuture<WebSocketSession> doHandshake(WebSocketHandler webSocketHandler,
|
||||
@Nullable WebSocketHttpHeaders headers, URI uri) {
|
||||
return new CompletableToListenableFutureAdapter<>(execute(webSocketHandler, headers, uri));
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a handshake request to the given url and handle the resulting
|
||||
* WebSocket session with the given handler.
|
||||
* @param webSocketHandler the session handler
|
||||
* @param uri the url
|
||||
* @return a future that completes when the session is available
|
||||
* @since 6.0
|
||||
*/
|
||||
CompletableFuture<WebSocketSession> execute(WebSocketHandler webSocketHandler,
|
||||
@Nullable WebSocketHttpHeaders headers, URI uri);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -17,12 +17,11 @@
|
||||
package org.springframework.web.socket.client;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.concurrent.ListenableFuture;
|
||||
import org.springframework.util.concurrent.ListenableFutureCallback;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.WebSocketHttpHeaders;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
@@ -137,17 +136,15 @@ public class WebSocketConnectionManager extends ConnectionManagerSupport {
|
||||
logger.info("Connecting to WebSocket at " + getUri());
|
||||
}
|
||||
|
||||
ListenableFuture<WebSocketSession> future =
|
||||
this.client.doHandshake(this.webSocketHandler, this.headers, getUri());
|
||||
CompletableFuture<WebSocketSession> future =
|
||||
this.client.execute(this.webSocketHandler, this.headers, getUri());
|
||||
|
||||
future.addCallback(new ListenableFutureCallback<WebSocketSession>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable WebSocketSession result) {
|
||||
webSocketSession = result;
|
||||
future.whenComplete((result, ex) -> {
|
||||
if (result != null) {
|
||||
this.webSocketSession = result;
|
||||
logger.info("Successfully connected");
|
||||
}
|
||||
@Override
|
||||
public void onFailure(Throwable ex) {
|
||||
else if (ex != null) {
|
||||
logger.error("Failed to connect", ex);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.security.Principal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -30,12 +31,12 @@ import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.core.task.AsyncListenableTaskExecutor;
|
||||
import org.springframework.core.task.AsyncTaskExecutor;
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.concurrent.ListenableFuture;
|
||||
import org.springframework.util.concurrent.ListenableFutureTask;
|
||||
import org.springframework.util.concurrent.FutureUtils;
|
||||
import org.springframework.web.socket.WebSocketExtension;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
@@ -43,8 +44,6 @@ import org.springframework.web.socket.adapter.jetty.JettyWebSocketHandlerAdapter
|
||||
import org.springframework.web.socket.adapter.jetty.JettyWebSocketSession;
|
||||
import org.springframework.web.socket.adapter.jetty.WebSocketToJettyExtensionConfigAdapter;
|
||||
import org.springframework.web.socket.client.AbstractWebSocketClient;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* Initiates WebSocket requests to a WebSocket server programmatically
|
||||
@@ -64,7 +63,7 @@ public class JettyWebSocketClient extends AbstractWebSocketClient implements Lif
|
||||
private final org.eclipse.jetty.websocket.client.WebSocketClient client;
|
||||
|
||||
@Nullable
|
||||
private AsyncListenableTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
|
||||
private AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
|
||||
|
||||
|
||||
/**
|
||||
@@ -90,7 +89,7 @@ public class JettyWebSocketClient extends AbstractWebSocketClient implements Lif
|
||||
* {@code doHandshake} methods will block until the connection is established.
|
||||
* <p>By default an instance of {@code SimpleAsyncTaskExecutor} is used.
|
||||
*/
|
||||
public void setTaskExecutor(@Nullable AsyncListenableTaskExecutor taskExecutor) {
|
||||
public void setTaskExecutor(@Nullable AsyncTaskExecutor taskExecutor) {
|
||||
this.taskExecutor = taskExecutor;
|
||||
}
|
||||
|
||||
@@ -98,7 +97,7 @@ public class JettyWebSocketClient extends AbstractWebSocketClient implements Lif
|
||||
* Return the configured {@link TaskExecutor}.
|
||||
*/
|
||||
@Nullable
|
||||
public AsyncListenableTaskExecutor getTaskExecutor() {
|
||||
public AsyncTaskExecutor getTaskExecutor() {
|
||||
return this.taskExecutor;
|
||||
}
|
||||
|
||||
@@ -130,15 +129,7 @@ public class JettyWebSocketClient extends AbstractWebSocketClient implements Lif
|
||||
|
||||
|
||||
@Override
|
||||
public ListenableFuture<WebSocketSession> doHandshake(WebSocketHandler webSocketHandler,
|
||||
String uriTemplate, Object... uriVars) {
|
||||
|
||||
UriComponents uriComponents = UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand(uriVars).encode();
|
||||
return doHandshake(webSocketHandler, null, uriComponents.toUri());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<WebSocketSession> doHandshakeInternal(WebSocketHandler wsHandler,
|
||||
public CompletableFuture<WebSocketSession> executeInternal(WebSocketHandler wsHandler,
|
||||
HttpHeaders headers, final URI uri, List<String> protocols,
|
||||
List<WebSocketExtension> extensions, Map<String, Object> attributes) {
|
||||
|
||||
@@ -162,12 +153,10 @@ public class JettyWebSocketClient extends AbstractWebSocketClient implements Lif
|
||||
};
|
||||
|
||||
if (this.taskExecutor != null) {
|
||||
return this.taskExecutor.submitListenable(connectTask);
|
||||
return FutureUtils.callAsync(connectTask, this.taskExecutor);
|
||||
}
|
||||
else {
|
||||
ListenableFutureTask<WebSocketSession> task = new ListenableFutureTask<>(connectTask);
|
||||
task.run();
|
||||
return task;
|
||||
return FutureUtils.callAsync(connectTask);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -26,6 +26,7 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import jakarta.websocket.ClientEndpointConfig;
|
||||
import jakarta.websocket.ClientEndpointConfig.Configurator;
|
||||
@@ -36,13 +37,13 @@ import jakarta.websocket.HandshakeResponse;
|
||||
import jakarta.websocket.WebSocketContainer;
|
||||
|
||||
import org.springframework.core.task.AsyncListenableTaskExecutor;
|
||||
import org.springframework.core.task.AsyncTaskExecutor;
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.concurrent.ListenableFuture;
|
||||
import org.springframework.util.concurrent.ListenableFutureTask;
|
||||
import org.springframework.util.concurrent.FutureUtils;
|
||||
import org.springframework.web.socket.WebSocketExtension;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
@@ -64,7 +65,7 @@ public class StandardWebSocketClient extends AbstractWebSocketClient {
|
||||
private final Map<String,Object> userProperties = new HashMap<>();
|
||||
|
||||
@Nullable
|
||||
private AsyncListenableTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
|
||||
private AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
|
||||
|
||||
|
||||
/**
|
||||
@@ -113,7 +114,7 @@ public class StandardWebSocketClient extends AbstractWebSocketClient {
|
||||
* {@code doHandshake} methods will block until the connection is established.
|
||||
* <p>By default, an instance of {@code SimpleAsyncTaskExecutor} is used.
|
||||
*/
|
||||
public void setTaskExecutor(@Nullable AsyncListenableTaskExecutor taskExecutor) {
|
||||
public void setTaskExecutor(@Nullable AsyncTaskExecutor taskExecutor) {
|
||||
this.taskExecutor = taskExecutor;
|
||||
}
|
||||
|
||||
@@ -121,13 +122,13 @@ public class StandardWebSocketClient extends AbstractWebSocketClient {
|
||||
* Return the configured {@link TaskExecutor}.
|
||||
*/
|
||||
@Nullable
|
||||
public AsyncListenableTaskExecutor getTaskExecutor() {
|
||||
public AsyncTaskExecutor getTaskExecutor() {
|
||||
return this.taskExecutor;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected ListenableFuture<WebSocketSession> doHandshakeInternal(WebSocketHandler webSocketHandler,
|
||||
protected CompletableFuture<WebSocketSession> executeInternal(WebSocketHandler webSocketHandler,
|
||||
HttpHeaders headers, final URI uri, List<String> protocols,
|
||||
List<WebSocketExtension> extensions, Map<String, Object> attributes) {
|
||||
|
||||
@@ -153,12 +154,10 @@ public class StandardWebSocketClient extends AbstractWebSocketClient {
|
||||
};
|
||||
|
||||
if (this.taskExecutor != null) {
|
||||
return this.taskExecutor.submitListenable(connectTask);
|
||||
return FutureUtils.callAsync(connectTask, this.taskExecutor);
|
||||
}
|
||||
else {
|
||||
ListenableFutureTask<WebSocketSession> task = new ListenableFutureTask<>(connectTask);
|
||||
task.run();
|
||||
return task;
|
||||
return FutureUtils.callAsync(connectTask);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,9 @@ import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -47,9 +49,8 @@ import org.springframework.messaging.tcp.TcpConnectionHandler;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
import org.springframework.util.concurrent.CompletableToListenableFutureAdapter;
|
||||
import org.springframework.util.concurrent.ListenableFuture;
|
||||
import org.springframework.util.concurrent.ListenableFutureCallback;
|
||||
import org.springframework.util.concurrent.SettableListenableFuture;
|
||||
import org.springframework.web.socket.BinaryMessage;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
@@ -210,9 +211,25 @@ public class WebSocketStompClient extends StompClientSupport implements SmartLif
|
||||
* @param handler the session handler
|
||||
* @param uriVars the URI variables to expand into the URL
|
||||
* @return a ListenableFuture for access to the session when ready for use
|
||||
* @deprecated as of 6.0, in favor of {@link #connectAsync(String, StompSessionHandler, Object...)}
|
||||
*/
|
||||
@Deprecated
|
||||
public ListenableFuture<StompSession> connect(String url, StompSessionHandler handler, Object... uriVars) {
|
||||
return connect(url, null, handler, uriVars);
|
||||
return new CompletableToListenableFutureAdapter<>(connectAsync(url, handler, uriVars));
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the given WebSocket URL and notify the given
|
||||
* {@link org.springframework.messaging.simp.stomp.StompSessionHandler}
|
||||
* when connected on the STOMP level after the CONNECTED frame is received.
|
||||
* @param url the url to connect to
|
||||
* @param handler the session handler
|
||||
* @param uriVars the URI variables to expand into the URL
|
||||
* @return a CompletableFuture for access to the session when ready for use
|
||||
* @since 6.0
|
||||
*/
|
||||
public CompletableFuture<StompSession> connectAsync(String url, StompSessionHandler handler, Object... uriVars) {
|
||||
return connectAsync(url, null, handler, uriVars);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,11 +241,31 @@ public class WebSocketStompClient extends StompClientSupport implements SmartLif
|
||||
* @param handler the session handler
|
||||
* @param uriVariables the URI variables to expand into the URL
|
||||
* @return a ListenableFuture for access to the session when ready for use
|
||||
* @deprecated as of 6.0, in favor of {@link #connectAsync(String, WebSocketHttpHeaders, StompSessionHandler, Object...)}
|
||||
*/
|
||||
@Deprecated
|
||||
public ListenableFuture<StompSession> connect(String url, @Nullable WebSocketHttpHeaders handshakeHeaders,
|
||||
StompSessionHandler handler, Object... uriVariables) {
|
||||
|
||||
return connect(url, handshakeHeaders, null, handler, uriVariables);
|
||||
return new CompletableToListenableFutureAdapter<>(
|
||||
connectAsync(url, handshakeHeaders, null, handler, uriVariables));
|
||||
}
|
||||
|
||||
/**
|
||||
* An overloaded version of
|
||||
* {@link #connect(String, StompSessionHandler, Object...)} that also
|
||||
* accepts {@link WebSocketHttpHeaders} to use for the WebSocket handshake.
|
||||
* @param url the url to connect to
|
||||
* @param handshakeHeaders the headers for the WebSocket handshake
|
||||
* @param handler the session handler
|
||||
* @param uriVariables the URI variables to expand into the URL
|
||||
* @return a ListenableFuture for access to the session when ready for use
|
||||
* @since 6.0
|
||||
*/
|
||||
public CompletableFuture<StompSession> connectAsync(String url, @Nullable WebSocketHttpHeaders handshakeHeaders,
|
||||
StompSessionHandler handler, Object... uriVariables) {
|
||||
|
||||
return connectAsync(url, handshakeHeaders, null, handler, uriVariables);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -242,13 +279,35 @@ public class WebSocketStompClient extends StompClientSupport implements SmartLif
|
||||
* @param handler the session handler
|
||||
* @param uriVariables the URI variables to expand into the URL
|
||||
* @return a ListenableFuture for access to the session when ready for use
|
||||
* @deprecated as of 6.0, in favor of {@link #connectAsync(String, WebSocketHttpHeaders, StompHeaders, StompSessionHandler, Object...)}
|
||||
*/
|
||||
@Deprecated
|
||||
public ListenableFuture<StompSession> connect(String url, @Nullable WebSocketHttpHeaders handshakeHeaders,
|
||||
@Nullable StompHeaders connectHeaders, StompSessionHandler handler, Object... uriVariables) {
|
||||
|
||||
return new CompletableToListenableFutureAdapter<>(
|
||||
connectAsync(url, handshakeHeaders, connectHeaders, handler, uriVariables));
|
||||
}
|
||||
|
||||
/**
|
||||
* An overloaded version of
|
||||
* {@link #connect(String, StompSessionHandler, Object...)} that also accepts
|
||||
* {@link WebSocketHttpHeaders} to use for the WebSocket handshake and
|
||||
* {@link StompHeaders} for the STOMP CONNECT frame.
|
||||
* @param url the url to connect to
|
||||
* @param handshakeHeaders headers for the WebSocket handshake
|
||||
* @param connectHeaders headers for the STOMP CONNECT frame
|
||||
* @param handler the session handler
|
||||
* @param uriVariables the URI variables to expand into the URL
|
||||
* @return a CompletableFuture for access to the session when ready for use
|
||||
* @since 6.0
|
||||
*/
|
||||
public CompletableFuture<StompSession> connectAsync(String url, @Nullable WebSocketHttpHeaders handshakeHeaders,
|
||||
@Nullable StompHeaders connectHeaders, StompSessionHandler handler, Object... uriVariables) {
|
||||
|
||||
Assert.notNull(url, "'url' must not be null");
|
||||
URI uri = UriComponentsBuilder.fromUriString(url).buildAndExpand(uriVariables).encode().toUri();
|
||||
return connect(uri, handshakeHeaders, connectHeaders, handler);
|
||||
return connectAsync(uri, handshakeHeaders, connectHeaders, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -260,17 +319,37 @@ public class WebSocketStompClient extends StompClientSupport implements SmartLif
|
||||
* @param connectHeaders headers for the STOMP CONNECT frame
|
||||
* @param sessionHandler the STOMP session handler
|
||||
* @return a ListenableFuture for access to the session when ready for use
|
||||
* @deprecated as of 6.0, in favor of {@link #connectAsync(URI, WebSocketHttpHeaders, StompHeaders, StompSessionHandler)}
|
||||
*/
|
||||
@Deprecated
|
||||
public ListenableFuture<StompSession> connect(URI url, @Nullable WebSocketHttpHeaders handshakeHeaders,
|
||||
@Nullable StompHeaders connectHeaders, StompSessionHandler sessionHandler) {
|
||||
|
||||
return new CompletableToListenableFutureAdapter<>(
|
||||
connectAsync(url, handshakeHeaders, connectHeaders, sessionHandler));
|
||||
}
|
||||
|
||||
/**
|
||||
* An overloaded version of
|
||||
* {@link #connect(String, WebSocketHttpHeaders, StompSessionHandler, Object...)}
|
||||
* that accepts a fully prepared {@link java.net.URI}.
|
||||
* @param url the url to connect to
|
||||
* @param handshakeHeaders the headers for the WebSocket handshake
|
||||
* @param connectHeaders headers for the STOMP CONNECT frame
|
||||
* @param sessionHandler the STOMP session handler
|
||||
* @return a CompletableFuture for access to the session when ready for use
|
||||
* @since 6.0
|
||||
*/
|
||||
public CompletableFuture<StompSession> connectAsync(URI url, @Nullable WebSocketHttpHeaders handshakeHeaders,
|
||||
@Nullable StompHeaders connectHeaders, StompSessionHandler sessionHandler) {
|
||||
|
||||
Assert.notNull(url, "'url' must not be null");
|
||||
ConnectionHandlingStompSession session = createSession(connectHeaders, sessionHandler);
|
||||
WebSocketTcpConnectionHandlerAdapter adapter = new WebSocketTcpConnectionHandlerAdapter(session);
|
||||
getWebSocketClient()
|
||||
.doHandshake(new LoggingWebSocketHandlerDecorator(adapter), handshakeHeaders, url)
|
||||
.addCallback(adapter);
|
||||
return session.getSessionFuture();
|
||||
.execute(new LoggingWebSocketHandlerDecorator(adapter), handshakeHeaders, url)
|
||||
.whenComplete(adapter);
|
||||
return session.getSession();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -286,7 +365,7 @@ public class WebSocketStompClient extends StompClientSupport implements SmartLif
|
||||
/**
|
||||
* Adapt WebSocket to the TcpConnectionHandler and TcpConnection contracts.
|
||||
*/
|
||||
private class WebSocketTcpConnectionHandlerAdapter implements ListenableFutureCallback<WebSocketSession>,
|
||||
private class WebSocketTcpConnectionHandlerAdapter implements BiConsumer<WebSocketSession, Throwable>,
|
||||
WebSocketHandler, TcpConnection<byte[]> {
|
||||
|
||||
private final TcpConnectionHandler<byte[]> connectionHandler;
|
||||
@@ -307,15 +386,13 @@ public class WebSocketStompClient extends StompClientSupport implements SmartLif
|
||||
this.connectionHandler = connectionHandler;
|
||||
}
|
||||
|
||||
// ListenableFutureCallback implementation: handshake outcome
|
||||
// CompletableFuture callback implementation: handshake outcome
|
||||
|
||||
@Override
|
||||
public void onSuccess(@Nullable WebSocketSession webSocketSession) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable ex) {
|
||||
this.connectionHandler.afterConnectFailure(ex);
|
||||
public void accept(@Nullable WebSocketSession webSocketSession, @Nullable Throwable throwable) {
|
||||
if (throwable != null) {
|
||||
this.connectionHandler.afterConnectFailure(throwable);
|
||||
}
|
||||
}
|
||||
|
||||
// WebSocketHandler implementation
|
||||
@@ -375,17 +452,17 @@ public class WebSocketStompClient extends StompClientSupport implements SmartLif
|
||||
// TcpConnection implementation
|
||||
|
||||
@Override
|
||||
public ListenableFuture<Void> send(Message<byte[]> message) {
|
||||
public CompletableFuture<Void> sendAsync(Message<byte[]> message) {
|
||||
updateLastWriteTime();
|
||||
SettableListenableFuture<Void> future = new SettableListenableFuture<>();
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
try {
|
||||
WebSocketSession session = this.session;
|
||||
Assert.state(session != null, "No WebSocketSession available");
|
||||
session.sendMessage(this.codec.encode(message, session.getClass()));
|
||||
future.set(null);
|
||||
future.complete(null);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
future.setException(ex);
|
||||
future.completeExceptionally(ex);
|
||||
}
|
||||
finally {
|
||||
updateLastWriteTime();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -20,6 +20,7 @@ import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.security.Principal;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
@@ -55,7 +56,7 @@ public abstract class AbstractClientSockJsSession implements WebSocketSession {
|
||||
|
||||
private final WebSocketHandler webSocketHandler;
|
||||
|
||||
private final SettableListenableFuture<WebSocketSession> connectFuture;
|
||||
private final CompletableFuture<WebSocketSession> connectFuture;
|
||||
|
||||
private final Map<String, Object> attributes = new ConcurrentHashMap<>();
|
||||
|
||||
@@ -65,9 +66,18 @@ public abstract class AbstractClientSockJsSession implements WebSocketSession {
|
||||
@Nullable
|
||||
private volatile CloseStatus closeStatus;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@code AbstractClientSockJsSession}.
|
||||
* @deprecated as of 6.0, in favor of {@link #AbstractClientSockJsSession(TransportRequest, WebSocketHandler, CompletableFuture)}
|
||||
*/
|
||||
@Deprecated
|
||||
protected AbstractClientSockJsSession(TransportRequest request, WebSocketHandler handler,
|
||||
SettableListenableFuture<WebSocketSession> connectFuture) {
|
||||
this(request, handler, connectFuture.completable());
|
||||
}
|
||||
|
||||
protected AbstractClientSockJsSession(TransportRequest request, WebSocketHandler handler,
|
||||
CompletableFuture<WebSocketSession> connectFuture) {
|
||||
|
||||
Assert.notNull(request, "'request' is required");
|
||||
Assert.notNull(handler, "'handler' is required");
|
||||
@@ -242,7 +252,7 @@ public abstract class AbstractClientSockJsSession implements WebSocketSession {
|
||||
this.state = State.OPEN;
|
||||
try {
|
||||
this.webSocketHandler.afterConnectionEstablished(this);
|
||||
this.connectFuture.set(this);
|
||||
this.connectFuture.complete(this);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (logger.isErrorEnabled()) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -20,6 +20,7 @@ import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -28,7 +29,6 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.concurrent.ListenableFuture;
|
||||
import org.springframework.util.concurrent.SettableListenableFuture;
|
||||
import org.springframework.web.client.HttpServerErrorException;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
@@ -91,8 +91,8 @@ public abstract class AbstractXhrTransport implements XhrTransport {
|
||||
// Transport methods
|
||||
|
||||
@Override
|
||||
public ListenableFuture<WebSocketSession> connect(TransportRequest request, WebSocketHandler handler) {
|
||||
SettableListenableFuture<WebSocketSession> connectFuture = new SettableListenableFuture<>();
|
||||
public CompletableFuture<WebSocketSession> connectAsync(TransportRequest request, WebSocketHandler handler) {
|
||||
CompletableFuture<WebSocketSession> connectFuture = new CompletableFuture<>();
|
||||
XhrClientSockJsSession session = new XhrClientSockJsSession(request, handler, this, connectFuture);
|
||||
request.addTimeoutTask(session.getTimeoutTask());
|
||||
|
||||
@@ -109,9 +109,16 @@ public abstract class AbstractXhrTransport implements XhrTransport {
|
||||
return connectFuture;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
protected void connectInternal(TransportRequest request, WebSocketHandler handler,
|
||||
URI receiveUrl, HttpHeaders handshakeHeaders, XhrClientSockJsSession session,
|
||||
SettableListenableFuture<WebSocketSession> connectFuture) {
|
||||
throw new UnsupportedOperationException("connectInternal has been deprecated in favor of connectInternal");
|
||||
}
|
||||
|
||||
protected abstract void connectInternal(TransportRequest request, WebSocketHandler handler,
|
||||
URI receiveUrl, HttpHeaders handshakeHeaders, XhrClientSockJsSession session,
|
||||
SettableListenableFuture<WebSocketSession> connectFuture);
|
||||
CompletableFuture<WebSocketSession> connectFuture);
|
||||
|
||||
|
||||
// InfoReceiver methods
|
||||
|
||||
@@ -22,7 +22,9 @@ import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -146,17 +148,40 @@ class DefaultTransportRequest implements TransportRequest {
|
||||
}
|
||||
|
||||
|
||||
@Deprecated
|
||||
public void connect(WebSocketHandler handler, SettableListenableFuture<WebSocketSession> future) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Starting " + this);
|
||||
}
|
||||
ConnectCallback connectCallback = new ConnectCallback(handler, future);
|
||||
ListenableConnectCallback connectCallback = new ListenableConnectCallback(handler, future);
|
||||
scheduleConnectTimeoutTask(connectCallback);
|
||||
this.transport.connect(this, handler).addCallback(connectCallback);
|
||||
}
|
||||
|
||||
public void connect(WebSocketHandler handler, CompletableFuture<WebSocketSession> future) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Starting " + this);
|
||||
}
|
||||
CompletableConnectCallback connectCallback = new CompletableConnectCallback(handler, future);
|
||||
scheduleConnectTimeoutTask(connectCallback);
|
||||
this.transport.connectAsync(this, handler).whenComplete(connectCallback);
|
||||
}
|
||||
|
||||
private void scheduleConnectTimeoutTask(ConnectCallback connectHandler) {
|
||||
|
||||
private void scheduleConnectTimeoutTask(ListenableConnectCallback connectHandler) {
|
||||
if (this.timeoutScheduler != null) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Scheduling connect to time out after " + this.timeoutValue + " ms.");
|
||||
}
|
||||
Instant timeoutDate = Instant.now().plus(this.timeoutValue, ChronoUnit.MILLIS);
|
||||
this.timeoutScheduler.schedule(connectHandler, timeoutDate);
|
||||
}
|
||||
else if (logger.isTraceEnabled()) {
|
||||
logger.trace("Connect timeout task not scheduled (no TaskScheduler configured).");
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleConnectTimeoutTask(CompletableConnectCallback connectHandler) {
|
||||
if (this.timeoutScheduler != null) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Scheduling connect to time out after " + this.timeoutValue + " ms.");
|
||||
@@ -182,7 +207,8 @@ class DefaultTransportRequest implements TransportRequest {
|
||||
* to connect. Also implements {@code Runnable} to handle a scheduled timeout
|
||||
* callback.
|
||||
*/
|
||||
private class ConnectCallback implements ListenableFutureCallback<WebSocketSession>, Runnable {
|
||||
@SuppressWarnings("deprecation")
|
||||
private class ListenableConnectCallback implements ListenableFutureCallback<WebSocketSession>, Runnable {
|
||||
|
||||
private final WebSocketHandler handler;
|
||||
|
||||
@@ -190,7 +216,7 @@ class DefaultTransportRequest implements TransportRequest {
|
||||
|
||||
private final AtomicBoolean handled = new AtomicBoolean();
|
||||
|
||||
public ConnectCallback(WebSocketHandler handler, SettableListenableFuture<WebSocketSession> future) {
|
||||
public ListenableConnectCallback(WebSocketHandler handler, SettableListenableFuture<WebSocketSession> future) {
|
||||
this.handler = handler;
|
||||
this.future = future;
|
||||
}
|
||||
@@ -250,4 +276,79 @@ class DefaultTransportRequest implements TransportRequest {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the given (global) future based success or failure to connect for
|
||||
* the entire SockJS request regardless of which transport actually managed
|
||||
* to connect. Also implements {@code Runnable} to handle a scheduled timeout
|
||||
* callback.
|
||||
*/
|
||||
private class CompletableConnectCallback
|
||||
implements Runnable, BiConsumer<WebSocketSession, Throwable> {
|
||||
|
||||
private final WebSocketHandler handler;
|
||||
|
||||
private final CompletableFuture<WebSocketSession> future;
|
||||
|
||||
private final AtomicBoolean handled = new AtomicBoolean();
|
||||
|
||||
public CompletableConnectCallback(WebSocketHandler handler, CompletableFuture<WebSocketSession> future) {
|
||||
this.handler = handler;
|
||||
this.future = future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(@Nullable WebSocketSession session, @Nullable Throwable throwable) {
|
||||
if (session != null) {
|
||||
if (this.handled.compareAndSet(false, true)) {
|
||||
this.future.complete(session);
|
||||
}
|
||||
else if (logger.isErrorEnabled()) {
|
||||
logger.error("Connect success/failure already handled for " + DefaultTransportRequest.this);
|
||||
}
|
||||
}
|
||||
else if (throwable != null) {
|
||||
handleFailure(throwable, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
handleFailure(null, true);
|
||||
}
|
||||
|
||||
private void handleFailure(@Nullable Throwable ex, boolean isTimeoutFailure) {
|
||||
if (this.handled.compareAndSet(false, true)) {
|
||||
if (isTimeoutFailure) {
|
||||
String message = "Connect timed out for " + DefaultTransportRequest.this;
|
||||
logger.error(message);
|
||||
ex = new SockJsTransportFailureException(message, getSockJsUrlInfo().getSessionId(), ex);
|
||||
}
|
||||
if (fallbackRequest != null) {
|
||||
logger.error(DefaultTransportRequest.this + " failed. Falling back on next transport.", ex);
|
||||
fallbackRequest.connect(this.handler, this.future);
|
||||
}
|
||||
else {
|
||||
logger.error("No more fallback transports after " + DefaultTransportRequest.this, ex);
|
||||
if (ex != null) {
|
||||
this.future.completeExceptionally(ex);
|
||||
}
|
||||
}
|
||||
if (isTimeoutFailure) {
|
||||
try {
|
||||
for (Runnable runnable : timeoutTasks) {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
catch (Throwable ex2) {
|
||||
logger.error("Transport failed to run timeout tasks for " + DefaultTransportRequest.this, ex2);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
logger.error("Connect success/failure events already took place for " +
|
||||
DefaultTransportRequest.this + ". Ignoring this additional failure event.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -20,6 +20,7 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Enumeration;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
@@ -36,7 +37,6 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import org.springframework.util.concurrent.SettableListenableFuture;
|
||||
import org.springframework.web.client.HttpServerErrorException;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
@@ -110,7 +110,7 @@ public class JettyXhrTransport extends AbstractXhrTransport implements Lifecycle
|
||||
@Override
|
||||
protected void connectInternal(TransportRequest transportRequest, WebSocketHandler handler,
|
||||
URI url, HttpHeaders handshakeHeaders, XhrClientSockJsSession session,
|
||||
SettableListenableFuture<WebSocketSession> connectFuture) {
|
||||
CompletableFuture<WebSocketSession> connectFuture) {
|
||||
|
||||
HttpHeaders httpHeaders = transportRequest.getHttpRequestHeaders();
|
||||
SockJsResponseListener listener = new SockJsResponseListener(url, httpHeaders, session, connectFuture);
|
||||
@@ -197,12 +197,12 @@ public class JettyXhrTransport extends AbstractXhrTransport implements Lifecycle
|
||||
|
||||
private final XhrClientSockJsSession sockJsSession;
|
||||
|
||||
private final SettableListenableFuture<WebSocketSession> connectFuture;
|
||||
private final CompletableFuture<WebSocketSession> connectFuture;
|
||||
|
||||
private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
public SockJsResponseListener(URI url, HttpHeaders headers, XhrClientSockJsSession sockJsSession,
|
||||
SettableListenableFuture<WebSocketSession> connectFuture) {
|
||||
CompletableFuture<WebSocketSession> connectFuture) {
|
||||
|
||||
this.transportUrl = url;
|
||||
this.receiveHeaders = headers;
|
||||
@@ -273,7 +273,7 @@ public class JettyXhrTransport extends AbstractXhrTransport implements Lifecycle
|
||||
|
||||
@Override
|
||||
public void onFailure(Response response, Throwable failure) {
|
||||
if (this.connectFuture.setException(failure)) {
|
||||
if (this.connectFuture.completeExceptionally(failure)) {
|
||||
return;
|
||||
}
|
||||
if (this.sockJsSession.isDisconnected()) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -20,6 +20,7 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
@@ -34,7 +35,6 @@ import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import org.springframework.util.concurrent.SettableListenableFuture;
|
||||
import org.springframework.web.client.HttpServerErrorException;
|
||||
import org.springframework.web.client.RequestCallback;
|
||||
import org.springframework.web.client.ResponseExtractor;
|
||||
@@ -99,7 +99,7 @@ public class RestTemplateXhrTransport extends AbstractXhrTransport {
|
||||
@Override
|
||||
protected void connectInternal(final TransportRequest transportRequest, final WebSocketHandler handler,
|
||||
final URI receiveUrl, final HttpHeaders handshakeHeaders, final XhrClientSockJsSession session,
|
||||
final SettableListenableFuture<WebSocketSession> connectFuture) {
|
||||
final CompletableFuture<WebSocketSession> connectFuture) {
|
||||
|
||||
getTaskExecutor().execute(() -> {
|
||||
HttpHeaders httpHeaders = transportRequest.getHttpRequestHeaders();
|
||||
@@ -120,7 +120,7 @@ public class RestTemplateXhrTransport extends AbstractXhrTransport {
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (!connectFuture.isDone()) {
|
||||
connectFuture.setException(ex);
|
||||
connectFuture.completeExceptionally(ex);
|
||||
}
|
||||
else {
|
||||
session.handleTransportError(ex);
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
@@ -35,8 +36,6 @@ import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.concurrent.ListenableFuture;
|
||||
import org.springframework.util.concurrent.SettableListenableFuture;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.WebSocketHttpHeaders;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
@@ -229,16 +228,16 @@ public class SockJsClient implements WebSocketClient, Lifecycle {
|
||||
|
||||
|
||||
@Override
|
||||
public ListenableFuture<WebSocketSession> doHandshake(
|
||||
public CompletableFuture<WebSocketSession> execute(
|
||||
WebSocketHandler handler, String uriTemplate, Object... uriVars) {
|
||||
|
||||
Assert.notNull(uriTemplate, "uriTemplate must not be null");
|
||||
URI uri = UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand(uriVars).encode().toUri();
|
||||
return doHandshake(handler, null, uri);
|
||||
return execute(handler, null, uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ListenableFuture<WebSocketSession> doHandshake(
|
||||
public final CompletableFuture<WebSocketSession> execute(
|
||||
WebSocketHandler handler, @Nullable WebSocketHttpHeaders headers, URI url) {
|
||||
|
||||
Assert.notNull(handler, "WebSocketHandler is required");
|
||||
@@ -249,7 +248,7 @@ public class SockJsClient implements WebSocketClient, Lifecycle {
|
||||
throw new IllegalArgumentException("Invalid scheme: '" + scheme + "'");
|
||||
}
|
||||
|
||||
SettableListenableFuture<WebSocketSession> connectFuture = new SettableListenableFuture<>();
|
||||
CompletableFuture<WebSocketSession> connectFuture = new CompletableFuture<>();
|
||||
try {
|
||||
SockJsUrlInfo sockJsUrlInfo = new SockJsUrlInfo(url);
|
||||
ServerInfo serverInfo = getServerInfo(sockJsUrlInfo, getHttpRequestHeaders(headers));
|
||||
@@ -259,7 +258,7 @@ public class SockJsClient implements WebSocketClient, Lifecycle {
|
||||
if (logger.isErrorEnabled()) {
|
||||
logger.error("Initial SockJS \"Info\" request to server failed, url=" + url, exception);
|
||||
}
|
||||
connectFuture.setException(exception);
|
||||
connectFuture.completeExceptionally(exception);
|
||||
}
|
||||
return connectFuture;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -17,7 +17,9 @@
|
||||
package org.springframework.web.socket.sockjs.client;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.springframework.util.concurrent.CompletableToListenableFutureAdapter;
|
||||
import org.springframework.util.concurrent.ListenableFuture;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
@@ -43,7 +45,20 @@ public interface Transport {
|
||||
* @param request the transport request.
|
||||
* @param webSocketHandler the application handler to delegate lifecycle events to.
|
||||
* @return a future to indicate success or failure to connect.
|
||||
* @deprecated as of 6.0, in favor of {@link #connectAsync(TransportRequest, WebSocketHandler)}
|
||||
*/
|
||||
ListenableFuture<WebSocketSession> connect(TransportRequest request, WebSocketHandler webSocketHandler);
|
||||
@Deprecated
|
||||
default ListenableFuture<WebSocketSession> connect(TransportRequest request, WebSocketHandler webSocketHandler) {
|
||||
return new CompletableToListenableFutureAdapter<>(connectAsync(request, webSocketHandler));
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect the transport.
|
||||
* @param request the transport request.
|
||||
* @param webSocketHandler the application handler to delegate lifecycle events to.
|
||||
* @return a future to indicate success or failure to connect.
|
||||
* @since 6.0
|
||||
*/
|
||||
CompletableFuture<WebSocketSession> connectAsync(TransportRequest request, WebSocketHandler webSocketHandler);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -21,6 +21,7 @@ import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
@@ -55,7 +56,6 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.concurrent.SettableListenableFuture;
|
||||
import org.springframework.web.client.HttpServerErrorException;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
@@ -135,14 +135,14 @@ public class UndertowXhrTransport extends AbstractXhrTransport {
|
||||
@Override
|
||||
protected void connectInternal(TransportRequest request, WebSocketHandler handler, URI receiveUrl,
|
||||
HttpHeaders handshakeHeaders, XhrClientSockJsSession session,
|
||||
SettableListenableFuture<WebSocketSession> connectFuture) {
|
||||
CompletableFuture<WebSocketSession> connectFuture) {
|
||||
|
||||
executeReceiveRequest(request, receiveUrl, handshakeHeaders, session, connectFuture);
|
||||
}
|
||||
|
||||
private void executeReceiveRequest(final TransportRequest transportRequest,
|
||||
final URI url, final HttpHeaders headers, final XhrClientSockJsSession session,
|
||||
final SettableListenableFuture<WebSocketSession> connectFuture) {
|
||||
final CompletableFuture<WebSocketSession> connectFuture) {
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Starting XHR receive request for " + url);
|
||||
@@ -180,7 +180,7 @@ public class UndertowXhrTransport extends AbstractXhrTransport {
|
||||
|
||||
private ClientCallback<ClientExchange> createReceiveCallback(final TransportRequest transportRequest,
|
||||
final URI url, final HttpHeaders headers, final XhrClientSockJsSession sockJsSession,
|
||||
final SettableListenableFuture<WebSocketSession> connectFuture) {
|
||||
final CompletableFuture<WebSocketSession> connectFuture) {
|
||||
|
||||
return new ClientCallback<>() {
|
||||
@Override
|
||||
@@ -231,7 +231,7 @@ public class UndertowXhrTransport extends AbstractXhrTransport {
|
||||
}
|
||||
|
||||
private void onFailure(Throwable failure) {
|
||||
if (connectFuture.setException(failure)) {
|
||||
if (connectFuture.completeExceptionally(failure)) {
|
||||
return;
|
||||
}
|
||||
if (sockJsSession.isDisconnected()) {
|
||||
@@ -374,13 +374,13 @@ public class UndertowXhrTransport extends AbstractXhrTransport {
|
||||
|
||||
private final XhrClientSockJsSession session;
|
||||
|
||||
private final SettableListenableFuture<WebSocketSession> connectFuture;
|
||||
private final CompletableFuture<WebSocketSession> connectFuture;
|
||||
|
||||
private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
public SockJsResponseListener(TransportRequest request, ClientConnection connection, URI url,
|
||||
HttpHeaders headers, XhrClientSockJsSession sockJsSession,
|
||||
SettableListenableFuture<WebSocketSession> connectFuture) {
|
||||
CompletableFuture<WebSocketSession> connectFuture) {
|
||||
|
||||
this.request = request;
|
||||
this.connection = connection;
|
||||
@@ -462,7 +462,7 @@ public class UndertowXhrTransport extends AbstractXhrTransport {
|
||||
|
||||
public void onFailure(Throwable failure) {
|
||||
IoUtils.safeClose(this.connection);
|
||||
if (this.connectFuture.setException(failure)) {
|
||||
if (this.connectFuture.completeExceptionally(failure)) {
|
||||
return;
|
||||
}
|
||||
if (this.session.isDisconnected()) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -19,6 +19,7 @@ package org.springframework.web.socket.sockjs.client;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -42,13 +43,23 @@ public class WebSocketClientSockJsSession extends AbstractClientSockJsSession im
|
||||
@Nullable
|
||||
private WebSocketSession webSocketSession;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@code WebSocketClientSockJsSession}.
|
||||
* @deprecated as of 6.0, in favor of {@link #WebSocketClientSockJsSession(TransportRequest, WebSocketHandler, CompletableFuture)}
|
||||
*/
|
||||
@Deprecated
|
||||
public WebSocketClientSockJsSession(TransportRequest request, WebSocketHandler handler,
|
||||
SettableListenableFuture<WebSocketSession> connectFuture) {
|
||||
|
||||
super(request, handler, connectFuture);
|
||||
}
|
||||
|
||||
public WebSocketClientSockJsSession(TransportRequest request, WebSocketHandler handler,
|
||||
CompletableFuture<WebSocketSession> connectFuture) {
|
||||
|
||||
super(request, handler, connectFuture);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Object getNativeSession() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -19,17 +19,14 @@ package org.springframework.web.socket.sockjs.client;
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.concurrent.ListenableFuture;
|
||||
import org.springframework.util.concurrent.ListenableFutureCallback;
|
||||
import org.springframework.util.concurrent.SettableListenableFuture;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
@@ -73,9 +70,11 @@ public class WebSocketTransport implements Transport, Lifecycle {
|
||||
return Collections.singletonList(TransportType.WEBSOCKET);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ListenableFuture<WebSocketSession> connect(TransportRequest request, WebSocketHandler handler) {
|
||||
final SettableListenableFuture<WebSocketSession> future = new SettableListenableFuture<>();
|
||||
public CompletableFuture<WebSocketSession> connectAsync(TransportRequest request,
|
||||
WebSocketHandler handler) {
|
||||
CompletableFuture<WebSocketSession> future = new CompletableFuture<>();
|
||||
WebSocketClientSockJsSession session = new WebSocketClientSockJsSession(request, handler, future);
|
||||
handler = new ClientSockJsWebSocketHandler(session);
|
||||
request.addTimeoutTask(session.getTimeoutTask());
|
||||
@@ -85,21 +84,14 @@ public class WebSocketTransport implements Transport, Lifecycle {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Starting WebSocket session on " + url);
|
||||
}
|
||||
this.webSocketClient.doHandshake(handler, headers, url).addCallback(
|
||||
new ListenableFutureCallback<WebSocketSession>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable WebSocketSession webSocketSession) {
|
||||
// WebSocket session ready, SockJS Session not yet
|
||||
}
|
||||
@Override
|
||||
public void onFailure(Throwable ex) {
|
||||
future.setException(ex);
|
||||
}
|
||||
});
|
||||
this.webSocketClient.execute(handler, headers, url).whenComplete((webSocketSession, throwable) -> {
|
||||
if (throwable != null) {
|
||||
future.completeExceptionally(throwable);
|
||||
}
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
if (!isRunning()) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -20,6 +20,7 @@ import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -53,7 +54,11 @@ public class XhrClientSockJsSession extends AbstractClientSockJsSession {
|
||||
|
||||
private int binaryMessageSizeLimit = -1;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@code XhrClientSockJsSession}.
|
||||
* @deprecated as of 6.0, in favor of {@link #XhrClientSockJsSession(TransportRequest, WebSocketHandler, XhrTransport, CompletableFuture)}
|
||||
*/
|
||||
@Deprecated
|
||||
public XhrClientSockJsSession(TransportRequest request, WebSocketHandler handler,
|
||||
XhrTransport transport, SettableListenableFuture<WebSocketSession> connectFuture) {
|
||||
|
||||
@@ -67,6 +72,19 @@ public class XhrClientSockJsSession extends AbstractClientSockJsSession {
|
||||
this.sendUrl = request.getSockJsUrlInfo().getTransportUrl(TransportType.XHR_SEND);
|
||||
}
|
||||
|
||||
public XhrClientSockJsSession(TransportRequest request, WebSocketHandler handler,
|
||||
XhrTransport transport, CompletableFuture<WebSocketSession> connectFuture) {
|
||||
|
||||
super(request, handler, connectFuture);
|
||||
Assert.notNull(transport, "XhrTransport is required");
|
||||
this.transport = transport;
|
||||
this.headers = request.getHttpRequestHeaders();
|
||||
this.sendHeaders = new HttpHeaders();
|
||||
this.sendHeaders.putAll(this.headers);
|
||||
this.sendHeaders.setContentType(MediaType.APPLICATION_JSON);
|
||||
this.sendUrl = request.getSockJsUrlInfo().getTransportUrl(TransportType.XHR_SEND);
|
||||
}
|
||||
|
||||
|
||||
public HttpHeaders getHeaders() {
|
||||
return this.headers;
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
@@ -35,7 +36,6 @@ import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.util.concurrent.ListenableFuture;
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||
import org.springframework.web.socket.client.WebSocketClient;
|
||||
import org.springframework.web.socket.client.jetty.JettyWebSocketClient;
|
||||
@@ -150,8 +150,8 @@ public abstract class AbstractWebSocketIntegrationTests {
|
||||
return "ws://localhost:" + this.server.getPort();
|
||||
}
|
||||
|
||||
protected ListenableFuture<WebSocketSession> doHandshake(WebSocketHandler clientHandler, String endpointPath) {
|
||||
return this.webSocketClient.doHandshake(clientHandler, getWsBaseUrl() + endpointPath);
|
||||
protected CompletableFuture<WebSocketSession> execute(WebSocketHandler clientHandler, String endpointPath) {
|
||||
return this.webSocketClient.execute(clientHandler, getWsBaseUrl() + endpointPath);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -19,13 +19,12 @@ package org.springframework.web.socket.client;
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.util.concurrent.ListenableFuture;
|
||||
import org.springframework.util.concurrent.ListenableFutureTask;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.WebSocketHttpHeaders;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
@@ -112,21 +111,21 @@ public class WebSocketConnectionManagerTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<WebSocketSession> doHandshake(WebSocketHandler handler,
|
||||
public CompletableFuture<WebSocketSession> execute(WebSocketHandler handler,
|
||||
String uriTemplate, Object... uriVars) {
|
||||
|
||||
URI uri = UriComponentsBuilder.fromUriString(uriTemplate).buildAndExpand(uriVars).encode().toUri();
|
||||
return doHandshake(handler, null, uri);
|
||||
return execute(handler, null, uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<WebSocketSession> doHandshake(WebSocketHandler handler,
|
||||
public CompletableFuture<WebSocketSession> execute(WebSocketHandler handler,
|
||||
WebSocketHttpHeaders headers, URI uri) {
|
||||
|
||||
this.webSocketHandler = handler;
|
||||
this.headers = headers;
|
||||
this.uri = uri;
|
||||
return new ListenableFutureTask<>(() -> null);
|
||||
return CompletableFuture.supplyAsync(() -> null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -79,7 +79,7 @@ class StompWebSocketIntegrationTests extends AbstractWebSocketIntegrationTests {
|
||||
|
||||
TextMessage message = create(StompCommand.SEND).headers("destination:/app/simple").build();
|
||||
|
||||
try (WebSocketSession session = doHandshake(new TestClientWebSocketHandler(0, message), "/ws").get()) {
|
||||
try (WebSocketSession session = execute(new TestClientWebSocketHandler(0, message), "/ws").get()) {
|
||||
assertThat(session).isNotNull();
|
||||
SimpleController controller = this.wac.getBean(SimpleController.class);
|
||||
assertThat(controller.latch.await(TIMEOUT, TimeUnit.SECONDS)).isTrue();
|
||||
@@ -98,7 +98,7 @@ class StompWebSocketIntegrationTests extends AbstractWebSocketIntegrationTests {
|
||||
|
||||
TestClientWebSocketHandler clientHandler = new TestClientWebSocketHandler(2, m0, m1, m2);
|
||||
|
||||
try (WebSocketSession session = doHandshake(clientHandler, "/ws").get()) {
|
||||
try (WebSocketSession session = execute(clientHandler, "/ws").get()) {
|
||||
assertThat(session).isNotNull();
|
||||
assertThat(clientHandler.latch.await(TIMEOUT, TimeUnit.SECONDS)).isTrue();
|
||||
}
|
||||
@@ -114,7 +114,7 @@ class StompWebSocketIntegrationTests extends AbstractWebSocketIntegrationTests {
|
||||
|
||||
TestClientWebSocketHandler clientHandler = new TestClientWebSocketHandler(2, m0, m1, m2);
|
||||
|
||||
try (WebSocketSession session = doHandshake(clientHandler, "/ws").get()) {
|
||||
try (WebSocketSession session = execute(clientHandler, "/ws").get()) {
|
||||
assertThat(session).isNotNull();
|
||||
assertThat(clientHandler.latch.await(TIMEOUT, TimeUnit.SECONDS)).isTrue();
|
||||
|
||||
@@ -133,7 +133,7 @@ class StompWebSocketIntegrationTests extends AbstractWebSocketIntegrationTests {
|
||||
|
||||
TestClientWebSocketHandler clientHandler = new TestClientWebSocketHandler(2, m0, m1);
|
||||
|
||||
try (WebSocketSession session = doHandshake(clientHandler, "/ws").get()) {
|
||||
try (WebSocketSession session = execute(clientHandler, "/ws").get()) {
|
||||
assertThat(session).isNotNull();
|
||||
assertThat(clientHandler.latch.await(TIMEOUT, TimeUnit.SECONDS)).isTrue();
|
||||
String payload = clientHandler.actual.get(1).getPayload();
|
||||
@@ -153,7 +153,7 @@ class StompWebSocketIntegrationTests extends AbstractWebSocketIntegrationTests {
|
||||
|
||||
TestClientWebSocketHandler clientHandler = new TestClientWebSocketHandler(2, m0, m1, m2);
|
||||
|
||||
try (WebSocketSession session = doHandshake(clientHandler, "/ws").get()) {
|
||||
try (WebSocketSession session = execute(clientHandler, "/ws").get()) {
|
||||
assertThat(session).isNotNull();
|
||||
assertThat(clientHandler.latch.await(TIMEOUT, TimeUnit.SECONDS)).isTrue();
|
||||
String payload = clientHandler.actual.get(1).getPayload();
|
||||
@@ -175,7 +175,7 @@ class StompWebSocketIntegrationTests extends AbstractWebSocketIntegrationTests {
|
||||
|
||||
TestClientWebSocketHandler clientHandler = new TestClientWebSocketHandler(2, m0, m1, m2);
|
||||
|
||||
try (WebSocketSession session = doHandshake(clientHandler, "/ws").get()) {
|
||||
try (WebSocketSession session = execute(clientHandler, "/ws").get()) {
|
||||
assertThat(session).isNotNull();
|
||||
assertThat(clientHandler.latch.await(TIMEOUT, TimeUnit.SECONDS)).isTrue();
|
||||
String payload = clientHandler.actual.get(1).getPayload();
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.springframework.web.socket.messaging;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -39,7 +40,6 @@ import org.springframework.messaging.support.MessageHeaderAccessor;
|
||||
import org.springframework.messaging.tcp.TcpConnection;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
import org.springframework.util.concurrent.SettableListenableFuture;
|
||||
import org.springframework.web.socket.BinaryMessage;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.PongMessage;
|
||||
@@ -82,7 +82,7 @@ public class WebSocketStompClientTests {
|
||||
|
||||
private ArgumentCaptor<WebSocketHandler> webSocketHandlerCaptor;
|
||||
|
||||
private SettableListenableFuture<WebSocketSession> handshakeFuture;
|
||||
private CompletableFuture<WebSocketSession> handshakeFuture;
|
||||
|
||||
|
||||
@BeforeEach
|
||||
@@ -93,8 +93,8 @@ public class WebSocketStompClientTests {
|
||||
this.stompClient.setStompSession(this.stompSession);
|
||||
|
||||
this.webSocketHandlerCaptor = ArgumentCaptor.forClass(WebSocketHandler.class);
|
||||
this.handshakeFuture = new SettableListenableFuture<>();
|
||||
given(webSocketClient.doHandshake(this.webSocketHandlerCaptor.capture(), any(), any(URI.class)))
|
||||
this.handshakeFuture = new CompletableFuture<>();
|
||||
given(webSocketClient.execute(this.webSocketHandlerCaptor.capture(), any(), any(URI.class)))
|
||||
.willReturn(this.handshakeFuture);
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ public class WebSocketStompClientTests {
|
||||
connect();
|
||||
|
||||
IllegalStateException handshakeFailure = new IllegalStateException("simulated exception");
|
||||
this.handshakeFuture.setException(handshakeFailure);
|
||||
this.handshakeFuture.completeExceptionally(handshakeFailure);
|
||||
|
||||
verify(this.stompSession).afterConnectFailure(same(handshakeFailure));
|
||||
}
|
||||
@@ -202,7 +202,7 @@ public class WebSocketStompClientTests {
|
||||
accessor.setDestination("/topic/foo");
|
||||
byte[] payload = "payload".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
getTcpConnection().send(MessageBuilder.createMessage(payload, accessor.getMessageHeaders()));
|
||||
getTcpConnection().sendAsync(MessageBuilder.createMessage(payload, accessor.getMessageHeaders()));
|
||||
|
||||
ArgumentCaptor<TextMessage> textMessageCaptor = ArgumentCaptor.forClass(TextMessage.class);
|
||||
verify(this.webSocketSession).sendMessage(textMessageCaptor.capture());
|
||||
@@ -218,7 +218,7 @@ public class WebSocketStompClientTests {
|
||||
accessor.setContentType(MimeTypeUtils.APPLICATION_OCTET_STREAM);
|
||||
byte[] payload = "payload".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
getTcpConnection().send(MessageBuilder.createMessage(payload, accessor.getMessageHeaders()));
|
||||
getTcpConnection().sendAsync(MessageBuilder.createMessage(payload, accessor.getMessageHeaders()));
|
||||
|
||||
ArgumentCaptor<BinaryMessage> binaryMessageCaptor = ArgumentCaptor.forClass(BinaryMessage.class);
|
||||
verify(this.webSocketSession).sendMessage(binaryMessageCaptor.capture());
|
||||
@@ -309,9 +309,9 @@ public class WebSocketStompClientTests {
|
||||
|
||||
|
||||
private WebSocketHandler connect() {
|
||||
this.stompClient.connect("/foo", mock(StompSessionHandler.class));
|
||||
this.stompClient.connectAsync("/foo", mock(StompSessionHandler.class));
|
||||
|
||||
verify(this.stompSession).getSessionFuture();
|
||||
verify(this.stompSession).getSession();
|
||||
verifyNoMoreInteractions(this.stompSession);
|
||||
|
||||
WebSocketHandler webSocketHandler = this.webSocketHandlerCaptor.getValue();
|
||||
|
||||
@@ -20,11 +20,11 @@ import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.util.concurrent.SettableListenableFuture;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
import org.springframework.web.socket.WebSocketExtension;
|
||||
@@ -56,7 +56,7 @@ public class ClientSockJsSessionTests {
|
||||
|
||||
private WebSocketHandler handler;
|
||||
|
||||
private SettableListenableFuture<WebSocketSession> connectFuture;
|
||||
private CompletableFuture<WebSocketSession> connectFuture;
|
||||
|
||||
|
||||
@BeforeEach
|
||||
@@ -65,7 +65,7 @@ public class ClientSockJsSessionTests {
|
||||
Transport transport = mock(Transport.class);
|
||||
TransportRequest request = new DefaultTransportRequest(urlInfo, null, null, transport, TransportType.XHR, CODEC);
|
||||
this.handler = mock(WebSocketHandler.class);
|
||||
this.connectFuture = new SettableListenableFuture<>();
|
||||
this.connectFuture = new CompletableFuture<>();
|
||||
this.session = new TestClientSockJsSession(request, this.handler, this.connectFuture);
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ public class ClientSockJsSessionTests {
|
||||
|
||||
|
||||
protected TestClientSockJsSession(TransportRequest request, WebSocketHandler handler,
|
||||
SettableListenableFuture<WebSocketSession> connectFuture) {
|
||||
CompletableFuture<WebSocketSession> connectFuture) {
|
||||
super(request, handler, connectFuture);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,9 @@ package org.springframework.web.socket.sockjs.client;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -27,8 +29,6 @@ import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.util.concurrent.ListenableFutureCallback;
|
||||
import org.springframework.util.concurrent.SettableListenableFuture;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.sockjs.frame.Jackson2SockJsMessageCodec;
|
||||
import org.springframework.web.socket.sockjs.transport.TransportType;
|
||||
@@ -50,9 +50,9 @@ public class DefaultTransportRequestTests {
|
||||
private static final Jackson2SockJsMessageCodec CODEC = new Jackson2SockJsMessageCodec();
|
||||
|
||||
|
||||
private SettableListenableFuture<WebSocketSession> connectFuture;
|
||||
private CompletableFuture<WebSocketSession> connectFuture;
|
||||
|
||||
private ListenableFutureCallback<WebSocketSession> connectCallback;
|
||||
private BiConsumer<WebSocketSession, Throwable> connectCallback;
|
||||
|
||||
private TestTransport webSocketTransport;
|
||||
|
||||
@@ -62,9 +62,9 @@ public class DefaultTransportRequestTests {
|
||||
@SuppressWarnings("unchecked")
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
this.connectCallback = mock(ListenableFutureCallback.class);
|
||||
this.connectFuture = new SettableListenableFuture<>();
|
||||
this.connectFuture.addCallback(this.connectCallback);
|
||||
this.connectCallback = mock(BiConsumer.class);
|
||||
this.connectFuture = new CompletableFuture<>();
|
||||
this.connectFuture.whenComplete(this.connectCallback);
|
||||
this.webSocketTransport = new TestTransport("WebSocketTestTransport");
|
||||
this.xhrTransport = new TestTransport("XhrTestTransport");
|
||||
}
|
||||
@@ -75,7 +75,7 @@ public class DefaultTransportRequestTests {
|
||||
DefaultTransportRequest request = createTransportRequest(this.webSocketTransport, TransportType.WEBSOCKET);
|
||||
request.connect(null, this.connectFuture);
|
||||
WebSocketSession session = mock(WebSocketSession.class);
|
||||
this.webSocketTransport.getConnectCallback().onSuccess(session);
|
||||
this.webSocketTransport.getConnectCallback().accept(session, null);
|
||||
assertThat(this.connectFuture.get()).isSameAs(session);
|
||||
}
|
||||
|
||||
@@ -87,12 +87,12 @@ public class DefaultTransportRequestTests {
|
||||
request1.connect(null, this.connectFuture);
|
||||
|
||||
// Transport error => fallback
|
||||
this.webSocketTransport.getConnectCallback().onFailure(new IOException("Fake exception 1"));
|
||||
this.webSocketTransport.getConnectCallback().accept(null, new IOException("Fake exception 1"));
|
||||
assertThat(this.connectFuture.isDone()).isFalse();
|
||||
assertThat(this.xhrTransport.invoked()).isTrue();
|
||||
|
||||
// Transport error => no more fallback
|
||||
this.xhrTransport.getConnectCallback().onFailure(new IOException("Fake exception 2"));
|
||||
this.xhrTransport.getConnectCallback().accept(null, new IOException("Fake exception 2"));
|
||||
assertThat(this.connectFuture.isDone()).isTrue();
|
||||
assertThatExceptionOfType(ExecutionException.class).isThrownBy(
|
||||
this.connectFuture::get)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -87,7 +87,7 @@ public class SockJsClientTests {
|
||||
this.sockJsClient.doHandshake(handler, URL).addCallback(this.connectCallback);
|
||||
assertThat(this.webSocketTransport.invoked()).isTrue();
|
||||
WebSocketSession session = mock(WebSocketSession.class);
|
||||
this.webSocketTransport.getConnectCallback().onSuccess(session);
|
||||
this.webSocketTransport.getConnectCallback().accept(session, null);
|
||||
verify(this.connectCallback).onSuccess(session);
|
||||
verifyNoMoreInteractions(this.connectCallback);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -20,12 +20,13 @@ import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.util.concurrent.ListenableFuture;
|
||||
import org.springframework.util.concurrent.ListenableFutureCallback;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
@@ -46,7 +47,8 @@ class TestTransport implements Transport {
|
||||
|
||||
private TransportRequest request;
|
||||
|
||||
private ListenableFuture future;
|
||||
@Nullable
|
||||
private CompletableFuture<WebSocketSession> future;
|
||||
|
||||
|
||||
public TestTransport(String name) {
|
||||
@@ -67,17 +69,17 @@ class TestTransport implements Transport {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public ListenableFutureCallback<WebSocketSession> getConnectCallback() {
|
||||
ArgumentCaptor<ListenableFutureCallback> captor = ArgumentCaptor.forClass(ListenableFutureCallback.class);
|
||||
verify(this.future).addCallback(captor.capture());
|
||||
public BiConsumer<WebSocketSession, Throwable> getConnectCallback() {
|
||||
ArgumentCaptor<BiConsumer> captor = ArgumentCaptor.forClass(BiConsumer.class);
|
||||
verify(this.future).whenComplete(captor.capture());
|
||||
return captor.getValue();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public ListenableFuture<WebSocketSession> connect(TransportRequest request, WebSocketHandler handler) {
|
||||
public CompletableFuture<WebSocketSession> connectAsync(TransportRequest request, WebSocketHandler handler) {
|
||||
this.request = request;
|
||||
this.future = mock(ListenableFuture.class);
|
||||
this.future = mock(CompletableFuture.class);
|
||||
return this.future;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.web.socket.sockjs.client;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
@@ -25,7 +26,6 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.concurrent.SettableListenableFuture;
|
||||
import org.springframework.web.client.HttpServerErrorException;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
@@ -142,7 +142,7 @@ public class XhrTransportTests {
|
||||
@Override
|
||||
protected void connectInternal(TransportRequest request, WebSocketHandler handler, URI receiveUrl,
|
||||
HttpHeaders handshakeHeaders, XhrClientSockJsSession session,
|
||||
SettableListenableFuture<WebSocketSession> connectFuture) {
|
||||
CompletableFuture<WebSocketSession> connectFuture) {
|
||||
|
||||
this.actualHandshakeHeaders = handshakeHeaders;
|
||||
this.actualSession = session;
|
||||
|
||||
Reference in New Issue
Block a user