Skip to content

Commit 4a45659

Browse files
authored
Support configuring timeout for outbound requests (firebase#158)
* Support configuring timeout for outbound requests * Documentation updates * Updated changelog * Updated docs
1 parent 76346cb commit 4a45659

10 files changed

Lines changed: 241 additions & 20 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Unreleased
22

3+
- [added] Connection timeout and read timeout for HTTP/REST connections
4+
can now be configured via `FirebaseOptions.Builder` at app
5+
initialization.
36
- [added] Added new `setMutableContent()`, `putCustomData()` and
47
`putAllCustomData()` methods to the `Aps.Builder` API.
58
- [fixed] Improved error handling in FCM by mapping more server-side

src/main/java/com/google/firebase/FirebaseOptions.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ public final class FirebaseOptions {
4747
private final Map<String, Object> databaseAuthVariableOverride;
4848
private final String projectId;
4949
private final HttpTransport httpTransport;
50+
private final int connectTimeout;
51+
private final int readTimeout;
5052
private final JsonFactory jsonFactory;
5153
private final ThreadManager threadManager;
5254

@@ -68,6 +70,10 @@ private FirebaseOptions(@NonNull FirebaseOptions.Builder builder) {
6870
"FirebaseOptions must be initialized with a non-null JsonFactory.");
6971
this.threadManager = checkNotNull(builder.threadManager,
7072
"FirebaseOptions must be initialized with a non-null ThreadManager.");
73+
checkArgument(builder.connectTimeout >= 0);
74+
this.connectTimeout = builder.connectTimeout;
75+
checkArgument(builder.readTimeout >= 0);
76+
this.readTimeout = builder.readTimeout;
7177
}
7278

7379
/**
@@ -132,6 +138,26 @@ public JsonFactory getJsonFactory() {
132138
return jsonFactory;
133139
}
134140

141+
/**
142+
* Returns the connect timeout in milliseconds, which is applied to outgoing REST calls
143+
* made by the SDK.
144+
*
145+
* @return Connect timeout in milliseconds. 0 indicates an infinite timeout.
146+
*/
147+
public int getConnectTimeout() {
148+
return connectTimeout;
149+
}
150+
151+
/**
152+
* Returns the read timeout in milliseconds, which is applied to outgoing REST calls
153+
* made by the SDK.
154+
*
155+
* @return Read timeout in milliseconds. 0 indicates an infinite timeout.
156+
*/
157+
public int getReadTimeout() {
158+
return readTimeout;
159+
}
160+
135161
@NonNull
136162
ThreadManager getThreadManager() {
137163
return threadManager;
@@ -157,6 +183,8 @@ public static final class Builder {
157183
private HttpTransport httpTransport = Utils.getDefaultTransport();
158184
private JsonFactory jsonFactory = Utils.getDefaultJsonFactory();
159185
private ThreadManager threadManager = FirebaseThreadManagers.DEFAULT_THREAD_MANAGER;
186+
private int connectTimeout;
187+
private int readTimeout;
160188

161189
/** Constructs an empty builder. */
162190
public Builder() {}
@@ -176,6 +204,8 @@ public Builder(FirebaseOptions options) {
176204
httpTransport = options.httpTransport;
177205
jsonFactory = options.jsonFactory;
178206
threadManager = options.threadManager;
207+
connectTimeout = options.connectTimeout;
208+
readTimeout = options.readTimeout;
179209
}
180210

181211
/**
@@ -321,6 +351,33 @@ public Builder setThreadManager(ThreadManager threadManager) {
321351
return this;
322352
}
323353

354+
/**
355+
* Sets the connect timeout for outgoing HTTP (REST) connections made by the SDK. This is used
356+
* when opening a communication link to a remote HTTP endpoint. This setting does not
357+
* affect the {@link com.google.firebase.database.FirebaseDatabase} and
358+
* {@link com.google.firebase.cloud.FirestoreClient} APIs.
359+
*
360+
* @param connectTimeout Connect timeout in milliseconds. Must not be negative.
361+
* @return This <code>Builder</code> instance is returned so subsequent calls can be chained.
362+
*/
363+
public Builder setConnectTimeout(int connectTimeout) {
364+
this.connectTimeout = connectTimeout;
365+
return this;
366+
}
367+
368+
/**
369+
* Sets the read timeout for outgoing HTTP (REST) calls made by the SDK. This does not affect
370+
* the {@link com.google.firebase.database.FirebaseDatabase} and
371+
* {@link com.google.firebase.cloud.FirestoreClient} APIs.
372+
*
373+
* @param readTimeout Read timeout in milliseconds. Must not be negative.
374+
* @return This <code>Builder</code> instance is returned so subsequent calls can be chained.
375+
*/
376+
public Builder setReadTimeout(int readTimeout) {
377+
this.readTimeout = readTimeout;
378+
return this;
379+
}
380+
324381
/**
325382
* Builds the {@link FirebaseOptions} instance from the previously set options.
326383
*

src/main/java/com/google/firebase/auth/FirebaseAuth.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,7 @@ private FirebaseAuth(FirebaseApp firebaseApp) {
8585
this.credentials = ImplFirebaseTrampolines.getCredentials(firebaseApp);
8686
this.projectId = ImplFirebaseTrampolines.getProjectId(firebaseApp);
8787
this.jsonFactory = firebaseApp.getOptions().getJsonFactory();
88-
this.userManager = new FirebaseUserManager(jsonFactory,
89-
firebaseApp.getOptions().getHttpTransport(), this.credentials);
88+
this.userManager = new FirebaseUserManager(firebaseApp);
9089
this.destroyed = new AtomicBoolean(false);
9190
this.lock = new Object();
9291
}

src/main/java/com/google/firebase/auth/FirebaseUserManager.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,19 @@
3030
import com.google.api.client.json.GenericJson;
3131
import com.google.api.client.json.JsonFactory;
3232
import com.google.api.client.json.JsonObjectParser;
33-
import com.google.auth.http.HttpCredentialsAdapter;
34-
import com.google.auth.oauth2.GoogleCredentials;
3533
import com.google.common.annotations.VisibleForTesting;
3634
import com.google.common.base.Strings;
3735
import com.google.common.collect.ImmutableList;
3836
import com.google.common.collect.ImmutableMap;
37+
import com.google.firebase.FirebaseApp;
3938
import com.google.firebase.auth.UserRecord.CreateRequest;
4039
import com.google.firebase.auth.UserRecord.UpdateRequest;
4140
import com.google.firebase.auth.internal.DownloadAccountResponse;
4241
import com.google.firebase.auth.internal.GetAccountInfoResponse;
4342

4443
import com.google.firebase.auth.internal.HttpErrorResponse;
44+
import com.google.firebase.internal.FirebaseRequestInitializer;
45+
import com.google.firebase.internal.NonNull;
4546
import com.google.firebase.internal.SdkUtils;
4647
import java.io.IOException;
4748
import java.util.List;
@@ -98,13 +99,13 @@ class FirebaseUserManager {
9899
/**
99100
* Creates a new FirebaseUserManager instance.
100101
*
101-
* @param jsonFactory JsonFactory instance used to transform Java objects into JSON and back.
102-
* @param transport HttpTransport used to make REST API calls.
102+
* @param app A non-null {@link FirebaseApp}.
103103
*/
104-
FirebaseUserManager(JsonFactory jsonFactory, HttpTransport transport,
105-
GoogleCredentials credentials) {
106-
this.jsonFactory = checkNotNull(jsonFactory, "jsonFactory must not be null");
107-
this.requestFactory = transport.createRequestFactory(new HttpCredentialsAdapter(credentials));
104+
FirebaseUserManager(@NonNull FirebaseApp app) {
105+
checkNotNull(app, "FirebaseApp must not be null");
106+
this.jsonFactory = app.getOptions().getJsonFactory();
107+
HttpTransport transport = app.getOptions().getHttpTransport();
108+
this.requestFactory = transport.createRequestFactory(new FirebaseRequestInitializer(app));
108109
}
109110

110111
@VisibleForTesting

src/main/java/com/google/firebase/iid/FirebaseInstanceId.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,13 @@
2828
import com.google.api.client.json.JsonFactory;
2929
import com.google.api.client.json.JsonObjectParser;
3030
import com.google.api.core.ApiFuture;
31-
import com.google.auth.http.HttpCredentialsAdapter;
32-
import com.google.auth.oauth2.GoogleCredentials;
3331
import com.google.common.annotations.VisibleForTesting;
3432
import com.google.common.base.Strings;
3533
import com.google.common.collect.ImmutableMap;
3634
import com.google.common.io.ByteStreams;
3735
import com.google.firebase.FirebaseApp;
3836
import com.google.firebase.ImplFirebaseTrampolines;
37+
import com.google.firebase.internal.FirebaseRequestInitializer;
3938
import com.google.firebase.internal.FirebaseService;
4039
import com.google.firebase.internal.NonNull;
4140
import com.google.firebase.internal.TaskToApiFuture;
@@ -74,10 +73,8 @@ public class FirebaseInstanceId {
7473

7574
private FirebaseInstanceId(FirebaseApp app) {
7675
HttpTransport httpTransport = app.getOptions().getHttpTransport();
77-
GoogleCredentials credentials = ImplFirebaseTrampolines.getCredentials(app);
7876
this.app = app;
79-
this.requestFactory = httpTransport.createRequestFactory(
80-
new HttpCredentialsAdapter(credentials));
77+
this.requestFactory = httpTransport.createRequestFactory(new FirebaseRequestInitializer(app));
8178
this.jsonFactory = app.getOptions().getJsonFactory();
8279
this.projectId = ImplFirebaseTrampolines.getProjectId(app);
8380
checkArgument(!Strings.isNullOrEmpty(projectId),
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2018 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.internal;
18+
19+
import com.google.api.client.http.HttpRequest;
20+
import com.google.api.client.http.HttpRequestInitializer;
21+
import com.google.auth.http.HttpCredentialsAdapter;
22+
import com.google.auth.oauth2.GoogleCredentials;
23+
import com.google.firebase.FirebaseApp;
24+
import com.google.firebase.ImplFirebaseTrampolines;
25+
import java.io.IOException;
26+
27+
/**
28+
* {@code HttpRequestInitializer} for configuring outgoing REST calls. Handles OAuth2 authorization
29+
* and setting timeout values.
30+
*/
31+
public class FirebaseRequestInitializer implements HttpRequestInitializer {
32+
33+
private final HttpCredentialsAdapter credentialsAdapter;
34+
private final int connectTimeout;
35+
private final int readTimeout;
36+
37+
public FirebaseRequestInitializer(FirebaseApp app) {
38+
GoogleCredentials credentials = ImplFirebaseTrampolines.getCredentials(app);
39+
this.credentialsAdapter = new HttpCredentialsAdapter(credentials);
40+
this.connectTimeout = app.getOptions().getConnectTimeout();
41+
this.readTimeout = app.getOptions().getReadTimeout();
42+
}
43+
44+
@Override
45+
public void initialize(HttpRequest httpRequest) throws IOException {
46+
credentialsAdapter.initialize(httpRequest);
47+
httpRequest.setConnectTimeout(connectTimeout);
48+
httpRequest.setReadTimeout(readTimeout);
49+
}
50+
}

src/main/java/com/google/firebase/messaging/FirebaseMessaging.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,12 @@
3333
import com.google.api.client.json.JsonParser;
3434
import com.google.api.client.util.Key;
3535
import com.google.api.core.ApiFuture;
36-
import com.google.auth.http.HttpCredentialsAdapter;
37-
import com.google.auth.oauth2.GoogleCredentials;
3836
import com.google.common.annotations.VisibleForTesting;
3937
import com.google.common.base.Strings;
4038
import com.google.common.collect.ImmutableMap;
4139
import com.google.firebase.FirebaseApp;
4240
import com.google.firebase.ImplFirebaseTrampolines;
41+
import com.google.firebase.internal.FirebaseRequestInitializer;
4342
import com.google.firebase.internal.FirebaseService;
4443
import com.google.firebase.internal.NonNull;
4544
import com.google.firebase.internal.TaskToApiFuture;
@@ -102,10 +101,8 @@ public class FirebaseMessaging {
102101

103102
private FirebaseMessaging(FirebaseApp app) {
104103
HttpTransport httpTransport = app.getOptions().getHttpTransport();
105-
GoogleCredentials credentials = ImplFirebaseTrampolines.getCredentials(app);
106104
this.app = app;
107-
this.requestFactory = httpTransport.createRequestFactory(
108-
new HttpCredentialsAdapter(credentials));
105+
this.requestFactory = httpTransport.createRequestFactory(new FirebaseRequestInitializer(app));
109106
this.jsonFactory = app.getOptions().getJsonFactory();
110107
String projectId = ImplFirebaseTrampolines.getProjectId(app);
111108
checkArgument(!Strings.isNullOrEmpty(projectId),

src/test/java/com/google/firebase/FirebaseOptionsTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,17 @@ public void createOptionsWithAllValuesSet() throws IOException, InterruptedExcep
8787
.setJsonFactory(jsonFactory)
8888
.setHttpTransport(httpTransport)
8989
.setThreadManager(MOCK_THREAD_MANAGER)
90+
.setConnectTimeout(30000)
91+
.setReadTimeout(60000)
9092
.build();
9193
assertEquals(FIREBASE_DB_URL, firebaseOptions.getDatabaseUrl());
9294
assertEquals(FIREBASE_STORAGE_BUCKET, firebaseOptions.getStorageBucket());
9395
assertEquals(FIREBASE_PROJECT_ID, firebaseOptions.getProjectId());
9496
assertSame(jsonFactory, firebaseOptions.getJsonFactory());
9597
assertSame(httpTransport, firebaseOptions.getHttpTransport());
9698
assertSame(MOCK_THREAD_MANAGER, firebaseOptions.getThreadManager());
99+
assertEquals(30000, firebaseOptions.getConnectTimeout());
100+
assertEquals(60000, firebaseOptions.getReadTimeout());
97101

98102
GoogleCredentials credentials = firebaseOptions.getCredentials();
99103
assertNotNull(credentials);
@@ -114,6 +118,8 @@ public void createOptionsWithOnlyMandatoryValuesSet() throws IOException, Interr
114118
assertNotNull(firebaseOptions.getThreadManager());
115119
assertNull(firebaseOptions.getDatabaseUrl());
116120
assertNull(firebaseOptions.getStorageBucket());
121+
assertEquals(0, firebaseOptions.getConnectTimeout());
122+
assertEquals(0, firebaseOptions.getReadTimeout());
117123

118124
GoogleCredentials credentials = firebaseOptions.getCredentials();
119125
assertNotNull(credentials);
@@ -200,6 +206,24 @@ public void checkToBuilderCreatesNewEquivalentInstance() {
200206
assertEquals(ALL_VALUES_OPTIONS.getJsonFactory(), allValuesOptionsCopy.getJsonFactory());
201207
assertEquals(ALL_VALUES_OPTIONS.getHttpTransport(), allValuesOptionsCopy.getHttpTransport());
202208
assertEquals(ALL_VALUES_OPTIONS.getThreadManager(), allValuesOptionsCopy.getThreadManager());
209+
assertEquals(ALL_VALUES_OPTIONS.getConnectTimeout(), allValuesOptionsCopy.getConnectTimeout());
210+
assertEquals(ALL_VALUES_OPTIONS.getReadTimeout(), allValuesOptionsCopy.getReadTimeout());
211+
}
212+
213+
@Test(expected = IllegalArgumentException.class)
214+
public void createOptionsWithInvalidConnectTimeout() {
215+
new FirebaseOptions.Builder()
216+
.setCredentials(TestUtils.getCertCredential(ServiceAccount.EDITOR.asStream()))
217+
.setConnectTimeout(-1)
218+
.build();
219+
}
220+
221+
@Test(expected = IllegalArgumentException.class)
222+
public void createOptionsWithInvalidReadTimeout() {
223+
new FirebaseOptions.Builder()
224+
.setCredentials(TestUtils.getCertCredential(ServiceAccount.EDITOR.asStream()))
225+
.setReadTimeout(-1)
226+
.build();
203227
}
204228

205229
@Test

src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import com.google.api.client.googleapis.util.Utils;
2727
import com.google.api.client.http.HttpHeaders;
28+
import com.google.api.client.http.HttpRequest;
2829
import com.google.api.client.http.HttpResponseException;
2930
import com.google.api.client.json.GenericJson;
3031
import com.google.api.client.json.JsonFactory;
@@ -382,6 +383,27 @@ public void testGetUserUnexpectedHttpError() throws Exception {
382383
}
383384
}
384385

386+
@Test
387+
public void testTimeout() throws Exception {
388+
MockHttpTransport transport = new MultiRequestMockHttpTransport(ImmutableList.of(
389+
new MockLowLevelHttpResponse().setContent(TestUtils.loadResource("getUser.json"))));
390+
FirebaseApp.initializeApp(new FirebaseOptions.Builder()
391+
.setCredentials(credentials)
392+
.setHttpTransport(transport)
393+
.setConnectTimeout(30000)
394+
.setReadTimeout(60000)
395+
.build());
396+
FirebaseAuth auth = FirebaseAuth.getInstance();
397+
FirebaseUserManager userManager = auth.getUserManager();
398+
TestResponseInterceptor interceptor = new TestResponseInterceptor();
399+
userManager.setInterceptor(interceptor);
400+
401+
FirebaseAuth.getInstance().getUserAsync("testuser").get();
402+
HttpRequest request = interceptor.getResponse().getRequest();
403+
assertEquals(30000, request.getConnectTimeout());
404+
assertEquals(60000, request.getReadTimeout());
405+
}
406+
385407
@Test
386408
public void testUserBuilder() {
387409
Map<String, Object> map = new CreateRequest().getProperties();

0 commit comments

Comments
 (0)