blob: 58a3bb3a116b01eed031fd3929a80c3bc80d15ec [file] [log] [blame]
/*
* 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());
}
}
}