Skip to content

Commit 504b9db

Browse files
authored
Merge 0c96b00 into 5bba4bc
2 parents 5bba4bc + 0c96b00 commit 504b9db

File tree

3 files changed

+116
-33
lines changed

3 files changed

+116
-33
lines changed

firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigAutoFetch.java

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -97,16 +97,9 @@ private String parseAndValidateConfigUpdateMessage(String message) {
9797
public void listenForNotifications() {
9898
if (httpURLConnection != null) {
9999
try {
100-
int responseCode = httpURLConnection.getResponseCode();
101-
if (responseCode == 200) {
102-
InputStream inputStream = httpURLConnection.getInputStream();
103-
handleNotifications(inputStream);
104-
inputStream.close();
105-
} else {
106-
propagateErrors(
107-
new FirebaseRemoteConfigRealtimeUpdateStreamException(
108-
"Http connection responded with error: " + responseCode));
109-
}
100+
InputStream inputStream = httpURLConnection.getInputStream();
101+
handleNotifications(inputStream);
102+
inputStream.close();
110103
} catch (IOException ex) {
111104
propagateErrors(
112105
new FirebaseRemoteConfigRealtimeUpdateFetchException(

firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigRealtimeHttpClient.java

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import static com.google.firebase.remoteconfig.FirebaseRemoteConfig.TAG;
1818
import static com.google.firebase.remoteconfig.RemoteConfigConstants.REALTIME_REGEX_URL;
19+
import static com.google.firebase.remoteconfig.internal.ConfigFetchHandler.HTTP_TOO_MANY_REQUESTS;
1920

2021
import android.annotation.SuppressLint;
2122
import android.content.Context;
@@ -236,7 +237,9 @@ private URL getUrl() {
236237
return realtimeURL;
237238
}
238239

239-
private HttpURLConnection createRealtimeConnection() throws IOException {
240+
/** Create HTTP connection and set headers. */
241+
@SuppressLint("VisibleForTests")
242+
public HttpURLConnection createRealtimeConnection() throws IOException {
240243
URL realtimeUrl = getUrl();
241244
HttpURLConnection httpURLConnection = (HttpURLConnection) realtimeUrl.openConnection();
242245
setCommonRequestHeaders(httpURLConnection);
@@ -245,8 +248,9 @@ private HttpURLConnection createRealtimeConnection() throws IOException {
245248
return httpURLConnection;
246249
}
247250

248-
// Try to reopen HTTP connection after a random amount of time
249-
private synchronized void retryHTTPConnection() {
251+
/** Retries HTTP stream connection asyncly in random time intervals. */
252+
@SuppressLint("VisibleForTests")
253+
public synchronized void retryHTTPConnection() {
250254
if (canMakeHttpStreamConnection() && httpRetriesRemaining > 0) {
251255
if (httpRetriesRemaining < ORIGINAL_RETRIES) {
252256
httpRetrySeconds *= getRetryMultiplier();
@@ -273,7 +277,12 @@ synchronized void stopRealtime() {
273277
scheduledExecutorService.shutdownNow();
274278
}
275279

276-
private synchronized ConfigAutoFetch startAutoFetch(HttpURLConnection httpURLConnection) {
280+
/**
281+
* Create Autofetch class that listens on HTTP stream for ConfigUpdate messages and calls Fetch
282+
* accordingly.
283+
*/
284+
@SuppressLint("VisibleForTests")
285+
public synchronized ConfigAutoFetch startAutoFetch(HttpURLConnection httpURLConnection) {
277286
ConfigUpdateListener retryCallback =
278287
new ConfigUpdateListener() {
279288
@Override
@@ -298,6 +307,15 @@ public void onError(Exception error) {
298307
return new ConfigAutoFetch(httpURLConnection, configFetchHandler, listeners, retryCallback);
299308
}
300309

310+
// HTTP status code that the Realtime client should retry on.
311+
private boolean isStatusCodeRetryable(int statusCode) {
312+
return statusCode == HttpURLConnection.HTTP_CLIENT_TIMEOUT
313+
|| statusCode == HTTP_TOO_MANY_REQUESTS
314+
|| statusCode == HttpURLConnection.HTTP_BAD_GATEWAY
315+
|| statusCode == HttpURLConnection.HTTP_UNAVAILABLE
316+
|| statusCode == HttpURLConnection.HTTP_GATEWAY_TIMEOUT;
317+
}
318+
301319
/**
302320
* Open the realtime connection, begin listening for updates, and auto-fetch when an update is
303321
* received.
@@ -306,39 +324,55 @@ public void onError(Exception error) {
306324
* chunk-encoded HTTP body. When the connection closes, it attempts to reestablish the stream.
307325
*/
308326
@SuppressLint("VisibleForTests")
309-
synchronized void beginRealtimeHttpStream() {
327+
public synchronized void beginRealtimeHttpStream() {
310328
if (!canMakeHttpStreamConnection()) {
311329
return;
312330
}
313331

332+
Integer responseCode = null;
314333
try {
315334
// Create the open the connection.
316335
httpURLConnection = createRealtimeConnection();
317-
httpURLConnection.connect();
336+
responseCode = httpURLConnection.getResponseCode();
318337

319-
// Reset the retries remaining if we opened the connection without an exception.
320-
resetRetryParameters();
338+
// If the connection returned a 200 response code, start listening for messages.
339+
if (responseCode == HttpURLConnection.HTTP_OK) {
340+
// Reset the retries remaining if we opened the connection without an exception.
341+
resetRetryParameters();
321342

322-
// Start listening for realtime notifications.
323-
ConfigAutoFetch configAutoFetch = startAutoFetch(httpURLConnection);
324-
configAutoFetch.listenForNotifications();
343+
// Start listening for realtime notifications.
344+
ConfigAutoFetch configAutoFetch = startAutoFetch(httpURLConnection);
345+
configAutoFetch.listenForNotifications();
346+
}
325347
} catch (IOException e) {
326348
Log.d(TAG, "Exception connecting to realtime stream. Retrying the connection...");
327349
} finally {
328350
closeRealtimeHttpStream();
329-
retryHTTPConnection();
351+
352+
// If responseCode is null then no connection was made to server and the SDK should still
353+
// retry.
354+
if (responseCode == null
355+
|| responseCode == HttpURLConnection.HTTP_OK
356+
|| isStatusCodeRetryable(responseCode)) {
357+
retryHTTPConnection();
358+
} else {
359+
propagateErrors(
360+
new FirebaseRemoteConfigRealtimeUpdateStreamException(
361+
"The server returned a status code that is not retryable. Realtime is shutting down."));
362+
}
330363
}
331364
}
332365

333366
// Pauses Http stream listening
334-
synchronized void closeRealtimeHttpStream() {
367+
public synchronized void closeRealtimeHttpStream() {
335368
if (httpURLConnection != null) {
336369
this.httpURLConnection.disconnect();
337370

338371
// Explicitly close the input stream due to a bug in the Android okhttp implementation.
339372
// See github.com/firebase/firebase-android-sdk/pull/808.
340373
try {
341374
this.httpURLConnection.getInputStream().close();
375+
this.httpURLConnection.getErrorStream().close();
342376
} catch (IOException e) {
343377
}
344378
this.httpURLConnection = null;

firebase-config/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigTest.java

Lines changed: 66 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,11 @@
3030
import static org.mockito.ArgumentMatchers.eq;
3131
import static org.mockito.Matchers.any;
3232
import static org.mockito.Mockito.doAnswer;
33+
import static org.mockito.Mockito.doNothing;
34+
import static org.mockito.Mockito.doReturn;
3335
import static org.mockito.Mockito.doThrow;
3436
import static org.mockito.Mockito.never;
37+
import static org.mockito.Mockito.spy;
3538
import static org.mockito.Mockito.times;
3639
import static org.mockito.Mockito.verify;
3740
import static org.mockito.Mockito.when;
@@ -65,6 +68,7 @@
6568
import com.google.firebase.remoteconfig.internal.ConfigGetParameterHandler;
6669
import com.google.firebase.remoteconfig.internal.ConfigMetadataClient;
6770
import com.google.firebase.remoteconfig.internal.ConfigRealtimeHandler;
71+
import com.google.firebase.remoteconfig.internal.ConfigRealtimeHttpClient;
6872
import com.google.firebase.remoteconfig.internal.Personalization;
6973
import java.io.ByteArrayInputStream;
7074
import java.io.IOException;
@@ -140,6 +144,7 @@ public final class FirebaseRemoteConfigTest {
140144
@Mock private ConfigMetadataClient metadataClient;
141145

142146
@Mock private ConfigRealtimeHandler mockConfigRealtimeHandler;
147+
@Mock private ConfigAutoFetch mockConfigAutoFetch;
143148
@Mock private ConfigUpdateListenerRegistration mockRealtimeRegistration;
144149
@Mock private HttpURLConnection mockHttpURLConnection;
145150
@Mock private ConfigUpdateListener mockListener;
@@ -165,6 +170,7 @@ public final class FirebaseRemoteConfigTest {
165170
private FetchResponse realtimeFetchedContainerResponse;
166171
private ConfigContainer realtimeFetchedContainer;
167172
private ConfigAutoFetch configAutoFetch;
173+
private ConfigRealtimeHttpClient configRealtimeHttpClient;
168174

169175
private FetchResponse firstFetchedContainerResponse;
170176

@@ -269,6 +275,14 @@ public void setUp() throws Exception {
269275
listeners.add(mockListener);
270276
configAutoFetch =
271277
new ConfigAutoFetch(mockHttpURLConnection, mockFetchHandler, listeners, mockRetryListener);
278+
configRealtimeHttpClient =
279+
new ConfigRealtimeHttpClient(
280+
firebaseApp,
281+
mockFirebaseInstallations,
282+
mockFetchHandler,
283+
context,
284+
"firebase",
285+
listeners);
272286
}
273287

274288
@Test
@@ -1107,7 +1121,6 @@ public void realtime_client_removeListener_success() {
11071121

11081122
@Test
11091123
public void realtime_stream_listen_and_retry_success() throws Exception {
1110-
when(mockHttpURLConnection.getResponseCode()).thenReturn(200);
11111124
when(mockHttpURLConnection.getInputStream())
11121125
.thenReturn(
11131126
new ByteArrayInputStream(
@@ -1121,17 +1134,60 @@ public void realtime_stream_listen_and_retry_success() throws Exception {
11211134

11221135
@Test
11231136
public void realtime_stream_listen_fail() throws Exception {
1124-
when(mockHttpURLConnection.getResponseCode()).thenReturn(400);
1125-
when(mockHttpURLConnection.getInputStream())
1126-
.thenReturn(
1127-
new ByteArrayInputStream(
1128-
"{\\r\\n \\\"latestTemplateVersionNumber\\\": 1\\r\\n}"
1129-
.getBytes(StandardCharsets.UTF_8)));
1130-
when(mockFetchHandler.getTemplateVersionNumber()).thenReturn(1L);
1131-
when(mockFetchHandler.fetch(0)).thenReturn(Tasks.forResult(realtimeFetchedContainerResponse));
1137+
when(mockHttpURLConnection.getInputStream()).thenThrow(IOException.class);
11321138
configAutoFetch.listenForNotifications();
11331139

1134-
verify(mockListener).onError(any(FirebaseRemoteConfigRealtimeUpdateStreamException.class));
1140+
verify(mockListener).onError(any(FirebaseRemoteConfigRealtimeUpdateFetchException.class));
1141+
}
1142+
1143+
@Test
1144+
public void realtime_redirectStatusCode_noRetries() throws Exception {
1145+
ConfigRealtimeHttpClient configRealtimeHttpClientSpy = spy(configRealtimeHttpClient);
1146+
doReturn(mockHttpURLConnection).when(configRealtimeHttpClientSpy).createRealtimeConnection();
1147+
doNothing().when(configRealtimeHttpClientSpy).closeRealtimeHttpStream();
1148+
when(mockHttpURLConnection.getResponseCode()).thenReturn(301);
1149+
configRealtimeHttpClientSpy.beginRealtimeHttpStream();
1150+
verify(configRealtimeHttpClientSpy, never()).startAutoFetch(any());
1151+
verify(configRealtimeHttpClientSpy, never()).retryHTTPConnection();
1152+
}
1153+
1154+
@Test
1155+
public void realtime_okStatusCode_startAutofetchAndRetries() throws Exception {
1156+
ConfigRealtimeHttpClient configRealtimeHttpClientSpy = spy(configRealtimeHttpClient);
1157+
doReturn(mockHttpURLConnection).when(configRealtimeHttpClientSpy).createRealtimeConnection();
1158+
doReturn(mockConfigAutoFetch).when(configRealtimeHttpClientSpy).startAutoFetch(any());
1159+
doNothing().when(configRealtimeHttpClientSpy).retryHTTPConnection();
1160+
doNothing().when(configRealtimeHttpClientSpy).closeRealtimeHttpStream();
1161+
when(mockHttpURLConnection.getResponseCode()).thenReturn(200);
1162+
1163+
configRealtimeHttpClientSpy.beginRealtimeHttpStream();
1164+
verify(mockConfigAutoFetch).listenForNotifications();
1165+
verify(configRealtimeHttpClientSpy).retryHTTPConnection();
1166+
}
1167+
1168+
@Test
1169+
public void realtime_badGatewayStatusCode_noAutofetchButRetries() throws Exception {
1170+
ConfigRealtimeHttpClient configRealtimeHttpClientSpy = spy(configRealtimeHttpClient);
1171+
doReturn(mockHttpURLConnection).when(configRealtimeHttpClientSpy).createRealtimeConnection();
1172+
doNothing().when(configRealtimeHttpClientSpy).retryHTTPConnection();
1173+
doNothing().when(configRealtimeHttpClientSpy).closeRealtimeHttpStream();
1174+
when(mockHttpURLConnection.getResponseCode()).thenReturn(502);
1175+
1176+
configRealtimeHttpClientSpy.beginRealtimeHttpStream();
1177+
verify(configRealtimeHttpClientSpy, never()).startAutoFetch(any());
1178+
verify(configRealtimeHttpClientSpy).retryHTTPConnection();
1179+
}
1180+
1181+
@Test
1182+
public void realtime_exceptionThrown_noAutofetchButRetries() throws Exception {
1183+
ConfigRealtimeHttpClient configRealtimeHttpClientSpy = spy(configRealtimeHttpClient);
1184+
doThrow(IOException.class).when(configRealtimeHttpClientSpy).createRealtimeConnection();
1185+
doNothing().when(configRealtimeHttpClientSpy).retryHTTPConnection();
1186+
doNothing().when(configRealtimeHttpClientSpy).closeRealtimeHttpStream();
1187+
1188+
configRealtimeHttpClientSpy.beginRealtimeHttpStream();
1189+
verify(configRealtimeHttpClientSpy, never()).startAutoFetch(any());
1190+
verify(configRealtimeHttpClientSpy).retryHTTPConnection();
11351191
}
11361192

11371193
@Test

0 commit comments

Comments
 (0)