1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.volley.toolbox; 18 19 import android.os.SystemClock; 20 import androidx.annotation.Nullable; 21 import com.android.volley.AuthFailureError; 22 import com.android.volley.Cache; 23 import com.android.volley.ClientError; 24 import com.android.volley.Header; 25 import com.android.volley.NetworkError; 26 import com.android.volley.NetworkResponse; 27 import com.android.volley.NoConnectionError; 28 import com.android.volley.Request; 29 import com.android.volley.RetryPolicy; 30 import com.android.volley.ServerError; 31 import com.android.volley.TimeoutError; 32 import com.android.volley.VolleyError; 33 import com.android.volley.VolleyLog; 34 import java.io.IOException; 35 import java.io.InputStream; 36 import java.net.HttpURLConnection; 37 import java.net.MalformedURLException; 38 import java.net.SocketTimeoutException; 39 import java.util.List; 40 41 /** 42 * Utility class for methods that are shared between {@link BasicNetwork} and {@link 43 * BasicAsyncNetwork} 44 */ 45 public final class NetworkUtility { 46 private static final int SLOW_REQUEST_THRESHOLD_MS = 3000; 47 NetworkUtility()48 private NetworkUtility() {} 49 50 /** Logs requests that took over SLOW_REQUEST_THRESHOLD_MS to complete. */ logSlowRequests( long requestLifetime, Request<?> request, byte[] responseContents, int statusCode)51 static void logSlowRequests( 52 long requestLifetime, Request<?> request, byte[] responseContents, int statusCode) { 53 if (VolleyLog.DEBUG || requestLifetime > SLOW_REQUEST_THRESHOLD_MS) { 54 VolleyLog.d( 55 "HTTP response for request=<%s> [lifetime=%d], [size=%s], " 56 + "[rc=%d], [retryCount=%s]", 57 request, 58 requestLifetime, 59 responseContents != null ? responseContents.length : "null", 60 statusCode, 61 request.getRetryPolicy().getCurrentRetryCount()); 62 } 63 } 64 getNotModifiedNetworkResponse( Request<?> request, long requestDuration, List<Header> responseHeaders)65 static NetworkResponse getNotModifiedNetworkResponse( 66 Request<?> request, long requestDuration, List<Header> responseHeaders) { 67 Cache.Entry entry = request.getCacheEntry(); 68 if (entry == null) { 69 return new NetworkResponse( 70 HttpURLConnection.HTTP_NOT_MODIFIED, 71 /* data= */ null, 72 /* notModified= */ true, 73 requestDuration, 74 responseHeaders); 75 } 76 // Combine cached and response headers so the response will be complete. 77 List<Header> combinedHeaders = HttpHeaderParser.combineHeaders(responseHeaders, entry); 78 return new NetworkResponse( 79 HttpURLConnection.HTTP_NOT_MODIFIED, 80 entry.data, 81 /* notModified= */ true, 82 requestDuration, 83 combinedHeaders); 84 } 85 86 /** Reads the contents of an InputStream into a byte[]. */ inputStreamToBytes(InputStream in, int contentLength, ByteArrayPool pool)87 static byte[] inputStreamToBytes(InputStream in, int contentLength, ByteArrayPool pool) 88 throws IOException { 89 PoolingByteArrayOutputStream bytes = new PoolingByteArrayOutputStream(pool, contentLength); 90 byte[] buffer = null; 91 try { 92 buffer = pool.getBuf(1024); 93 int count; 94 while ((count = in.read(buffer)) != -1) { 95 bytes.write(buffer, 0, count); 96 } 97 return bytes.toByteArray(); 98 } finally { 99 try { 100 // Close the InputStream and release the resources by "consuming the content". 101 if (in != null) { 102 in.close(); 103 } 104 } catch (IOException e) { 105 // This can happen if there was an exception above that left the stream in 106 // an invalid state. 107 VolleyLog.v("Error occurred when closing InputStream"); 108 } 109 pool.returnBuf(buffer); 110 bytes.close(); 111 } 112 } 113 114 /** 115 * Attempts to prepare the request for a retry. If there are no more attempts remaining in the 116 * request's retry policy, a timeout exception is thrown. 117 * 118 * @param request The request to use. 119 */ attemptRetryOnException( final String logPrefix, final Request<?> request, final VolleyError exception)120 private static void attemptRetryOnException( 121 final String logPrefix, final Request<?> request, final VolleyError exception) 122 throws VolleyError { 123 final RetryPolicy retryPolicy = request.getRetryPolicy(); 124 final int oldTimeout = request.getTimeoutMs(); 125 try { 126 retryPolicy.retry(exception); 127 } catch (VolleyError e) { 128 request.addMarker( 129 String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout)); 130 throw e; 131 } 132 request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout)); 133 } 134 135 /** 136 * Based on the exception thrown, decides whether to attempt to retry, or to throw the error. 137 * Also handles logging. 138 */ handleException( Request<?> request, IOException exception, long requestStartMs, @Nullable HttpResponse httpResponse, @Nullable byte[] responseContents)139 static void handleException( 140 Request<?> request, 141 IOException exception, 142 long requestStartMs, 143 @Nullable HttpResponse httpResponse, 144 @Nullable byte[] responseContents) 145 throws VolleyError { 146 if (exception instanceof SocketTimeoutException) { 147 attemptRetryOnException("socket", request, new TimeoutError()); 148 } else if (exception instanceof MalformedURLException) { 149 throw new RuntimeException("Bad URL " + request.getUrl(), exception); 150 } else { 151 int statusCode; 152 if (httpResponse != null) { 153 statusCode = httpResponse.getStatusCode(); 154 } else { 155 if (request.shouldRetryConnectionErrors()) { 156 attemptRetryOnException("connection", request, new NoConnectionError()); 157 return; 158 } else { 159 throw new NoConnectionError(exception); 160 } 161 } 162 VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl()); 163 NetworkResponse networkResponse; 164 if (responseContents != null) { 165 List<Header> responseHeaders; 166 responseHeaders = httpResponse.getHeaders(); 167 networkResponse = 168 new NetworkResponse( 169 statusCode, 170 responseContents, 171 /* notModified= */ false, 172 SystemClock.elapsedRealtime() - requestStartMs, 173 responseHeaders); 174 if (statusCode == HttpURLConnection.HTTP_UNAUTHORIZED 175 || statusCode == HttpURLConnection.HTTP_FORBIDDEN) { 176 attemptRetryOnException("auth", request, new AuthFailureError(networkResponse)); 177 } else if (statusCode >= 400 && statusCode <= 499) { 178 // Don't retry other client errors. 179 throw new ClientError(networkResponse); 180 } else if (statusCode >= 500 && statusCode <= 599) { 181 if (request.shouldRetryServerErrors()) { 182 attemptRetryOnException( 183 "server", request, new ServerError(networkResponse)); 184 } else { 185 throw new ServerError(networkResponse); 186 } 187 } else { 188 // 3xx? No reason to retry. 189 throw new ServerError(networkResponse); 190 } 191 } else { 192 attemptRetryOnException("network", request, new NetworkError()); 193 } 194 } 195 } 196 } 197