• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 package com.google.android.downloader;
16 
17 import com.google.auto.value.AutoValue;
18 import com.google.common.base.Strings;
19 import com.google.common.net.HttpHeaders;
20 import java.net.HttpURLConnection;
21 import java.util.List;
22 import java.util.Map;
23 import javax.annotation.Nullable;
24 
25 /** Simple container object that contains information about a request error. */
26 @AutoValue
27 public abstract class ErrorDetails {
28   // Additional HTTP status codes not listed in HttpURLConnection.
29   static final int HTTP_TOO_MANY_REQUESTS = 429;
30 
31   /**
32    * Returns the underlying numerical error value associated with this error. The meaning of this
33    * value depends on the network stack in use. Consult the network stack documentation to determine
34    * the meaning of this value. By default this returns the value 0.
35    */
getInternalErrorCode()36   public abstract int getInternalErrorCode();
37 
38   /**
39    * Returns the human-readable error message associated with this error. This message is for
40    * debugging purposes only and should not be parsed programmatically. By default this returns the
41    * empty string.
42    */
getErrorMessage()43   public abstract String getErrorMessage();
44 
45   /**
46    * Returns the HTTP status value associated with this error, if any. If the request succeeded but
47    * the server returned an error (e.g. server error 500), then this field can help classify the
48    * problem. If no HTTP status value is associated with this error, then the value -1 will be
49    * returned instead.
50    */
getHttpStatusCode()51   public abstract int getHttpStatusCode();
52 
53   /**
54    * Returns whether the request that triggered the error is retryable as-is without further changes
55    * to the request or state of the client.
56    *
57    * <p>Whether or not a request can be retried can depend on nuanced details of how an error
58    * occurred, so individual URL engines may make local decisions on whether an error should be
59    * retried. For example, TCP's connection reset error is often caused by a crash of the server
60    * during processing. Resending the exact same request, perhaps after some delay, has a good
61    * chance of hitting a different, healthy server and so in general this error is retryable. On the
62    * other hand, it doesn't make sense to spend resources retrying a HTTP_NOT_FOUND/404 since it
63    * isn't likely that the requested URL will spontaneously appear. A request that fails with
64    * HTTP_UNAUTHORIZED/401 is also not retryable under this definition. Although it makes sense to
65    * send an authenticated version of the failed request, this modification must happen at a higher
66    * layer.
67    */
isRetryableAsIs()68   public abstract boolean isRetryableAsIs();
69 
70   /** Creates a new builder instance for constructing request errors. */
builder()71   public static Builder builder() {
72     return new AutoValue_ErrorDetails.Builder()
73         .setInternalErrorCode(0)
74         .setErrorMessage("")
75         .setHttpStatusCode(-1)
76         .setRetryableAsIs(false);
77   }
78 
79   /**
80    * Create a new error instance for the given value and message. A convenience factory function for
81    * the most common use case.
82    */
create(@ullable String message)83   public static ErrorDetails create(@Nullable String message) {
84     return builder().setErrorMessage(Strings.nullToEmpty(message)).build();
85   }
86 
87   /**
88    * Create a new error instance for an HTTP error response, as represented by the provided {@code
89    * httpResponseCode} and {@code httpResponseHeaders}. The canonical error code and retryability
90    * bit is computed based on the values of the response.
91    */
createFromHttpErrorResponse( int httpResponseCode, Map<String, List<String>> httpResponseHeaders, @Nullable String message)92   public static ErrorDetails createFromHttpErrorResponse(
93       int httpResponseCode,
94       Map<String, List<String>> httpResponseHeaders,
95       @Nullable String message) {
96     return builder()
97         .setErrorMessage(Strings.nullToEmpty(message))
98         .setRetryableAsIs(isRetryableHttpError(httpResponseCode, httpResponseHeaders))
99         .setHttpStatusCode(httpResponseCode)
100         .setInternalErrorCode(httpResponseCode)
101         .build();
102   }
103 
104   /**
105    * Obtains a connection error from a {@link Throwable}. If the throwable is an instance of {@link
106    * RequestException}, then it returns the error instance associated with that exception.
107    * Otherwise, a new connection error is constructed with default values and an error message set
108    * to the value returned by {@link Throwable#getMessage}.
109    */
fromThrowable(Throwable throwable)110   public static ErrorDetails fromThrowable(Throwable throwable) {
111     if (throwable instanceof RequestException) {
112       RequestException requestException = (RequestException) throwable;
113       return requestException.getErrorDetails();
114     } else {
115       return builder().setErrorMessage(Strings.nullToEmpty(throwable.getMessage())).build();
116     }
117   }
118 
119   /**
120    * Determine if a given HTTP error, as represented by an HTTP response code and response headers,
121    * is retryable. See the comment on {@link #isRetryableAsIs} for a longer explanation on how this
122    * related to the canonical error code.
123    */
isRetryableHttpError( int httpCode, Map<String, List<String>> responseHeaders)124   private static boolean isRetryableHttpError(
125       int httpCode, Map<String, List<String>> responseHeaders) {
126     switch (httpCode) {
127       case HttpURLConnection.HTTP_CLIENT_TIMEOUT:
128         // Client timeout means some client-side timeout was encountered. Retrying is safe.
129         return true;
130       case HttpURLConnection.HTTP_ENTITY_TOO_LARGE:
131         // Entity too large means the request was too large for the server to process. Retrying is
132         // safe if the server provided the retry-after header.
133         return responseHeaders.containsKey(HttpHeaders.RETRY_AFTER);
134       case HTTP_TOO_MANY_REQUESTS:
135         // Too many requests means the server is overloaded and is rejecting requests to temporarily
136         // reduce load. See go/rfc/6585. Retrying is safe.
137         return true;
138       case HttpURLConnection.HTTP_UNAVAILABLE:
139         // Unavailable means the server is currently unable to service the request. Retrying is
140         // safe if the server provided the retry-after header.
141         return responseHeaders.containsKey(HttpHeaders.RETRY_AFTER);
142       case HttpURLConnection.HTTP_GATEWAY_TIMEOUT:
143         // Gateway timeout means there was a server timeout somewhere. Retrying is safe.
144         return true;
145       default:
146         // By default, assume any other HTTP error is not retryable.
147         return false;
148     }
149   }
150 
151   /** Builder for creating instances of {@link ErrorDetails}. */
152   @AutoValue.Builder
153   public abstract static class Builder {
154     /** Sets the error value. */
setInternalErrorCode(int internalErrorCode)155     public abstract Builder setInternalErrorCode(int internalErrorCode);
156 
157     /** Sets the error message. */
setErrorMessage(String errorMessage)158     public abstract Builder setErrorMessage(String errorMessage);
159 
160     /** Sets the http status value. */
setHttpStatusCode(int httpStatusCode)161     public abstract Builder setHttpStatusCode(int httpStatusCode);
162 
163     /** Sets whether the error is retryable as-is. */
setRetryableAsIs(boolean retryable)164     public abstract Builder setRetryableAsIs(boolean retryable);
165 
166     /** Builds the request error instance. */
build()167     public abstract ErrorDetails build();
168   }
169 }
170