diff --git a/README.md b/README.md index bbe79a84..58219203 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ The pusher-java-client is available in Maven Central. com.pusher pusher-java-client - 1.7.0 + 1.8.0 ``` @@ -60,7 +60,7 @@ The pusher-java-client is available in Maven Central. ```groovy dependencies { - compile 'com.pusher:pusher-java-client:1.7.0' + compile 'com.pusher:pusher-java-client:1.8.0' } ``` diff --git a/build.gradle b/build.gradle index c8e317c2..95c38923 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ apply plugin: 'org.ajoberstar.github-pages' apply plugin: 'signing' group = "com.pusher" -version = "1.7.1-SNAPSHOT" +version = "1.8.1-SNAPSHOT" sourceCompatibility = "1.6" targetCompatibility = "1.6" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 45a4d68f..1b47c1b9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ -distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-bin.zip +#Wed Feb 21 14:42:32 GMT 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip diff --git a/src/main/java/com/pusher/client/PusherOptions.java b/src/main/java/com/pusher/client/PusherOptions.java index f9b92f0d..77b525e2 100644 --- a/src/main/java/com/pusher/client/PusherOptions.java +++ b/src/main/java/com/pusher/client/PusherOptions.java @@ -25,6 +25,9 @@ public class PusherOptions { private static final long DEFAULT_ACTIVITY_TIMEOUT = 120000; private static final long DEFAULT_PONG_TIMEOUT = 30000; + private static final int MAX_RECONNECTION_ATTEMPTS = 6; //Taken from the Swift lib + private static final int MAX_RECONNECT_GAP_IN_SECONDS = 30; + // Note that the primary cluster lives on a different domain // (others are subdomains of pusher.com). This is not an oversight. // Legacy reasons. @@ -36,6 +39,8 @@ public class PusherOptions { private long pongTimeout = DEFAULT_PONG_TIMEOUT; private Authorizer authorizer; private Proxy proxy = Proxy.NO_PROXY; + private int maxReconnectionAttempts = MAX_RECONNECTION_ATTEMPTS; + private int maxReconnectGapInSeconds = MAX_RECONNECT_GAP_IN_SECONDS; /** * Gets whether an encrypted (SSL) connection should be used when connecting @@ -182,7 +187,30 @@ public PusherOptions setPongTimeout(final long pongTimeout) { return this; } - public long getPongTimeout() { + /** + * Number of reconnect attempts when websocket connection failed + * @param maxReconnectionAttempts + * number of max reconnection attempts, default = {@link #MAX_RECONNECTION_ATTEMPTS} 6 + * @return this, for chaining + */ + public PusherOptions setMaxReconnectionAttempts(int maxReconnectionAttempts) { + this.maxReconnectionAttempts = maxReconnectionAttempts; + return this; + } + + /** + * The delay in two reconnection extends exponentially (1, 2, 4, .. seconds) This property sets the maximum in between two + * reconnection attempts. + * @param maxReconnectGapInSeconds + * time in seconds of the maximum gab between two reconnection attempts, default = {@link #MAX_RECONNECT_GAP_IN_SECONDS} 30s + * @return this, for chaining + */ + public PusherOptions setMaxReconnectGapInSeconds(int maxReconnectGapInSeconds) { + this.maxReconnectGapInSeconds = maxReconnectGapInSeconds; + return this; + } + + public long getPongTimeout() { return pongTimeout; } @@ -221,7 +249,21 @@ public Proxy getProxy() { return this.proxy; } - private static String readVersionFromProperties() { + /** + * @return the maximum reconnection attempts + */ + public int getMaxReconnectionAttempts() { + return maxReconnectionAttempts; + } + + /** + * @return the maximum reconnection gap in seconds + */ + public int getMaxReconnectGapInSeconds() { + return maxReconnectGapInSeconds; + } + + private static String readVersionFromProperties() { InputStream inStream = null; try { final Properties p = new Properties(); diff --git a/src/main/java/com/pusher/client/connection/websocket/WebSocketConnection.java b/src/main/java/com/pusher/client/connection/websocket/WebSocketConnection.java index b7db6450..199c249f 100644 --- a/src/main/java/com/pusher/client/connection/websocket/WebSocketConnection.java +++ b/src/main/java/com/pusher/client/connection/websocket/WebSocketConnection.java @@ -30,14 +30,14 @@ public class WebSocketConnection implements InternalConnection, WebSocketListene private static final String INTERNAL_EVENT_PREFIX = "pusher:"; private static final String PING_EVENT_SERIALIZED = "{\"event\": \"pusher:ping\"}"; - private static final int MAX_RECONNECTION_ATTEMPTS = 6; //Taken from the Swift lib - private static final int MAX_RECONNECT_GAP_IN_SECONDS = 30; private final Factory factory; private final ActivityTimer activityTimer; private final Map> eventListeners = new ConcurrentHashMap>(); private final URI webSocketUri; private final Proxy proxy; + private final int maxReconnectionAttempts; + private final int maxReconnectionGap; private volatile ConnectionState state = ConnectionState.DISCONNECTED; private WebSocketClientWrapper underlyingConnection; @@ -49,10 +49,14 @@ public WebSocketConnection( final String url, final long activityTimeout, final long pongTimeout, + int maxReconnectionAttempts, + int maxReconnectionGap, final Proxy proxy, final Factory factory) throws URISyntaxException { webSocketUri = new URI(url); activityTimer = new ActivityTimer(activityTimeout, pongTimeout); + this.maxReconnectionAttempts = maxReconnectionAttempts; + this.maxReconnectionGap = maxReconnectionGap; this.proxy = proxy; this.factory = factory; @@ -270,7 +274,7 @@ public void onClose(final int code, final String reason, final boolean remote) { //Reconnection logic if(state == ConnectionState.CONNECTED || state == ConnectionState.CONNECTING){ - if(reconnectAttempts < MAX_RECONNECTION_ATTEMPTS){ + if(reconnectAttempts < maxReconnectionAttempts){ tryReconnecting(); } else{ @@ -288,7 +292,7 @@ public void onClose(final int code, final String reason, final boolean remote) { private void tryReconnecting() { reconnectAttempts++; updateState(ConnectionState.RECONNECTING); - long reconnectInterval = Math.min(MAX_RECONNECT_GAP_IN_SECONDS, reconnectAttempts * reconnectAttempts); + long reconnectInterval = Math.min(maxReconnectionGap, reconnectAttempts * reconnectAttempts); factory.getTimers().schedule(new Runnable() { @Override diff --git a/src/main/java/com/pusher/client/util/Factory.java b/src/main/java/com/pusher/client/util/Factory.java index ed93424b..b7622c78 100644 --- a/src/main/java/com/pusher/client/util/Factory.java +++ b/src/main/java/com/pusher/client/util/Factory.java @@ -50,8 +50,14 @@ public class Factory { public synchronized InternalConnection getConnection(final String apiKey, final PusherOptions options) { if (connection == null) { try { - connection = new WebSocketConnection(options.buildUrl(apiKey), options.getActivityTimeout(), - options.getPongTimeout(), options.getProxy(), this); + connection = new WebSocketConnection( + options.buildUrl(apiKey), + options.getActivityTimeout(), + options.getPongTimeout(), + options.getMaxReconnectionAttempts(), + options.getMaxReconnectGapInSeconds(), + options.getProxy(), + this); } catch (final URISyntaxException e) { throw new IllegalArgumentException("Failed to initialise connection", e); diff --git a/src/test/java/com/pusher/client/EndToEndTest.java b/src/test/java/com/pusher/client/EndToEndTest.java index cb1be7f1..fd042875 100644 --- a/src/test/java/com/pusher/client/EndToEndTest.java +++ b/src/test/java/com/pusher/client/EndToEndTest.java @@ -1,12 +1,17 @@ package com.pusher.client; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import java.net.Proxy; import java.net.URI; -import com.pusher.java_websocket.handshake.ServerHandshake; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -26,6 +31,7 @@ import com.pusher.client.connection.websocket.WebSocketListener; import com.pusher.client.util.DoNothingExecutor; import com.pusher.client.util.Factory; +import com.pusher.java_websocket.handshake.ServerHandshake; @RunWith(MockitoJUnitRunner.class) public class EndToEndTest { @@ -38,6 +44,7 @@ public class EndToEndTest { + PRIVATE_CHANNEL_NAME + "\",\"auth\":\"" + AUTH_KEY + "\"}}"; private static final long ACTIVITY_TIMEOUT = 120000; private static final long PONG_TIMEOUT = 120000; + private static final Proxy proxy = Proxy.NO_PROXY; private @Mock Authorizer mockAuthorizer; @@ -53,7 +60,8 @@ public class EndToEndTest { public void setUp() throws Exception { pusherOptions = new PusherOptions().setAuthorizer(mockAuthorizer).setEncrypted(false); - connection = new WebSocketConnection(pusherOptions.buildUrl(API_KEY), ACTIVITY_TIMEOUT, PONG_TIMEOUT, proxy, factory); + connection = new WebSocketConnection(pusherOptions.buildUrl(API_KEY), ACTIVITY_TIMEOUT, PONG_TIMEOUT, pusherOptions.getMaxReconnectionAttempts(), + pusherOptions.getMaxReconnectGapInSeconds(), proxy, factory); doAnswer(new Answer() { @Override diff --git a/src/test/java/com/pusher/client/connection/websocket/WebSocketConnectionTest.java b/src/test/java/com/pusher/client/connection/websocket/WebSocketConnectionTest.java index cf7d6c50..ca7869fb 100644 --- a/src/test/java/com/pusher/client/connection/websocket/WebSocketConnectionTest.java +++ b/src/test/java/com/pusher/client/connection/websocket/WebSocketConnectionTest.java @@ -32,6 +32,8 @@ public class WebSocketConnectionTest { private static final long ACTIVITY_TIMEOUT = 500; private static final long PONG_TIMEOUT = 500; + private static final int MAX_RECONNECTIONS = 6; + private static final int MAX_GAP = 30; private static final String URL = "ws://ws.example.com/"; private static final String EVENT_NAME = "my-event"; private static final String CONN_ESTABLISHED_EVENT = "{\"event\":\"pusher:connection_established\",\"data\":\"{\\\"socket_id\\\":\\\"21112.816204\\\"}\"}"; @@ -65,14 +67,15 @@ public Object answer(InvocationOnMock invocation) throws Throwable { }).when(factory).queueOnEventThread(any(Runnable.class)); when(factory.getTimers()).thenReturn(new DoNothingExecutor()); - connection = new WebSocketConnection(URL, ACTIVITY_TIMEOUT, PONG_TIMEOUT, PROXY, factory); + connection = new WebSocketConnection(URL, ACTIVITY_TIMEOUT, PONG_TIMEOUT, MAX_RECONNECTIONS, MAX_GAP, PROXY, factory); connection.bind(ConnectionState.ALL, mockEventListener); } @Test public void testUnbindingWhenNotAlreadyBoundReturnsFalse() throws URISyntaxException { final ConnectionEventListener listener = mock(ConnectionEventListener.class); - final WebSocketConnection connection = new WebSocketConnection(URL, ACTIVITY_TIMEOUT, PONG_TIMEOUT, PROXY, factory); + final WebSocketConnection connection = new WebSocketConnection(URL, ACTIVITY_TIMEOUT, PONG_TIMEOUT, MAX_RECONNECTIONS, MAX_GAP, + PROXY, factory); final boolean unbound = connection.unbind(ConnectionState.ALL, listener); assertEquals(false, unbound); } @@ -80,7 +83,8 @@ public void testUnbindingWhenNotAlreadyBoundReturnsFalse() throws URISyntaxExcep @Test public void testUnbindingWhenBoundReturnsTrue() throws URISyntaxException { final ConnectionEventListener listener = mock(ConnectionEventListener.class); - final WebSocketConnection connection = new WebSocketConnection(URL, ACTIVITY_TIMEOUT, PONG_TIMEOUT, PROXY, factory); + final WebSocketConnection connection = new WebSocketConnection(URL, ACTIVITY_TIMEOUT, PONG_TIMEOUT, MAX_RECONNECTIONS, MAX_GAP, + PROXY, factory); connection.bind(ConnectionState.ALL, listener); @@ -118,7 +122,8 @@ public void testConnectDoesNotCallConnectOnUnderlyingConnectionIfAlreadyInConnec @Test public void testListenerDoesNotReceiveConnectingEventIfItIsOnlyBoundToTheConnectedEvent() throws URISyntaxException { - connection = new WebSocketConnection(URL, ACTIVITY_TIMEOUT, PONG_TIMEOUT, PROXY, factory); + connection = new WebSocketConnection(URL, ACTIVITY_TIMEOUT, PONG_TIMEOUT, MAX_RECONNECTIONS, MAX_GAP, + PROXY, factory); connection.bind(ConnectionState.CONNECTED, mockEventListener); connection.connect(); @@ -219,7 +224,8 @@ public void testOnCloseCallbackUpdatesStateToDisconnectedWhenPreviousStateIsDisc @Test public void testOnCloseCallbackDoesNotCallListenerIfItIsNotBoundToDisconnectedEvent() throws URISyntaxException { - connection = new WebSocketConnection(URL, ACTIVITY_TIMEOUT, PONG_TIMEOUT, PROXY, factory); + connection = new WebSocketConnection(URL, ACTIVITY_TIMEOUT, PONG_TIMEOUT, MAX_RECONNECTIONS, MAX_GAP, + PROXY, factory); connection.bind(ConnectionState.CONNECTED, mockEventListener); connection.connect();