• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.odp.module.common.http;
18 
19 import static com.android.odp.module.common.http.HttpClientUtils.HTTP_OK_STATUS;
20 
21 import android.annotation.NonNull;
22 
23 import com.google.common.annotations.VisibleForTesting;
24 import com.google.common.util.concurrent.Futures;
25 import com.google.common.util.concurrent.ListenableFuture;
26 import com.google.common.util.concurrent.ListeningExecutorService;
27 
28 import java.io.IOException;
29 import java.util.concurrent.Callable;
30 
31 /**
32  * The HTTP client to be used by FederatedCompute and ODP services/jobs to communicate with remote
33  * servers.
34  */
35 public class HttpClient {
36 
37     interface HttpIOSupplier<T> {
get()38         T get() throws IOException; // Declared to throw IOException
39     }
40 
41     private final int mRetryLimit;
42 
43     /** The executor to use for making http requests. */
44     private final ListeningExecutorService mBlockingExecutor;
45 
HttpClient(int retryLimit, ListeningExecutorService blockingExecutor)46     public HttpClient(int retryLimit, ListeningExecutorService blockingExecutor) {
47         mRetryLimit = retryLimit;
48         mBlockingExecutor = blockingExecutor;
49     }
50 
51     /**
52      * Perform HTTP requests based on given {@link OdpHttpRequest} asynchronously with configured
53      * number of retries.
54      *
55      * <p>Retry limit provided during construction is used in case http does not return {@code OK}
56      * response code.
57      */
58     @NonNull
performRequestAsyncWithRetry(OdpHttpRequest request)59     public ListenableFuture<OdpHttpResponse> performRequestAsyncWithRetry(OdpHttpRequest request) {
60         return performCallableAsync(
61                 () -> performRequestWithRetry(() -> HttpClientUtils.performRequest(request)));
62     }
63 
64     /**
65      * Perform HTTP requests based on given information asynchronously with retries in case http
66      * will return not OK response code. Payload will be saved directly into the file.
67      */
68     @NonNull
performRequestIntoFileAsyncWithRetry( OdpHttpRequest request)69     public ListenableFuture<OdpHttpResponse> performRequestIntoFileAsyncWithRetry(
70             OdpHttpRequest request) {
71         return performCallableAsync(
72                 () -> performRequestWithRetry(() -> HttpClientUtils.performRequest(request, true)));
73     }
74 
75     /**
76      * Perform HTTP requests based on given information asynchronously with retries in case http
77      * will return not OK response code.
78      */
79     @NonNull
performCallableAsync( Callable<OdpHttpResponse> callable)80     private ListenableFuture<OdpHttpResponse> performCallableAsync(
81             Callable<OdpHttpResponse> callable) {
82         try {
83             return mBlockingExecutor.submit(callable);
84         } catch (Exception e) {
85             return Futures.immediateFailedFuture(e);
86         }
87     }
88 
89     /** Perform HTTP requests based on given information with retries. */
90     @NonNull
91     @VisibleForTesting
performRequestWithRetry(HttpIOSupplier<OdpHttpResponse> supplier)92     OdpHttpResponse performRequestWithRetry(HttpIOSupplier<OdpHttpResponse> supplier)
93             throws IOException {
94         OdpHttpResponse response = null;
95         int retryLimit = mRetryLimit;
96         while (retryLimit > 0) {
97             try {
98                 response = supplier.get();
99                 if (HTTP_OK_STATUS.contains(response.getStatusCode())) {
100                     return response;
101                 }
102                 // we want to continue retry in case it is IO exception.
103             } catch (IOException e) {
104                 // propagate IO exception after RETRY_LIMIT times attempt.
105                 if (retryLimit <= 1) {
106                     throw e;
107                 }
108             } finally {
109                 retryLimit--;
110             }
111         }
112         return response;
113     }
114 }
115