diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-04-08 16:02:05 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-04-08 16:02:05 +0000 |
commit | 731235a5ac85d8b69ae743f7e626d825a24a1361 (patch) | |
tree | 353cb45ae683a91486dc4266c97e8aa55b52189a | |
parent | 6ad29c646be387e7c477efd01c3ea2ea2c8d3407 (diff) | |
parent | 5c532a6de0f207c1d42c3ef5578b82f46c8641d1 (diff) | |
download | volley-android12-mainline-tzdata2-release.tar.gz |
Snap for 8426163 from 5c532a6de0f207c1d42c3ef5578b82f46c8641d1 to mainline-tzdata2-releaseandroid-mainline-12.0.0_r112aml_tz2_305400500aml_tz2_305400300aml_tz2_305400100aml_tz2_304500300aml_tz2_303900110aml_tz2_303900102aml_tz2_303800002aml_tz2_303800001aml_tz2_303200001android12-mainline-tzdata2-releaseaml_tz2_305400100
Change-Id: Id96e2b9096e7992762a888df0752e49a498f3caf
43 files changed, 635 insertions, 4166 deletions
@@ -14,38 +14,12 @@ // limitations under the License. // -package { - default_applicable_licenses: ["external_volley_license"], -} - -// Added automatically by a large-scale-change -// http://go/android-license-faq -license { - name: "external_volley_license", - visibility: [":__subpackages__"], - license_kinds: [ - "SPDX-license-identifier-Apache-2.0", - ], - license_text: [ - "LICENSE", - ], -} - java_library { name: "volley", - sdk_version: "28", - min_sdk_version: "8", + sdk_version: "17", srcs: ["src/main/java/**/*.java"], - // Exclude Cronet support for now. Can be enabled later if/when Cronet is made available as a - // compilation dependency for Volley clients. - exclude_srcs: ["src/main/java/com/android/volley/cronet/**/*"], - - libs: [ - // Only needed at compile-time. - "androidx.annotation_annotation", - - "org.apache.http.legacy", - ], + // Only needed at compile-time. + libs: ["androidx.annotation_annotation"], } diff --git a/METADATA b/METADATA deleted file mode 100644 index d97975c..0000000 --- a/METADATA +++ /dev/null @@ -1,3 +0,0 @@ -third_party { - license_type: NOTICE -} @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/bintray.gradle b/bintray.gradle index b642b41..9007c31 100644 --- a/bintray.gradle +++ b/bintray.gradle @@ -43,12 +43,6 @@ publishing { version project.version pom { packaging 'aar' - licenses { - license { - name = "The Apache License, Version 2.0" - url = "http://www.apache.org/licenses/LICENSE-2.0.txt" - } - } } // Release AAR, Sources, and JavaDoc diff --git a/build.gradle b/build.gradle index 544771c..828a192 100644 --- a/build.gradle +++ b/build.gradle @@ -62,6 +62,7 @@ android { buildToolsVersion = '28.0.3' defaultConfig { + // Keep in sync with src/main/AndroidManifest.xml minSdkVersion 8 } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 104b82e..9d8a946 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-bin.zip diff --git a/rules.gradle b/rules.gradle index e0aef80..fd660cd 100644 --- a/rules.gradle +++ b/rules.gradle @@ -21,16 +21,14 @@ tasks.withType(JavaCompile) { dependencies { implementation "androidx.annotation:annotation:1.0.1" - compileOnly "org.chromium.net:cronet-embedded:76.3809.111" } // Check if the android plugin version supports unit testing. -if (configurations.findByName("testImplementation")) { +if (configurations.findByName("testCompile")) { dependencies { - testImplementation "org.chromium.net:cronet-embedded:76.3809.111" - testImplementation "junit:junit:4.12" - testImplementation "org.hamcrest:hamcrest-library:1.3" - testImplementation "org.mockito:mockito-core:2.19.0" - testImplementation "org.robolectric:robolectric:3.4.2" + testCompile "junit:junit:4.12" + testCompile "org.hamcrest:hamcrest-library:1.3" + testCompile "org.mockito:mockito-core:2.19.0" + testCompile "org.robolectric:robolectric:3.4.2" } } diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index ba3a2a7..da8d33e 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -1,2 +1,15 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="com.android.volley" /> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + package="com.android.volley" + android:versionCode="1" + android:versionName="1.0" > + + <!-- Keep in sync with build.gradle --> + <uses-sdk + android:minSdkVersion="8" + tools:ignore="GradleOverrides" /> + + <application /> + +</manifest> diff --git a/src/main/java/com/android/volley/AsyncCache.java b/src/main/java/com/android/volley/AsyncCache.java deleted file mode 100644 index 3cddb4b..0000000 --- a/src/main/java/com/android/volley/AsyncCache.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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; - -import androidx.annotation.Nullable; - -/** Asynchronous equivalent to the {@link Cache} interface. */ -public abstract class AsyncCache { - - public interface OnGetCompleteCallback { - /** - * Invoked when the read from the cache is complete. - * - * @param entry The entry read from the cache, or null if the read failed or the key did not - * exist in the cache. - */ - void onGetComplete(@Nullable Cache.Entry entry); - } - - /** - * Retrieves an entry from the cache and sends it back through the {@link - * OnGetCompleteCallback#onGetComplete} function - * - * @param key Cache key - * @param callback Callback that will be notified when the information has been retrieved - */ - public abstract void get(String key, OnGetCompleteCallback callback); - - public interface OnWriteCompleteCallback { - /** Invoked when the cache operation is complete */ - void onWriteComplete(); - } - - /** - * Writes a {@link Cache.Entry} to the cache, and calls {@link - * OnWriteCompleteCallback#onWriteComplete} after the operation is finished. - * - * @param key Cache key - * @param entry The entry to be written to the cache - * @param callback Callback that will be notified when the information has been written - */ - public abstract void put(String key, Cache.Entry entry, OnWriteCompleteCallback callback); - - /** - * Clears the cache. Deletes all cached files from disk. Calls {@link - * OnWriteCompleteCallback#onWriteComplete} after the operation is finished. - */ - public abstract void clear(OnWriteCompleteCallback callback); - - /** - * Initializes the cache and calls {@link OnWriteCompleteCallback#onWriteComplete} after the - * operation is finished. - */ - public abstract void initialize(OnWriteCompleteCallback callback); - - /** - * Invalidates an entry in the cache and calls {@link OnWriteCompleteCallback#onWriteComplete} - * after the operation is finished. - * - * @param key Cache key - * @param fullExpire True to fully expire the entry, false to soft expire - * @param callback Callback that's invoked once the entry has been invalidated - */ - public abstract void invalidate( - String key, boolean fullExpire, OnWriteCompleteCallback callback); - - /** - * Removes a {@link Cache.Entry} from the cache, and calls {@link - * OnWriteCompleteCallback#onWriteComplete} after the operation is finished. - * - * @param key Cache key - * @param callback Callback that's invoked once the entry has been removed - */ - public abstract void remove(String key, OnWriteCompleteCallback callback); -} diff --git a/src/main/java/com/android/volley/AsyncNetwork.java b/src/main/java/com/android/volley/AsyncNetwork.java deleted file mode 100644 index ad19c03..0000000 --- a/src/main/java/com/android/volley/AsyncNetwork.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * 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; - -import androidx.annotation.RestrictTo; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.atomic.AtomicReference; - -/** An asynchronous implementation of {@link Network} to perform requests. */ -public abstract class AsyncNetwork implements Network { - private ExecutorService mBlockingExecutor; - private ExecutorService mNonBlockingExecutor; - private ScheduledExecutorService mNonBlockingScheduledExecutor; - - protected AsyncNetwork() {} - - /** Interface for callback to be called after request is processed. */ - public interface OnRequestComplete { - /** Method to be called after successful network request. */ - void onSuccess(NetworkResponse networkResponse); - - /** Method to be called after unsuccessful network request. */ - void onError(VolleyError volleyError); - } - - /** - * Non-blocking method to perform the specified request. - * - * @param request Request to process - * @param callback to be called once NetworkResponse is received - */ - public abstract void performRequest(Request<?> request, OnRequestComplete callback); - - /** - * Blocking method to perform network request. - * - * @param request Request to process - * @return response retrieved from the network - * @throws VolleyError in the event of an error - */ - @Override - public NetworkResponse performRequest(Request<?> request) throws VolleyError { - final CountDownLatch latch = new CountDownLatch(1); - final AtomicReference<NetworkResponse> response = new AtomicReference<>(); - final AtomicReference<VolleyError> error = new AtomicReference<>(); - performRequest( - request, - new OnRequestComplete() { - @Override - public void onSuccess(NetworkResponse networkResponse) { - response.set(networkResponse); - latch.countDown(); - } - - @Override - public void onError(VolleyError volleyError) { - error.set(volleyError); - latch.countDown(); - } - }); - try { - latch.await(); - } catch (InterruptedException e) { - VolleyLog.e(e, "while waiting for CountDownLatch"); - Thread.currentThread().interrupt(); - throw new VolleyError(e); - } - - if (response.get() != null) { - return response.get(); - } else if (error.get() != null) { - throw error.get(); - } else { - throw new VolleyError("Neither response entry was set"); - } - } - - /** - * This method sets the non blocking executor to be used by the network for non-blocking tasks. - * - * <p>This method must be called before performing any requests. - */ - @RestrictTo({RestrictTo.Scope.LIBRARY_GROUP}) - public void setNonBlockingExecutor(ExecutorService executor) { - mNonBlockingExecutor = executor; - } - - /** - * This method sets the blocking executor to be used by the network for potentially blocking - * tasks. - * - * <p>This method must be called before performing any requests. - */ - @RestrictTo({RestrictTo.Scope.LIBRARY_GROUP}) - public void setBlockingExecutor(ExecutorService executor) { - mBlockingExecutor = executor; - } - - /** - * This method sets the scheduled executor to be used by the network for non-blocking tasks to - * be scheduled. - * - * <p>This method must be called before performing any requests. - */ - @RestrictTo({RestrictTo.Scope.LIBRARY_GROUP}) - public void setNonBlockingScheduledExecutor(ScheduledExecutorService executor) { - mNonBlockingScheduledExecutor = executor; - } - - /** Gets blocking executor to perform any potentially blocking tasks. */ - protected ExecutorService getBlockingExecutor() { - return mBlockingExecutor; - } - - /** Gets non-blocking executor to perform any non-blocking tasks. */ - protected ExecutorService getNonBlockingExecutor() { - return mNonBlockingExecutor; - } - - /** Gets scheduled executor to perform any non-blocking tasks that need to be scheduled. */ - protected ScheduledExecutorService getNonBlockingScheduledExecutor() { - return mNonBlockingScheduledExecutor; - } -} diff --git a/src/main/java/com/android/volley/AsyncRequestQueue.java b/src/main/java/com/android/volley/AsyncRequestQueue.java deleted file mode 100644 index 3754866..0000000 --- a/src/main/java/com/android/volley/AsyncRequestQueue.java +++ /dev/null @@ -1,626 +0,0 @@ -/* - * 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; - -import android.os.Handler; -import android.os.Looper; -import android.os.SystemClock; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import com.android.volley.AsyncCache.OnGetCompleteCallback; -import com.android.volley.AsyncNetwork.OnRequestComplete; -import com.android.volley.Cache.Entry; -import java.net.HttpURLConnection; -import java.util.Comparator; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.PriorityBlockingQueue; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -/** - * An asynchronous request dispatch queue. - * - * <p>Add requests to the queue with {@link #add(Request)}. Once completed, responses will be - * delivered on the main thread (unless a custom {@link ResponseDelivery} has been provided) - */ -public class AsyncRequestQueue extends RequestQueue { - /** Default number of blocking threads to start. */ - private static final int DEFAULT_BLOCKING_THREAD_POOL_SIZE = 4; - - /** - * AsyncCache used to retrieve and store responses. - * - * <p>{@code null} indicates use of blocking Cache. - */ - @Nullable private final AsyncCache mAsyncCache; - - /** AsyncNetwork used to perform nework requests. */ - private final AsyncNetwork mNetwork; - - /** Executor for non-blocking tasks. */ - private ExecutorService mNonBlockingExecutor; - - /** Executor to be used for non-blocking tasks that need to be scheduled. */ - private ScheduledExecutorService mNonBlockingScheduledExecutor; - - /** - * Executor for blocking tasks. - * - * <p>Some tasks in handling requests may not be easy to implement in a non-blocking way, such - * as reading or parsing the response data. This executor is used to run these tasks. - */ - private ExecutorService mBlockingExecutor; - - /** - * This interface may be used by advanced applications to provide custom executors according to - * their needs. Apps must create ExecutorServices dynamically given a blocking queue rather than - * providing them directly so that Volley can provide a PriorityQueue which will prioritize - * requests according to Request#getPriority. - */ - private ExecutorFactory mExecutorFactory; - - /** Manage list of waiting requests and de-duplicate requests with same cache key. */ - private final WaitingRequestManager mWaitingRequestManager = new WaitingRequestManager(this); - - /** - * Sets all the variables, but processing does not begin until {@link #start()} is called. - * - * @param cache to use for persisting responses to disk. If an AsyncCache was provided, then - * this will be a {@link ThrowingCache} - * @param network to perform HTTP requests - * @param asyncCache to use for persisting responses to disk. May be null to indicate use of - * blocking cache - * @param responseDelivery interface for posting responses and errors - * @param executorFactory Interface to be used to provide custom executors according to the - * users needs. - */ - private AsyncRequestQueue( - Cache cache, - AsyncNetwork network, - @Nullable AsyncCache asyncCache, - ResponseDelivery responseDelivery, - ExecutorFactory executorFactory) { - super(cache, network, /* threadPoolSize= */ 0, responseDelivery); - mAsyncCache = asyncCache; - mNetwork = network; - mExecutorFactory = executorFactory; - } - - /** Sets the executors and initializes the cache. */ - @Override - public void start() { - stop(); // Make sure any currently running threads are stopped - - // Create blocking / non-blocking executors and set them in the network and stack. - mNonBlockingExecutor = mExecutorFactory.createNonBlockingExecutor(getBlockingQueue()); - mBlockingExecutor = mExecutorFactory.createBlockingExecutor(getBlockingQueue()); - mNonBlockingScheduledExecutor = mExecutorFactory.createNonBlockingScheduledExecutor(); - mNetwork.setBlockingExecutor(mBlockingExecutor); - mNetwork.setNonBlockingExecutor(mNonBlockingExecutor); - mNetwork.setNonBlockingScheduledExecutor(mNonBlockingScheduledExecutor); - - mNonBlockingExecutor.execute( - new Runnable() { - @Override - public void run() { - // This is intentionally blocking, because we don't want to process any - // requests until the cache is initialized. - if (mAsyncCache != null) { - final CountDownLatch latch = new CountDownLatch(1); - mAsyncCache.initialize( - new AsyncCache.OnWriteCompleteCallback() { - @Override - public void onWriteComplete() { - latch.countDown(); - } - }); - try { - latch.await(); - } catch (InterruptedException e) { - VolleyLog.e( - e, "Thread was interrupted while initializing the cache."); - Thread.currentThread().interrupt(); - throw new RuntimeException(e); - } - } else { - getCache().initialize(); - } - } - }); - } - - /** Shuts down and nullifies both executors */ - @Override - public void stop() { - if (mNonBlockingExecutor != null) { - mNonBlockingExecutor.shutdownNow(); - mNonBlockingExecutor = null; - } - if (mBlockingExecutor != null) { - mBlockingExecutor.shutdownNow(); - mBlockingExecutor = null; - } - if (mNonBlockingScheduledExecutor != null) { - mNonBlockingScheduledExecutor.shutdownNow(); - mNonBlockingScheduledExecutor = null; - } - } - - /** Begins the request by sending it to the Cache or Network. */ - @Override - <T> void beginRequest(Request<T> request) { - // If the request is uncacheable, send it over the network. - if (request.shouldCache()) { - if (mAsyncCache != null) { - mNonBlockingExecutor.execute(new CacheTask<>(request)); - } else { - mBlockingExecutor.execute(new CacheTask<>(request)); - } - } else { - sendRequestOverNetwork(request); - } - } - - @Override - <T> void sendRequestOverNetwork(Request<T> request) { - mNonBlockingExecutor.execute(new NetworkTask<>(request)); - } - - /** Runnable that gets an entry from the cache. */ - private class CacheTask<T> extends RequestTask<T> { - CacheTask(Request<T> request) { - super(request); - } - - @Override - public void run() { - // If the request has been canceled, don't bother dispatching it. - if (mRequest.isCanceled()) { - mRequest.finish("cache-discard-canceled"); - return; - } - - mRequest.addMarker("cache-queue-take"); - - // Attempt to retrieve this item from cache. - if (mAsyncCache != null) { - mAsyncCache.get( - mRequest.getCacheKey(), - new OnGetCompleteCallback() { - @Override - public void onGetComplete(Entry entry) { - handleEntry(entry, mRequest); - } - }); - } else { - Entry entry = getCache().get(mRequest.getCacheKey()); - handleEntry(entry, mRequest); - } - } - } - - /** Helper method that handles the cache entry after getting it from the Cache. */ - private void handleEntry(final Entry entry, final Request<?> mRequest) { - if (entry == null) { - mRequest.addMarker("cache-miss"); - // Cache miss; send off to the network dispatcher. - if (!mWaitingRequestManager.maybeAddToWaitingRequests(mRequest)) { - sendRequestOverNetwork(mRequest); - } - return; - } - - // If it is completely expired, just send it to the network. - if (entry.isExpired()) { - mRequest.addMarker("cache-hit-expired"); - mRequest.setCacheEntry(entry); - if (!mWaitingRequestManager.maybeAddToWaitingRequests(mRequest)) { - sendRequestOverNetwork(mRequest); - } - return; - } - - // We have a cache hit; parse its data for delivery back to the request. - mBlockingExecutor.execute(new CacheParseTask<>(mRequest, entry)); - } - - private class CacheParseTask<T> extends RequestTask<T> { - Cache.Entry entry; - - CacheParseTask(Request<T> request, Cache.Entry entry) { - super(request); - this.entry = entry; - } - - @Override - public void run() { - mRequest.addMarker("cache-hit"); - Response<?> response = - mRequest.parseNetworkResponse( - new NetworkResponse( - HttpURLConnection.HTTP_OK, - entry.data, - /* notModified= */ false, - /* networkTimeMs= */ 0, - entry.allResponseHeaders)); - mRequest.addMarker("cache-hit-parsed"); - - if (!entry.refreshNeeded()) { - // Completely unexpired cache hit. Just deliver the response. - getResponseDelivery().postResponse(mRequest, response); - } else { - // Soft-expired cache hit. We can deliver the cached response, - // but we need to also send the request to the network for - // refreshing. - mRequest.addMarker("cache-hit-refresh-needed"); - mRequest.setCacheEntry(entry); - // Mark the response as intermediate. - response.intermediate = true; - - if (!mWaitingRequestManager.maybeAddToWaitingRequests(mRequest)) { - // Post the intermediate response back to the user and have - // the delivery then forward the request along to the network. - getResponseDelivery() - .postResponse( - mRequest, - response, - new Runnable() { - @Override - public void run() { - sendRequestOverNetwork(mRequest); - } - }); - } else { - // request has been added to list of waiting requests - // to receive the network response from the first request once it - // returns. - getResponseDelivery().postResponse(mRequest, response); - } - } - } - } - - private class ParseErrorTask<T> extends RequestTask<T> { - VolleyError volleyError; - - ParseErrorTask(Request<T> request, VolleyError volleyError) { - super(request); - this.volleyError = volleyError; - } - - @Override - public void run() { - VolleyError parsedError = mRequest.parseNetworkError(volleyError); - getResponseDelivery().postError(mRequest, parsedError); - mRequest.notifyListenerResponseNotUsable(); - } - } - - /** Runnable that performs the network request */ - private class NetworkTask<T> extends RequestTask<T> { - NetworkTask(Request<T> request) { - super(request); - } - - @Override - public void run() { - // If the request was cancelled already, do not perform the network request. - if (mRequest.isCanceled()) { - mRequest.finish("network-discard-cancelled"); - mRequest.notifyListenerResponseNotUsable(); - return; - } - - final long startTimeMs = SystemClock.elapsedRealtime(); - mRequest.addMarker("network-queue-take"); - - // TODO: Figure out what to do with traffic stats tags. Can this be pushed to the - // HTTP stack, or is it no longer feasible to support? - - // Perform the network request. - mNetwork.performRequest( - mRequest, - new OnRequestComplete() { - @Override - public void onSuccess(final NetworkResponse networkResponse) { - mRequest.addMarker("network-http-complete"); - - // If the server returned 304 AND we delivered a response already, - // we're done -- don't deliver a second identical response. - if (networkResponse.notModified && mRequest.hasHadResponseDelivered()) { - mRequest.finish("not-modified"); - mRequest.notifyListenerResponseNotUsable(); - return; - } - - // Parse the response here on the worker thread. - mBlockingExecutor.execute( - new NetworkParseTask<>(mRequest, networkResponse)); - } - - @Override - public void onError(final VolleyError volleyError) { - volleyError.setNetworkTimeMs( - SystemClock.elapsedRealtime() - startTimeMs); - mBlockingExecutor.execute(new ParseErrorTask<>(mRequest, volleyError)); - } - }); - } - } - - /** Runnable that parses a network response. */ - private class NetworkParseTask<T> extends RequestTask<T> { - NetworkResponse networkResponse; - - NetworkParseTask(Request<T> request, NetworkResponse networkResponse) { - super(request); - this.networkResponse = networkResponse; - } - - @Override - public void run() { - final Response<?> response = mRequest.parseNetworkResponse(networkResponse); - mRequest.addMarker("network-parse-complete"); - - // Write to cache if applicable. - // TODO: Only update cache metadata instead of entire - // record for 304s. - if (mRequest.shouldCache() && response.cacheEntry != null) { - if (mAsyncCache != null) { - mNonBlockingExecutor.execute(new CachePutTask<>(mRequest, response)); - } else { - mBlockingExecutor.execute(new CachePutTask<>(mRequest, response)); - } - } else { - finishRequest(mRequest, response, /* cached= */ false); - } - } - } - - private class CachePutTask<T> extends RequestTask<T> { - Response<?> response; - - CachePutTask(Request<T> request, Response<?> response) { - super(request); - this.response = response; - } - - @Override - public void run() { - if (mAsyncCache != null) { - mAsyncCache.put( - mRequest.getCacheKey(), - response.cacheEntry, - new AsyncCache.OnWriteCompleteCallback() { - @Override - public void onWriteComplete() { - finishRequest(mRequest, response, /* cached= */ true); - } - }); - } else { - getCache().put(mRequest.getCacheKey(), response.cacheEntry); - finishRequest(mRequest, response, /* cached= */ true); - } - } - } - - /** Posts response and notifies listener */ - private void finishRequest(Request<?> mRequest, Response<?> response, boolean cached) { - if (cached) { - mRequest.addMarker("network-cache-written"); - } - // Post the response back. - mRequest.markDelivered(); - getResponseDelivery().postResponse(mRequest, response); - mRequest.notifyListenerResponseReceived(response); - } - - /** - * This class may be used by advanced applications to provide custom executors according to - * their needs. Apps must create ExecutorServices dynamically given a blocking queue rather than - * providing them directly so that Volley can provide a PriorityQueue which will prioritize - * requests according to Request#getPriority. - */ - public abstract static class ExecutorFactory { - abstract ExecutorService createNonBlockingExecutor(BlockingQueue<Runnable> taskQueue); - - abstract ExecutorService createBlockingExecutor(BlockingQueue<Runnable> taskQueue); - - abstract ScheduledExecutorService createNonBlockingScheduledExecutor(); - } - - /** Provides a BlockingQueue to be used to create executors. */ - private static PriorityBlockingQueue<Runnable> getBlockingQueue() { - return new PriorityBlockingQueue<>( - /* initialCapacity= */ 11, - new Comparator<Runnable>() { - @Override - public int compare(Runnable r1, Runnable r2) { - // Vanilla runnables are prioritized first, then RequestTasks are ordered - // by the underlying Request. - if (r1 instanceof RequestTask) { - if (r2 instanceof RequestTask) { - return ((RequestTask<?>) r1).compareTo(((RequestTask<?>) r2)); - } - return 1; - } - return r2 instanceof RequestTask ? -1 : 0; - } - }); - } - - /** - * Builder is used to build an instance of {@link AsyncRequestQueue} from values configured by - * the setters. - */ - public static class Builder { - @Nullable private AsyncCache mAsyncCache = null; - private final AsyncNetwork mNetwork; - @Nullable private Cache mCache = null; - @Nullable private ExecutorFactory mExecutorFactory = null; - @Nullable private ResponseDelivery mResponseDelivery = null; - - public Builder(AsyncNetwork asyncNetwork) { - if (asyncNetwork == null) { - throw new IllegalArgumentException("Network cannot be null"); - } - mNetwork = asyncNetwork; - } - - /** - * Sets the executor factory to be used by the AsyncRequestQueue. If this is not called, - * Volley will create suitable private thread pools. - */ - public Builder setExecutorFactory(ExecutorFactory executorFactory) { - mExecutorFactory = executorFactory; - return this; - } - - /** - * Sets the response deliver to be used by the AsyncRequestQueue. If this is not called, we - * will default to creating a new {@link ExecutorDelivery} with the application's main - * thread. - */ - public Builder setResponseDelivery(ResponseDelivery responseDelivery) { - mResponseDelivery = responseDelivery; - return this; - } - - /** Sets the AsyncCache to be used by the AsyncRequestQueue. */ - public Builder setAsyncCache(AsyncCache asyncCache) { - mAsyncCache = asyncCache; - return this; - } - - /** Sets the Cache to be used by the AsyncRequestQueue. */ - public Builder setCache(Cache cache) { - mCache = cache; - return this; - } - - /** Provides a default ExecutorFactory to use, if one is never set. */ - private ExecutorFactory getDefaultExecutorFactory() { - return new ExecutorFactory() { - @Override - public ExecutorService createNonBlockingExecutor( - BlockingQueue<Runnable> taskQueue) { - return getNewThreadPoolExecutor( - /* maximumPoolSize= */ 1, - /* threadNameSuffix= */ "Non-BlockingExecutor", - taskQueue); - } - - @Override - public ExecutorService createBlockingExecutor(BlockingQueue<Runnable> taskQueue) { - return getNewThreadPoolExecutor( - /* maximumPoolSize= */ DEFAULT_BLOCKING_THREAD_POOL_SIZE, - /* threadNameSuffix= */ "BlockingExecutor", - taskQueue); - } - - @Override - public ScheduledExecutorService createNonBlockingScheduledExecutor() { - return new ScheduledThreadPoolExecutor( - /* corePoolSize= */ 0, getThreadFactory("ScheduledExecutor")); - } - - private ThreadPoolExecutor getNewThreadPoolExecutor( - int maximumPoolSize, - final String threadNameSuffix, - BlockingQueue<Runnable> taskQueue) { - return new ThreadPoolExecutor( - /* corePoolSize= */ 0, - /* maximumPoolSize= */ maximumPoolSize, - /* keepAliveTime= */ 60, - /* unit= */ TimeUnit.SECONDS, - taskQueue, - getThreadFactory(threadNameSuffix)); - } - - private ThreadFactory getThreadFactory(final String threadNameSuffix) { - return new ThreadFactory() { - @Override - public Thread newThread(@NonNull Runnable runnable) { - Thread t = Executors.defaultThreadFactory().newThread(runnable); - t.setName("Volley-" + threadNameSuffix); - return t; - } - }; - } - }; - } - - public AsyncRequestQueue build() { - // If neither cache is set by the caller, throw an illegal argument exception. - if (mCache == null && mAsyncCache == null) { - throw new IllegalArgumentException("You must set one of the cache objects"); - } - if (mCache == null) { - // if no cache is provided, we will provide one that throws - // UnsupportedOperationExceptions to pass into the parent class. - mCache = new ThrowingCache(); - } - if (mResponseDelivery == null) { - mResponseDelivery = new ExecutorDelivery(new Handler(Looper.getMainLooper())); - } - if (mExecutorFactory == null) { - mExecutorFactory = getDefaultExecutorFactory(); - } - return new AsyncRequestQueue( - mCache, mNetwork, mAsyncCache, mResponseDelivery, mExecutorFactory); - } - } - - /** A cache that throws an error if a method is called. */ - private static class ThrowingCache implements Cache { - @Override - public Entry get(String key) { - throw new UnsupportedOperationException(); - } - - @Override - public void put(String key, Entry entry) { - throw new UnsupportedOperationException(); - } - - @Override - public void initialize() { - throw new UnsupportedOperationException(); - } - - @Override - public void invalidate(String key, boolean fullExpire) { - throw new UnsupportedOperationException(); - } - - @Override - public void remove(String key) { - throw new UnsupportedOperationException(); - } - - @Override - public void clear() { - throw new UnsupportedOperationException(); - } - } -} diff --git a/src/main/java/com/android/volley/Cache.java b/src/main/java/com/android/volley/Cache.java index b8908ac..35b2a96 100644 --- a/src/main/java/com/android/volley/Cache.java +++ b/src/main/java/com/android/volley/Cache.java @@ -16,7 +16,6 @@ package com.android.volley; -import androidx.annotation.Nullable; import java.util.Collections; import java.util.List; import java.util.Map; @@ -29,7 +28,6 @@ public interface Cache { * @param key Cache key * @return An {@link Entry} or null in the event of a cache miss */ - @Nullable Entry get(String key); /** diff --git a/src/main/java/com/android/volley/CacheDispatcher.java b/src/main/java/com/android/volley/CacheDispatcher.java index 1bfc0ea..be06d1f 100644 --- a/src/main/java/com/android/volley/CacheDispatcher.java +++ b/src/main/java/com/android/volley/CacheDispatcher.java @@ -18,6 +18,10 @@ package com.android.volley; import android.os.Process; import androidx.annotation.VisibleForTesting; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.BlockingQueue; /** @@ -68,7 +72,7 @@ public class CacheDispatcher extends Thread { mNetworkQueue = networkQueue; mCache = cache; mDelivery = delivery; - mWaitingRequestManager = new WaitingRequestManager(this, networkQueue, delivery); + mWaitingRequestManager = new WaitingRequestManager(this); } /** @@ -155,15 +159,6 @@ public class CacheDispatcher extends Thread { new NetworkResponse(entry.data, entry.responseHeaders)); request.addMarker("cache-hit-parsed"); - if (!response.isSuccess()) { - request.addMarker("cache-parsing-failed"); - mCache.invalidate(request.getCacheKey(), true); - request.setCacheEntry(null); - if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) { - mNetworkQueue.put(request); - } - return; - } if (!entry.refreshNeeded()) { // Completely unexpired cache hit. Just deliver the response. mDelivery.postResponse(request, response); @@ -203,4 +198,113 @@ public class CacheDispatcher extends Thread { request.sendEvent(RequestQueue.RequestEvent.REQUEST_CACHE_LOOKUP_FINISHED); } } + + private static class WaitingRequestManager implements Request.NetworkRequestCompleteListener { + + /** + * Staging area for requests that already have a duplicate request in flight. + * + * <ul> + * <li>containsKey(cacheKey) indicates that there is a request in flight for the given + * cache key. + * <li>get(cacheKey) returns waiting requests for the given cache key. The in flight + * request is <em>not</em> contained in that list. Is null if no requests are staged. + * </ul> + */ + private final Map<String, List<Request<?>>> mWaitingRequests = new HashMap<>(); + + private final CacheDispatcher mCacheDispatcher; + + WaitingRequestManager(CacheDispatcher cacheDispatcher) { + mCacheDispatcher = cacheDispatcher; + } + + /** Request received a valid response that can be used by other waiting requests. */ + @Override + public void onResponseReceived(Request<?> request, Response<?> response) { + if (response.cacheEntry == null || response.cacheEntry.isExpired()) { + onNoUsableResponseReceived(request); + return; + } + String cacheKey = request.getCacheKey(); + List<Request<?>> waitingRequests; + synchronized (this) { + waitingRequests = mWaitingRequests.remove(cacheKey); + } + if (waitingRequests != null) { + if (VolleyLog.DEBUG) { + VolleyLog.v( + "Releasing %d waiting requests for cacheKey=%s.", + waitingRequests.size(), cacheKey); + } + // Process all queued up requests. + for (Request<?> waiting : waitingRequests) { + mCacheDispatcher.mDelivery.postResponse(waiting, response); + } + } + } + + /** No valid response received from network, release waiting requests. */ + @Override + public synchronized void onNoUsableResponseReceived(Request<?> request) { + String cacheKey = request.getCacheKey(); + List<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey); + if (waitingRequests != null && !waitingRequests.isEmpty()) { + if (VolleyLog.DEBUG) { + VolleyLog.v( + "%d waiting requests for cacheKey=%s; resend to network", + waitingRequests.size(), cacheKey); + } + Request<?> nextInLine = waitingRequests.remove(0); + mWaitingRequests.put(cacheKey, waitingRequests); + nextInLine.setNetworkRequestCompleteListener(this); + try { + mCacheDispatcher.mNetworkQueue.put(nextInLine); + } catch (InterruptedException iex) { + VolleyLog.e("Couldn't add request to queue. %s", iex.toString()); + // Restore the interrupted status of the calling thread (i.e. NetworkDispatcher) + Thread.currentThread().interrupt(); + // Quit the current CacheDispatcher thread. + mCacheDispatcher.quit(); + } + } + } + + /** + * For cacheable requests, if a request for the same cache key is already in flight, add it + * to a queue to wait for that in-flight request to finish. + * + * @return whether the request was queued. If false, we should continue issuing the request + * over the network. If true, we should put the request on hold to be processed when the + * in-flight request finishes. + */ + private synchronized boolean maybeAddToWaitingRequests(Request<?> request) { + String cacheKey = request.getCacheKey(); + // Insert request into stage if there's already a request with the same cache key + // in flight. + if (mWaitingRequests.containsKey(cacheKey)) { + // There is already a request in flight. Queue up. + List<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey); + if (stagedRequests == null) { + stagedRequests = new ArrayList<>(); + } + request.addMarker("waiting-for-response"); + stagedRequests.add(request); + mWaitingRequests.put(cacheKey, stagedRequests); + if (VolleyLog.DEBUG) { + VolleyLog.d("Request for cacheKey=%s is in flight, putting on hold.", cacheKey); + } + return true; + } else { + // Insert 'null' queue for this cacheKey, indicating there is now a request in + // flight. + mWaitingRequests.put(cacheKey, null); + request.setNetworkRequestCompleteListener(this); + if (VolleyLog.DEBUG) { + VolleyLog.d("new request, sending to network %s", cacheKey); + } + return false; + } + } + } } diff --git a/src/main/java/com/android/volley/NetworkResponse.java b/src/main/java/com/android/volley/NetworkResponse.java index cfbc371..01f48c6 100644 --- a/src/main/java/com/android/volley/NetworkResponse.java +++ b/src/main/java/com/android/volley/NetworkResponse.java @@ -16,7 +16,6 @@ package com.android.volley; -import androidx.annotation.Nullable; import java.net.HttpURLConnection; import java.util.ArrayList; import java.util.Collections; @@ -43,7 +42,7 @@ public class NetworkResponse { public NetworkResponse( int statusCode, byte[] data, - @Nullable Map<String, String> headers, + Map<String, String> headers, boolean notModified, long networkTimeMs) { this(statusCode, data, headers, toAllHeaderList(headers), notModified, networkTimeMs); @@ -63,7 +62,7 @@ public class NetworkResponse { byte[] data, boolean notModified, long networkTimeMs, - @Nullable List<Header> allHeaders) { + List<Header> allHeaders) { this(statusCode, data, toHeaderMap(allHeaders), allHeaders, notModified, networkTimeMs); } @@ -80,10 +79,7 @@ public class NetworkResponse { */ @Deprecated public NetworkResponse( - int statusCode, - byte[] data, - @Nullable Map<String, String> headers, - boolean notModified) { + int statusCode, byte[] data, Map<String, String> headers, boolean notModified) { this(statusCode, data, headers, notModified, /* networkTimeMs= */ 0); } @@ -111,7 +107,7 @@ public class NetworkResponse { * constructor may be removed in a future release of Volley. */ @Deprecated - public NetworkResponse(byte[] data, @Nullable Map<String, String> headers) { + public NetworkResponse(byte[] data, Map<String, String> headers) { this( HttpURLConnection.HTTP_OK, data, @@ -123,8 +119,8 @@ public class NetworkResponse { private NetworkResponse( int statusCode, byte[] data, - @Nullable Map<String, String> headers, - @Nullable List<Header> allHeaders, + Map<String, String> headers, + List<Header> allHeaders, boolean notModified, long networkTimeMs) { this.statusCode = statusCode; @@ -154,10 +150,10 @@ public class NetworkResponse { * map will only contain the last one. Use {@link #allHeaders} to inspect all headers returned * by the server. */ - @Nullable public final Map<String, String> headers; + public final Map<String, String> headers; /** All response headers. Must not be mutated directly. */ - @Nullable public final List<Header> allHeaders; + public final List<Header> allHeaders; /** True if the server returned a 304 (Not Modified). */ public final boolean notModified; @@ -165,8 +161,7 @@ public class NetworkResponse { /** Network roundtrip time in milliseconds. */ public final long networkTimeMs; - @Nullable - private static Map<String, String> toHeaderMap(@Nullable List<Header> allHeaders) { + private static Map<String, String> toHeaderMap(List<Header> allHeaders) { if (allHeaders == null) { return null; } @@ -181,8 +176,7 @@ public class NetworkResponse { return headers; } - @Nullable - private static List<Header> toAllHeaderList(@Nullable Map<String, String> headers) { + private static List<Header> toAllHeaderList(Map<String, String> headers) { if (headers == null) { return null; } diff --git a/src/main/java/com/android/volley/Request.java b/src/main/java/com/android/volley/Request.java index b60dc74..104b046 100644 --- a/src/main/java/com/android/volley/Request.java +++ b/src/main/java/com/android/volley/Request.java @@ -107,9 +107,6 @@ public abstract class Request<T> implements Comparable<Request<T>> { /** Whether the request should be retried in the event of an HTTP 5xx (server) error. */ private boolean mShouldRetryServerErrors = false; - /** Whether the request should be retried in the event of a {@link NoConnectionError}. */ - private boolean mShouldRetryConnectionErrors = false; - /** The retry policy for this request. */ private RetryPolicy mRetryPolicy; @@ -118,7 +115,7 @@ public abstract class Request<T> implements Comparable<Request<T>> { * entry will be stored here so that in the event of a "Not Modified" response, we can be sure * it hasn't been evicted from cache. */ - @Nullable private Cache.Entry mCacheEntry = null; + private Cache.Entry mCacheEntry = null; /** An opaque token tagging this request; used for bulk cancellation. */ private Object mTag; @@ -322,7 +319,6 @@ public abstract class Request<T> implements Comparable<Request<T>> { } /** Returns the annotated cache entry, or null if there isn't one. */ - @Nullable public Cache.Entry getCacheEntry() { return mCacheEntry; } @@ -378,7 +374,6 @@ public abstract class Request<T> implements Comparable<Request<T>> { * @deprecated Use {@link #getParams()} instead. */ @Deprecated - @Nullable protected Map<String, String> getPostParams() throws AuthFailureError { return getParams(); } @@ -436,7 +431,6 @@ public abstract class Request<T> implements Comparable<Request<T>> { * * @throws AuthFailureError in the event of auth failure */ - @Nullable protected Map<String, String> getParams() throws AuthFailureError { return null; } @@ -537,25 +531,6 @@ public abstract class Request<T> implements Comparable<Request<T>> { } /** - * Sets whether or not the request should be retried in the event that no connection could be - * established. - * - * @return This Request object to allow for chaining. - */ - public final Request<?> setShouldRetryConnectionErrors(boolean shouldRetryConnectionErrors) { - mShouldRetryConnectionErrors = shouldRetryConnectionErrors; - return this; - } - - /** - * Returns true if this request should be retried in the event that no connection could be - * established. - */ - public final boolean shouldRetryConnectionErrors() { - return mShouldRetryConnectionErrors; - } - - /** * Priority values. Requests will be processed from higher priorities to lower priorities, in * FIFO order. */ diff --git a/src/main/java/com/android/volley/RequestQueue.java b/src/main/java/com/android/volley/RequestQueue.java index 6db0b1c..c127c7f 100644 --- a/src/main/java/com/android/volley/RequestQueue.java +++ b/src/main/java/com/android/volley/RequestQueue.java @@ -263,17 +263,13 @@ public class RequestQueue { request.addMarker("add-to-queue"); sendRequestEvent(request, RequestEvent.REQUEST_QUEUED); - beginRequest(request); - return request; - } - - <T> void beginRequest(Request<T> request) { // If the request is uncacheable, skip the cache queue and go straight to the network. if (!request.shouldCache()) { - sendRequestOverNetwork(request); - } else { - mCacheQueue.add(request); + mNetworkQueue.add(request); + return request; } + mCacheQueue.add(request); + return request; } /** @@ -331,12 +327,4 @@ public class RequestQueue { mFinishedListeners.remove(listener); } } - - public ResponseDelivery getResponseDelivery() { - return mDelivery; - } - - <T> void sendRequestOverNetwork(Request<T> request) { - mNetworkQueue.add(request); - } } diff --git a/src/main/java/com/android/volley/RequestTask.java b/src/main/java/com/android/volley/RequestTask.java deleted file mode 100644 index 8eeaf2c..0000000 --- a/src/main/java/com/android/volley/RequestTask.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.android.volley; - -/** Abstract runnable that's a task to be completed by the RequestQueue. */ -public abstract class RequestTask<T> implements Runnable { - final Request<T> mRequest; - - public RequestTask(Request<T> request) { - mRequest = request; - } - - @SuppressWarnings("unchecked") - public int compareTo(RequestTask<?> other) { - return mRequest.compareTo((Request<T>) other.mRequest); - } -} diff --git a/src/main/java/com/android/volley/Response.java b/src/main/java/com/android/volley/Response.java index 622bdc4..2f50e2d 100644 --- a/src/main/java/com/android/volley/Response.java +++ b/src/main/java/com/android/volley/Response.java @@ -16,8 +16,6 @@ package com.android.volley; -import androidx.annotation.Nullable; - /** * Encapsulates a parsed response for delivery. * @@ -41,7 +39,7 @@ public class Response<T> { } /** Returns a successful response containing the parsed result. */ - public static <T> Response<T> success(@Nullable T result, @Nullable Cache.Entry cacheEntry) { + public static <T> Response<T> success(T result, Cache.Entry cacheEntry) { return new Response<>(result, cacheEntry); } @@ -53,14 +51,14 @@ public class Response<T> { return new Response<>(error); } - /** Parsed response, can be null; always null in the case of error. */ - @Nullable public final T result; + /** Parsed response, or null in the case of error. */ + public final T result; - /** Cache metadata for this response; null if not cached or in the case of error. */ - @Nullable public final Cache.Entry cacheEntry; + /** Cache metadata for this response, or null in the case of error. */ + public final Cache.Entry cacheEntry; /** Detailed error information if <code>errorCode != OK</code>. */ - @Nullable public final VolleyError error; + public final VolleyError error; /** True if this response was a soft-expired one and a second one MAY be coming. */ public boolean intermediate = false; @@ -70,7 +68,7 @@ public class Response<T> { return error == null; } - private Response(@Nullable T result, @Nullable Cache.Entry cacheEntry) { + private Response(T result, Cache.Entry cacheEntry) { this.result = result; this.cacheEntry = cacheEntry; this.error = null; diff --git a/src/main/java/com/android/volley/WaitingRequestManager.java b/src/main/java/com/android/volley/WaitingRequestManager.java deleted file mode 100644 index 682e339..0000000 --- a/src/main/java/com/android/volley/WaitingRequestManager.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * 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; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.BlockingQueue; - -/** - * Callback to notify the caller when the network request returns. Valid responses can be used by - * all duplicate requests. - */ -class WaitingRequestManager implements Request.NetworkRequestCompleteListener { - - /** - * Staging area for requests that already have a duplicate request in flight. - * - * <ul> - * <li>containsKey(cacheKey) indicates that there is a request in flight for the given cache - * key. - * <li>get(cacheKey) returns waiting requests for the given cache key. The in flight request - * is <em>not</em> contained in that list. Is null if no requests are staged. - * </ul> - */ - private final Map<String, List<Request<?>>> mWaitingRequests = new HashMap<>(); - - private final ResponseDelivery mResponseDelivery; - - /** - * RequestQueue that is passed in by the AsyncRequestQueue. This is null when this instance is - * initialized by the {@link CacheDispatcher} - */ - @Nullable private final RequestQueue mRequestQueue; - - /** - * CacheDispacter that is passed in by the CacheDispatcher. This is null when this instance is - * initialized by the {@link AsyncRequestQueue} - */ - @Nullable private final CacheDispatcher mCacheDispatcher; - - /** - * BlockingQueue that is passed in by the CacheDispatcher. This is null when this instance is - * initialized by the {@link AsyncRequestQueue} - */ - @Nullable private final BlockingQueue<Request<?>> mNetworkQueue; - - WaitingRequestManager(@NonNull RequestQueue requestQueue) { - mRequestQueue = requestQueue; - mResponseDelivery = mRequestQueue.getResponseDelivery(); - mCacheDispatcher = null; - mNetworkQueue = null; - } - - WaitingRequestManager( - @NonNull CacheDispatcher cacheDispatcher, - @NonNull BlockingQueue<Request<?>> networkQueue, - ResponseDelivery responseDelivery) { - mRequestQueue = null; - mResponseDelivery = responseDelivery; - mCacheDispatcher = cacheDispatcher; - mNetworkQueue = networkQueue; - } - - /** Request received a valid response that can be used by other waiting requests. */ - @Override - public void onResponseReceived(Request<?> request, Response<?> response) { - if (response.cacheEntry == null || response.cacheEntry.isExpired()) { - onNoUsableResponseReceived(request); - return; - } - String cacheKey = request.getCacheKey(); - List<Request<?>> waitingRequests; - synchronized (this) { - waitingRequests = mWaitingRequests.remove(cacheKey); - } - if (waitingRequests != null) { - if (VolleyLog.DEBUG) { - VolleyLog.v( - "Releasing %d waiting requests for cacheKey=%s.", - waitingRequests.size(), cacheKey); - } - // Process all queued up requests. - for (Request<?> waiting : waitingRequests) { - mResponseDelivery.postResponse(waiting, response); - } - } - } - - /** No valid response received from network, release waiting requests. */ - @Override - public synchronized void onNoUsableResponseReceived(Request<?> request) { - String cacheKey = request.getCacheKey(); - List<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey); - if (waitingRequests != null && !waitingRequests.isEmpty()) { - if (VolleyLog.DEBUG) { - VolleyLog.v( - "%d waiting requests for cacheKey=%s; resend to network", - waitingRequests.size(), cacheKey); - } - Request<?> nextInLine = waitingRequests.remove(0); - mWaitingRequests.put(cacheKey, waitingRequests); - nextInLine.setNetworkRequestCompleteListener(this); - // RequestQueue will be non-null if this instance was created in AsyncRequestQueue. - if (mRequestQueue != null) { - // Will send the network request from the RequestQueue. - mRequestQueue.sendRequestOverNetwork(nextInLine); - } else if (mCacheDispatcher != null && mNetworkQueue != null) { - // If we're not using the AsyncRequestQueue, then submit it to the network queue. - try { - mNetworkQueue.put(nextInLine); - } catch (InterruptedException iex) { - VolleyLog.e("Couldn't add request to queue. %s", iex.toString()); - // Restore the interrupted status of the calling thread (i.e. NetworkDispatcher) - Thread.currentThread().interrupt(); - // Quit the current CacheDispatcher thread. - mCacheDispatcher.quit(); - } - } - } - } - - /** - * For cacheable requests, if a request for the same cache key is already in flight, add it to a - * queue to wait for that in-flight request to finish. - * - * @return whether the request was queued. If false, we should continue issuing the request over - * the network. If true, we should put the request on hold to be processed when the - * in-flight request finishes. - */ - synchronized boolean maybeAddToWaitingRequests(Request<?> request) { - String cacheKey = request.getCacheKey(); - // Insert request into stage if there's already a request with the same cache key - // in flight. - if (mWaitingRequests.containsKey(cacheKey)) { - // There is already a request in flight. Queue up. - List<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey); - if (stagedRequests == null) { - stagedRequests = new ArrayList<>(); - } - request.addMarker("waiting-for-response"); - stagedRequests.add(request); - mWaitingRequests.put(cacheKey, stagedRequests); - if (VolleyLog.DEBUG) { - VolleyLog.d("Request for cacheKey=%s is in flight, putting on hold.", cacheKey); - } - return true; - } else { - // Insert 'null' queue for this cacheKey, indicating there is now a request in - // flight. - mWaitingRequests.put(cacheKey, null); - request.setNetworkRequestCompleteListener(this); - if (VolleyLog.DEBUG) { - VolleyLog.d("new request, sending to network %s", cacheKey); - } - return false; - } - } -} diff --git a/src/main/java/com/android/volley/cronet/CronetHttpStack.java b/src/main/java/com/android/volley/cronet/CronetHttpStack.java deleted file mode 100644 index f3baace..0000000 --- a/src/main/java/com/android/volley/cronet/CronetHttpStack.java +++ /dev/null @@ -1,631 +0,0 @@ -/* - * 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.cronet; - -import android.content.Context; -import android.text.TextUtils; -import android.util.Base64; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; -import com.android.volley.AuthFailureError; -import com.android.volley.Header; -import com.android.volley.Request; -import com.android.volley.RequestTask; -import com.android.volley.VolleyLog; -import com.android.volley.toolbox.AsyncHttpStack; -import com.android.volley.toolbox.ByteArrayPool; -import com.android.volley.toolbox.HttpHeaderParser; -import com.android.volley.toolbox.HttpResponse; -import com.android.volley.toolbox.PoolingByteArrayOutputStream; -import com.android.volley.toolbox.UrlRewriter; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.nio.ByteBuffer; -import java.nio.channels.Channels; -import java.nio.channels.WritableByteChannel; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import org.chromium.net.CronetEngine; -import org.chromium.net.CronetException; -import org.chromium.net.UploadDataProvider; -import org.chromium.net.UploadDataProviders; -import org.chromium.net.UrlRequest; -import org.chromium.net.UrlRequest.Callback; -import org.chromium.net.UrlResponseInfo; - -/** - * A {@link AsyncHttpStack} that's based on Cronet's fully asynchronous API for network requests. - */ -public class CronetHttpStack extends AsyncHttpStack { - - private final CronetEngine mCronetEngine; - private final ByteArrayPool mPool; - private final UrlRewriter mUrlRewriter; - private final RequestListener mRequestListener; - - // cURL logging support - private final boolean mCurlLoggingEnabled; - private final CurlCommandLogger mCurlCommandLogger; - private final boolean mLogAuthTokensInCurlCommands; - - private CronetHttpStack( - CronetEngine cronetEngine, - ByteArrayPool pool, - UrlRewriter urlRewriter, - RequestListener requestListener, - boolean curlLoggingEnabled, - CurlCommandLogger curlCommandLogger, - boolean logAuthTokensInCurlCommands) { - mCronetEngine = cronetEngine; - mPool = pool; - mUrlRewriter = urlRewriter; - mRequestListener = requestListener; - mCurlLoggingEnabled = curlLoggingEnabled; - mCurlCommandLogger = curlCommandLogger; - mLogAuthTokensInCurlCommands = logAuthTokensInCurlCommands; - - mRequestListener.initialize(this); - } - - @Override - public void executeRequest( - final Request<?> request, - final Map<String, String> additionalHeaders, - final OnRequestComplete callback) { - if (getBlockingExecutor() == null || getNonBlockingExecutor() == null) { - throw new IllegalStateException("Must set blocking and non-blocking executors"); - } - final Callback urlCallback = - new Callback() { - PoolingByteArrayOutputStream bytesReceived = null; - WritableByteChannel receiveChannel = null; - - @Override - public void onRedirectReceived( - UrlRequest urlRequest, - UrlResponseInfo urlResponseInfo, - String newLocationUrl) { - urlRequest.followRedirect(); - } - - @Override - public void onResponseStarted( - UrlRequest urlRequest, UrlResponseInfo urlResponseInfo) { - bytesReceived = - new PoolingByteArrayOutputStream( - mPool, getContentLength(urlResponseInfo)); - receiveChannel = Channels.newChannel(bytesReceived); - urlRequest.read(ByteBuffer.allocateDirect(1024)); - } - - @Override - public void onReadCompleted( - UrlRequest urlRequest, - UrlResponseInfo urlResponseInfo, - ByteBuffer byteBuffer) { - byteBuffer.flip(); - try { - receiveChannel.write(byteBuffer); - byteBuffer.clear(); - urlRequest.read(byteBuffer); - } catch (IOException e) { - urlRequest.cancel(); - callback.onError(e); - } - } - - @Override - public void onSucceeded( - UrlRequest urlRequest, UrlResponseInfo urlResponseInfo) { - List<Header> headers = getHeaders(urlResponseInfo.getAllHeadersAsList()); - HttpResponse response = - new HttpResponse( - urlResponseInfo.getHttpStatusCode(), - headers, - bytesReceived.toByteArray()); - callback.onSuccess(response); - } - - @Override - public void onFailed( - UrlRequest urlRequest, - UrlResponseInfo urlResponseInfo, - CronetException e) { - callback.onError(e); - } - }; - - String url = request.getUrl(); - String rewritten = mUrlRewriter.rewriteUrl(url); - if (rewritten == null) { - callback.onError(new IOException("URL blocked by rewriter: " + url)); - return; - } - url = rewritten; - - // We can call allowDirectExecutor here and run directly on the network thread, since all - // the callbacks are non-blocking. - final UrlRequest.Builder builder = - mCronetEngine - .newUrlRequestBuilder(url, urlCallback, getNonBlockingExecutor()) - .allowDirectExecutor() - .disableCache() - .setPriority(getPriority(request)); - // request.getHeaders() may be blocking, so submit it to the blocking executor. - getBlockingExecutor() - .execute( - new SetUpRequestTask<>(request, url, builder, additionalHeaders, callback)); - } - - private class SetUpRequestTask<T> extends RequestTask<T> { - UrlRequest.Builder builder; - String url; - Map<String, String> additionalHeaders; - OnRequestComplete callback; - Request<T> request; - - SetUpRequestTask( - Request<T> request, - String url, - UrlRequest.Builder builder, - Map<String, String> additionalHeaders, - OnRequestComplete callback) { - super(request); - // Note that this URL may be different from Request#getUrl() due to the UrlRewriter. - this.url = url; - this.builder = builder; - this.additionalHeaders = additionalHeaders; - this.callback = callback; - this.request = request; - } - - @Override - public void run() { - try { - mRequestListener.onRequestPrepared(request, builder); - CurlLoggedRequestParameters requestParameters = new CurlLoggedRequestParameters(); - setHttpMethod(requestParameters, request); - setRequestHeaders(requestParameters, request, additionalHeaders); - requestParameters.applyToRequest(builder, getNonBlockingExecutor()); - UrlRequest urlRequest = builder.build(); - if (mCurlLoggingEnabled) { - mCurlCommandLogger.logCurlCommand(generateCurlCommand(url, requestParameters)); - } - urlRequest.start(); - } catch (AuthFailureError authFailureError) { - callback.onAuthError(authFailureError); - } - } - } - - @VisibleForTesting - public static List<Header> getHeaders(List<Map.Entry<String, String>> headersList) { - List<Header> headers = new ArrayList<>(); - for (Map.Entry<String, String> header : headersList) { - headers.add(new Header(header.getKey(), header.getValue())); - } - return headers; - } - - /** Sets the connection parameters for the UrlRequest */ - private void setHttpMethod(CurlLoggedRequestParameters requestParameters, Request<?> request) - throws AuthFailureError { - switch (request.getMethod()) { - case Request.Method.DEPRECATED_GET_OR_POST: - // This is the deprecated way that needs to be handled for backwards compatibility. - // If the request's post body is null, then the assumption is that the request is - // GET. Otherwise, it is assumed that the request is a POST. - byte[] postBody = request.getPostBody(); - if (postBody != null) { - requestParameters.setHttpMethod("POST"); - addBodyIfExists(requestParameters, request.getPostBodyContentType(), postBody); - } else { - requestParameters.setHttpMethod("GET"); - } - break; - case Request.Method.GET: - // Not necessary to set the request method because connection defaults to GET but - // being explicit here. - requestParameters.setHttpMethod("GET"); - break; - case Request.Method.DELETE: - requestParameters.setHttpMethod("DELETE"); - break; - case Request.Method.POST: - requestParameters.setHttpMethod("POST"); - addBodyIfExists(requestParameters, request.getBodyContentType(), request.getBody()); - break; - case Request.Method.PUT: - requestParameters.setHttpMethod("PUT"); - addBodyIfExists(requestParameters, request.getBodyContentType(), request.getBody()); - break; - case Request.Method.HEAD: - requestParameters.setHttpMethod("HEAD"); - break; - case Request.Method.OPTIONS: - requestParameters.setHttpMethod("OPTIONS"); - break; - case Request.Method.TRACE: - requestParameters.setHttpMethod("TRACE"); - break; - case Request.Method.PATCH: - requestParameters.setHttpMethod("PATCH"); - addBodyIfExists(requestParameters, request.getBodyContentType(), request.getBody()); - break; - default: - throw new IllegalStateException("Unknown method type."); - } - } - - /** - * Sets the request headers for the UrlRequest. - * - * @param requestParameters parameters that we are adding the request headers to - * @param request to get the headers from - * @param additionalHeaders for the UrlRequest - * @throws AuthFailureError is thrown if Request#getHeaders throws ones - */ - private void setRequestHeaders( - CurlLoggedRequestParameters requestParameters, - Request<?> request, - Map<String, String> additionalHeaders) - throws AuthFailureError { - requestParameters.putAllHeaders(additionalHeaders); - // Request.getHeaders() takes precedence over the given additional (cache) headers). - requestParameters.putAllHeaders(request.getHeaders()); - } - - /** Sets the UploadDataProvider of the UrlRequest.Builder */ - private void addBodyIfExists( - CurlLoggedRequestParameters requestParameters, - String contentType, - @Nullable byte[] body) { - requestParameters.setBody(contentType, body); - } - - /** Helper method that maps Volley's request priority to Cronet's */ - private int getPriority(Request<?> request) { - switch (request.getPriority()) { - case LOW: - return UrlRequest.Builder.REQUEST_PRIORITY_LOW; - case HIGH: - case IMMEDIATE: - return UrlRequest.Builder.REQUEST_PRIORITY_HIGHEST; - case NORMAL: - default: - return UrlRequest.Builder.REQUEST_PRIORITY_MEDIUM; - } - } - - private int getContentLength(UrlResponseInfo urlResponseInfo) { - List<String> content = urlResponseInfo.getAllHeaders().get("Content-Length"); - if (content == null) { - return 1024; - } else { - return Integer.parseInt(content.get(0)); - } - } - - private String generateCurlCommand(String url, CurlLoggedRequestParameters requestParameters) { - StringBuilder builder = new StringBuilder("curl "); - - // HTTP method - builder.append("-X ").append(requestParameters.getHttpMethod()).append(" "); - - // Request headers - for (Map.Entry<String, String> header : requestParameters.getHeaders().entrySet()) { - builder.append("--header \"").append(header.getKey()).append(": "); - if (!mLogAuthTokensInCurlCommands - && ("Authorization".equals(header.getKey()) - || "Cookie".equals(header.getKey()))) { - builder.append("[REDACTED]"); - } else { - builder.append(header.getValue()); - } - builder.append("\" "); - } - - // URL - builder.append("\"").append(url).append("\""); - - // Request body (if any) - if (requestParameters.getBody() != null) { - if (requestParameters.getBody().length >= 1024) { - builder.append(" [REQUEST BODY TOO LARGE TO INCLUDE]"); - } else if (isBinaryContentForLogging(requestParameters)) { - String base64 = Base64.encodeToString(requestParameters.getBody(), Base64.NO_WRAP); - builder.insert(0, "echo '" + base64 + "' | base64 -d > /tmp/$$.bin; ") - .append(" --data-binary @/tmp/$$.bin"); - } else { - // Just assume the request body is UTF-8 since this is for debugging. - try { - builder.append(" --data-ascii \"") - .append(new String(requestParameters.getBody(), "UTF-8")) - .append("\""); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("Could not encode to UTF-8", e); - } - } - } - - return builder.toString(); - } - - /** Rough heuristic to determine whether the request body is binary, for logging purposes. */ - private boolean isBinaryContentForLogging(CurlLoggedRequestParameters requestParameters) { - // Check to see if the content is gzip compressed - this means it should be treated as - // binary content regardless of the content type. - String contentEncoding = requestParameters.getHeaders().get("Content-Encoding"); - if (contentEncoding != null) { - String[] encodings = TextUtils.split(contentEncoding, ","); - for (String encoding : encodings) { - if ("gzip".equals(encoding.trim())) { - return true; - } - } - } - - // If the content type is a known text type, treat it as text content. - String contentType = requestParameters.getHeaders().get("Content-Type"); - if (contentType != null) { - return !contentType.startsWith("text/") - && !contentType.startsWith("application/xml") - && !contentType.startsWith("application/json"); - } - - // Otherwise, assume it is binary content. - return true; - } - - /** - * Builder is used to build an instance of {@link CronetHttpStack} from values configured by the - * setters. - */ - public static class Builder { - private static final int DEFAULT_POOL_SIZE = 4096; - private CronetEngine mCronetEngine; - private final Context context; - private ByteArrayPool mPool; - private UrlRewriter mUrlRewriter; - private RequestListener mRequestListener; - private boolean mCurlLoggingEnabled; - private CurlCommandLogger mCurlCommandLogger; - private boolean mLogAuthTokensInCurlCommands; - - public Builder(Context context) { - this.context = context; - } - - /** Sets the CronetEngine to be used. Defaults to a vanialla CronetEngine. */ - public Builder setCronetEngine(CronetEngine engine) { - mCronetEngine = engine; - return this; - } - - /** Sets the ByteArrayPool to be used. Defaults to a new pool with 4096 bytes. */ - public Builder setPool(ByteArrayPool pool) { - mPool = pool; - return this; - } - - /** Sets the UrlRewriter to be used. Default is to return the original string. */ - public Builder setUrlRewriter(UrlRewriter urlRewriter) { - mUrlRewriter = urlRewriter; - return this; - } - - /** Set the optional RequestListener to be used. */ - public Builder setRequestListener(RequestListener requestListener) { - mRequestListener = requestListener; - return this; - } - - /** - * Sets whether cURL logging should be enabled for debugging purposes. - * - * <p>When enabled, for each request dispatched to the network, a roughly-equivalent cURL - * command will be logged to logcat. - * - * <p>The command may be missing some headers that are added by Cronet automatically, and - * the full request body may not be included if it is too large. To inspect the full - * requests and responses, see {@code CronetEngine#startNetLogToFile}. - * - * <p>WARNING: This is only intended for debugging purposes and should never be enabled on - * production devices. - * - * @see #setCurlCommandLogger(CurlCommandLogger) - * @see #setLogAuthTokensInCurlCommands(boolean) - */ - public Builder setCurlLoggingEnabled(boolean curlLoggingEnabled) { - mCurlLoggingEnabled = curlLoggingEnabled; - return this; - } - - /** - * Sets the function used to log cURL commands. - * - * <p>Allows customization of the logging performed when cURL logging is enabled. - * - * <p>By default, when cURL logging is enabled, cURL commands are logged using {@link - * VolleyLog#v}, e.g. at the verbose log level with the same log tag used by the rest of - * Volley. This function may optionally be invoked to provide a custom logger. - * - * @see #setCurlLoggingEnabled(boolean) - */ - public Builder setCurlCommandLogger(CurlCommandLogger curlCommandLogger) { - mCurlCommandLogger = curlCommandLogger; - return this; - } - - /** - * Sets whether to log known auth tokens in cURL commands, or redact them. - * - * <p>By default, headers which may contain auth tokens (e.g. Authorization or Cookie) will - * have their values redacted. Passing true to this method will disable this redaction and - * log the values of these headers. - * - * <p>This heuristic is not perfect; tokens that are logged in unknown headers, or in the - * request body itself, will not be redacted as they cannot be detected generically. - * - * @see #setCurlLoggingEnabled(boolean) - */ - public Builder setLogAuthTokensInCurlCommands(boolean logAuthTokensInCurlCommands) { - mLogAuthTokensInCurlCommands = logAuthTokensInCurlCommands; - return this; - } - - public CronetHttpStack build() { - if (mCronetEngine == null) { - mCronetEngine = new CronetEngine.Builder(context).build(); - } - if (mUrlRewriter == null) { - mUrlRewriter = - new UrlRewriter() { - @Override - public String rewriteUrl(String originalUrl) { - return originalUrl; - } - }; - } - if (mRequestListener == null) { - mRequestListener = new RequestListener() {}; - } - if (mPool == null) { - mPool = new ByteArrayPool(DEFAULT_POOL_SIZE); - } - if (mCurlCommandLogger == null) { - mCurlCommandLogger = - new CurlCommandLogger() { - @Override - public void logCurlCommand(String curlCommand) { - VolleyLog.v(curlCommand); - } - }; - } - return new CronetHttpStack( - mCronetEngine, - mPool, - mUrlRewriter, - mRequestListener, - mCurlLoggingEnabled, - mCurlCommandLogger, - mLogAuthTokensInCurlCommands); - } - } - - /** Callback interface allowing clients to intercept different parts of the request flow. */ - public abstract static class RequestListener { - private CronetHttpStack mStack; - - void initialize(CronetHttpStack stack) { - mStack = stack; - } - - /** - * Called when a request is prepared and about to be sent over the network. - * - * <p>Clients may use this callback to customize UrlRequests before they are dispatched, - * e.g. to enable socket tagging or request finished listeners. - */ - public void onRequestPrepared(Request<?> request, UrlRequest.Builder requestBuilder) {} - - /** @see AsyncHttpStack#getNonBlockingExecutor() */ - protected Executor getNonBlockingExecutor() { - return mStack.getNonBlockingExecutor(); - } - - /** @see AsyncHttpStack#getBlockingExecutor() */ - protected Executor getBlockingExecutor() { - return mStack.getBlockingExecutor(); - } - } - - /** - * Interface for logging cURL commands for requests. - * - * @see Builder#setCurlCommandLogger(CurlCommandLogger) - */ - public interface CurlCommandLogger { - /** Log the given cURL command. */ - void logCurlCommand(String curlCommand); - } - - /** - * Internal container class for request parameters that impact logged cURL commands. - * - * <p>When cURL logging is enabled, an equivalent cURL command to a given request must be - * generated and logged. However, the Cronet UrlRequest object is write-only. So, we write any - * relevant parameters into this read-write container so they can be referenced when generating - * the cURL command (if needed) and then merged into the UrlRequest. - */ - private static class CurlLoggedRequestParameters { - private final TreeMap<String, String> mHeaders = - new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - private String mHttpMethod; - @Nullable private byte[] mBody; - - /** - * Return the headers to be used for the request. - * - * <p>The returned map is case-insensitive. - */ - TreeMap<String, String> getHeaders() { - return mHeaders; - } - - /** Apply all the headers in the given map to the request. */ - void putAllHeaders(Map<String, String> headers) { - mHeaders.putAll(headers); - } - - String getHttpMethod() { - return mHttpMethod; - } - - void setHttpMethod(String httpMethod) { - mHttpMethod = httpMethod; - } - - @Nullable - byte[] getBody() { - return mBody; - } - - void setBody(String contentType, @Nullable byte[] body) { - mBody = body; - if (body != null && !mHeaders.containsKey(HttpHeaderParser.HEADER_CONTENT_TYPE)) { - // Set the content-type unless it was already set (by Request#getHeaders). - mHeaders.put(HttpHeaderParser.HEADER_CONTENT_TYPE, contentType); - } - } - - void applyToRequest(UrlRequest.Builder builder, ExecutorService nonBlockingExecutor) { - for (Map.Entry<String, String> header : mHeaders.entrySet()) { - builder.addHeader(header.getKey(), header.getValue()); - } - builder.setHttpMethod(mHttpMethod); - if (mBody != null) { - UploadDataProvider dataProvider = UploadDataProviders.create(mBody); - builder.setUploadDataProvider(dataProvider, nonBlockingExecutor); - } - } - } -} diff --git a/src/main/java/com/android/volley/toolbox/AsyncHttpStack.java b/src/main/java/com/android/volley/toolbox/AsyncHttpStack.java deleted file mode 100644 index bafab8c..0000000 --- a/src/main/java/com/android/volley/toolbox/AsyncHttpStack.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * 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 androidx.annotation.Nullable; -import androidx.annotation.RestrictTo; -import com.android.volley.AuthFailureError; -import com.android.volley.Request; -import com.android.volley.VolleyLog; -import java.io.IOException; -import java.io.InterruptedIOException; -import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.atomic.AtomicReference; - -/** Asynchronous extension of the {@link BaseHttpStack} class. */ -public abstract class AsyncHttpStack extends BaseHttpStack { - private ExecutorService mBlockingExecutor; - private ExecutorService mNonBlockingExecutor; - - public interface OnRequestComplete { - /** Invoked when the stack successfully completes a request. */ - void onSuccess(HttpResponse httpResponse); - - /** Invoked when the stack throws an {@link AuthFailureError} during a request. */ - void onAuthError(AuthFailureError authFailureError); - - /** Invoked when the stack throws an {@link IOException} during a request. */ - void onError(IOException ioException); - } - - /** - * Makes an HTTP request with the given parameters, and calls the {@link OnRequestComplete} - * callback, with either the {@link HttpResponse} or error that was thrown. - * - * @param request to perform - * @param additionalHeaders to be sent together with {@link Request#getHeaders()} - * @param callback to be called after retrieving the {@link HttpResponse} or throwing an error. - */ - public abstract void executeRequest( - Request<?> request, Map<String, String> additionalHeaders, OnRequestComplete callback); - - /** - * This method sets the non blocking executor to be used by the stack for non-blocking tasks. - * This method must be called before executing any requests. - */ - @RestrictTo({RestrictTo.Scope.LIBRARY_GROUP}) - public void setNonBlockingExecutor(ExecutorService executor) { - mNonBlockingExecutor = executor; - } - - /** - * This method sets the blocking executor to be used by the stack for potentially blocking - * tasks. This method must be called before executing any requests. - */ - @RestrictTo({RestrictTo.Scope.LIBRARY_GROUP}) - public void setBlockingExecutor(ExecutorService executor) { - mBlockingExecutor = executor; - } - - /** Gets blocking executor to perform any potentially blocking tasks. */ - protected ExecutorService getBlockingExecutor() { - return mBlockingExecutor; - } - - /** Gets non-blocking executor to perform any non-blocking tasks. */ - protected ExecutorService getNonBlockingExecutor() { - return mNonBlockingExecutor; - } - - /** - * Performs an HTTP request with the given parameters. - * - * @param request the request to perform - * @param additionalHeaders additional headers to be sent together with {@link - * Request#getHeaders()} - * @return the {@link HttpResponse} - * @throws IOException if an I/O error occurs during the request - * @throws AuthFailureError if an authentication failure occurs during the request - */ - @Override - public final HttpResponse executeRequest( - Request<?> request, Map<String, String> additionalHeaders) - throws IOException, AuthFailureError { - final CountDownLatch latch = new CountDownLatch(1); - final AtomicReference<Response> entry = new AtomicReference<>(); - executeRequest( - request, - additionalHeaders, - new OnRequestComplete() { - @Override - public void onSuccess(HttpResponse httpResponse) { - Response response = - new Response( - httpResponse, - /* ioException= */ null, - /* authFailureError= */ null); - entry.set(response); - latch.countDown(); - } - - @Override - public void onAuthError(AuthFailureError authFailureError) { - Response response = - new Response( - /* httpResponse= */ null, - /* ioException= */ null, - authFailureError); - entry.set(response); - latch.countDown(); - } - - @Override - public void onError(IOException ioException) { - Response response = - new Response( - /* httpResponse= */ null, - ioException, - /* authFailureError= */ null); - entry.set(response); - latch.countDown(); - } - }); - try { - latch.await(); - } catch (InterruptedException e) { - VolleyLog.e(e, "while waiting for CountDownLatch"); - Thread.currentThread().interrupt(); - throw new InterruptedIOException(e.toString()); - } - Response response = entry.get(); - if (response.httpResponse != null) { - return response.httpResponse; - } else if (response.ioException != null) { - throw response.ioException; - } else { - throw response.authFailureError; - } - } - - private static class Response { - HttpResponse httpResponse; - IOException ioException; - AuthFailureError authFailureError; - - private Response( - @Nullable HttpResponse httpResponse, - @Nullable IOException ioException, - @Nullable AuthFailureError authFailureError) { - this.httpResponse = httpResponse; - this.ioException = ioException; - this.authFailureError = authFailureError; - } - } -} diff --git a/src/main/java/com/android/volley/toolbox/BasicAsyncNetwork.java b/src/main/java/com/android/volley/toolbox/BasicAsyncNetwork.java deleted file mode 100644 index 55892a0..0000000 --- a/src/main/java/com/android/volley/toolbox/BasicAsyncNetwork.java +++ /dev/null @@ -1,288 +0,0 @@ -/* - * 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); - } - } -} diff --git a/src/main/java/com/android/volley/toolbox/BasicNetwork.java b/src/main/java/com/android/volley/toolbox/BasicNetwork.java index 06427fe..b527cb9 100644 --- a/src/main/java/com/android/volley/toolbox/BasicNetwork.java +++ b/src/main/java/com/android/volley/toolbox/BasicNetwork.java @@ -17,21 +17,41 @@ package com.android.volley.toolbox; import android.os.SystemClock; +import com.android.volley.AuthFailureError; +import com.android.volley.Cache; +import com.android.volley.Cache.Entry; +import com.android.volley.ClientError; import com.android.volley.Header; import com.android.volley.Network; +import com.android.volley.NetworkError; import com.android.volley.NetworkResponse; +import com.android.volley.NoConnectionError; import com.android.volley.Request; +import com.android.volley.RetryPolicy; +import com.android.volley.ServerError; +import com.android.volley.TimeoutError; import com.android.volley.VolleyError; +import com.android.volley.VolleyLog; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.SocketTimeoutException; +import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.TreeMap; +import java.util.TreeSet; /** A network performing Volley requests over an {@link HttpStack}. */ public class BasicNetwork implements Network { + protected static final boolean DEBUG = VolleyLog.DEBUG; + + private static final int SLOW_REQUEST_THRESHOLD_MS = 3000; + private static final int DEFAULT_POOL_SIZE = 4096; /** @@ -99,24 +119,37 @@ public class BasicNetwork implements Network { try { // Gather headers. Map<String, String> additionalRequestHeaders = - HttpHeaderParser.getCacheHeaders(request.getCacheEntry()); + getCacheHeaders(request.getCacheEntry()); httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders); int statusCode = httpResponse.getStatusCode(); responseHeaders = httpResponse.getHeaders(); // Handle cache validation. if (statusCode == HttpURLConnection.HTTP_NOT_MODIFIED) { - long requestDuration = SystemClock.elapsedRealtime() - requestStart; - return NetworkUtility.getNotModifiedNetworkResponse( - request, requestDuration, responseHeaders); + Entry entry = request.getCacheEntry(); + if (entry == null) { + return new NetworkResponse( + HttpURLConnection.HTTP_NOT_MODIFIED, + /* data= */ null, + /* notModified= */ true, + SystemClock.elapsedRealtime() - requestStart, + responseHeaders); + } + // Combine cached and response headers so the response will be complete. + List<Header> combinedHeaders = combineHeaders(responseHeaders, entry); + return new NetworkResponse( + HttpURLConnection.HTTP_NOT_MODIFIED, + entry.data, + /* notModified= */ true, + SystemClock.elapsedRealtime() - requestStart, + combinedHeaders); } // Some responses such as 204s do not have content. We must check. InputStream inputStream = httpResponse.getContent(); if (inputStream != null) { responseContents = - NetworkUtility.inputStreamToBytes( - inputStream, httpResponse.getContentLength(), mPool); + inputStreamToBytes(inputStream, httpResponse.getContentLength()); } else { // Add 0 byte response as a way of honestly representing a // no-content request. @@ -125,8 +158,7 @@ public class BasicNetwork implements Network { // if the request is slow, log it. long requestLifetime = SystemClock.elapsedRealtime() - requestStart; - NetworkUtility.logSlowRequests( - requestLifetime, request, responseContents, statusCode); + logSlowRequests(requestLifetime, request, responseContents, statusCode); if (statusCode < 200 || statusCode > 299) { throw new IOException(); @@ -137,12 +169,141 @@ public class BasicNetwork implements Network { /* notModified= */ false, SystemClock.elapsedRealtime() - requestStart, responseHeaders); + } catch (SocketTimeoutException e) { + attemptRetryOnException("socket", request, new TimeoutError()); + } catch (MalformedURLException e) { + throw new RuntimeException("Bad URL " + request.getUrl(), e); + } catch (IOException e) { + int statusCode; + if (httpResponse != null) { + statusCode = httpResponse.getStatusCode(); + } else { + throw new NoConnectionError(e); + } + VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl()); + NetworkResponse networkResponse; + if (responseContents != null) { + networkResponse = + new NetworkResponse( + statusCode, + responseContents, + /* notModified= */ false, + SystemClock.elapsedRealtime() - requestStart, + responseHeaders); + if (statusCode == HttpURLConnection.HTTP_UNAUTHORIZED + || statusCode == HttpURLConnection.HTTP_FORBIDDEN) { + attemptRetryOnException( + "auth", request, new AuthFailureError(networkResponse)); + } else if (statusCode >= 400 && statusCode <= 499) { + // Don't retry other client errors. + throw new ClientError(networkResponse); + } else if (statusCode >= 500 && statusCode <= 599) { + if (request.shouldRetryServerErrors()) { + attemptRetryOnException( + "server", request, new ServerError(networkResponse)); + } else { + throw new ServerError(networkResponse); + } + } else { + // 3xx? No reason to retry. + throw new ServerError(networkResponse); + } + } else { + attemptRetryOnException("network", request, new NetworkError()); + } + } + } + } + + /** Logs requests that took over SLOW_REQUEST_THRESHOLD_MS to complete. */ + private void logSlowRequests( + long requestLifetime, Request<?> request, byte[] responseContents, int statusCode) { + if (DEBUG || requestLifetime > SLOW_REQUEST_THRESHOLD_MS) { + VolleyLog.d( + "HTTP response for request=<%s> [lifetime=%d], [size=%s], " + + "[rc=%d], [retryCount=%s]", + request, + requestLifetime, + responseContents != null ? responseContents.length : "null", + statusCode, + request.getRetryPolicy().getCurrentRetryCount()); + } + } + + /** + * Attempts to prepare the request for a retry. If there are no more attempts remaining in the + * request's retry policy, a timeout exception is thrown. + * + * @param request The request to use. + */ + private static void attemptRetryOnException( + String logPrefix, Request<?> request, VolleyError exception) throws VolleyError { + RetryPolicy retryPolicy = request.getRetryPolicy(); + int oldTimeout = request.getTimeoutMs(); + + try { + retryPolicy.retry(exception); + } catch (VolleyError e) { + request.addMarker( + String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout)); + throw e; + } + request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout)); + } + + private Map<String, String> getCacheHeaders(Cache.Entry entry) { + // If there's no cache entry, we're done. + if (entry == null) { + return Collections.emptyMap(); + } + + Map<String, String> headers = new HashMap<>(); + + if (entry.etag != null) { + headers.put("If-None-Match", entry.etag); + } + + if (entry.lastModified > 0) { + headers.put( + "If-Modified-Since", HttpHeaderParser.formatEpochAsRfc1123(entry.lastModified)); + } + + return headers; + } + + protected void logError(String what, String url, long start) { + long now = SystemClock.elapsedRealtime(); + VolleyLog.v("HTTP ERROR(%s) %d ms to fetch %s", what, (now - start), url); + } + + /** Reads the contents of an InputStream into a byte[]. */ + private byte[] inputStreamToBytes(InputStream in, int contentLength) + throws IOException, ServerError { + PoolingByteArrayOutputStream bytes = new PoolingByteArrayOutputStream(mPool, contentLength); + byte[] buffer = null; + try { + if (in == null) { + throw new ServerError(); + } + buffer = mPool.getBuf(1024); + int count; + while ((count = in.read(buffer)) != -1) { + bytes.write(buffer, 0, count); + } + return bytes.toByteArray(); + } finally { + try { + // Close the InputStream and release the resources by "consuming the content". + if (in != null) { + in.close(); + } } catch (IOException e) { - // This will either throw an exception, breaking us from the loop, or will loop - // again and retry the request. - NetworkUtility.handleException( - request, e, requestStart, httpResponse, responseContents); + // This can happen if there was an exception above that left the stream in + // an invalid state. + VolleyLog.v("Error occurred when closing InputStream"); } + mPool.returnBuf(buffer); + bytes.close(); } } @@ -160,4 +321,49 @@ public class BasicNetwork implements Network { } return result; } + + /** + * Combine cache headers with network response headers for an HTTP 304 response. + * + * <p>An HTTP 304 response does not have all header fields. We have to use the header fields + * from the cache entry plus the new ones from the response. See also: + * http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5 + * + * @param responseHeaders Headers from the network response. + * @param entry The cached response. + * @return The combined list of headers. + */ + private static List<Header> combineHeaders(List<Header> responseHeaders, Entry entry) { + // First, create a case-insensitive set of header names from the network + // response. + Set<String> headerNamesFromNetworkResponse = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + if (!responseHeaders.isEmpty()) { + for (Header header : responseHeaders) { + headerNamesFromNetworkResponse.add(header.getName()); + } + } + + // Second, add headers from the cache entry to the network response as long as + // they didn't appear in the network response, which should take precedence. + List<Header> combinedHeaders = new ArrayList<>(responseHeaders); + if (entry.allResponseHeaders != null) { + if (!entry.allResponseHeaders.isEmpty()) { + for (Header header : entry.allResponseHeaders) { + if (!headerNamesFromNetworkResponse.contains(header.getName())) { + combinedHeaders.add(header); + } + } + } + } else { + // Legacy caches only have entry.responseHeaders. + if (!entry.responseHeaders.isEmpty()) { + for (Map.Entry<String, String> header : entry.responseHeaders.entrySet()) { + if (!headerNamesFromNetworkResponse.contains(header.getKey())) { + combinedHeaders.add(new Header(header.getKey(), header.getValue())); + } + } + } + } + return combinedHeaders; + } } diff --git a/src/main/java/com/android/volley/toolbox/DiskBasedCache.java b/src/main/java/com/android/volley/toolbox/DiskBasedCache.java index d4310e0..a6a0c83 100644 --- a/src/main/java/com/android/volley/toolbox/DiskBasedCache.java +++ b/src/main/java/com/android/volley/toolbox/DiskBasedCache.java @@ -55,8 +55,8 @@ public class DiskBasedCache implements Cache { /** Total amount of space currently used by the cache in bytes. */ private long mTotalSize = 0; - /** The supplier for the root directory to use for the cache. */ - private final FileSupplier mRootDirectorySupplier; + /** The root directory to use for the cache. */ + private final File mRootDirectory; /** The maximum size of the cache in bytes. */ private final int mMaxCacheSizeInBytes; @@ -78,27 +78,8 @@ public class DiskBasedCache implements Cache { * briefly exceed this size on disk when writing a new entry that pushes it over the limit * until the ensuing pruning completes. */ - public DiskBasedCache(final File rootDirectory, int maxCacheSizeInBytes) { - mRootDirectorySupplier = - new FileSupplier() { - @Override - public File get() { - return rootDirectory; - } - }; - mMaxCacheSizeInBytes = maxCacheSizeInBytes; - } - - /** - * Constructs an instance of the DiskBasedCache at the specified directory. - * - * @param rootDirectorySupplier The supplier for the root directory of the cache. - * @param maxCacheSizeInBytes The maximum size of the cache in bytes. Note that the cache may - * briefly exceed this size on disk when writing a new entry that pushes it over the limit - * until the ensuing pruning completes. - */ - public DiskBasedCache(FileSupplier rootDirectorySupplier, int maxCacheSizeInBytes) { - mRootDirectorySupplier = rootDirectorySupplier; + public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) { + mRootDirectory = rootDirectory; mMaxCacheSizeInBytes = maxCacheSizeInBytes; } @@ -112,20 +93,10 @@ public class DiskBasedCache implements Cache { this(rootDirectory, DEFAULT_DISK_USAGE_BYTES); } - /** - * Constructs an instance of the DiskBasedCache at the specified directory using the default - * maximum cache size of 5MB. - * - * @param rootDirectorySupplier The supplier for the root directory of the cache. - */ - public DiskBasedCache(FileSupplier rootDirectorySupplier) { - this(rootDirectorySupplier, DEFAULT_DISK_USAGE_BYTES); - } - /** Clears the cache. Deletes all cached files from disk. */ @Override public synchronized void clear() { - File[] files = mRootDirectorySupplier.get().listFiles(); + File[] files = mRootDirectory.listFiles(); if (files != null) { for (File file : files) { file.delete(); @@ -179,14 +150,13 @@ public class DiskBasedCache implements Cache { */ @Override public synchronized void initialize() { - File rootDirectory = mRootDirectorySupplier.get(); - if (!rootDirectory.exists()) { - if (!rootDirectory.mkdirs()) { - VolleyLog.e("Unable to create cache dir %s", rootDirectory.getAbsolutePath()); + if (!mRootDirectory.exists()) { + if (!mRootDirectory.mkdirs()) { + VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath()); } return; } - File[] files = rootDirectory.listFiles(); + File[] files = mRootDirectory.listFiles(); if (files == null) { return; } @@ -256,12 +226,12 @@ public class DiskBasedCache implements Cache { e.size = file.length(); putEntry(key, e); pruneIfNeeded(); + return; } catch (IOException e) { - boolean deleted = file.delete(); - if (!deleted) { - VolleyLog.d("Could not clean up file %s", file.getAbsolutePath()); - } - initializeIfRootDirectoryDeleted(); + } + boolean deleted = file.delete(); + if (!deleted) { + VolleyLog.d("Could not clean up file %s", file.getAbsolutePath()); } } @@ -292,22 +262,7 @@ public class DiskBasedCache implements Cache { /** Returns a file object for the given cache key. */ public File getFileForKey(String key) { - return new File(mRootDirectorySupplier.get(), getFilenameForKey(key)); - } - - /** Re-initialize the cache if the directory was deleted. */ - private void initializeIfRootDirectoryDeleted() { - if (!mRootDirectorySupplier.get().exists()) { - VolleyLog.d("Re-initializing cache after external clearing."); - mEntries.clear(); - mTotalSize = 0; - initialize(); - } - } - - /** Represents a supplier for {@link File}s. */ - public interface FileSupplier { - File get(); + return new File(mRootDirectory, getFilenameForKey(key)); } /** Prunes the cache to fit the maximum size. */ diff --git a/src/main/java/com/android/volley/toolbox/FileSupplier.java b/src/main/java/com/android/volley/toolbox/FileSupplier.java deleted file mode 100644 index 70898a6..0000000 --- a/src/main/java/com/android/volley/toolbox/FileSupplier.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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 java.io.File; - -/** Represents a supplier for {@link File}s. */ -public interface FileSupplier { - File get(); -} diff --git a/src/main/java/com/android/volley/toolbox/HttpHeaderParser.java b/src/main/java/com/android/volley/toolbox/HttpHeaderParser.java index 0b29e80..27d1268 100644 --- a/src/main/java/com/android/volley/toolbox/HttpHeaderParser.java +++ b/src/main/java/com/android/volley/toolbox/HttpHeaderParser.java @@ -16,9 +16,6 @@ package com.android.volley.toolbox; -import androidx.annotation.Nullable; -import androidx.annotation.RestrictTo; -import androidx.annotation.RestrictTo.Scope; import com.android.volley.Cache; import com.android.volley.Header; import com.android.volley.NetworkResponse; @@ -26,30 +23,21 @@ import com.android.volley.VolleyLog; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Collections; import java.util.Date; -import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Set; import java.util.TimeZone; import java.util.TreeMap; -import java.util.TreeSet; /** Utility methods for parsing HTTP headers. */ public class HttpHeaderParser { - @RestrictTo({Scope.LIBRARY_GROUP}) - public static final String HEADER_CONTENT_TYPE = "Content-Type"; + static final String HEADER_CONTENT_TYPE = "Content-Type"; private static final String DEFAULT_CONTENT_CHARSET = "ISO-8859-1"; - private static final String RFC1123_PARSE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz"; - - // Hardcode 'GMT' rather than using 'zzz' since some platforms append an extraneous +00:00. - // See #287. - private static final String RFC1123_OUTPUT_FORMAT = "EEE, dd MMM yyyy HH:mm:ss 'GMT'"; + private static final String RFC1123_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz"; /** * Extracts a {@link com.android.volley.Cache.Entry} from a {@link NetworkResponse}. @@ -57,14 +45,10 @@ public class HttpHeaderParser { * @param response The network response to parse headers from * @return a cache entry for the given response, or null if the response is not cacheable. */ - @Nullable public static Cache.Entry parseCacheHeaders(NetworkResponse response) { long now = System.currentTimeMillis(); Map<String, String> headers = response.headers; - if (headers == null) { - return null; - } long serverDate = 0; long lastModified = 0; @@ -148,29 +132,21 @@ public class HttpHeaderParser { public static long parseDateAsEpoch(String dateStr) { try { // Parse date in RFC1123 format if this header contains one - return newUsGmtFormatter(RFC1123_PARSE_FORMAT).parse(dateStr).getTime(); + return newRfc1123Formatter().parse(dateStr).getTime(); } catch (ParseException e) { // Date in invalid format, fallback to 0 - // If the value is either "0" or "-1" we only log to verbose, - // these values are pretty common and cause log spam. - String message = "Unable to parse dateStr: %s, falling back to 0"; - if ("0".equals(dateStr) || "-1".equals(dateStr)) { - VolleyLog.v(message, dateStr); - } else { - VolleyLog.e(e, message, dateStr); - } - + VolleyLog.e(e, "Unable to parse dateStr: %s, falling back to 0", dateStr); return 0; } } /** Format an epoch date in RFC1123 format. */ static String formatEpochAsRfc1123(long epoch) { - return newUsGmtFormatter(RFC1123_OUTPUT_FORMAT).format(new Date(epoch)); + return newRfc1123Formatter().format(new Date(epoch)); } - private static SimpleDateFormat newUsGmtFormatter(String format) { - SimpleDateFormat formatter = new SimpleDateFormat(format, Locale.US); + private static SimpleDateFormat newRfc1123Formatter() { + SimpleDateFormat formatter = new SimpleDateFormat(RFC1123_FORMAT, Locale.US); formatter.setTimeZone(TimeZone.getTimeZone("GMT")); return formatter; } @@ -183,11 +159,7 @@ public class HttpHeaderParser { * @return Returns the charset specified in the Content-Type of this header, or the * defaultCharset if none can be found. */ - public static String parseCharset( - @Nullable Map<String, String> headers, String defaultCharset) { - if (headers == null) { - return defaultCharset; - } + public static String parseCharset(Map<String, String> headers, String defaultCharset) { String contentType = headers.get(HEADER_CONTENT_TYPE); if (contentType != null) { String[] params = contentType.split(";", 0); @@ -208,7 +180,7 @@ public class HttpHeaderParser { * Returns the charset specified in the Content-Type of this header, or the HTTP default * (ISO-8859-1) if none can be found. */ - public static String parseCharset(@Nullable Map<String, String> headers) { + public static String parseCharset(Map<String, String> headers) { return parseCharset(headers, DEFAULT_CONTENT_CHARSET); } @@ -233,69 +205,4 @@ public class HttpHeaderParser { } return allHeaders; } - - /** - * Combine cache headers with network response headers for an HTTP 304 response. - * - * <p>An HTTP 304 response does not have all header fields. We have to use the header fields - * from the cache entry plus the new ones from the response. See also: - * http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5 - * - * @param responseHeaders Headers from the network response. - * @param entry The cached response. - * @return The combined list of headers. - */ - static List<Header> combineHeaders(List<Header> responseHeaders, Cache.Entry entry) { - // First, create a case-insensitive set of header names from the network - // response. - Set<String> headerNamesFromNetworkResponse = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); - if (!responseHeaders.isEmpty()) { - for (Header header : responseHeaders) { - headerNamesFromNetworkResponse.add(header.getName()); - } - } - - // Second, add headers from the cache entry to the network response as long as - // they didn't appear in the network response, which should take precedence. - List<Header> combinedHeaders = new ArrayList<>(responseHeaders); - if (entry.allResponseHeaders != null) { - if (!entry.allResponseHeaders.isEmpty()) { - for (Header header : entry.allResponseHeaders) { - if (!headerNamesFromNetworkResponse.contains(header.getName())) { - combinedHeaders.add(header); - } - } - } - } else { - // Legacy caches only have entry.responseHeaders. - if (!entry.responseHeaders.isEmpty()) { - for (Map.Entry<String, String> header : entry.responseHeaders.entrySet()) { - if (!headerNamesFromNetworkResponse.contains(header.getKey())) { - combinedHeaders.add(new Header(header.getKey(), header.getValue())); - } - } - } - } - return combinedHeaders; - } - - static Map<String, String> getCacheHeaders(Cache.Entry entry) { - // If there's no cache entry, we're done. - if (entry == null) { - return Collections.emptyMap(); - } - - Map<String, String> headers = new HashMap<>(); - - if (entry.etag != null) { - headers.put("If-None-Match", entry.etag); - } - - if (entry.lastModified > 0) { - headers.put( - "If-Modified-Since", HttpHeaderParser.formatEpochAsRfc1123(entry.lastModified)); - } - - return headers; - } } diff --git a/src/main/java/com/android/volley/toolbox/HttpResponse.java b/src/main/java/com/android/volley/toolbox/HttpResponse.java index 595f926..9a9294f 100644 --- a/src/main/java/com/android/volley/toolbox/HttpResponse.java +++ b/src/main/java/com/android/volley/toolbox/HttpResponse.java @@ -15,9 +15,7 @@ */ package com.android.volley.toolbox; -import androidx.annotation.Nullable; import com.android.volley.Header; -import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.Collections; import java.util.List; @@ -28,8 +26,7 @@ public final class HttpResponse { private final int mStatusCode; private final List<Header> mHeaders; private final int mContentLength; - @Nullable private final InputStream mContent; - @Nullable private final byte[] mContentBytes; + private final InputStream mContent; /** * Construct a new HttpResponse for an empty response body. @@ -56,23 +53,6 @@ public final class HttpResponse { mHeaders = headers; mContentLength = contentLength; mContent = content; - mContentBytes = null; - } - - /** - * Construct a new HttpResponse. - * - * @param statusCode the HTTP status code of the response - * @param headers the response headers - * @param contentBytes a byte[] of the response content. This is an optimization for HTTP stacks - * that natively support returning a byte[]. - */ - public HttpResponse(int statusCode, List<Header> headers, byte[] contentBytes) { - mStatusCode = statusCode; - mHeaders = headers; - mContentLength = contentBytes.length; - mContentBytes = contentBytes; - mContent = null; } /** Returns the HTTP status code of the response. */ @@ -91,28 +71,10 @@ public final class HttpResponse { } /** - * If a byte[] was already provided by an HTTP stack that natively supports returning one, this - * method will return that byte[] as an optimization over copying the bytes from an input - * stream. It may return null, even if the response has content, as long as mContent is - * provided. - */ - @Nullable - public final byte[] getContentBytes() { - return mContentBytes; - } - - /** * Returns an {@link InputStream} of the response content. May be null to indicate that the * response has no content. */ - @Nullable public final InputStream getContent() { - if (mContent != null) { - return mContent; - } else if (mContentBytes != null) { - return new ByteArrayInputStream(mContentBytes); - } else { - return null; - } + return mContent; } } diff --git a/src/main/java/com/android/volley/toolbox/HurlStack.java b/src/main/java/com/android/volley/toolbox/HurlStack.java index 35c6a72..f85d42c 100644 --- a/src/main/java/com/android/volley/toolbox/HurlStack.java +++ b/src/main/java/com/android/volley/toolbox/HurlStack.java @@ -25,7 +25,6 @@ import java.io.DataOutputStream; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayList; @@ -41,7 +40,13 @@ public class HurlStack extends BaseHttpStack { private static final int HTTP_CONTINUE = 100; /** An interface for transforming URLs before use. */ - public interface UrlRewriter extends com.android.volley.toolbox.UrlRewriter {} + public interface UrlRewriter { + /** + * Returns a URL to use instead of the provided one, or null to indicate this URL should not + * be used at all. + */ + String rewriteUrl(String originalUrl); + } private final UrlRewriter mUrlRewriter; private final SSLSocketFactory mSslSocketFactory; @@ -106,7 +111,7 @@ public class HurlStack extends BaseHttpStack { responseCode, convertHeaders(connection.getHeaderFields()), connection.getContentLength(), - createInputStream(request, connection)); + new UrlConnectionInputStream(connection)); } finally { if (!keepConnectionOpen) { connection.disconnect(); @@ -164,19 +169,6 @@ public class HurlStack extends BaseHttpStack { } /** - * Create and return an InputStream from which the response will be read. - * - * <p>May be overridden by subclasses to manipulate or monitor this input stream. - * - * @param request current request. - * @param connection current connection of request. - * @return an InputStream from which the response will be read. - */ - protected InputStream createInputStream(Request<?> request, HttpURLConnection connection) { - return new UrlConnectionInputStream(connection); - } - - /** * Initializes an {@link InputStream} from the given {@link HttpURLConnection}. * * @param connection @@ -231,7 +223,7 @@ public class HurlStack extends BaseHttpStack { // NOTE: Any request headers added here (via setRequestProperty or addRequestProperty) should be // checked against the existing properties in the connection and not overridden if already set. @SuppressWarnings("deprecation") - /* package */ void setConnectionParametersForRequest( + /* package */ static void setConnectionParametersForRequest( HttpURLConnection connection, Request<?> request) throws IOException, AuthFailureError { switch (request.getMethod()) { case Method.DEPRECATED_GET_OR_POST: @@ -278,7 +270,7 @@ public class HurlStack extends BaseHttpStack { } } - private void addBodyIfExists(HttpURLConnection connection, Request<?> request) + private static void addBodyIfExists(HttpURLConnection connection, Request<?> request) throws IOException, AuthFailureError { byte[] body = request.getBody(); if (body != null) { @@ -286,7 +278,7 @@ public class HurlStack extends BaseHttpStack { } } - private void addBody(HttpURLConnection connection, Request<?> request, byte[] body) + private static void addBody(HttpURLConnection connection, Request<?> request, byte[] body) throws IOException { // Prepare output. There is no need to set Content-Length explicitly, // since this is handled by HttpURLConnection using the size of the prepared @@ -297,25 +289,8 @@ public class HurlStack extends BaseHttpStack { connection.setRequestProperty( HttpHeaderParser.HEADER_CONTENT_TYPE, request.getBodyContentType()); } - DataOutputStream out = - new DataOutputStream(createOutputStream(request, connection, body.length)); + DataOutputStream out = new DataOutputStream(connection.getOutputStream()); out.write(body); out.close(); } - - /** - * Create and return an OutputStream to which the request body will be written. - * - * <p>May be overridden by subclasses to manipulate or monitor this output stream. - * - * @param request current request. - * @param connection current connection of request. - * @param length size of stream to write. - * @return an OutputStream to which the request body will be written. - * @throws IOException if an I/O error occurs while creating the stream. - */ - protected OutputStream createOutputStream( - Request<?> request, HttpURLConnection connection, int length) throws IOException { - return connection.getOutputStream(); - } } diff --git a/src/main/java/com/android/volley/toolbox/ImageLoader.java b/src/main/java/com/android/volley/toolbox/ImageLoader.java index eece2cf..b80072b 100644 --- a/src/main/java/com/android/volley/toolbox/ImageLoader.java +++ b/src/main/java/com/android/volley/toolbox/ImageLoader.java @@ -20,7 +20,6 @@ import android.os.Looper; import android.widget.ImageView; import android.widget.ImageView.ScaleType; import androidx.annotation.MainThread; -import androidx.annotation.Nullable; import com.android.volley.Request; import com.android.volley.RequestQueue; import com.android.volley.Response.ErrorListener; @@ -71,7 +70,6 @@ public class ImageLoader { * LruCache is recommended. */ public interface ImageCache { - @Nullable Bitmap getBitmap(String url); void putBitmap(String url, Bitmap bitmap); diff --git a/src/main/java/com/android/volley/toolbox/NetworkUtility.java b/src/main/java/com/android/volley/toolbox/NetworkUtility.java deleted file mode 100644 index 44d5904..0000000 --- a/src/main/java/com/android/volley/toolbox/NetworkUtility.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * 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 android.os.SystemClock; -import androidx.annotation.Nullable; -import com.android.volley.AuthFailureError; -import com.android.volley.Cache; -import com.android.volley.ClientError; -import com.android.volley.Header; -import com.android.volley.NetworkError; -import com.android.volley.NetworkResponse; -import com.android.volley.NoConnectionError; -import com.android.volley.Request; -import com.android.volley.RetryPolicy; -import com.android.volley.ServerError; -import com.android.volley.TimeoutError; -import com.android.volley.VolleyError; -import com.android.volley.VolleyLog; -import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.SocketTimeoutException; -import java.util.List; - -/** - * Utility class for methods that are shared between {@link BasicNetwork} and {@link - * BasicAsyncNetwork} - */ -public final class NetworkUtility { - private static final int SLOW_REQUEST_THRESHOLD_MS = 3000; - - private NetworkUtility() {} - - /** Logs requests that took over SLOW_REQUEST_THRESHOLD_MS to complete. */ - static void logSlowRequests( - long requestLifetime, Request<?> request, byte[] responseContents, int statusCode) { - if (VolleyLog.DEBUG || requestLifetime > SLOW_REQUEST_THRESHOLD_MS) { - VolleyLog.d( - "HTTP response for request=<%s> [lifetime=%d], [size=%s], " - + "[rc=%d], [retryCount=%s]", - request, - requestLifetime, - responseContents != null ? responseContents.length : "null", - statusCode, - request.getRetryPolicy().getCurrentRetryCount()); - } - } - - static NetworkResponse getNotModifiedNetworkResponse( - Request<?> request, long requestDuration, List<Header> responseHeaders) { - Cache.Entry entry = request.getCacheEntry(); - if (entry == null) { - return new NetworkResponse( - HttpURLConnection.HTTP_NOT_MODIFIED, - /* data= */ null, - /* notModified= */ true, - requestDuration, - responseHeaders); - } - // Combine cached and response headers so the response will be complete. - List<Header> combinedHeaders = HttpHeaderParser.combineHeaders(responseHeaders, entry); - return new NetworkResponse( - HttpURLConnection.HTTP_NOT_MODIFIED, - entry.data, - /* notModified= */ true, - requestDuration, - combinedHeaders); - } - - /** Reads the contents of an InputStream into a byte[]. */ - static byte[] inputStreamToBytes(InputStream in, int contentLength, ByteArrayPool pool) - throws IOException { - PoolingByteArrayOutputStream bytes = new PoolingByteArrayOutputStream(pool, contentLength); - byte[] buffer = null; - try { - buffer = pool.getBuf(1024); - int count; - while ((count = in.read(buffer)) != -1) { - bytes.write(buffer, 0, count); - } - return bytes.toByteArray(); - } finally { - try { - // Close the InputStream and release the resources by "consuming the content". - if (in != null) { - in.close(); - } - } catch (IOException e) { - // This can happen if there was an exception above that left the stream in - // an invalid state. - VolleyLog.v("Error occurred when closing InputStream"); - } - pool.returnBuf(buffer); - bytes.close(); - } - } - - /** - * Attempts to prepare the request for a retry. If there are no more attempts remaining in the - * request's retry policy, a timeout exception is thrown. - * - * @param request The request to use. - */ - private static void attemptRetryOnException( - final String logPrefix, final Request<?> request, final VolleyError exception) - throws VolleyError { - final RetryPolicy retryPolicy = request.getRetryPolicy(); - final int oldTimeout = request.getTimeoutMs(); - try { - retryPolicy.retry(exception); - } catch (VolleyError e) { - request.addMarker( - String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout)); - throw e; - } - request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout)); - } - - /** - * Based on the exception thrown, decides whether to attempt to retry, or to throw the error. - * Also handles logging. - */ - static void handleException( - Request<?> request, - IOException exception, - long requestStartMs, - @Nullable HttpResponse httpResponse, - @Nullable byte[] responseContents) - throws VolleyError { - if (exception instanceof SocketTimeoutException) { - attemptRetryOnException("socket", request, new TimeoutError()); - } else if (exception instanceof MalformedURLException) { - throw new RuntimeException("Bad URL " + request.getUrl(), exception); - } else { - int statusCode; - if (httpResponse != null) { - statusCode = httpResponse.getStatusCode(); - } else { - if (request.shouldRetryConnectionErrors()) { - attemptRetryOnException("connection", request, new NoConnectionError()); - return; - } else { - throw new NoConnectionError(exception); - } - } - VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl()); - NetworkResponse networkResponse; - if (responseContents != null) { - List<Header> responseHeaders; - responseHeaders = httpResponse.getHeaders(); - networkResponse = - new NetworkResponse( - statusCode, - responseContents, - /* notModified= */ false, - SystemClock.elapsedRealtime() - requestStartMs, - responseHeaders); - if (statusCode == HttpURLConnection.HTTP_UNAUTHORIZED - || statusCode == HttpURLConnection.HTTP_FORBIDDEN) { - attemptRetryOnException("auth", request, new AuthFailureError(networkResponse)); - } else if (statusCode >= 400 && statusCode <= 499) { - // Don't retry other client errors. - throw new ClientError(networkResponse); - } else if (statusCode >= 500 && statusCode <= 599) { - if (request.shouldRetryServerErrors()) { - attemptRetryOnException( - "server", request, new ServerError(networkResponse)); - } else { - throw new ServerError(networkResponse); - } - } else { - // 3xx? No reason to retry. - throw new ServerError(networkResponse); - } - } else { - attemptRetryOnException("network", request, new NetworkError()); - } - } - } -} diff --git a/src/main/java/com/android/volley/toolbox/NoAsyncCache.java b/src/main/java/com/android/volley/toolbox/NoAsyncCache.java deleted file mode 100644 index aa4aeea..0000000 --- a/src/main/java/com/android/volley/toolbox/NoAsyncCache.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.android.volley.toolbox; - -import com.android.volley.AsyncCache; -import com.android.volley.Cache; - -/** An AsyncCache that doesn't cache anything. */ -public class NoAsyncCache extends AsyncCache { - @Override - public void get(String key, OnGetCompleteCallback callback) { - callback.onGetComplete(null); - } - - @Override - public void put(String key, Cache.Entry entry, OnWriteCompleteCallback callback) { - callback.onWriteComplete(); - } - - @Override - public void clear(OnWriteCompleteCallback callback) { - callback.onWriteComplete(); - } - - @Override - public void initialize(OnWriteCompleteCallback callback) { - callback.onWriteComplete(); - } - - @Override - public void invalidate(String key, boolean fullExpire, OnWriteCompleteCallback callback) { - callback.onWriteComplete(); - } - - @Override - public void remove(String key, OnWriteCompleteCallback callback) { - callback.onWriteComplete(); - } -} diff --git a/src/main/java/com/android/volley/toolbox/UrlRewriter.java b/src/main/java/com/android/volley/toolbox/UrlRewriter.java deleted file mode 100644 index 8bbb770..0000000 --- a/src/main/java/com/android/volley/toolbox/UrlRewriter.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 androidx.annotation.Nullable; - -/** An interface for transforming URLs before use. */ -public interface UrlRewriter { - /** - * Returns a URL to use instead of the provided one, or null to indicate this URL should not be - * used at all. - */ - @Nullable - String rewriteUrl(String originalUrl); -} diff --git a/src/main/java/com/android/volley/toolbox/Volley.java b/src/main/java/com/android/volley/toolbox/Volley.java index bc65c9c..1982802 100644 --- a/src/main/java/com/android/volley/toolbox/Volley.java +++ b/src/main/java/com/android/volley/toolbox/Volley.java @@ -86,22 +86,8 @@ public class Volley { } private static RequestQueue newRequestQueue(Context context, Network network) { - final Context appContext = context.getApplicationContext(); - // Use a lazy supplier for the cache directory so that newRequestQueue() can be called on - // main thread without causing strict mode violation. - DiskBasedCache.FileSupplier cacheSupplier = - new DiskBasedCache.FileSupplier() { - private File cacheDir = null; - - @Override - public File get() { - if (cacheDir == null) { - cacheDir = new File(appContext.getCacheDir(), DEFAULT_CACHE_DIR); - } - return cacheDir; - } - }; - RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheSupplier), network); + File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR); + RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network); queue.start(); return queue; } diff --git a/src/test/java/com/android/volley/AsyncRequestQueueTest.java b/src/test/java/com/android/volley/AsyncRequestQueueTest.java deleted file mode 100644 index 54ff0a1..0000000 --- a/src/test/java/com/android/volley/AsyncRequestQueueTest.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) 2011 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; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; -import static org.mockito.MockitoAnnotations.initMocks; - -import com.android.volley.mock.ShadowSystemClock; -import com.android.volley.toolbox.NoAsyncCache; -import com.android.volley.toolbox.StringRequest; -import com.android.volley.utils.ImmediateResponseDelivery; -import com.google.common.util.concurrent.MoreExecutors; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.ScheduledExecutorService; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -/** Unit tests for AsyncRequestQueue, with all dependencies mocked out */ -@RunWith(RobolectricTestRunner.class) -@Config(shadows = {ShadowSystemClock.class}) -public class AsyncRequestQueueTest { - - @Mock private AsyncNetwork mMockNetwork; - @Mock private ScheduledExecutorService mMockScheduledExecutor; - private AsyncRequestQueue queue; - - @Before - public void setUp() throws Exception { - ResponseDelivery mDelivery = new ImmediateResponseDelivery(); - initMocks(this); - queue = - new AsyncRequestQueue.Builder(mMockNetwork) - .setAsyncCache(new NoAsyncCache()) - .setResponseDelivery(mDelivery) - .setExecutorFactory( - new AsyncRequestQueue.ExecutorFactory() { - @Override - public ExecutorService createNonBlockingExecutor( - BlockingQueue<Runnable> taskQueue) { - return MoreExecutors.newDirectExecutorService(); - } - - @Override - public ExecutorService createBlockingExecutor( - BlockingQueue<Runnable> taskQueue) { - return MoreExecutors.newDirectExecutorService(); - } - - @Override - public ScheduledExecutorService - createNonBlockingScheduledExecutor() { - return mMockScheduledExecutor; - } - }) - .build(); - } - - @Test - public void cancelAll_onlyCorrectTag() throws Exception { - queue.start(); - Object tagA = new Object(); - Object tagB = new Object(); - StringRequest req1 = mock(StringRequest.class); - when(req1.getTag()).thenReturn(tagA); - StringRequest req2 = mock(StringRequest.class); - when(req2.getTag()).thenReturn(tagB); - StringRequest req3 = mock(StringRequest.class); - when(req3.getTag()).thenReturn(tagA); - StringRequest req4 = mock(StringRequest.class); - when(req4.getTag()).thenReturn(tagA); - - queue.add(req1); // A - queue.add(req2); // B - queue.add(req3); // A - queue.cancelAll(tagA); - queue.add(req4); // A - - verify(req1).cancel(); // A cancelled - verify(req3).cancel(); // A cancelled - verify(req2, never()).cancel(); // B not cancelled - verify(req4, never()).cancel(); // A added after cancel not cancelled - queue.stop(); - } - - @Test - public void add_notifiesListener() throws Exception { - RequestQueue.RequestEventListener listener = mock(RequestQueue.RequestEventListener.class); - queue.start(); - queue.addRequestEventListener(listener); - StringRequest req = mock(StringRequest.class); - - queue.add(req); - - verify(listener).onRequestEvent(req, RequestQueue.RequestEvent.REQUEST_QUEUED); - verifyNoMoreInteractions(listener); - queue.stop(); - } - - @Test - public void finish_notifiesListener() throws Exception { - RequestQueue.RequestEventListener listener = mock(RequestQueue.RequestEventListener.class); - queue.start(); - queue.addRequestEventListener(listener); - StringRequest req = mock(StringRequest.class); - - queue.finish(req); - - verify(listener).onRequestEvent(req, RequestQueue.RequestEvent.REQUEST_FINISHED); - verifyNoMoreInteractions(listener); - queue.stop(); - } - - @Test - public void sendRequestEvent_notifiesListener() throws Exception { - StringRequest req = mock(StringRequest.class); - RequestQueue.RequestEventListener listener = mock(RequestQueue.RequestEventListener.class); - queue.start(); - queue.addRequestEventListener(listener); - - queue.sendRequestEvent(req, RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_STARTED); - - verify(listener) - .onRequestEvent(req, RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_STARTED); - verifyNoMoreInteractions(listener); - queue.stop(); - } - - @Test - public void removeRequestEventListener_removesListener() throws Exception { - StringRequest req = mock(StringRequest.class); - RequestQueue.RequestEventListener listener = mock(RequestQueue.RequestEventListener.class); - queue.start(); - queue.addRequestEventListener(listener); - queue.removeRequestEventListener(listener); - - queue.sendRequestEvent(req, RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_STARTED); - - verifyNoMoreInteractions(listener); - queue.stop(); - } -} diff --git a/src/test/java/com/android/volley/CacheDispatcherTest.java b/src/test/java/com/android/volley/CacheDispatcherTest.java index aef6785..2592a0b 100644 --- a/src/test/java/com/android/volley/CacheDispatcherTest.java +++ b/src/test/java/com/android/volley/CacheDispatcherTest.java @@ -140,25 +140,6 @@ public class CacheDispatcherTest { assertSame(entry, mRequest.getCacheEntry()); } - // An fresh cache hit with parse error, does not post a response and queues to the network. - @Test - public void freshCacheHit_parseError() throws Exception { - Request request = mock(Request.class); - when(request.parseNetworkResponse(any(NetworkResponse.class))) - .thenReturn(Response.error(new ParseError())); - when(request.getCacheKey()).thenReturn("cache/key"); - Cache.Entry entry = CacheTestUtils.makeRandomCacheEntry(null, false, false); - when(mCache.get(anyString())).thenReturn(entry); - - mDispatcher.processRequest(request); - - verifyNoResponse(mDelivery); - verify(mNetworkQueue).put(request); - assertNull(request.getCacheEntry()); - verify(mCache).invalidate("cache/key", true); - verify(request).addMarker("cache-parsing-failed"); - } - @Test public void duplicateCacheMiss() throws Exception { StringRequest secondRequest = diff --git a/src/test/java/com/android/volley/cronet/CronetHttpStackTest.java b/src/test/java/com/android/volley/cronet/CronetHttpStackTest.java deleted file mode 100644 index cedb6ff..0000000 --- a/src/test/java/com/android/volley/cronet/CronetHttpStackTest.java +++ /dev/null @@ -1,381 +0,0 @@ -/* - * 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.cronet; - -import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import com.android.volley.Header; -import com.android.volley.cronet.CronetHttpStack.CurlCommandLogger; -import com.android.volley.mock.TestRequest; -import com.android.volley.toolbox.AsyncHttpStack.OnRequestComplete; -import com.android.volley.toolbox.UrlRewriter; -import com.google.common.collect.ImmutableMap; -import com.google.common.util.concurrent.MoreExecutors; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; -import org.chromium.net.CronetEngine; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Answers; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; - -@RunWith(RobolectricTestRunner.class) -public class CronetHttpStackTest { - @Mock private CurlCommandLogger mMockCurlCommandLogger; - @Mock private OnRequestComplete mMockOnRequestComplete; - @Mock private UrlRewriter mMockUrlRewriter; - - // A fake would be ideal here, but Cronet doesn't (yet) provide one, and at the moment we aren't - // exercising the full response flow. - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private CronetEngine mMockCronetEngine; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - } - - @Test - public void curlLogging_disabled() { - CronetHttpStack stack = - createStack( - new Consumer<CronetHttpStack.Builder>() { - @Override - public void accept(CronetHttpStack.Builder builder) { - // Default parameters should not enable cURL logging. - } - }); - - stack.executeRequest( - new TestRequest.Get(), ImmutableMap.<String, String>of(), mMockOnRequestComplete); - - verify(mMockCurlCommandLogger, never()).logCurlCommand(anyString()); - } - - @Test - public void curlLogging_simpleTextRequest() { - CronetHttpStack stack = - createStack( - new Consumer<CronetHttpStack.Builder>() { - @Override - public void accept(CronetHttpStack.Builder builder) { - builder.setCurlLoggingEnabled(true); - } - }); - - stack.executeRequest( - new TestRequest.Get(), ImmutableMap.<String, String>of(), mMockOnRequestComplete); - - ArgumentCaptor<String> curlCommandCaptor = ArgumentCaptor.forClass(String.class); - verify(mMockCurlCommandLogger).logCurlCommand(curlCommandCaptor.capture()); - assertEquals("curl -X GET \"http://foo.com\"", curlCommandCaptor.getValue()); - } - - @Test - public void curlLogging_rewrittenUrl() { - CronetHttpStack stack = - createStack( - new Consumer<CronetHttpStack.Builder>() { - @Override - public void accept(CronetHttpStack.Builder builder) { - builder.setCurlLoggingEnabled(true) - .setUrlRewriter(mMockUrlRewriter); - } - }); - when(mMockUrlRewriter.rewriteUrl("http://foo.com")).thenReturn("http://bar.com"); - - stack.executeRequest( - new TestRequest.Get(), ImmutableMap.<String, String>of(), mMockOnRequestComplete); - - ArgumentCaptor<String> curlCommandCaptor = ArgumentCaptor.forClass(String.class); - verify(mMockCurlCommandLogger).logCurlCommand(curlCommandCaptor.capture()); - assertEquals("curl -X GET \"http://bar.com\"", curlCommandCaptor.getValue()); - } - - @Test - public void curlLogging_headers_withoutTokens() { - CronetHttpStack stack = - createStack( - new Consumer<CronetHttpStack.Builder>() { - @Override - public void accept(CronetHttpStack.Builder builder) { - builder.setCurlLoggingEnabled(true); - } - }); - - stack.executeRequest( - new TestRequest.Delete() { - @Override - public Map<String, String> getHeaders() { - return ImmutableMap.of( - "SomeHeader", "SomeValue", - "Authorization", "SecretToken"); - } - }, - ImmutableMap.of("SomeOtherHeader", "SomeValue"), - mMockOnRequestComplete); - - ArgumentCaptor<String> curlCommandCaptor = ArgumentCaptor.forClass(String.class); - verify(mMockCurlCommandLogger).logCurlCommand(curlCommandCaptor.capture()); - // NOTE: Header order is stable because the implementation uses a TreeMap. - assertEquals( - "curl -X DELETE --header \"Authorization: [REDACTED]\" " - + "--header \"SomeHeader: SomeValue\" " - + "--header \"SomeOtherHeader: SomeValue\" \"http://foo.com\"", - curlCommandCaptor.getValue()); - } - - @Test - public void curlLogging_headers_withTokens() { - CronetHttpStack stack = - createStack( - new Consumer<CronetHttpStack.Builder>() { - @Override - public void accept(CronetHttpStack.Builder builder) { - builder.setCurlLoggingEnabled(true) - .setLogAuthTokensInCurlCommands(true); - } - }); - - stack.executeRequest( - new TestRequest.Delete() { - @Override - public Map<String, String> getHeaders() { - return ImmutableMap.of( - "SomeHeader", "SomeValue", - "Authorization", "SecretToken"); - } - }, - ImmutableMap.of("SomeOtherHeader", "SomeValue"), - mMockOnRequestComplete); - - ArgumentCaptor<String> curlCommandCaptor = ArgumentCaptor.forClass(String.class); - verify(mMockCurlCommandLogger).logCurlCommand(curlCommandCaptor.capture()); - // NOTE: Header order is stable because the implementation uses a TreeMap. - assertEquals( - "curl -X DELETE --header \"Authorization: SecretToken\" " - + "--header \"SomeHeader: SomeValue\" " - + "--header \"SomeOtherHeader: SomeValue\" \"http://foo.com\"", - curlCommandCaptor.getValue()); - } - - @Test - public void curlLogging_textRequest() { - CronetHttpStack stack = - createStack( - new Consumer<CronetHttpStack.Builder>() { - @Override - public void accept(CronetHttpStack.Builder builder) { - builder.setCurlLoggingEnabled(true); - } - }); - - stack.executeRequest( - new TestRequest.PostWithBody() { - @Override - public byte[] getBody() { - try { - return "hello".getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - - @Override - public String getBodyContentType() { - return "text/plain; charset=UTF-8"; - } - }, - ImmutableMap.<String, String>of(), - mMockOnRequestComplete); - - ArgumentCaptor<String> curlCommandCaptor = ArgumentCaptor.forClass(String.class); - verify(mMockCurlCommandLogger).logCurlCommand(curlCommandCaptor.capture()); - assertEquals( - "curl -X POST " - + "--header \"Content-Type: text/plain; charset=UTF-8\" \"http://foo.com\" " - + "--data-ascii \"hello\"", - curlCommandCaptor.getValue()); - } - - @Test - public void curlLogging_gzipTextRequest() { - CronetHttpStack stack = - createStack( - new Consumer<CronetHttpStack.Builder>() { - @Override - public void accept(CronetHttpStack.Builder builder) { - builder.setCurlLoggingEnabled(true); - } - }); - - stack.executeRequest( - new TestRequest.PostWithBody() { - @Override - public byte[] getBody() { - return new byte[] {1, 2, 3, 4, 5}; - } - - @Override - public String getBodyContentType() { - return "text/plain"; - } - - @Override - public Map<String, String> getHeaders() { - return ImmutableMap.of("Content-Encoding", "gzip, identity"); - } - }, - ImmutableMap.<String, String>of(), - mMockOnRequestComplete); - - ArgumentCaptor<String> curlCommandCaptor = ArgumentCaptor.forClass(String.class); - verify(mMockCurlCommandLogger).logCurlCommand(curlCommandCaptor.capture()); - assertEquals( - "echo 'AQIDBAU=' | base64 -d > /tmp/$$.bin; curl -X POST " - + "--header \"Content-Encoding: gzip, identity\" " - + "--header \"Content-Type: text/plain\" \"http://foo.com\" " - + "--data-binary @/tmp/$$.bin", - curlCommandCaptor.getValue()); - } - - @Test - public void curlLogging_binaryRequest() { - CronetHttpStack stack = - createStack( - new Consumer<CronetHttpStack.Builder>() { - @Override - public void accept(CronetHttpStack.Builder builder) { - builder.setCurlLoggingEnabled(true); - } - }); - - stack.executeRequest( - new TestRequest.PostWithBody() { - @Override - public byte[] getBody() { - return new byte[] {1, 2, 3, 4, 5}; - } - - @Override - public String getBodyContentType() { - return "application/octet-stream"; - } - }, - ImmutableMap.<String, String>of(), - mMockOnRequestComplete); - - ArgumentCaptor<String> curlCommandCaptor = ArgumentCaptor.forClass(String.class); - verify(mMockCurlCommandLogger).logCurlCommand(curlCommandCaptor.capture()); - assertEquals( - "echo 'AQIDBAU=' | base64 -d > /tmp/$$.bin; curl -X POST " - + "--header \"Content-Type: application/octet-stream\" \"http://foo.com\" " - + "--data-binary @/tmp/$$.bin", - curlCommandCaptor.getValue()); - } - - @Test - public void curlLogging_largeRequest() { - CronetHttpStack stack = - createStack( - new Consumer<CronetHttpStack.Builder>() { - @Override - public void accept(CronetHttpStack.Builder builder) { - builder.setCurlLoggingEnabled(true); - } - }); - - stack.executeRequest( - new TestRequest.PostWithBody() { - @Override - public byte[] getBody() { - return new byte[2048]; - } - - @Override - public String getBodyContentType() { - return "application/octet-stream"; - } - }, - ImmutableMap.<String, String>of(), - mMockOnRequestComplete); - - ArgumentCaptor<String> curlCommandCaptor = ArgumentCaptor.forClass(String.class); - verify(mMockCurlCommandLogger).logCurlCommand(curlCommandCaptor.capture()); - assertEquals( - "curl -X POST " - + "--header \"Content-Type: application/octet-stream\" \"http://foo.com\" " - + "[REQUEST BODY TOO LARGE TO INCLUDE]", - curlCommandCaptor.getValue()); - } - - @Test - public void getHeadersEmptyTest() { - List<Map.Entry<String, String>> list = new ArrayList<>(); - List<Header> actual = CronetHttpStack.getHeaders(list); - List<Header> expected = new ArrayList<>(); - assertEquals(expected, actual); - } - - @Test - public void getHeadersNonEmptyTest() { - Map<String, String> headers = new HashMap<>(); - for (int i = 1; i < 5; i++) { - headers.put("key" + i, "value" + i); - } - List<Map.Entry<String, String>> list = new ArrayList<>(headers.entrySet()); - List<Header> actual = CronetHttpStack.getHeaders(list); - List<Header> expected = new ArrayList<>(); - for (int i = 1; i < 5; i++) { - expected.add(new Header("key" + i, "value" + i)); - } - assertHeaderListsEqual(expected, actual); - } - - private void assertHeaderListsEqual(List<Header> expected, List<Header> actual) { - assertEquals(expected.size(), actual.size()); - for (int i = 0; i < expected.size(); i++) { - assertEquals(expected.get(i).getName(), actual.get(i).getName()); - assertEquals(expected.get(i).getValue(), actual.get(i).getValue()); - } - } - - private CronetHttpStack createStack(Consumer<CronetHttpStack.Builder> stackEditor) { - CronetHttpStack.Builder builder = - new CronetHttpStack.Builder(RuntimeEnvironment.application) - .setCronetEngine(mMockCronetEngine) - .setCurlCommandLogger(mMockCurlCommandLogger); - stackEditor.accept(builder); - CronetHttpStack stack = builder.build(); - stack.setBlockingExecutor(MoreExecutors.newDirectExecutorService()); - stack.setNonBlockingExecutor(MoreExecutors.newDirectExecutorService()); - return stack; - } -} diff --git a/src/test/java/com/android/volley/mock/MockAsyncStack.java b/src/test/java/com/android/volley/mock/MockAsyncStack.java deleted file mode 100644 index 5ea8343..0000000 --- a/src/test/java/com/android/volley/mock/MockAsyncStack.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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.mock; - -import com.android.volley.AuthFailureError; -import com.android.volley.Request; -import com.android.volley.toolbox.AsyncHttpStack; -import com.android.volley.toolbox.HttpResponse; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -public class MockAsyncStack extends AsyncHttpStack { - - private HttpResponse mResponseToReturn; - - private IOException mExceptionToThrow; - - private String mLastUrl; - - private Map<String, String> mLastHeaders; - - private byte[] mLastPostBody; - - public String getLastUrl() { - return mLastUrl; - } - - public Map<String, String> getLastHeaders() { - return mLastHeaders; - } - - public byte[] getLastPostBody() { - return mLastPostBody; - } - - public void setResponseToReturn(HttpResponse response) { - mResponseToReturn = response; - } - - public void setExceptionToThrow(IOException exception) { - mExceptionToThrow = exception; - } - - @Override - public void executeRequest( - Request<?> request, Map<String, String> additionalHeaders, OnRequestComplete callback) { - if (mExceptionToThrow != null) { - callback.onError(mExceptionToThrow); - return; - } - mLastUrl = request.getUrl(); - mLastHeaders = new HashMap<>(); - try { - if (request.getHeaders() != null) { - mLastHeaders.putAll(request.getHeaders()); - } - } catch (AuthFailureError authFailureError) { - callback.onAuthError(authFailureError); - return; - } - if (additionalHeaders != null) { - mLastHeaders.putAll(additionalHeaders); - } - try { - mLastPostBody = request.getBody(); - } catch (AuthFailureError e) { - mLastPostBody = null; - } - callback.onSuccess(mResponseToReturn); - } -} diff --git a/src/test/java/com/android/volley/toolbox/BasicAsyncNetworkTest.java b/src/test/java/com/android/volley/toolbox/BasicAsyncNetworkTest.java deleted file mode 100644 index 91d4062..0000000 --- a/src/test/java/com/android/volley/toolbox/BasicAsyncNetworkTest.java +++ /dev/null @@ -1,508 +0,0 @@ -/* - * 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 org.hamcrest.Matchers.containsInAnyOrder; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.*; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; -import static org.mockito.MockitoAnnotations.initMocks; - -import com.android.volley.AsyncNetwork; -import com.android.volley.AuthFailureError; -import com.android.volley.Cache.Entry; -import com.android.volley.Header; -import com.android.volley.NetworkResponse; -import com.android.volley.NoConnectionError; -import com.android.volley.Request; -import com.android.volley.Response; -import com.android.volley.RetryPolicy; -import com.android.volley.ServerError; -import com.android.volley.TimeoutError; -import com.android.volley.VolleyError; -import com.android.volley.mock.MockAsyncStack; -import com.google.common.util.concurrent.MoreExecutors; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.SocketTimeoutException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.robolectric.RobolectricTestRunner; - -@RunWith(RobolectricTestRunner.class) -public class BasicAsyncNetworkTest { - - @Mock private RetryPolicy mMockRetryPolicy; - @Mock private AsyncNetwork.OnRequestComplete mockCallback; - private ExecutorService executor = MoreExecutors.newDirectExecutorService(); - - @Before - public void setUp() throws Exception { - initMocks(this); - } - - @Test - public void headersAndPostParams() throws Exception { - MockAsyncStack mockAsyncStack = new MockAsyncStack(); - HttpResponse fakeResponse = - new HttpResponse( - 200, - Collections.<Header>emptyList(), - "foobar".getBytes(StandardCharsets.UTF_8)); - mockAsyncStack.setResponseToReturn(fakeResponse); - BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build(); - httpNetwork.setBlockingExecutor(executor); - Request<String> request = buildRequest(); - Entry entry = new Entry(); - entry.etag = "foobar"; - entry.lastModified = 1503102002000L; - request.setCacheEntry(entry); - perform(request, httpNetwork).get(); - assertEquals("foo", mockAsyncStack.getLastHeaders().get("requestheader")); - assertEquals("foobar", mockAsyncStack.getLastHeaders().get("If-None-Match")); - assertEquals( - "Sat, 19 Aug 2017 00:20:02 GMT", - mockAsyncStack.getLastHeaders().get("If-Modified-Since")); - assertEquals( - "requestpost=foo&", - new String(mockAsyncStack.getLastPostBody(), StandardCharsets.UTF_8)); - } - - @Test - public void headersAndPostParamsStream() throws Exception { - MockAsyncStack mockAsyncStack = new MockAsyncStack(); - ByteArrayInputStream stream = new ByteArrayInputStream("foobar".getBytes("UTF-8")); - HttpResponse fakeResponse = - new HttpResponse(200, Collections.<Header>emptyList(), 6, stream); - mockAsyncStack.setResponseToReturn(fakeResponse); - BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build(); - httpNetwork.setBlockingExecutor(executor); - Request<String> request = buildRequest(); - Entry entry = new Entry(); - entry.etag = "foobar"; - entry.lastModified = 1503102002000L; - request.setCacheEntry(entry); - perform(request, httpNetwork).get(); - assertEquals("foo", mockAsyncStack.getLastHeaders().get("requestheader")); - assertEquals("foobar", mockAsyncStack.getLastHeaders().get("If-None-Match")); - assertEquals( - "Sat, 19 Aug 2017 00:20:02 GMT", - mockAsyncStack.getLastHeaders().get("If-Modified-Since")); - assertEquals( - "requestpost=foo&", - new String(mockAsyncStack.getLastPostBody(), StandardCharsets.UTF_8)); - } - - @Test - public void notModified() throws Exception { - MockAsyncStack mockAsyncStack = new MockAsyncStack(); - List<Header> headers = new ArrayList<>(); - headers.add(new Header("ServerKeyA", "ServerValueA")); - headers.add(new Header("ServerKeyB", "ServerValueB")); - headers.add(new Header("SharedKey", "ServerValueShared")); - headers.add(new Header("sharedcaseinsensitivekey", "ServerValueShared1")); - headers.add(new Header("SharedCaseInsensitiveKey", "ServerValueShared2")); - HttpResponse fakeResponse = new HttpResponse(HttpURLConnection.HTTP_NOT_MODIFIED, headers); - mockAsyncStack.setResponseToReturn(fakeResponse); - BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build(); - httpNetwork.setBlockingExecutor(executor); - Request<String> request = buildRequest(); - Entry entry = new Entry(); - entry.allResponseHeaders = new ArrayList<>(); - entry.allResponseHeaders.add(new Header("CachedKeyA", "CachedValueA")); - entry.allResponseHeaders.add(new Header("CachedKeyB", "CachedValueB")); - entry.allResponseHeaders.add(new Header("SharedKey", "CachedValueShared")); - entry.allResponseHeaders.add(new Header("SHAREDCASEINSENSITIVEKEY", "CachedValueShared1")); - entry.allResponseHeaders.add(new Header("shAREDcaSEinSENSITIVEkeY", "CachedValueShared2")); - request.setCacheEntry(entry); - httpNetwork.performRequest(request, mockCallback); - NetworkResponse response = perform(request, httpNetwork).get(); - List<Header> expectedHeaders = new ArrayList<>(); - // Should have all server headers + cache headers that didn't show up in server response. - expectedHeaders.add(new Header("ServerKeyA", "ServerValueA")); - expectedHeaders.add(new Header("ServerKeyB", "ServerValueB")); - expectedHeaders.add(new Header("SharedKey", "ServerValueShared")); - expectedHeaders.add(new Header("sharedcaseinsensitivekey", "ServerValueShared1")); - expectedHeaders.add(new Header("SharedCaseInsensitiveKey", "ServerValueShared2")); - expectedHeaders.add(new Header("CachedKeyA", "CachedValueA")); - expectedHeaders.add(new Header("CachedKeyB", "CachedValueB")); - assertThat(expectedHeaders, containsInAnyOrder(response.allHeaders.toArray(new Header[0]))); - } - - @Test - public void notModified_legacyCache() throws Exception { - MockAsyncStack mockAsyncStack = new MockAsyncStack(); - List<Header> headers = new ArrayList<>(); - headers.add(new Header("ServerKeyA", "ServerValueA")); - headers.add(new Header("ServerKeyB", "ServerValueB")); - headers.add(new Header("SharedKey", "ServerValueShared")); - headers.add(new Header("sharedcaseinsensitivekey", "ServerValueShared1")); - headers.add(new Header("SharedCaseInsensitiveKey", "ServerValueShared2")); - HttpResponse fakeResponse = new HttpResponse(HttpURLConnection.HTTP_NOT_MODIFIED, headers); - mockAsyncStack.setResponseToReturn(fakeResponse); - BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build(); - httpNetwork.setBlockingExecutor(executor); - Request<String> request = buildRequest(); - Entry entry = new Entry(); - entry.responseHeaders = new HashMap<>(); - entry.responseHeaders.put("CachedKeyA", "CachedValueA"); - entry.responseHeaders.put("CachedKeyB", "CachedValueB"); - entry.responseHeaders.put("SharedKey", "CachedValueShared"); - entry.responseHeaders.put("SHAREDCASEINSENSITIVEKEY", "CachedValueShared1"); - entry.responseHeaders.put("shAREDcaSEinSENSITIVEkeY", "CachedValueShared2"); - request.setCacheEntry(entry); - NetworkResponse response = perform(request, httpNetwork).get(); - List<Header> expectedHeaders = new ArrayList<>(); - // Should have all server headers + cache headers that didn't show up in server response. - expectedHeaders.add(new Header("ServerKeyA", "ServerValueA")); - expectedHeaders.add(new Header("ServerKeyB", "ServerValueB")); - expectedHeaders.add(new Header("SharedKey", "ServerValueShared")); - expectedHeaders.add(new Header("sharedcaseinsensitivekey", "ServerValueShared1")); - expectedHeaders.add(new Header("SharedCaseInsensitiveKey", "ServerValueShared2")); - expectedHeaders.add(new Header("CachedKeyA", "CachedValueA")); - expectedHeaders.add(new Header("CachedKeyB", "CachedValueB")); - assertThat(expectedHeaders, containsInAnyOrder(response.allHeaders.toArray(new Header[0]))); - } - - @Test - public void socketTimeout() throws Exception { - MockAsyncStack mockAsyncStack = new MockAsyncStack(); - mockAsyncStack.setExceptionToThrow(new SocketTimeoutException()); - BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build(); - httpNetwork.setBlockingExecutor(executor); - Request<String> request = buildRequest(); - request.setRetryPolicy(mMockRetryPolicy); - doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class)); - httpNetwork.performRequest(request, mockCallback); - verify(mockCallback, times(1)).onError(any(VolleyError.class)); - verify(mockCallback, never()).onSuccess(any(NetworkResponse.class)); - // should retry socket timeouts - verify(mMockRetryPolicy).retry(any(TimeoutError.class)); - reset(mMockRetryPolicy, mockCallback); - } - - @Test - public void noConnectionDefault() throws Exception { - MockAsyncStack mockAsyncStack = new MockAsyncStack(); - mockAsyncStack.setExceptionToThrow(new IOException()); - BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build(); - httpNetwork.setBlockingExecutor(executor); - Request<String> request = buildRequest(); - request.setRetryPolicy(mMockRetryPolicy); - doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class)); - httpNetwork.performRequest(request, mockCallback); - verify(mockCallback, times(1)).onError(any(VolleyError.class)); - verify(mockCallback, never()).onSuccess(any(NetworkResponse.class)); - // should not retry when there is no connection - verify(mMockRetryPolicy, never()).retry(any(VolleyError.class)); - reset(mMockRetryPolicy, mockCallback); - } - - @Test - public void noConnectionRetry() throws Exception { - MockAsyncStack mockAsyncStack = new MockAsyncStack(); - mockAsyncStack.setExceptionToThrow(new IOException()); - BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build(); - httpNetwork.setBlockingExecutor(executor); - Request<String> request = buildRequest(); - request.setRetryPolicy(mMockRetryPolicy); - request.setShouldRetryConnectionErrors(true); - doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class)); - httpNetwork.performRequest(request, mockCallback); - verify(mockCallback, times(1)).onError(any(VolleyError.class)); - verify(mockCallback, never()).onSuccess(any(NetworkResponse.class)); - // should retry when there is no connection - verify(mMockRetryPolicy).retry(any(NoConnectionError.class)); - reset(mMockRetryPolicy, mockCallback); - } - - @Test - public void noConnectionNoRetry() throws Exception { - MockAsyncStack mockAsyncStack = new MockAsyncStack(); - mockAsyncStack.setExceptionToThrow(new IOException()); - BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build(); - httpNetwork.setBlockingExecutor(executor); - Request<String> request = buildRequest(); - request.setRetryPolicy(mMockRetryPolicy); - request.setShouldRetryConnectionErrors(false); - doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class)); - httpNetwork.performRequest(request, mockCallback); - verify(mockCallback, times(1)).onError(any(VolleyError.class)); - verify(mockCallback, never()).onSuccess(any(NetworkResponse.class)); - // should not retry when there is no connection - verify(mMockRetryPolicy, never()).retry(any(VolleyError.class)); - reset(mMockRetryPolicy, mockCallback); - } - - @Test - public void unauthorized() throws Exception { - MockAsyncStack mockAsyncStack = new MockAsyncStack(); - HttpResponse fakeResponse = new HttpResponse(401, Collections.<Header>emptyList()); - mockAsyncStack.setResponseToReturn(fakeResponse); - BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build(); - httpNetwork.setBlockingExecutor(executor); - Request<String> request = buildRequest(); - request.setRetryPolicy(mMockRetryPolicy); - doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class)); - httpNetwork.performRequest(request, mockCallback); - verify(mockCallback, times(1)).onError(any(VolleyError.class)); - verify(mockCallback, never()).onSuccess(any(NetworkResponse.class)); - // should retry in case it's an auth failure. - verify(mMockRetryPolicy).retry(any(AuthFailureError.class)); - reset(mMockRetryPolicy, mockCallback); - } - - @Test(expected = RuntimeException.class) - public void malformedUrlRequest() throws VolleyError, ExecutionException, InterruptedException { - MockAsyncStack mockAsyncStack = new MockAsyncStack(); - mockAsyncStack.setExceptionToThrow(new MalformedURLException()); - BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build(); - httpNetwork.setBlockingExecutor(executor); - Request<String> request = buildRequest(); - request.setRetryPolicy(mMockRetryPolicy); - doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class)); - perform(request, httpNetwork).get(); - } - - @Test - public void forbidden() throws Exception { - MockAsyncStack mockAsyncStack = new MockAsyncStack(); - HttpResponse fakeResponse = new HttpResponse(403, Collections.<Header>emptyList()); - mockAsyncStack.setResponseToReturn(fakeResponse); - BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build(); - httpNetwork.setBlockingExecutor(executor); - Request<String> request = buildRequest(); - request.setRetryPolicy(mMockRetryPolicy); - doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class)); - httpNetwork.performRequest(request, mockCallback); - verify(mockCallback, times(1)).onError(any(VolleyError.class)); - verify(mockCallback, never()).onSuccess(any(NetworkResponse.class)); - // should retry in case it's an auth failure. - verify(mMockRetryPolicy).retry(any(AuthFailureError.class)); - reset(mMockRetryPolicy, mockCallback); - } - - @Test - public void redirect() throws Exception { - for (int i = 300; i <= 399; i++) { - MockAsyncStack mockAsyncStack = new MockAsyncStack(); - HttpResponse fakeResponse = new HttpResponse(i, Collections.<Header>emptyList()); - mockAsyncStack.setResponseToReturn(fakeResponse); - BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build(); - httpNetwork.setBlockingExecutor(executor); - Request<String> request = buildRequest(); - request.setRetryPolicy(mMockRetryPolicy); - doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class)); - httpNetwork.performRequest(request, mockCallback); - if (i != 304) { - verify(mockCallback, times(1)).onError(any(VolleyError.class)); - verify(mockCallback, never()).onSuccess(any(NetworkResponse.class)); - } else { - verify(mockCallback, never()).onError(any(VolleyError.class)); - verify(mockCallback, times(1)).onSuccess(any(NetworkResponse.class)); - } - // should not retry 300 responses. - verify(mMockRetryPolicy, never()).retry(any(VolleyError.class)); - reset(mMockRetryPolicy, mockCallback); - } - } - - @Test - public void otherClientError() throws Exception { - for (int i = 400; i <= 499; i++) { - if (i == 401 || i == 403) { - // covered above. - continue; - } - MockAsyncStack mockAsyncStack = new MockAsyncStack(); - HttpResponse fakeResponse = new HttpResponse(i, Collections.<Header>emptyList()); - mockAsyncStack.setResponseToReturn(fakeResponse); - BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build(); - httpNetwork.setBlockingExecutor(executor); - Request<String> request = buildRequest(); - request.setRetryPolicy(mMockRetryPolicy); - doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class)); - httpNetwork.performRequest(request, mockCallback); - verify(mockCallback, times(1)).onError(any(VolleyError.class)); - verify(mockCallback, never()).onSuccess(any(NetworkResponse.class)); - // should not retry other 400 errors. - verify(mMockRetryPolicy, never()).retry(any(VolleyError.class)); - reset(mMockRetryPolicy, mockCallback); - } - } - - @Test - public void serverError_enableRetries() throws Exception { - for (int i = 500; i <= 599; i++) { - MockAsyncStack mockAsyncStack = new MockAsyncStack(); - HttpResponse fakeResponse = new HttpResponse(i, Collections.<Header>emptyList()); - mockAsyncStack.setResponseToReturn(fakeResponse); - BasicAsyncNetwork httpNetwork = - new BasicAsyncNetwork.Builder(mockAsyncStack) - .setPool(new ByteArrayPool(4096)) - .build(); - httpNetwork.setBlockingExecutor(executor); - Request<String> request = buildRequest(); - request.setRetryPolicy(mMockRetryPolicy); - request.setShouldRetryServerErrors(true); - doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class)); - httpNetwork.performRequest(request, mockCallback); - verify(mockCallback, times(1)).onError(any(VolleyError.class)); - verify(mockCallback, never()).onSuccess(any(NetworkResponse.class)); - // should retry all 500 errors - verify(mMockRetryPolicy).retry(any(ServerError.class)); - reset(mMockRetryPolicy, mockCallback); - } - } - - @Test - public void serverError_disableRetries() throws Exception { - for (int i = 500; i <= 599; i++) { - MockAsyncStack mockAsyncStack = new MockAsyncStack(); - HttpResponse fakeResponse = new HttpResponse(i, Collections.<Header>emptyList()); - mockAsyncStack.setResponseToReturn(fakeResponse); - BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build(); - httpNetwork.setBlockingExecutor(executor); - Request<String> request = buildRequest(); - request.setRetryPolicy(mMockRetryPolicy); - doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class)); - httpNetwork.performRequest(request, mockCallback); - verify(mockCallback, times(1)).onError(any(VolleyError.class)); - verify(mockCallback, never()).onSuccess(any(NetworkResponse.class)); - // should not retry any 500 error w/ HTTP 500 retries turned off (the default). - verify(mMockRetryPolicy, never()).retry(any(VolleyError.class)); - reset(mMockRetryPolicy, mockCallback); - } - } - - @Test - public void notModifiedShortCircuit() throws Exception { - MockAsyncStack mockAsyncStack = new MockAsyncStack(); - List<Header> headers = new ArrayList<>(); - headers.add(new Header("ServerKeyA", "ServerValueA")); - headers.add(new Header("ServerKeyB", "ServerValueB")); - headers.add(new Header("SharedKey", "ServerValueShared")); - headers.add(new Header("sharedcaseinsensitivekey", "ServerValueShared1")); - headers.add(new Header("SharedCaseInsensitiveKey", "ServerValueShared2")); - HttpResponse fakeResponse = new HttpResponse(HttpURLConnection.HTTP_NOT_MODIFIED, headers); - mockAsyncStack.setResponseToReturn(fakeResponse); - BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build(); - httpNetwork.setBlockingExecutor(executor); - Request<String> request = buildRequest(); - httpNetwork.performRequest(request, mockCallback); - verify(mockCallback, times(1)).onSuccess(any(NetworkResponse.class)); - verify(mockCallback, never()).onError(any(VolleyError.class)); - reset(mMockRetryPolicy, mockCallback); - } - - @Test - public void performRequestSuccess() throws Exception { - MockAsyncStack mockAsyncStack = new MockAsyncStack(); - HttpResponse fakeResponse = - new HttpResponse( - 200, - Collections.<Header>emptyList(), - "foobar".getBytes(StandardCharsets.UTF_8)); - mockAsyncStack.setResponseToReturn(fakeResponse); - BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build(); - httpNetwork.setBlockingExecutor(executor); - Request<String> request = buildRequest(); - Entry entry = new Entry(); - entry.etag = "foobar"; - entry.lastModified = 1503102002000L; - request.setCacheEntry(entry); - httpNetwork.performRequest(request, mockCallback); - verify(mockCallback, times(1)).onSuccess(any(NetworkResponse.class)); - verify(mockCallback, never()).onError(any(VolleyError.class)); - reset(mMockRetryPolicy, mockCallback); - } - - @Test(expected = IllegalStateException.class) - public void performRequestNeverSetExecutorTest() throws Exception { - MockAsyncStack mockAsyncStack = new MockAsyncStack(); - HttpResponse fakeResponse = new HttpResponse(200, Collections.<Header>emptyList()); - mockAsyncStack.setResponseToReturn(fakeResponse); - BasicAsyncNetwork httpNetwork = new BasicAsyncNetwork.Builder(mockAsyncStack).build(); - Request<String> request = buildRequest(); - perform(request, httpNetwork).get(); - } - - /** Helper functions */ - private CompletableFuture<NetworkResponse> perform(Request<?> request, AsyncNetwork network) - throws VolleyError { - final CompletableFuture<NetworkResponse> future = new CompletableFuture<>(); - network.performRequest( - request, - new AsyncNetwork.OnRequestComplete() { - @Override - public void onSuccess(NetworkResponse networkResponse) { - future.complete(networkResponse); - } - - @Override - public void onError(VolleyError volleyError) { - future.complete(null); - } - }); - return future; - } - - private static Request<String> buildRequest() { - return new Request<String>(Request.Method.GET, "http://foo", null) { - - @Override - protected Response<String> parseNetworkResponse(NetworkResponse response) { - return null; - } - - @Override - protected void deliverResponse(String response) {} - - @Override - public Map<String, String> getHeaders() { - Map<String, String> result = new HashMap<String, String>(); - result.put("requestheader", "foo"); - return result; - } - - @Override - public Map<String, String> getParams() { - Map<String, String> result = new HashMap<String, String>(); - result.put("requestpost", "foo"); - return result; - } - }; - } -} diff --git a/src/test/java/com/android/volley/toolbox/BasicNetworkTest.java b/src/test/java/com/android/volley/toolbox/BasicNetworkTest.java index 3630379..fec0694 100644 --- a/src/test/java/com/android/volley/toolbox/BasicNetworkTest.java +++ b/src/test/java/com/android/volley/toolbox/BasicNetworkTest.java @@ -30,7 +30,6 @@ import com.android.volley.AuthFailureError; import com.android.volley.Cache.Entry; import com.android.volley.Header; import com.android.volley.NetworkResponse; -import com.android.volley.NoConnectionError; import com.android.volley.Request; import com.android.volley.Response; import com.android.volley.RetryPolicy; @@ -177,7 +176,7 @@ public class BasicNetworkTest { } @Test - public void noConnectionDefault() throws Exception { + public void noConnection() throws Exception { MockHttpStack mockHttpStack = new MockHttpStack(); mockHttpStack.setExceptionToThrow(new IOException()); BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack); @@ -194,43 +193,6 @@ public class BasicNetworkTest { } @Test - public void noConnectionRetry() throws Exception { - MockHttpStack mockHttpStack = new MockHttpStack(); - mockHttpStack.setExceptionToThrow(new IOException()); - BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack); - Request<String> request = buildRequest(); - request.setRetryPolicy(mMockRetryPolicy); - request.setShouldRetryConnectionErrors(true); - doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class)); - try { - httpNetwork.performRequest(request); - } catch (VolleyError e) { - // expected - } - // should retry when there is no connection - verify(mMockRetryPolicy).retry(any(NoConnectionError.class)); - reset(mMockRetryPolicy); - } - - @Test - public void noConnectionNoRetry() throws Exception { - MockHttpStack mockHttpStack = new MockHttpStack(); - mockHttpStack.setExceptionToThrow(new IOException()); - BasicNetwork httpNetwork = new BasicNetwork(mockHttpStack); - Request<String> request = buildRequest(); - request.setRetryPolicy(mMockRetryPolicy); - request.setShouldRetryConnectionErrors(false); - doThrow(new VolleyError()).when(mMockRetryPolicy).retry(any(VolleyError.class)); - try { - httpNetwork.performRequest(request); - } catch (VolleyError e) { - // expected - } - // should not retry when there is no connection - verify(mMockRetryPolicy, never()).retry(any(VolleyError.class)); - } - - @Test public void unauthorized() throws Exception { MockHttpStack mockHttpStack = new MockHttpStack(); HttpResponse fakeResponse = new HttpResponse(401, Collections.<Header>emptyList()); diff --git a/src/test/java/com/android/volley/toolbox/DiskBasedCacheTest.java b/src/test/java/com/android/volley/toolbox/DiskBasedCacheTest.java index db6e491..e499a37 100644 --- a/src/test/java/com/android/volley/toolbox/DiskBasedCacheTest.java +++ b/src/test/java/com/android/volley/toolbox/DiskBasedCacheTest.java @@ -59,7 +59,7 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) -@Config(sdk = 16) +@Config(manifest = "src/main/AndroidManifest.xml", sdk = 16) public class DiskBasedCacheTest { private static final int MAX_SIZE = 1024 * 1024; @@ -587,28 +587,11 @@ public class DiskBasedCacheTest { public void publicMethods() throws Exception { // Catch-all test to find API-breaking changes. assertNotNull(DiskBasedCache.class.getConstructor(File.class, int.class)); - assertNotNull( - DiskBasedCache.class.getConstructor(DiskBasedCache.FileSupplier.class, int.class)); assertNotNull(DiskBasedCache.class.getConstructor(File.class)); - assertNotNull(DiskBasedCache.class.getConstructor(DiskBasedCache.FileSupplier.class)); assertNotNull(DiskBasedCache.class.getMethod("getFileForKey", String.class)); } - @Test - public void initializeIfRootDirectoryDeleted() { - temporaryFolder.delete(); - - Cache.Entry entry = randomData(101); - cache.put("key1", entry); - - assertThat(cache.get("key1"), is(nullValue())); - - // confirm that we can now store entries - cache.put("key2", entry); - assertThatEntriesAreEqual(cache.get("key2"), entry); - } - /* Test helpers */ private void assertThatEntriesAreEqual(Cache.Entry actual, Cache.Entry expected) { diff --git a/src/test/java/com/android/volley/toolbox/HttpHeaderParserTest.java b/src/test/java/com/android/volley/toolbox/HttpHeaderParserTest.java index 7780c3e..9b670f9 100644 --- a/src/test/java/com/android/volley/toolbox/HttpHeaderParserTest.java +++ b/src/test/java/com/android/volley/toolbox/HttpHeaderParserTest.java @@ -67,12 +67,6 @@ public class HttpHeaderParserTest { } @Test - public void parseCacheHeaders_nullHeaders() { - response = new NetworkResponse(0, null, null, false); - assertNull(HttpHeaderParser.parseCacheHeaders(response)); - } - - @Test public void parseCacheHeaders_headersSet() { headers.put("MyCustomHeader", "42"); @@ -288,9 +282,6 @@ public class HttpHeaderParserTest { // None specified, extra semicolon headers.put("Content-Type", "text/plain;"); assertEquals("ISO-8859-1", HttpHeaderParser.parseCharset(headers)); - - // No headers, use default charset - assertEquals("utf-8", HttpHeaderParser.parseCharset(null, "utf-8")); } @Test diff --git a/src/test/java/com/android/volley/toolbox/HurlStackTest.java b/src/test/java/com/android/volley/toolbox/HurlStackTest.java index 7508244..c1fc92d 100644 --- a/src/test/java/com/android/volley/toolbox/HurlStackTest.java +++ b/src/test/java/com/android/volley/toolbox/HurlStackTest.java @@ -17,7 +17,6 @@ package com.android.volley.toolbox; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.never; @@ -25,16 +24,11 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.android.volley.Header; -import com.android.volley.Request; import com.android.volley.Request.Method; import com.android.volley.mock.TestRequest; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.FilterInputStream; -import java.io.FilterOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.SocketTimeoutException; import java.net.URL; @@ -68,26 +62,6 @@ public class HurlStackTest { protected HttpURLConnection createConnection(URL url) { return mMockConnection; } - - @Override - protected InputStream createInputStream( - Request<?> request, HttpURLConnection connection) { - return new MonitoringInputStream( - super.createInputStream(request, connection)); - } - - @Override - protected OutputStream createOutputStream( - Request<?> request, HttpURLConnection connection, int length) - throws IOException { - if (request instanceof MonitoredRequest) { - return new MonitoringOutputStream( - super.createOutputStream(request, connection, length), - (MonitoredRequest) request, - length); - } - return super.createOutputStream(request, connection, length); - } }; } @@ -96,7 +70,7 @@ public class HurlStackTest { TestRequest.DeprecatedGet request = new TestRequest.DeprecatedGet(); assertEquals(request.getMethod(), Method.DEPRECATED_GET_OR_POST); - mHurlStack.setConnectionParametersForRequest(mMockConnection, request); + HurlStack.setConnectionParametersForRequest(mMockConnection, request); verify(mMockConnection, never()).setRequestMethod(anyString()); verify(mMockConnection, never()).setDoOutput(true); } @@ -106,7 +80,7 @@ public class HurlStackTest { TestRequest.DeprecatedPost request = new TestRequest.DeprecatedPost(); assertEquals(request.getMethod(), Method.DEPRECATED_GET_OR_POST); - mHurlStack.setConnectionParametersForRequest(mMockConnection, request); + HurlStack.setConnectionParametersForRequest(mMockConnection, request); verify(mMockConnection).setRequestMethod("POST"); verify(mMockConnection).setDoOutput(true); } @@ -116,7 +90,7 @@ public class HurlStackTest { TestRequest.Get request = new TestRequest.Get(); assertEquals(request.getMethod(), Method.GET); - mHurlStack.setConnectionParametersForRequest(mMockConnection, request); + HurlStack.setConnectionParametersForRequest(mMockConnection, request); verify(mMockConnection).setRequestMethod("GET"); verify(mMockConnection, never()).setDoOutput(true); } @@ -126,7 +100,7 @@ public class HurlStackTest { TestRequest.Post request = new TestRequest.Post(); assertEquals(request.getMethod(), Method.POST); - mHurlStack.setConnectionParametersForRequest(mMockConnection, request); + HurlStack.setConnectionParametersForRequest(mMockConnection, request); verify(mMockConnection).setRequestMethod("POST"); verify(mMockConnection, never()).setDoOutput(true); } @@ -136,7 +110,7 @@ public class HurlStackTest { TestRequest.PostWithBody request = new TestRequest.PostWithBody(); assertEquals(request.getMethod(), Method.POST); - mHurlStack.setConnectionParametersForRequest(mMockConnection, request); + HurlStack.setConnectionParametersForRequest(mMockConnection, request); verify(mMockConnection).setRequestMethod("POST"); verify(mMockConnection).setDoOutput(true); } @@ -146,7 +120,7 @@ public class HurlStackTest { TestRequest.Put request = new TestRequest.Put(); assertEquals(request.getMethod(), Method.PUT); - mHurlStack.setConnectionParametersForRequest(mMockConnection, request); + HurlStack.setConnectionParametersForRequest(mMockConnection, request); verify(mMockConnection).setRequestMethod("PUT"); verify(mMockConnection, never()).setDoOutput(true); } @@ -156,7 +130,7 @@ public class HurlStackTest { TestRequest.PutWithBody request = new TestRequest.PutWithBody(); assertEquals(request.getMethod(), Method.PUT); - mHurlStack.setConnectionParametersForRequest(mMockConnection, request); + HurlStack.setConnectionParametersForRequest(mMockConnection, request); verify(mMockConnection).setRequestMethod("PUT"); verify(mMockConnection).setDoOutput(true); } @@ -166,7 +140,7 @@ public class HurlStackTest { TestRequest.Delete request = new TestRequest.Delete(); assertEquals(request.getMethod(), Method.DELETE); - mHurlStack.setConnectionParametersForRequest(mMockConnection, request); + HurlStack.setConnectionParametersForRequest(mMockConnection, request); verify(mMockConnection).setRequestMethod("DELETE"); verify(mMockConnection, never()).setDoOutput(true); } @@ -176,7 +150,7 @@ public class HurlStackTest { TestRequest.Head request = new TestRequest.Head(); assertEquals(request.getMethod(), Method.HEAD); - mHurlStack.setConnectionParametersForRequest(mMockConnection, request); + HurlStack.setConnectionParametersForRequest(mMockConnection, request); verify(mMockConnection).setRequestMethod("HEAD"); verify(mMockConnection, never()).setDoOutput(true); } @@ -186,7 +160,7 @@ public class HurlStackTest { TestRequest.Options request = new TestRequest.Options(); assertEquals(request.getMethod(), Method.OPTIONS); - mHurlStack.setConnectionParametersForRequest(mMockConnection, request); + HurlStack.setConnectionParametersForRequest(mMockConnection, request); verify(mMockConnection).setRequestMethod("OPTIONS"); verify(mMockConnection, never()).setDoOutput(true); } @@ -196,7 +170,7 @@ public class HurlStackTest { TestRequest.Trace request = new TestRequest.Trace(); assertEquals(request.getMethod(), Method.TRACE); - mHurlStack.setConnectionParametersForRequest(mMockConnection, request); + HurlStack.setConnectionParametersForRequest(mMockConnection, request); verify(mMockConnection).setRequestMethod("TRACE"); verify(mMockConnection, never()).setDoOutput(true); } @@ -206,7 +180,7 @@ public class HurlStackTest { TestRequest.Patch request = new TestRequest.Patch(); assertEquals(request.getMethod(), Method.PATCH); - mHurlStack.setConnectionParametersForRequest(mMockConnection, request); + HurlStack.setConnectionParametersForRequest(mMockConnection, request); verify(mMockConnection).setRequestMethod("PATCH"); verify(mMockConnection, never()).setDoOutput(true); } @@ -216,7 +190,7 @@ public class HurlStackTest { TestRequest.PatchWithBody request = new TestRequest.PatchWithBody(); assertEquals(request.getMethod(), Method.PATCH); - mHurlStack.setConnectionParametersForRequest(mMockConnection, request); + HurlStack.setConnectionParametersForRequest(mMockConnection, request); verify(mMockConnection).setRequestMethod("PATCH"); verify(mMockConnection).setDoOutput(true); } @@ -282,56 +256,4 @@ public class HurlStackTest { expected.add(new Header("HeaderB", "ValueB_2")); assertEquals(expected, result); } - - @Test - public void interceptResponseStream() throws Exception { - when(mMockConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK); - when(mMockConnection.getInputStream()) - .thenReturn(new ByteArrayInputStream("hello".getBytes(StandardCharsets.UTF_8))); - HttpResponse response = - mHurlStack.executeRequest( - new TestRequest.Get(), Collections.<String, String>emptyMap()); - assertTrue(response.getContent() instanceof MonitoringInputStream); - } - - @Test - public void interceptRequestStream() throws Exception { - MonitoredRequest request = new MonitoredRequest(); - mHurlStack.executeRequest(request, Collections.<String, String>emptyMap()); - assertTrue(request.totalRequestBytes > 0); - assertEquals(request.totalRequestBytes, request.requestBytesRead); - } - - private static class MonitoringInputStream extends FilterInputStream { - private MonitoringInputStream(InputStream in) { - super(in); - } - } - - private static class MonitoringOutputStream extends FilterOutputStream { - private MonitoredRequest request; - - private MonitoringOutputStream(OutputStream out, MonitoredRequest request, int length) { - super(out); - this.request = request; - this.request.totalRequestBytes = length; - } - - @Override - public void write(int b) throws IOException { - this.request.requestBytesRead++; - out.write(b); - } - - @Override - public void write(byte[] b, int off, int len) throws IOException { - this.request.requestBytesRead += len; - out.write(b, off, len); - } - } - - private static class MonitoredRequest extends TestRequest.PostWithBody { - int requestBytesRead = 0; - int totalRequestBytes = 0; - } } diff --git a/src/test/java/com/android/volley/utils/CacheTestUtils.java b/src/test/java/com/android/volley/utils/CacheTestUtils.java index 5980712..49ab996 100644 --- a/src/test/java/com/android/volley/utils/CacheTestUtils.java +++ b/src/test/java/com/android/volley/utils/CacheTestUtils.java @@ -16,11 +16,6 @@ package com.android.volley.utils; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; - import com.android.volley.Cache; import java.util.Random; @@ -56,34 +51,4 @@ public class CacheTestUtils { public static Cache.Entry makeRandomCacheEntry(byte[] data) { return makeRandomCacheEntry(data, false, false); } - - public static void assertThatEntriesAreEqual(Cache.Entry actual, Cache.Entry expected) { - assertNotNull(actual); - assertThat(actual.data, is(equalTo(expected.data))); - assertThat(actual.etag, is(equalTo(expected.etag))); - assertThat(actual.lastModified, is(equalTo(expected.lastModified))); - assertThat(actual.responseHeaders, is(equalTo(expected.responseHeaders))); - assertThat(actual.serverDate, is(equalTo(expected.serverDate))); - assertThat(actual.softTtl, is(equalTo(expected.softTtl))); - assertThat(actual.ttl, is(equalTo(expected.ttl))); - } - - public static Cache.Entry randomData(int length) { - Cache.Entry entry = new Cache.Entry(); - byte[] data = new byte[length]; - new Random(42).nextBytes(data); // explicit seed for reproducible results - entry.data = data; - return entry; - } - - public static int getEntrySizeOnDisk(String key) { - // Header size is: - // 4 bytes for magic int - // 8 + len(key) bytes for key (long length) - // 8 bytes for etag (long length + 0 characters) - // 32 bytes for serverDate, lastModified, ttl, and softTtl longs - // 4 bytes for length of header list int - // == 56 + len(key) bytes total. - return 56 + key.length(); - } } |