diff options
Diffstat (limited to 'core/src/main/java/com/android/volley/toolbox/NetworkUtility.java')
-rw-r--r-- | core/src/main/java/com/android/volley/toolbox/NetworkUtility.java | 206 |
1 files changed, 206 insertions, 0 deletions
diff --git a/core/src/main/java/com/android/volley/toolbox/NetworkUtility.java b/core/src/main/java/com/android/volley/toolbox/NetworkUtility.java new file mode 100644 index 0000000..58a3bb3 --- /dev/null +++ b/core/src/main/java/com/android/volley/toolbox/NetworkUtility.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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.android.volley.toolbox; + +import android.os.SystemClock; +import androidx.annotation.Nullable; +import com.android.volley.AuthFailureError; +import com.android.volley.Cache; +import com.android.volley.ClientError; +import com.android.volley.Header; +import com.android.volley.NetworkError; +import com.android.volley.NetworkResponse; +import com.android.volley.NoConnectionError; +import com.android.volley.Request; +import com.android.volley.RetryPolicy; +import com.android.volley.ServerError; +import com.android.volley.TimeoutError; +import com.android.volley.VolleyError; +import com.android.volley.VolleyLog; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.SocketTimeoutException; +import java.util.List; + +/** + * Utility class for methods that are shared between {@link BasicNetwork} and {@link + * BasicAsyncNetwork} + */ +final class NetworkUtility { + private static final int SLOW_REQUEST_THRESHOLD_MS = 3000; + + private NetworkUtility() {} + + /** Logs requests that took over SLOW_REQUEST_THRESHOLD_MS to complete. */ + static void logSlowRequests( + long requestLifetime, Request<?> request, byte[] responseContents, int statusCode) { + if (VolleyLog.DEBUG || requestLifetime > SLOW_REQUEST_THRESHOLD_MS) { + VolleyLog.d( + "HTTP response for request=<%s> [lifetime=%d], [size=%s], " + + "[rc=%d], [retryCount=%s]", + request, + requestLifetime, + responseContents != null ? responseContents.length : "null", + statusCode, + request.getRetryPolicy().getCurrentRetryCount()); + } + } + + static NetworkResponse getNotModifiedNetworkResponse( + Request<?> request, long requestDuration, List<Header> responseHeaders) { + Cache.Entry entry = request.getCacheEntry(); + if (entry == null) { + return new NetworkResponse( + HttpURLConnection.HTTP_NOT_MODIFIED, + /* data= */ null, + /* notModified= */ true, + requestDuration, + responseHeaders); + } + // Combine cached and response headers so the response will be complete. + List<Header> combinedHeaders = HttpHeaderParser.combineHeaders(responseHeaders, entry); + return new NetworkResponse( + HttpURLConnection.HTTP_NOT_MODIFIED, + entry.data, + /* notModified= */ true, + requestDuration, + combinedHeaders); + } + + /** Reads the contents of an InputStream into a byte[]. */ + static byte[] inputStreamToBytes(InputStream in, int contentLength, ByteArrayPool pool) + throws IOException { + PoolingByteArrayOutputStream bytes = new PoolingByteArrayOutputStream(pool, contentLength); + byte[] buffer = null; + try { + buffer = pool.getBuf(1024); + int count; + while ((count = in.read(buffer)) != -1) { + bytes.write(buffer, 0, count); + } + return bytes.toByteArray(); + } finally { + try { + // Close the InputStream and release the resources by "consuming the content". + if (in != null) { + in.close(); + } + } catch (IOException e) { + // This can happen if there was an exception above that left the stream in + // an invalid state. + VolleyLog.v("Error occurred when closing InputStream"); + } + pool.returnBuf(buffer); + bytes.close(); + } + } + + /** + * Attempts to prepare the request for a retry. If there are no more attempts remaining in the + * request's retry policy, the provided exception is thrown. + * + * <p>Must be invoked from a background thread, as client implementations of RetryPolicy#retry + * may make blocking calls. + * + * @param request The request to use. + */ + static void attemptRetryOnException(final Request<?> request, final RetryInfo retryInfo) + throws VolleyError { + final RetryPolicy retryPolicy = request.getRetryPolicy(); + final int oldTimeout = request.getTimeoutMs(); + try { + retryPolicy.retry(retryInfo.errorToRetry); + } catch (VolleyError e) { + request.addMarker( + String.format( + "%s-timeout-giveup [timeout=%s]", retryInfo.logPrefix, oldTimeout)); + throw e; + } + request.addMarker(String.format("%s-retry [timeout=%s]", retryInfo.logPrefix, oldTimeout)); + } + + static class RetryInfo { + private final String logPrefix; + private final VolleyError errorToRetry; + + private RetryInfo(String logPrefix, VolleyError errorToRetry) { + this.logPrefix = logPrefix; + this.errorToRetry = errorToRetry; + } + } + + /** + * Based on the exception thrown, decides whether to attempt to retry, or to throw the error. + * + * <p>If this method returns without throwing, {@link #attemptRetryOnException} should be called + * with the provided {@link RetryInfo} to consult the client's retry policy. + */ + static RetryInfo shouldRetryException( + Request<?> request, + IOException exception, + long requestStartMs, + @Nullable HttpResponse httpResponse, + @Nullable byte[] responseContents) + throws VolleyError { + if (exception instanceof SocketTimeoutException) { + return new RetryInfo("socket", new TimeoutError()); + } else if (exception instanceof MalformedURLException) { + throw new RuntimeException("Bad URL " + request.getUrl(), exception); + } else { + int statusCode; + if (httpResponse != null) { + statusCode = httpResponse.getStatusCode(); + } else { + if (request.shouldRetryConnectionErrors()) { + return new RetryInfo("connection", new NoConnectionError()); + } + throw new NoConnectionError(exception); + } + VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl()); + NetworkResponse networkResponse; + if (responseContents != null) { + List<Header> responseHeaders; + responseHeaders = httpResponse.getHeaders(); + networkResponse = + new NetworkResponse( + statusCode, + responseContents, + /* notModified= */ false, + SystemClock.elapsedRealtime() - requestStartMs, + responseHeaders); + if (statusCode == HttpURLConnection.HTTP_UNAUTHORIZED + || statusCode == HttpURLConnection.HTTP_FORBIDDEN) { + return new RetryInfo("auth", new AuthFailureError(networkResponse)); + } + if (statusCode >= 400 && statusCode <= 499) { + // Don't retry other client errors. + throw new ClientError(networkResponse); + } + if (statusCode >= 500 && statusCode <= 599) { + if (request.shouldRetryServerErrors()) { + return new RetryInfo("server", new ServerError(networkResponse)); + } + } + // Server error and client has opted out of retries, or 3xx. No reason to retry. + throw new ServerError(networkResponse); + } + return new RetryInfo("network", new NetworkError()); + } + } +} |