| /* |
| * 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 static com.android.volley.toolbox.NetworkUtility.logSlowRequests; |
| |
| import android.os.SystemClock; |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.annotation.RestrictTo; |
| import com.android.volley.AsyncNetwork; |
| import com.android.volley.AuthFailureError; |
| import com.android.volley.Header; |
| import com.android.volley.NetworkResponse; |
| import com.android.volley.Request; |
| import com.android.volley.RequestTask; |
| import com.android.volley.VolleyError; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.HttpURLConnection; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.ExecutorService; |
| |
| /** A network performing Volley requests over an {@link HttpStack}. */ |
| public class BasicAsyncNetwork extends AsyncNetwork { |
| |
| private final AsyncHttpStack mAsyncStack; |
| private final ByteArrayPool mPool; |
| |
| /** |
| * @param httpStack HTTP stack to be used |
| * @param pool a buffer pool that improves GC performance in copy operations |
| */ |
| private BasicAsyncNetwork(AsyncHttpStack httpStack, ByteArrayPool pool) { |
| mAsyncStack = httpStack; |
| mPool = pool; |
| } |
| |
| @RestrictTo({RestrictTo.Scope.LIBRARY_GROUP}) |
| @Override |
| public void setBlockingExecutor(ExecutorService executor) { |
| super.setBlockingExecutor(executor); |
| mAsyncStack.setBlockingExecutor(executor); |
| } |
| |
| @RestrictTo({RestrictTo.Scope.LIBRARY_GROUP}) |
| @Override |
| public void setNonBlockingExecutor(ExecutorService executor) { |
| super.setNonBlockingExecutor(executor); |
| mAsyncStack.setNonBlockingExecutor(executor); |
| } |
| |
| /* Method to be called after a successful network request */ |
| private void onRequestSucceeded( |
| final Request<?> request, |
| final long requestStartMs, |
| final HttpResponse httpResponse, |
| final OnRequestComplete callback) { |
| final int statusCode = httpResponse.getStatusCode(); |
| final List<Header> responseHeaders = httpResponse.getHeaders(); |
| // Handle cache validation. |
| if (statusCode == HttpURLConnection.HTTP_NOT_MODIFIED) { |
| long requestDuration = SystemClock.elapsedRealtime() - requestStartMs; |
| callback.onSuccess( |
| NetworkUtility.getNotModifiedNetworkResponse( |
| request, requestDuration, responseHeaders)); |
| return; |
| } |
| |
| byte[] responseContents = httpResponse.getContentBytes(); |
| if (responseContents == null && httpResponse.getContent() == null) { |
| // Add 0 byte response as a way of honestly representing a |
| // no-content request. |
| responseContents = new byte[0]; |
| } |
| |
| if (responseContents != null) { |
| onResponseRead( |
| requestStartMs, |
| statusCode, |
| httpResponse, |
| request, |
| callback, |
| responseHeaders, |
| responseContents); |
| return; |
| } |
| |
| // The underlying AsyncHttpStack does not support asynchronous reading of the response into |
| // a byte array, so we need to submit a blocking task to copy the response from the |
| // InputStream instead. |
| final InputStream inputStream = httpResponse.getContent(); |
| getBlockingExecutor() |
| .execute( |
| new ResponseParsingTask<>( |
| inputStream, |
| httpResponse, |
| request, |
| callback, |
| requestStartMs, |
| responseHeaders, |
| statusCode)); |
| } |
| |
| /* Method to be called after a failed network request */ |
| private void onRequestFailed( |
| Request<?> request, |
| OnRequestComplete callback, |
| IOException exception, |
| long requestStartMs, |
| @Nullable HttpResponse httpResponse, |
| @Nullable byte[] responseContents) { |
| try { |
| NetworkUtility.handleException( |
| request, exception, requestStartMs, httpResponse, responseContents); |
| } catch (VolleyError volleyError) { |
| callback.onError(volleyError); |
| return; |
| } |
| performRequest(request, callback); |
| } |
| |
| @Override |
| public void performRequest(final Request<?> request, final OnRequestComplete callback) { |
| if (getBlockingExecutor() == null) { |
| throw new IllegalStateException( |
| "mBlockingExecuter must be set before making a request"); |
| } |
| final long requestStartMs = SystemClock.elapsedRealtime(); |
| // Gather headers. |
| final Map<String, String> additionalRequestHeaders = |
| HttpHeaderParser.getCacheHeaders(request.getCacheEntry()); |
| mAsyncStack.executeRequest( |
| request, |
| additionalRequestHeaders, |
| new AsyncHttpStack.OnRequestComplete() { |
| @Override |
| public void onSuccess(HttpResponse httpResponse) { |
| onRequestSucceeded(request, requestStartMs, httpResponse, callback); |
| } |
| |
| @Override |
| public void onAuthError(AuthFailureError authFailureError) { |
| callback.onError(authFailureError); |
| } |
| |
| @Override |
| public void onError(IOException ioException) { |
| onRequestFailed( |
| request, |
| callback, |
| ioException, |
| requestStartMs, |
| /* httpResponse= */ null, |
| /* responseContents= */ null); |
| } |
| }); |
| } |
| |
| /* Helper method that determines what to do after byte[] is received */ |
| private void onResponseRead( |
| long requestStartMs, |
| int statusCode, |
| HttpResponse httpResponse, |
| Request<?> request, |
| OnRequestComplete callback, |
| List<Header> responseHeaders, |
| byte[] responseContents) { |
| // if the request is slow, log it. |
| long requestLifetime = SystemClock.elapsedRealtime() - requestStartMs; |
| logSlowRequests(requestLifetime, request, responseContents, statusCode); |
| |
| if (statusCode < 200 || statusCode > 299) { |
| onRequestFailed( |
| request, |
| callback, |
| new IOException(), |
| requestStartMs, |
| httpResponse, |
| responseContents); |
| return; |
| } |
| |
| callback.onSuccess( |
| new NetworkResponse( |
| statusCode, |
| responseContents, |
| /* notModified= */ false, |
| SystemClock.elapsedRealtime() - requestStartMs, |
| responseHeaders)); |
| } |
| |
| private class ResponseParsingTask<T> extends RequestTask<T> { |
| InputStream inputStream; |
| HttpResponse httpResponse; |
| Request<T> request; |
| OnRequestComplete callback; |
| long requestStartMs; |
| List<Header> responseHeaders; |
| int statusCode; |
| |
| ResponseParsingTask( |
| InputStream inputStream, |
| HttpResponse httpResponse, |
| Request<T> request, |
| OnRequestComplete callback, |
| long requestStartMs, |
| List<Header> responseHeaders, |
| int statusCode) { |
| super(request); |
| this.inputStream = inputStream; |
| this.httpResponse = httpResponse; |
| this.request = request; |
| this.callback = callback; |
| this.requestStartMs = requestStartMs; |
| this.responseHeaders = responseHeaders; |
| this.statusCode = statusCode; |
| } |
| |
| @Override |
| public void run() { |
| byte[] finalResponseContents; |
| try { |
| finalResponseContents = |
| NetworkUtility.inputStreamToBytes( |
| inputStream, httpResponse.getContentLength(), mPool); |
| } catch (IOException e) { |
| onRequestFailed(request, callback, e, requestStartMs, httpResponse, null); |
| return; |
| } |
| onResponseRead( |
| requestStartMs, |
| statusCode, |
| httpResponse, |
| request, |
| callback, |
| responseHeaders, |
| finalResponseContents); |
| } |
| } |
| |
| /** |
| * Builder is used to build an instance of {@link BasicAsyncNetwork} from values configured by |
| * the setters. |
| */ |
| public static class Builder { |
| private static final int DEFAULT_POOL_SIZE = 4096; |
| @NonNull private AsyncHttpStack mAsyncStack; |
| private ByteArrayPool mPool; |
| |
| public Builder(@NonNull AsyncHttpStack httpStack) { |
| mAsyncStack = httpStack; |
| mPool = null; |
| } |
| |
| /** |
| * Sets the ByteArrayPool to be used. If not set, it will default to a pool with the default |
| * pool size. |
| */ |
| public Builder setPool(ByteArrayPool pool) { |
| mPool = pool; |
| return this; |
| } |
| |
| /** Builds the {@link com.android.volley.toolbox.BasicAsyncNetwork} */ |
| public BasicAsyncNetwork build() { |
| if (mPool == null) { |
| mPool = new ByteArrayPool(DEFAULT_POOL_SIZE); |
| } |
| return new BasicAsyncNetwork(mAsyncStack, mPool); |
| } |
| } |
| } |