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