1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.volley.toolbox;
18 
19 import androidx.annotation.Nullable;
20 import androidx.annotation.RestrictTo;
21 import com.android.volley.AuthFailureError;
22 import com.android.volley.Request;
23 import com.android.volley.VolleyLog;
24 import java.io.IOException;
25 import java.io.InterruptedIOException;
26 import java.util.Map;
27 import java.util.concurrent.CountDownLatch;
28 import java.util.concurrent.ExecutorService;
29 import java.util.concurrent.atomic.AtomicReference;
30 
31 /** Asynchronous extension of the {@link BaseHttpStack} class. */
32 public abstract class AsyncHttpStack extends BaseHttpStack {
33     private ExecutorService mBlockingExecutor;
34     private ExecutorService mNonBlockingExecutor;
35 
36     public interface OnRequestComplete {
37         /** Invoked when the stack successfully completes a request. */
onSuccess(HttpResponse httpResponse)38         void onSuccess(HttpResponse httpResponse);
39 
40         /** Invoked when the stack throws an {@link AuthFailureError} during a request. */
onAuthError(AuthFailureError authFailureError)41         void onAuthError(AuthFailureError authFailureError);
42 
43         /** Invoked when the stack throws an {@link IOException} during a request. */
onError(IOException ioException)44         void onError(IOException ioException);
45     }
46 
47     /**
48      * Makes an HTTP request with the given parameters, and calls the {@link OnRequestComplete}
49      * callback, with either the {@link HttpResponse} or error that was thrown.
50      *
51      * @param request to perform
52      * @param additionalHeaders to be sent together with {@link Request#getHeaders()}
53      * @param callback to be called after retrieving the {@link HttpResponse} or throwing an error.
54      */
executeRequest( Request<?> request, Map<String, String> additionalHeaders, OnRequestComplete callback)55     public abstract void executeRequest(
56             Request<?> request, Map<String, String> additionalHeaders, OnRequestComplete callback);
57 
58     /**
59      * This method sets the non blocking executor to be used by the stack for non-blocking tasks.
60      * This method must be called before executing any requests.
61      */
62     @RestrictTo({RestrictTo.Scope.LIBRARY_GROUP})
setNonBlockingExecutor(ExecutorService executor)63     public void setNonBlockingExecutor(ExecutorService executor) {
64         mNonBlockingExecutor = executor;
65     }
66 
67     /**
68      * This method sets the blocking executor to be used by the stack for potentially blocking
69      * tasks. This method must be called before executing any requests.
70      */
71     @RestrictTo({RestrictTo.Scope.LIBRARY_GROUP})
setBlockingExecutor(ExecutorService executor)72     public void setBlockingExecutor(ExecutorService executor) {
73         mBlockingExecutor = executor;
74     }
75 
76     /** Gets blocking executor to perform any potentially blocking tasks. */
getBlockingExecutor()77     protected ExecutorService getBlockingExecutor() {
78         return mBlockingExecutor;
79     }
80 
81     /** Gets non-blocking executor to perform any non-blocking tasks. */
getNonBlockingExecutor()82     protected ExecutorService getNonBlockingExecutor() {
83         return mNonBlockingExecutor;
84     }
85 
86     /**
87      * Performs an HTTP request with the given parameters.
88      *
89      * @param request the request to perform
90      * @param additionalHeaders additional headers to be sent together with {@link
91      *     Request#getHeaders()}
92      * @return the {@link HttpResponse}
93      * @throws IOException if an I/O error occurs during the request
94      * @throws AuthFailureError if an authentication failure occurs during the request
95      */
96     @Override
executeRequest( Request<?> request, Map<String, String> additionalHeaders)97     public final HttpResponse executeRequest(
98             Request<?> request, Map<String, String> additionalHeaders)
99             throws IOException, AuthFailureError {
100         final CountDownLatch latch = new CountDownLatch(1);
101         final AtomicReference<Response> entry = new AtomicReference<>();
102         executeRequest(
103                 request,
104                 additionalHeaders,
105                 new OnRequestComplete() {
106                     @Override
107                     public void onSuccess(HttpResponse httpResponse) {
108                         Response response =
109                                 new Response(
110                                         httpResponse,
111                                         /* ioException= */ null,
112                                         /* authFailureError= */ null);
113                         entry.set(response);
114                         latch.countDown();
115                     }
116 
117                     @Override
118                     public void onAuthError(AuthFailureError authFailureError) {
119                         Response response =
120                                 new Response(
121                                         /* httpResponse= */ null,
122                                         /* ioException= */ null,
123                                         authFailureError);
124                         entry.set(response);
125                         latch.countDown();
126                     }
127 
128                     @Override
129                     public void onError(IOException ioException) {
130                         Response response =
131                                 new Response(
132                                         /* httpResponse= */ null,
133                                         ioException,
134                                         /* authFailureError= */ null);
135                         entry.set(response);
136                         latch.countDown();
137                     }
138                 });
139         try {
140             latch.await();
141         } catch (InterruptedException e) {
142             VolleyLog.e(e, "while waiting for CountDownLatch");
143             Thread.currentThread().interrupt();
144             throw new InterruptedIOException(e.toString());
145         }
146         Response response = entry.get();
147         if (response.httpResponse != null) {
148             return response.httpResponse;
149         } else if (response.ioException != null) {
150             throw response.ioException;
151         } else {
152             throw response.authFailureError;
153         }
154     }
155 
156     private static class Response {
157         HttpResponse httpResponse;
158         IOException ioException;
159         AuthFailureError authFailureError;
160 
Response( @ullable HttpResponse httpResponse, @Nullable IOException ioException, @Nullable AuthFailureError authFailureError)161         private Response(
162                 @Nullable HttpResponse httpResponse,
163                 @Nullable IOException ioException,
164                 @Nullable AuthFailureError authFailureError) {
165             this.httpResponse = httpResponse;
166             this.ioException = ioException;
167             this.authFailureError = authFailureError;
168         }
169     }
170 }
171