Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion firebase-functions/firebase-functions.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ dependencies {
}
implementation 'com.google.firebase:firebase-iid-interop:16.0.1'

implementation 'com.squareup.okhttp:okhttp:2.7.5'
implementation 'com.squareup.okhttp3:okhttp:3.12.1'

annotationProcessor 'com.google.auto.value:auto-value:1.6'

Expand Down
3 changes: 2 additions & 1 deletion firebase-functions/src/androidTest/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
<!--Although the *SdkVersion is captured in gradle build files, this is required for non gradle builds-->
<!--<uses-sdk android:minSdkVersion="15" android:targetSdkVersion="23" />-->
<uses-permission android:name="android.permission.INTERNET"/>
<application>
<application
android:usesCleartextTraffic="true">
<uses-library android:name="android.test.runner" />
</application>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
Expand Down Expand Up @@ -238,4 +239,21 @@ public void testHttpError() throws InterruptedException, ExecutionException {
assertEquals("INVALID_ARGUMENT", ffe.getMessage());
assertNull(ffe.getDetails());
}

@Test
public void testTimeout() throws InterruptedException, ExecutionException {
FirebaseFunctions functions = FirebaseFunctions.getInstance(app);
useTestURL(functions);

HttpsCallableReference function =
functions.getHttpsCallable("timeoutTest").withTimeout(10, TimeUnit.MILLISECONDS);
Task<HttpsCallableResult> result = function.call();
ExecutionException exe = assertThrows(ExecutionException.class, () -> Tasks.await(result));
Throwable cause = exe.getCause();
assertTrue(cause.toString(), cause instanceof FirebaseFunctionsException);
FirebaseFunctionsException ffe = (FirebaseFunctionsException) cause;
assertEquals(Code.DEADLINE_EXCEEDED, ffe.getCode());
assertEquals("DEADLINE_EXCEEDED", ffe.getMessage());
assertNull(ffe.getDetails());
}
}
5 changes: 5 additions & 0 deletions firebase-functions/src/androidTest/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,8 @@ exports.httpErrorTest = functions.https.onRequest((request, response) => {
// Send an http error with no body.
response.status(400).send();
});

exports.timeoutTest = functions.https.onRequest((request, response) => {
// Wait for longer than 500ms.
setTimeout(() => response.send({data: true}), 500);
});
1 change: 1 addition & 0 deletions firebase-functions/src/androidTest/test/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,6 @@ ${FUNCTIONS} deploy unknownErrorTest --trigger-http
${FUNCTIONS} deploy unhandledErrorTest --trigger-http
${FUNCTIONS} deploy explicitErrorTest --trigger-http
${FUNCTIONS} deploy httpErrorTest --trigger-http
${FUNCTIONS} deploy timeoutTest --trigger-http

echo "Finished setting up Cloud Functions emulator."
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,19 @@
import com.google.android.gms.tasks.Tasks;
import com.google.firebase.FirebaseApp;
import com.google.firebase.functions.FirebaseFunctionsException.Code;
import com.squareup.okhttp.Call;
import com.squareup.okhttp.Callback;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.json.JSONException;
import org.json.JSONObject;

Expand Down Expand Up @@ -197,7 +198,7 @@ public void useFunctionsEmulator(String origin) {
* @param data Parameters to pass to the function. Can be anything encodable as JSON.
* @return A Task that will be completed when the request is complete.
*/
Task<HttpsCallableResult> call(String name, @Nullable Object data) {
Task<HttpsCallableResult> call(String name, @Nullable Object data, HttpsCallOptions options) {
return providerInstalled
.getTask()
.continueWithTask(task -> contextProvider.getContext())
Expand All @@ -207,7 +208,7 @@ Task<HttpsCallableResult> call(String name, @Nullable Object data) {
return Tasks.forException(task.getException());
}
HttpsCallableContext context = task.getResult();
return call(name, data, context);
return call(name, data, context, options);
});
}

