• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2022 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.net.apihelpers;
6 
7 import androidx.annotation.Nullable;
8 
9 import org.json.JSONObject;
10 
11 import org.chromium.net.CronetException;
12 import org.chromium.net.UrlResponseInfo;
13 
14 import java.util.concurrent.CompletableFuture;
15 import java.util.concurrent.Future;
16 
17 /**
18  * Utility class for creating simple, convenient {@code UrlRequest.Callback} implementations for
19  * reading common types of responses.
20  *
21  * <p>Note that the convenience callbacks store the entire response body in memory. We do not
22  * recommend using them if it's possible to stream the response body, or if the response body sizes
23  * can cause strain on the on-device resources.
24  *
25  * <p>The helper callbacks come in two flavors - either the caller provides a callback to be
26  * invoked when the request finishes (successfully or not), or the caller is given a {@link Future}
27  * which completes when Cronet finishes processing the request.
28  */
29 public class UrlRequestCallbacks {
forByteArrayBody( RedirectHandler redirectHandler, CronetRequestCompletionListener<byte[]> listener)30     public static ByteArrayCronetCallback forByteArrayBody(
31             RedirectHandler redirectHandler, CronetRequestCompletionListener<byte[]> listener) {
32         return newByteArrayCallback(redirectHandler).addCompletionListener(listener);
33     }
34 
forByteArrayBody( RedirectHandler redirectHandler)35     public static CallbackAndResponseFuturePair<byte[], ByteArrayCronetCallback> forByteArrayBody(
36             RedirectHandler redirectHandler) {
37         ByteArrayCronetCallback callback = newByteArrayCallback(redirectHandler);
38         Future<CronetResponse<byte[]>> future = addResponseFutureListener(callback);
39         return new CallbackAndResponseFuturePair<>(future, callback);
40     }
41 
forStringBody( RedirectHandler redirectHandler, CronetRequestCompletionListener<String> listener)42     public static StringCronetCallback forStringBody(
43             RedirectHandler redirectHandler, CronetRequestCompletionListener<String> listener) {
44         return newStringCallback(redirectHandler).addCompletionListener(listener);
45     }
46 
forStringBody( RedirectHandler redirectHandler)47     public static CallbackAndResponseFuturePair<String, StringCronetCallback> forStringBody(
48             RedirectHandler redirectHandler) {
49         StringCronetCallback callback = newStringCallback(redirectHandler);
50         Future<CronetResponse<String>> future = addResponseFutureListener(callback);
51         return new CallbackAndResponseFuturePair<>(future, callback);
52     }
53 
forJsonBody( RedirectHandler redirectHandler, CronetRequestCompletionListener<JSONObject> listener)54     public static JsonCronetCallback forJsonBody(
55             RedirectHandler redirectHandler, CronetRequestCompletionListener<JSONObject> listener) {
56         return newJsonCallback(redirectHandler).addCompletionListener(listener);
57     }
58 
forJsonBody( RedirectHandler redirectHandler)59     public static CallbackAndResponseFuturePair<JSONObject, JsonCronetCallback> forJsonBody(
60             RedirectHandler redirectHandler) {
61         JsonCronetCallback callback = newJsonCallback(redirectHandler);
62         Future<CronetResponse<JSONObject>> future = addResponseFutureListener(callback);
63         return new CallbackAndResponseFuturePair<>(future, callback);
64     }
65 
newByteArrayCallback(RedirectHandler redirectHandler)66     private static ByteArrayCronetCallback newByteArrayCallback(RedirectHandler redirectHandler) {
67         return new ByteArrayCronetCallback() {
68             @Override
69             protected boolean shouldFollowRedirect(UrlResponseInfo info, String newLocationUrl)
70                     throws Exception {
71                 return redirectHandler.shouldFollowRedirect(info, newLocationUrl);
72             }
73         };
74     }
75 
76     private static StringCronetCallback newStringCallback(RedirectHandler redirectHandler) {
77         return new StringCronetCallback() {
78             @Override
79             protected boolean shouldFollowRedirect(UrlResponseInfo info, String newLocationUrl)
80                     throws Exception {
81                 return redirectHandler.shouldFollowRedirect(info, newLocationUrl);
82             }
83         };
84     }
85 
86     private static JsonCronetCallback newJsonCallback(RedirectHandler redirectHandler) {
87         return new JsonCronetCallback() {
88             @Override
89             protected boolean shouldFollowRedirect(UrlResponseInfo info, String newLocationUrl)
90                     throws Exception {
91                 return redirectHandler.shouldFollowRedirect(info, newLocationUrl);
92             }
93         };
94     }
95 
96     private static <T> Future<CronetResponse<T>> addResponseFutureListener(
97             InMemoryTransformCronetCallback<T> callback) {
98         CompletableFuture<CronetResponse<T>> completableFuture = new CompletableFuture<>();
99         callback.addCompletionListener(
100                 new CronetRequestCompletionListener<T>() {
101                     @Override
102                     public void onFailed(
103                             @Nullable UrlResponseInfo info, CronetException exception) {
104                         completableFuture.completeExceptionally(exception);
105                     }
106 
107                     @Override
108                     public void onCanceled(@Nullable UrlResponseInfo info) {
109                         completableFuture.completeExceptionally(
110                                 new CronetException("The request was canceled!", null) {});
111                     }
112 
113                     @Override
114                     public void onSucceeded(UrlResponseInfo info, T body) {
115                         completableFuture.complete(new CronetResponse<>(info, body));
116                     }
117                 });
118         return completableFuture;
119     }
120 
121     /**
122      * A named pair-like structure encapsulating Cronet callbacks and associated response futures.
123      *
124      * <p>The request should be used to pass to {@code CronetEngine.newUrlRequest()}, the future
125      * will contain the response to the request.
126      *
127      * @param <CallbackT> the subtype of the callback
128      * @param <ResponseBodyT> The type of the deserialized response body
129      */
130     public static class CallbackAndResponseFuturePair<
131             ResponseBodyT, CallbackT extends InMemoryTransformCronetCallback<ResponseBodyT>> {
132         private final Future<CronetResponse<ResponseBodyT>> mFuture;
133         private final CallbackT mCallback;
134 
135         CallbackAndResponseFuturePair(
136                 Future<CronetResponse<ResponseBodyT>> future, CallbackT callback) {
137             this.mFuture = future;
138             this.mCallback = callback;
139         }
140 
141         public Future<CronetResponse<ResponseBodyT>> getFuture() {
142             return mFuture;
143         }
144 
145         public CallbackT getCallback() {
146             return mCallback;
147         }
148     }
149 
150     private UrlRequestCallbacks() {}
151 }
152