Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions NOTICE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Firebase Admin Java SDK
Copyright 2019 Google Inc.

This product includes software developed at
The Apache Software Foundation (http://www.apache.org/).
108 changes: 108 additions & 0 deletions src/main/java/com/google/firebase/internal/DateUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* 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.internal;

import static com.google.common.base.Preconditions.checkNotNull;

import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

/**
* A utility class for parsing and formatting HTTP dates as used in cookies and
* other headers. This class handles dates as defined by RFC 2616 section
* 3.3.1 as well as some other common non-standard formats.
*
* <p>Most of this class was borrowed from the
* <a href="http://svn.apache.org/repos/asf/httpcomponents/httpclient/tags/4.3/httpclient/src/main/java/org/apache/http/client/utils/DateUtils.java">
* Apache HTTP client</a> in order to avoid a direct dependency on it. We currently
* have a transitive dependency on this library (via Google API client), but the API
* client team is working towards removing it, so we won't have it in the classpath for long.
*
* <p>The original implementation of this class uses
* thread locals to cache the {@code SimpleDateFormat} instances. Instead, this implementation
* uses static constants and explicit locking to ensure thread safety. This is probably slower,
* but also simpler and avoids memory leaks that may result from unreleased thread locals.
*/
final class DateUtils {

/**
* Date format pattern used to parse HTTP date headers in RFC 1123 format.
*/
static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz";

/**
* Date format pattern used to parse HTTP date headers in RFC 1036 format.
*/
static final String PATTERN_RFC1036 = "EEE, dd-MMM-yy HH:mm:ss zzz";

/**
* Date format pattern used to parse HTTP date headers in ANSI C
* {@code asctime()} format.
*/
static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy";

private static final SimpleDateFormat[] DEFAULT_PATTERNS = new SimpleDateFormat[] {
new SimpleDateFormat(PATTERN_RFC1123),
new SimpleDateFormat(PATTERN_RFC1036),
new SimpleDateFormat(PATTERN_ASCTIME)
};

static final TimeZone GMT = TimeZone.getTimeZone("GMT");

static {
final Calendar calendar = Calendar.getInstance();
calendar.setTimeZone(GMT);
calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
calendar.set(Calendar.MILLISECOND, 0);
final Date defaultTwoDigitYearStart = calendar.getTime();

for (final SimpleDateFormat datePattern : DEFAULT_PATTERNS) {
datePattern.set2DigitYearStart(defaultTwoDigitYearStart);
}
}

/**
* Parses the date value using the given date formats.
*
* @param dateValue the date value to parse
* @return the parsed date or null if input could not be parsed
*/
public static Date parseDate(final String dateValue) {
String v = checkNotNull(dateValue);
// trim single quotes around date if present
// see issue #5279
if (v.length() > 1 && v.startsWith("'") && v.endsWith("'")) {
v = v.substring(1, v.length() - 1);
}

for (final SimpleDateFormat datePattern : DEFAULT_PATTERNS) {
final ParsePosition pos = new ParsePosition(0);
synchronized (datePattern) {
final Date result = datePattern.parse(v, pos);
if (pos.getIndex() != 0) {
return result;
}
}
}
return null;
}

/** This class should not be instantiated. */
private DateUtils() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,32 +19,57 @@
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.common.collect.ImmutableList;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.ImplFirebaseTrampolines;
import java.io.IOException;
import java.util.List;

/**
* {@code HttpRequestInitializer} for configuring outgoing REST calls. Handles OAuth2 authorization
* and setting timeout values.
* {@code HttpRequestInitializer} for configuring outgoing REST calls. Initializes requests with
* OAuth2 credentials, timeout and retry settings.
*/
public class FirebaseRequestInitializer implements HttpRequestInitializer {
public final class FirebaseRequestInitializer implements HttpRequestInitializer {

private final HttpCredentialsAdapter credentialsAdapter;
private final int connectTimeout;
private final int readTimeout;
private final List<HttpRequestInitializer> initializers;

public FirebaseRequestInitializer(FirebaseApp app) {
GoogleCredentials credentials = ImplFirebaseTrampolines.getCredentials(app);
this.credentialsAdapter = new HttpCredentialsAdapter(credentials);
this.connectTimeout = app.getOptions().getConnectTimeout();
this.readTimeout = app.getOptions().getReadTimeout();
this(app, null);
}

public FirebaseRequestInitializer(FirebaseApp app, @Nullable RetryConfig retryConfig) {
ImmutableList.Builder<HttpRequestInitializer> initializers =
ImmutableList.<HttpRequestInitializer>builder()
.add(new HttpCredentialsAdapter(ImplFirebaseTrampolines.getCredentials(app)))
.add(new TimeoutInitializer(app.getOptions()));
if (retryConfig != null) {
initializers.add(new RetryInitializer(retryConfig));
}
this.initializers = initializers.build();
}

@Override
public void initialize(HttpRequest httpRequest) throws IOException {
credentialsAdapter.initialize(httpRequest);
httpRequest.setConnectTimeout(connectTimeout);
httpRequest.setReadTimeout(readTimeout);
public void initialize(HttpRequest request) throws IOException {
for (HttpRequestInitializer initializer : initializers) {
initializer.initialize(request);
}
}

private static class TimeoutInitializer implements HttpRequestInitializer {

private final int connectTimeoutMillis;
private final int readTimeoutMillis;

TimeoutInitializer(FirebaseOptions options) {
this.connectTimeoutMillis = options.getConnectTimeout();
this.readTimeoutMillis = options.getReadTimeout();
}

@Override
public void initialize(HttpRequest request) {
request.setConnectTimeout(connectTimeoutMillis);
request.setReadTimeout(readTimeoutMillis);
}
}
}
177 changes: 177 additions & 0 deletions src/main/java/com/google/firebase/internal/RetryConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/*
* Copyright 2019 Google Inc.
*
* 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.internal;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import com.google.api.client.util.BackOff;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

import com.google.api.client.util.ExponentialBackOff;
import com.google.api.client.util.Sleeper;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
* Configures when and how HTTP requests should be retried.
*/
public final class RetryConfig {
Comment thread
hiranya911 marked this conversation as resolved.

private static final int INITIAL_INTERVAL_MILLIS = 500;

private final List<Integer> retryStatusCodes;
private final boolean retryOnIOExceptions;
private final int maxRetries;
private final Sleeper sleeper;
private final ExponentialBackOff.Builder backOffBuilder;

private RetryConfig(Builder builder) {
Comment thread
hiranya911 marked this conversation as resolved.
if (builder.retryStatusCodes != null) {
this.retryStatusCodes = ImmutableList.copyOf(builder.retryStatusCodes);
} else {
this.retryStatusCodes = ImmutableList.of();
}

this.retryOnIOExceptions = builder.retryOnIOExceptions;
checkArgument(builder.maxRetries >= 0, "maxRetries must not be negative");
this.maxRetries = builder.maxRetries;
this.sleeper = checkNotNull(builder.sleeper);
this.backOffBuilder = new ExponentialBackOff.Builder()
.setInitialIntervalMillis(INITIAL_INTERVAL_MILLIS)
.setMaxIntervalMillis(builder.maxIntervalMillis)
Comment thread
hiranya911 marked this conversation as resolved.
.setMultiplier(builder.backOffMultiplier)
.setRandomizationFactor(0);

// Force validation of arguments by building the BackOff object
this.backOffBuilder.build();
}

List<Integer> getRetryStatusCodes() {
return retryStatusCodes;
}

boolean isRetryOnIOExceptions() {
return retryOnIOExceptions;
}

int getMaxRetries() {
return maxRetries;
}

int getMaxIntervalMillis() {
return backOffBuilder.getMaxIntervalMillis();
}

double getBackOffMultiplier() {
return backOffBuilder.getMultiplier();
}

Sleeper getSleeper() {
return sleeper;
}

BackOff newBackOff() {
return backOffBuilder.build();
}

public static Builder builder() {
return new Builder();
}

public static final class Builder {

private List<Integer> retryStatusCodes;
private boolean retryOnIOExceptions;
private int maxRetries;
private int maxIntervalMillis = (int) TimeUnit.MINUTES.toMillis(2);
private double backOffMultiplier = 2.0;
private Sleeper sleeper = Sleeper.DEFAULT;

private Builder() { }

/**
* Sets a list of HTTP status codes that should be retried. If null or empty, HTTP requests
* will not be retried as long as they result in some HTTP response message.
*
* @param retryStatusCodes A list of status codes.
* @return This builder.
*/
public Builder setRetryStatusCodes(List<Integer> retryStatusCodes) {
Comment thread
hiranya911 marked this conversation as resolved.
this.retryStatusCodes = retryStatusCodes;
return this;
}

/**
* Sets whether requests should be retried on IOExceptions.
*
* @param retryOnIOExceptions A boolean indicating whether to retry on IOExceptions.
* @return This builder.
*/
public Builder setRetryOnIOExceptions(boolean retryOnIOExceptions) {
this.retryOnIOExceptions = retryOnIOExceptions;
return this;
}

/**
* Maximum number of retry attempts for a request. This is the cumulative total for all retries
* regardless of their cause (I/O errors and HTTP error responses).
*
* @param maxRetries A non-negative integer.
* @return This builder.
*/
public Builder setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
return this;
}

/**
* Maximum interval to wait before a request should be retried. Must be at least 500
* milliseconds. Defaults to 2 minutes.
*
* @param maxIntervalMillis Interval in milliseconds.
* @return This builder.
*/
public Builder setMaxIntervalMillis(int maxIntervalMillis) {
this.maxIntervalMillis = maxIntervalMillis;
return this;
}

/**
* Factor by which the retry interval is multiplied when employing exponential back
* off to delay consecutive retries of the same request. Must be at least 1. Defaults
* to 2.
*
* @param backOffMultiplier Multiplication factor for exponential back off.
* @return This builder.
*/
public Builder setBackOffMultiplier(double backOffMultiplier) {
this.backOffMultiplier = backOffMultiplier;
return this;
}

@VisibleForTesting
Builder setSleeper(Sleeper sleeper) {
this.sleeper = sleeper;
return this;
}

public RetryConfig build() {
return new RetryConfig(this);
}
}
}
Loading