Expand All @@ -220,7 +221,7 @@ Task<HttpsCallableResult> call(String name, @Nullable Object data) {
* @return A Task that will be completed when the request is complete.
*/
private Task<HttpsCallableResult> call(
String name, @Nullable Object data, HttpsCallableContext context) {
String name, @Nullable Object data, HttpsCallableContext context, HttpsCallOptions options) {
if (name == null) {
throw new IllegalArgumentException("name cannot be null");
}
Expand All @@ -243,17 +244,28 @@ private Task<HttpsCallableResult> call(
request = request.header("Firebase-Instance-ID-Token", context.getInstanceIdToken());
}

OkHttpClient callClient = options.apply(client);
Call call = callClient.newCall(request.build());

TaskCompletionSource<HttpsCallableResult> tcs = new TaskCompletionSource<>();
Call call = client.newCall(request.build());
call.enqueue(
new Callback() {
@Override
public void onFailure(Request request, IOException e) {
tcs.setException(e);
public void onFailure(Call ignored, IOException e) {
if (e instanceof InterruptedIOException) {
FirebaseFunctionsException exception =
new FirebaseFunctionsException(
Code.DEADLINE_EXCEEDED.name(), Code.DEADLINE_EXCEEDED, null, e);
tcs.setException(exception);
} else {
FirebaseFunctionsException exception =
new FirebaseFunctionsException(Code.INTERNAL.name(), Code.INTERNAL, null, e);
tcs.setException(exception);
}
}

@Override
public void onResponse(Response response) throws IOException {
public void onResponse(Call ignored, Response response) throws IOException {
Code code = Code.fromHttpStatus(response.code());
String body = response.body().string();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.firebase.functions;

import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;

/** An internal class for keeping track of options applied to an HttpsCallableReference. */
class HttpsCallOptions {

// The default timeout to use for all calls.
private static final long DEFAULT_TIMEOUT = 70;
private static final TimeUnit DEFAULT_TIMEOUT_UNITS = TimeUnit.SECONDS;

// The timeout to use for calls from references created by this Functions.
private long timeout = DEFAULT_TIMEOUT;
private TimeUnit timeoutUnits = DEFAULT_TIMEOUT_UNITS;

/**
* Changes the timeout for calls from this instance of Functions. The default is 60 seconds.
*
* @param timeout The length of the timeout, in the given units.
* @param units The units for the specified timeout.
*/
void setTimeout(long timeout, TimeUnit units) {
this.timeout = timeout;
this.timeoutUnits = units;
}

/** Creates a new OkHttpClient with these options applied to it. */
OkHttpClient apply(OkHttpClient client) {
return client.newBuilder().callTimeout(timeout, timeoutUnits).build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,20 @@

import android.support.annotation.Nullable;
import com.google.android.gms.tasks.Task;
import java.util.concurrent.TimeUnit;

/** A reference to a particular Callable HTTPS trigger in Cloud Functions. */
public class HttpsCallableReference {

// The functions client to use for making calls.
private final FirebaseFunctions functionsClient;

// The name of the HTTPS endpoint this reference refers to.
private final String name;

// Options for how to do the HTTPS call.
HttpsCallOptions options = new HttpsCallOptions();

/** Creates a new reference with the given options. */
HttpsCallableReference(FirebaseFunctions functionsClient, String name) {
this.functionsClient = functionsClient;
Expand Down Expand Up @@ -71,7 +76,7 @@ public class HttpsCallableReference {
* @see FirebaseFunctionsException
*/
public Task<HttpsCallableResult> call(@Nullable Object data) {
return functionsClient.call(name, data);
return functionsClient.call(name, data, options);
}

/**
Expand All @@ -89,6 +94,28 @@ public Task<HttpsCallableResult> call(@Nullable Object data) {
* @return A Task that will be completed when the HTTPS request has completed.
*/
public Task<HttpsCallableResult> call() {
return functionsClient.call(name, null);
return functionsClient.call(name, null, options);
}

/**
* Changes the timeout for calls from this instance of Functions. The default is 60 seconds.
*
* @param timeout The length of the timeout, in the given units.
* @param units The units for the specified timeout.
*/
public void setTimeout(long timeout, TimeUnit units) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this require an API review?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the API has been approved. go/callable-timeouts

options.setTimeout(timeout, units);
}

/**
* Creates a new reference with the given timeout for calls. The default is 60 seconds.
*
* @param timeout The length of the timeout, in the given units.
* @param units The units for the specified timeout.
*/
public HttpsCallableReference withTimeout(long timeout, TimeUnit units) {
HttpsCallableReference other = new HttpsCallableReference(functionsClient, name);
other.setTimeout(timeout, units);
return other;
}
}