blob: 55892a037809958ce209e81e073c399368037fb3 [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 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);
}
}
}