chromium/components/cronet/android/api/src/org/chromium/net/apihelpers/ImplicitFlowControlCallback.java

// 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.chromium.net.CallbackException;
import org.chromium.net.CronetException;
import org.chromium.net.UrlRequest;
import org.chromium.net.UrlResponseInfo;

import java.nio.ByteBuffer;

/**
 * An implementation of {@link UrlRequest.Callback} that takes away the difficulty of managing the
 * request lifecycle away, and automatically proceeds to read the response entirely.
 */
public abstract class ImplicitFlowControlCallback extends UrlRequest.Callback {
    private static final int BYTE_BUFFER_CAPACITY = 32 * 1024;

    /**
     * Invoked whenever a redirect is encountered. This will only be invoked between the call to
     * {@link UrlRequest#start} and {@link UrlRequest.Callback#onResponseStarted
     * onResponseStarted()}. The body of the redirect response, if it has one, will be ignored.
     *
     * @param info Response information.
     * @param newLocationUrl Location where request is redirected.
     * @return true if the redirect should be followed, false if the request should be canceled.
     * @throws Exception if an error occurs while processing a redirect. {@link #onFailed} will be
     * called with the thrown exception set as the cause of the {@link CallbackException}.
     */
    protected abstract boolean shouldFollowRedirect(UrlResponseInfo info, String newLocationUrl)
            throws Exception;

    /**
     * Invoked when the final set of headers, after all redirects, is received. Will only be invoked
     * once for each request. It's guaranteed that Cronet doesn't start reading the body until this
     * method returns.
     *
     * @param info Response information.
     * @throws Exception if an error occurs while processing response start. {@link #onFailed} will
     *         be
     * called with the thrown exception set as the cause of the {@link CallbackException}.
     */
    protected abstract void onResponseStarted(UrlResponseInfo info) throws Exception;

    /**
     * Invoked whenever part of the response body has been read. Only part of the buffer may be
     * populated, even if the entire response body has not yet been consumed. The buffer is ready
     * for reading. Buffers are reused internally so the implementing class shouldn't store the
     * buffer or use it anywhere else than in the implementation of this method.
     *
     * @param info Response information.
     * @param bodyChunk The buffer that contains the received data, flipped for reading.
     * @throws Exception if an error occurs while processing a read completion. {@link #onFailed}
     *         will
     * be called with the thrown exception set as the cause of the {@link CallbackException}.
     */
    protected abstract void onBodyChunkRead(UrlResponseInfo info, ByteBuffer bodyChunk)
            throws Exception;

    /**
     * Invoked when request is completed successfully. Once invoked, no other {@link
     * UrlRequest.Callback} methods will be invoked.
     *
     * @param info Response information.
     */
    protected abstract void onSucceeded(UrlResponseInfo info);

    /**
     * Invoked if request failed for any reason after {@link UrlRequest#start}. Once invoked, no
     * other
     * {@link UrlRequest.Callback} methods will be invoked. {@code error} provides information about
     * the failure.
     *
     * @param info Response information. May be {@code null} if no response was received.
     * @param exception information about error.
     */
    protected abstract void onFailed(@Nullable UrlResponseInfo info, CronetException exception);

    /**
     * Invoked if request was canceled via {@link UrlRequest#cancel}. Once invoked, no other {@link
     * UrlRequest.Callback} methods will be invoked.
     *
     * @param info Response information. May be {@code null} if no response was received.
     */
    protected abstract void onCanceled(@Nullable UrlResponseInfo info);

    @Override
    public final void onResponseStarted(UrlRequest request, UrlResponseInfo info) throws Exception {
        onResponseStarted(info);
        request.read(ByteBuffer.allocateDirect(BYTE_BUFFER_CAPACITY));
    }

    @Override
    public final void onRedirectReceived(
            UrlRequest request, UrlResponseInfo info, String newLocationUrl) throws Exception {
        if (shouldFollowRedirect(info, newLocationUrl)) {
            request.followRedirect();
        } else {
            request.cancel();
        }
    }

    @Override
    public final void onReadCompleted(
            UrlRequest request, UrlResponseInfo info, ByteBuffer byteBuffer) throws Exception {
        byteBuffer.flip();
        onBodyChunkRead(info, byteBuffer);
        byteBuffer.clear();
        request.read(byteBuffer);
    }

    @Override
    public final void onSucceeded(UrlRequest request, UrlResponseInfo info) {
        onSucceeded(info);
    }

    @Override
    public final void onFailed(UrlRequest request, UrlResponseInfo info, CronetException error) {
        onFailed(info, error);
    }

    @Override
    public final void onCanceled(UrlRequest request, UrlResponseInfo info) {
        onCanceled(info);
    }
}