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