blob: e7916463796ae5ee7a120dd38365e1ca619e63f3 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net.apihelpers;
import androidx.annotation.Nullable;
import org.json.JSONObject;
import org.chromium.net.CronetException;
import org.chromium.net.UrlResponseInfo;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
/**
* Utility class for creating simple, convenient {@code UrlRequest.Callback} implementations for
* reading common types of responses.
*
* <p>Note that the convenience callbacks store the entire response body in memory. We do not
* recommend using them if it's possible to stream the response body, or if the response body sizes
* can cause strain on the on-device resources.
*
* <p>The helper callbacks come in two flavors - either the caller provides a callback to be
* invoked when the request finishes (successfully or not), or the caller is given a {@link Future}
* which completes when Cronet finishes processing the request.
*/
public class UrlRequestCallbacks {
public static ByteArrayCronetCallback forByteArrayBody(
RedirectHandler redirectHandler, CronetRequestCompletionListener<byte[]> listener) {
return newByteArrayCallback(redirectHandler).addCompletionListener(listener);
}
public static CallbackAndResponseFuturePair<byte[], ByteArrayCronetCallback> forByteArrayBody(
RedirectHandler redirectHandler) {
ByteArrayCronetCallback callback = newByteArrayCallback(redirectHandler);
Future<CronetResponse<byte[]>> future = addResponseFutureListener(callback);
return new CallbackAndResponseFuturePair<>(future, callback);
}
public static StringCronetCallback forStringBody(
RedirectHandler redirectHandler, CronetRequestCompletionListener<String> listener) {
return newStringCallback(redirectHandler).addCompletionListener(listener);
}
public static CallbackAndResponseFuturePair<String, StringCronetCallback> forStringBody(
RedirectHandler redirectHandler) {
StringCronetCallback callback = newStringCallback(redirectHandler);
Future<CronetResponse<String>> future = addResponseFutureListener(callback);
return new CallbackAndResponseFuturePair<>(future, callback);
}
public static JsonCronetCallback forJsonBody(
RedirectHandler redirectHandler, CronetRequestCompletionListener<JSONObject> listener) {
return newJsonCallback(redirectHandler).addCompletionListener(listener);
}
public static CallbackAndResponseFuturePair<JSONObject, JsonCronetCallback> forJsonBody(
RedirectHandler redirectHandler) {
JsonCronetCallback callback = newJsonCallback(redirectHandler);
Future<CronetResponse<JSONObject>> future = addResponseFutureListener(callback);
return new CallbackAndResponseFuturePair<>(future, callback);
}
private static ByteArrayCronetCallback newByteArrayCallback(RedirectHandler redirectHandler) {
return new ByteArrayCronetCallback() {
@Override
protected boolean shouldFollowRedirect(UrlResponseInfo info, String newLocationUrl)
throws Exception {
return redirectHandler.shouldFollowRedirect(info, newLocationUrl);
}
};
}
private static StringCronetCallback newStringCallback(RedirectHandler redirectHandler) {
return new StringCronetCallback() {
@Override
protected boolean shouldFollowRedirect(UrlResponseInfo info, String newLocationUrl)
throws Exception {
return redirectHandler.shouldFollowRedirect(info, newLocationUrl);
}
};
}
private static JsonCronetCallback newJsonCallback(RedirectHandler redirectHandler) {
return new JsonCronetCallback() {
@Override
protected boolean shouldFollowRedirect(UrlResponseInfo info, String newLocationUrl)
throws Exception {
return redirectHandler.shouldFollowRedirect(info, newLocationUrl);
}
};
}
private static <T> Future<CronetResponse<T>> addResponseFutureListener(
InMemoryTransformCronetCallback<T> callback) {
CompletableFuture<CronetResponse<T>> completableFuture = new CompletableFuture<>();
callback.addCompletionListener(
new CronetRequestCompletionListener<T>() {
@Override
public void onFailed(
@Nullable UrlResponseInfo info, CronetException exception) {
completableFuture.completeExceptionally(exception);
}
@Override
public void onCanceled(@Nullable UrlResponseInfo info) {
completableFuture.completeExceptionally(
new CronetException("The request was canceled!", null) {});
}
@Override
public void onSucceeded(UrlResponseInfo info, T body) {
completableFuture.complete(new CronetResponse<>(info, body));
}
});
return completableFuture;
}
/**
* A named pair-like structure encapsulating Cronet callbacks and associated response futures.
*
* <p>The request should be used to pass to {@code CronetEngine.newUrlRequest()}, the future
* will contain the response to the request.
*
* @param <CallbackT> the subtype of the callback
* @param <ResponseBodyT> The type of the deserialized response body
*/
public static class CallbackAndResponseFuturePair<
ResponseBodyT, CallbackT extends InMemoryTransformCronetCallback<ResponseBodyT>> {
private final Future<CronetResponse<ResponseBodyT>> mFuture;
private final CallbackT mCallback;
CallbackAndResponseFuturePair(
Future<CronetResponse<ResponseBodyT>> future, CallbackT callback) {
this.mFuture = future;
this.mCallback = callback;
}
public Future<CronetResponse<ResponseBodyT>> getFuture() {
return mFuture;
}
public CallbackT getCallback() {
return mCallback;
}
}
private UrlRequestCallbacks() {}
}