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 static com.android.volley.toolbox.NetworkUtility.logSlowRequests; 20 21 import android.os.SystemClock; 22 import androidx.annotation.NonNull; 23 import androidx.annotation.Nullable; 24 import androidx.annotation.RestrictTo; 25 import com.android.volley.AsyncNetwork; 26 import com.android.volley.AuthFailureError; 27 import com.android.volley.Header; 28 import com.android.volley.NetworkResponse; 29 import com.android.volley.Request; 30 import com.android.volley.RequestTask; 31 import com.android.volley.VolleyError; 32 import java.io.IOException; 33 import java.io.InputStream; 34 import java.net.HttpURLConnection; 35 import java.util.List; 36 import java.util.Map; 37 import java.util.concurrent.ExecutorService; 38 39 /** A network performing Volley requests over an {@link HttpStack}. */ 40 public class BasicAsyncNetwork extends AsyncNetwork { 41 42 private final AsyncHttpStack mAsyncStack; 43 private final ByteArrayPool mPool; 44 45 /** 46 * @param httpStack HTTP stack to be used 47 * @param pool a buffer pool that improves GC performance in copy operations 48 */ BasicAsyncNetwork(AsyncHttpStack httpStack, ByteArrayPool pool)49 private BasicAsyncNetwork(AsyncHttpStack httpStack, ByteArrayPool pool) { 50 mAsyncStack = httpStack; 51 mPool = pool; 52 } 53 54 @RestrictTo({RestrictTo.Scope.LIBRARY_GROUP}) 55 @Override setBlockingExecutor(ExecutorService executor)56 public void setBlockingExecutor(ExecutorService executor) { 57 super.setBlockingExecutor(executor); 58 mAsyncStack.setBlockingExecutor(executor); 59 } 60 61 @RestrictTo({RestrictTo.Scope.LIBRARY_GROUP}) 62 @Override setNonBlockingExecutor(ExecutorService executor)63 public void setNonBlockingExecutor(ExecutorService executor) { 64 super.setNonBlockingExecutor(executor); 65 mAsyncStack.setNonBlockingExecutor(executor); 66 } 67 68 /* Method to be called after a successful network request */ onRequestSucceeded( final Request<?> request, final long requestStartMs, final HttpResponse httpResponse, final OnRequestComplete callback)69 private void onRequestSucceeded( 70 final Request<?> request, 71 final long requestStartMs, 72 final HttpResponse httpResponse, 73 final OnRequestComplete callback) { 74 final int statusCode = httpResponse.getStatusCode(); 75 final List<Header> responseHeaders = httpResponse.getHeaders(); 76 // Handle cache validation. 77 if (statusCode == HttpURLConnection.HTTP_NOT_MODIFIED) { 78 long requestDuration = SystemClock.elapsedRealtime() - requestStartMs; 79 callback.onSuccess( 80 NetworkUtility.getNotModifiedNetworkResponse( 81 request, requestDuration, responseHeaders)); 82 return; 83 } 84 85 byte[] responseContents = httpResponse.getContentBytes(); 86 if (responseContents == null && httpResponse.getContent() == null) { 87 // Add 0 byte response as a way of honestly representing a 88 // no-content request. 89 responseContents = new byte[0]; 90 } 91 92 if (responseContents != null) { 93 onResponseRead( 94 requestStartMs, 95 statusCode, 96 httpResponse, 97 request, 98 callback, 99 responseHeaders, 100 responseContents); 101 return; 102 } 103 104 // The underlying AsyncHttpStack does not support asynchronous reading of the response into 105 // a byte array, so we need to submit a blocking task to copy the response from the 106 // InputStream instead. 107 final InputStream inputStream = httpResponse.getContent(); 108 getBlockingExecutor() 109 .execute( 110 new ResponseParsingTask<>( 111 inputStream, 112 httpResponse, 113 request, 114 callback, 115 requestStartMs, 116 responseHeaders, 117 statusCode)); 118 } 119 120 /* Method to be called after a failed network request */ onRequestFailed( Request<?> request, OnRequestComplete callback, IOException exception, long requestStartMs, @Nullable HttpResponse httpResponse, @Nullable byte[] responseContents)121 private void onRequestFailed( 122 Request<?> request, 123 OnRequestComplete callback, 124 IOException exception, 125 long requestStartMs, 126 @Nullable HttpResponse httpResponse, 127 @Nullable byte[] responseContents) { 128 try { 129 NetworkUtility.handleException( 130 request, exception, requestStartMs, httpResponse, responseContents); 131 } catch (VolleyError volleyError) { 132 callback.onError(volleyError); 133 return; 134 } 135 performRequest(request, callback); 136 } 137 138 @Override performRequest(final Request<?> request, final OnRequestComplete callback)139 public void performRequest(final Request<?> request, final OnRequestComplete callback) { 140 if (getBlockingExecutor() == null) { 141 throw new IllegalStateException( 142 "mBlockingExecuter must be set before making a request"); 143 } 144 final long requestStartMs = SystemClock.elapsedRealtime(); 145 // Gather headers. 146 final Map<String, String> additionalRequestHeaders = 147 HttpHeaderParser.getCacheHeaders(request.getCacheEntry()); 148 mAsyncStack.executeRequest( 149 request, 150 additionalRequestHeaders, 151 new AsyncHttpStack.OnRequestComplete() { 152 @Override 153 public void onSuccess(HttpResponse httpResponse) { 154 onRequestSucceeded(request, requestStartMs, httpResponse, callback); 155 } 156 157 @Override 158 public void onAuthError(AuthFailureError authFailureError) { 159 callback.onError(authFailureError); 160 } 161 162 @Override 163 public void onError(IOException ioException) { 164 onRequestFailed( 165 request, 166 callback, 167 ioException, 168 requestStartMs, 169 /* httpResponse= */ null, 170 /* responseContents= */ null); 171 } 172 }); 173 } 174 175 /* Helper method that determines what to do after byte[] is received */ onResponseRead( long requestStartMs, int statusCode, HttpResponse httpResponse, Request<?> request, OnRequestComplete callback, List<Header> responseHeaders, byte[] responseContents)176 private void onResponseRead( 177 long requestStartMs, 178 int statusCode, 179 HttpResponse httpResponse, 180 Request<?> request, 181 OnRequestComplete callback, 182 List<Header> responseHeaders, 183 byte[] responseContents) { 184 // if the request is slow, log it. 185 long requestLifetime = SystemClock.elapsedRealtime() - requestStartMs; 186 logSlowRequests(requestLifetime, request, responseContents, statusCode); 187 188 if (statusCode < 200 || statusCode > 299) { 189 onRequestFailed( 190 request, 191 callback, 192 new IOException(), 193 requestStartMs, 194 httpResponse, 195 responseContents); 196 return; 197 } 198 199 callback.onSuccess( 200 new NetworkResponse( 201 statusCode, 202 responseContents, 203 /* notModified= */ false, 204 SystemClock.elapsedRealtime() - requestStartMs, 205 responseHeaders)); 206 } 207 208 private class ResponseParsingTask<T> extends RequestTask<T> { 209 InputStream inputStream; 210 HttpResponse httpResponse; 211 Request<T> request; 212 OnRequestComplete callback; 213 long requestStartMs; 214 List<Header> responseHeaders; 215 int statusCode; 216 ResponseParsingTask( InputStream inputStream, HttpResponse httpResponse, Request<T> request, OnRequestComplete callback, long requestStartMs, List<Header> responseHeaders, int statusCode)217 ResponseParsingTask( 218 InputStream inputStream, 219 HttpResponse httpResponse, 220 Request<T> request, 221 OnRequestComplete callback, 222 long requestStartMs, 223 List<Header> responseHeaders, 224 int statusCode) { 225 super(request); 226 this.inputStream = inputStream; 227 this.httpResponse = httpResponse; 228 this.request = request; 229 this.callback = callback; 230 this.requestStartMs = requestStartMs; 231 this.responseHeaders = responseHeaders; 232 this.statusCode = statusCode; 233 } 234 235 @Override run()236 public void run() { 237 byte[] finalResponseContents; 238 try { 239 finalResponseContents = 240 NetworkUtility.inputStreamToBytes( 241 inputStream, httpResponse.getContentLength(), mPool); 242 } catch (IOException e) { 243 onRequestFailed(request, callback, e, requestStartMs, httpResponse, null); 244 return; 245 } 246 onResponseRead( 247 requestStartMs, 248 statusCode, 249 httpResponse, 250 request, 251 callback, 252 responseHeaders, 253 finalResponseContents); 254 } 255 } 256 257 /** 258 * Builder is used to build an instance of {@link BasicAsyncNetwork} from values configured by 259 * the setters. 260 */ 261 public static class Builder { 262 private static final int DEFAULT_POOL_SIZE = 4096; 263 @NonNull private AsyncHttpStack mAsyncStack; 264 private ByteArrayPool mPool; 265 Builder(@onNull AsyncHttpStack httpStack)266 public Builder(@NonNull AsyncHttpStack httpStack) { 267 mAsyncStack = httpStack; 268 mPool = null; 269 } 270 271 /** 272 * Sets the ByteArrayPool to be used. If not set, it will default to a pool with the default 273 * pool size. 274 */ setPool(ByteArrayPool pool)275 public Builder setPool(ByteArrayPool pool) { 276 mPool = pool; 277 return this; 278 } 279 280 /** Builds the {@link com.android.volley.toolbox.BasicAsyncNetwork} */ build()281 public BasicAsyncNetwork build() { 282 if (mPool == null) { 283 mPool = new ByteArrayPool(DEFAULT_POOL_SIZE); 284 } 285 return new BasicAsyncNetwork(mAsyncStack, mPool); 286 } 287 } 288 } 289