Bug 969227 - Handle X-Backoff headers. r=nalexander
|
@ -265,6 +265,10 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
|
|||
Logger.error(LOG_TAG, "Failed to get token.", e);
|
||||
callback.handleError(null, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleBackoff(int backoffSeconds) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
До Ширина: | Высота: | Размер: 601 B После Ширина: | Высота: | Размер: 2.1 KiB |
До Ширина: | Высота: | Размер: 283 B После Ширина: | Высота: | Размер: 1.6 KiB |
До Ширина: | Высота: | Размер: 283 B После Ширина: | Высота: | Размер: 1.5 KiB |
Двоичные данные
mobile/android/base/resources/drawable-hdpi/fxaccount_icon.png
До Ширина: | Высота: | Размер: 1.4 KiB После Ширина: | Высота: | Размер: 4.0 KiB |
Двоичные данные
mobile/android/base/resources/drawable-hdpi/fxaccount_mail.png
До Ширина: | Высота: | Размер: 2.6 KiB После Ширина: | Высота: | Размер: 9.5 KiB |
До Ширина: | Высота: | Размер: 1.0 KiB После Ширина: | Высота: | Размер: 3.1 KiB |
До Ширина: | Высота: | Размер: 569 B После Ширина: | Высота: | Размер: 3.5 KiB |
До Ширина: | Высота: | Размер: 213 B После Ширина: | Высота: | Размер: 1.4 KiB |
До Ширина: | Высота: | Размер: 213 B После Ширина: | Высота: | Размер: 1.3 KiB |
Двоичные данные
mobile/android/base/resources/drawable-mdpi/fxaccount_icon.png
До Ширина: | Высота: | Размер: 901 B После Ширина: | Высота: | Размер: 2.4 KiB |
Двоичные данные
mobile/android/base/resources/drawable-mdpi/fxaccount_mail.png
До Ширина: | Высота: | Размер: 1.3 KiB После Ширина: | Высота: | Размер: 4.8 KiB |
До Ширина: | Высота: | Размер: 421 B После Ширина: | Высота: | Размер: 603 B |
Двоичные данные
mobile/android/base/resources/drawable-mdpi/sync_desktop.png
До Ширина: | Высота: | Размер: 206 B После Ширина: | Высота: | Размер: 1.1 KiB |
Двоичные данные
mobile/android/base/resources/drawable-mdpi/sync_mobile.png
До Ширина: | Высота: | Размер: 213 B После Ширина: | Высота: | Размер: 1.1 KiB |
|
@ -49,6 +49,20 @@ public class SyncResponse {
|
|||
return this.getStatusCode() == 200;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the content type of the HTTP response body.
|
||||
*
|
||||
* @return a <code>Header</code> instance, or <code>null</code> if there was
|
||||
* no body or no valid Content-Type.
|
||||
*/
|
||||
public Header getContentType() {
|
||||
HttpEntity entity = this.response.getEntity();
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
return entity.getContentType();
|
||||
}
|
||||
|
||||
private String body = null;
|
||||
public String body() throws IllegalStateException, IOException {
|
||||
if (body != null) {
|
||||
|
@ -145,6 +159,14 @@ public class SyncResponse {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A number of seconds, or -1 if the 'X-Backoff' header was not
|
||||
* present.
|
||||
*/
|
||||
public int backoffInSeconds() throws NumberFormatException {
|
||||
return this.getIntegerHeader("x-backoff");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A number of seconds, or -1 if the 'X-Weave-Backoff' header was not
|
||||
* present.
|
||||
|
@ -154,14 +176,21 @@ public class SyncResponse {
|
|||
}
|
||||
|
||||
/**
|
||||
* @return A number of milliseconds, or -1 if neither the 'Retry-After' or
|
||||
* 'X-Weave-Backoff' header was present.
|
||||
* Extract a number of seconds, or -1 if none of the specified headers were present.
|
||||
*
|
||||
* @param includeRetryAfter
|
||||
* if <code>true</code>, the Retry-After header is excluded. This is
|
||||
* useful for processing non-error responses where a Retry-After
|
||||
* header would be unexpected.
|
||||
* @return the maximum of the three possible backoff headers, in seconds.
|
||||
*/
|
||||
public long totalBackoffInMilliseconds() {
|
||||
public int totalBackoffInSeconds(boolean includeRetryAfter) {
|
||||
int retryAfterInSeconds = -1;
|
||||
try {
|
||||
retryAfterInSeconds = retryAfterInSeconds();
|
||||
} catch (NumberFormatException e) {
|
||||
if (includeRetryAfter) {
|
||||
try {
|
||||
retryAfterInSeconds = retryAfterInSeconds();
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
}
|
||||
|
||||
int weaveBackoffInSeconds = -1;
|
||||
|
@ -170,7 +199,26 @@ public class SyncResponse {
|
|||
} catch (NumberFormatException e) {
|
||||
}
|
||||
|
||||
long totalBackoff = (long) Math.max(retryAfterInSeconds, weaveBackoffInSeconds);
|
||||
int backoffInSeconds = -1;
|
||||
try {
|
||||
backoffInSeconds = backoffInSeconds();
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
|
||||
int totalBackoff = Math.max(retryAfterInSeconds, Math.max(backoffInSeconds, weaveBackoffInSeconds));
|
||||
if (totalBackoff < 0) {
|
||||
return -1;
|
||||
} else {
|
||||
return totalBackoff;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A number of milliseconds, or -1 if neither the 'Retry-After',
|
||||
* 'X-Backoff', or 'X-Weave-Backoff' header were present.
|
||||
*/
|
||||
public long totalBackoffInMilliseconds() {
|
||||
long totalBackoff = totalBackoffInSeconds(true);
|
||||
if (totalBackoff < 0) {
|
||||
return -1;
|
||||
} else {
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.mozilla.gecko.sync.ThreadPool;
|
|||
* * Headers:
|
||||
* * Retry-After
|
||||
* * X-Weave-Backoff
|
||||
* * X-Backoff
|
||||
* * X-Weave-Records?
|
||||
* * ...
|
||||
* * Timeouts
|
||||
|
|
|
@ -29,6 +29,7 @@ import org.mozilla.gecko.tokenserver.TokenServerException.TokenServerMalformedRe
|
|||
import org.mozilla.gecko.tokenserver.TokenServerException.TokenServerMalformedResponseException;
|
||||
import org.mozilla.gecko.tokenserver.TokenServerException.TokenServerUnknownServiceException;
|
||||
|
||||
import ch.boye.httpclientandroidlib.Header;
|
||||
import ch.boye.httpclientandroidlib.HttpHeaders;
|
||||
import ch.boye.httpclientandroidlib.HttpResponse;
|
||||
import ch.boye.httpclientandroidlib.client.ClientProtocolException;
|
||||
|
@ -93,6 +94,28 @@ public class TokenServerClient {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the delegate that some kind of backoff header (X-Backoff,
|
||||
* X-Weave-Backoff, Retry-After) was received and should be acted upon.
|
||||
*
|
||||
* This method is non-terminal, and will be followed by a separate
|
||||
* <code>invoke*</code> call.
|
||||
*
|
||||
* @param delegate
|
||||
* the delegate to inform.
|
||||
* @param backoffSeconds
|
||||
* the number of seconds for which the system should wait before
|
||||
* making another token server request to this server.
|
||||
*/
|
||||
protected void notifyBackoff(final TokenServerClientDelegate delegate, final int backoffSeconds) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
delegate.handleBackoff(backoffSeconds);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void invokeHandleError(final TokenServerClientDelegate delegate, final Exception e) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
|
@ -102,17 +125,21 @@ public class TokenServerClient {
|
|||
});
|
||||
}
|
||||
|
||||
public TokenServerToken processResponse(HttpResponse response)
|
||||
throws TokenServerException {
|
||||
SyncResponse res = new SyncResponse(response);
|
||||
public TokenServerToken processResponse(SyncResponse res) throws TokenServerException {
|
||||
int statusCode = res.getStatusCode();
|
||||
|
||||
Logger.debug(LOG_TAG, "Got token response with status code " + statusCode + ".");
|
||||
|
||||
// Responses should *always* be JSON, even in the case of 4xx and 5xx
|
||||
// errors. If we don't see JSON, the server is likely very unhappy.
|
||||
String contentType = response.getEntity().getContentType().getValue();
|
||||
if (!contentType.equals("application/json") && !contentType.startsWith("application/json;")) {
|
||||
final Header contentType = res.getContentType();
|
||||
if (contentType == null) {
|
||||
throw new TokenServerMalformedResponseException(null, "Non-JSON response Content-Type.");
|
||||
}
|
||||
|
||||
final String type = contentType.getValue();
|
||||
if (!type.equals("application/json") &&
|
||||
!type.startsWith("application/json;")) {
|
||||
Logger.warn(LOG_TAG, "Got non-JSON response with Content-Type " +
|
||||
contentType + ". Misconfigured server?");
|
||||
throw new TokenServerMalformedResponseException(null, "Non-JSON response Content-Type.");
|
||||
|
@ -230,10 +257,21 @@ public class TokenServerClient {
|
|||
|
||||
@Override
|
||||
public void handleHttpResponse(HttpResponse response) {
|
||||
// Skew.
|
||||
SkewHandler skewHandler = SkewHandler.getSkewHandlerForResource(resource);
|
||||
skewHandler.updateSkew(response, System.currentTimeMillis());
|
||||
|
||||
// Extract backoff regardless of whether this was an error response, and
|
||||
// Retry-After for 503 responses. The error will be handled elsewhere.)
|
||||
SyncResponse res = new SyncResponse(response);
|
||||
final boolean includeRetryAfter = res.getStatusCode() == 503;
|
||||
int backoffInSeconds = res.totalBackoffInSeconds(includeRetryAfter);
|
||||
if (backoffInSeconds > -1) {
|
||||
client.notifyBackoff(delegate, backoffInSeconds);
|
||||
}
|
||||
|
||||
try {
|
||||
TokenServerToken token = client.processResponse(response);
|
||||
TokenServerToken token = client.processResponse(res);
|
||||
client.invokeHandleSuccess(delegate, token);
|
||||
} catch (TokenServerException e) {
|
||||
client.invokeHandleFailure(delegate, e);
|
||||
|
|
|
@ -4,9 +4,13 @@
|
|||
|
||||
package org.mozilla.gecko.tokenserver;
|
||||
|
||||
|
||||
public interface TokenServerClientDelegate {
|
||||
void handleSuccess(TokenServerToken token);
|
||||
void handleFailure(TokenServerException e);
|
||||
void handleError(Exception e);
|
||||
|
||||
/**
|
||||
* Might be called multiple times, in addition to the other terminating handler methods.
|
||||
*/
|
||||
void handleBackoff(int backoffSeconds);
|
||||
}
|