blob: 3e55fd425e9da4f834d173e5c0d1d6497bd548f4 [file] [log] [blame]
// Copyright 2014 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.urlconnection;
import androidx.annotation.VisibleForTesting;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
/**
* An InputStream that is used by {@link CronetHttpURLConnection} to request
* data from the network stack as needed.
*/
@VisibleForTesting
public class CronetInputStream extends InputStream {
private final CronetHttpURLConnection mHttpURLConnection;
// Indicates whether listener's onSucceeded or onFailed callback is invoked.
private boolean mResponseDataCompleted;
private ByteBuffer mBuffer;
private IOException mException;
private static final int READ_BUFFER_SIZE = 32 * 1024;
/**
* Constructs a CronetInputStream.
* @param httpURLConnection the CronetHttpURLConnection that is associated
* with this InputStream.
*/
public CronetInputStream(CronetHttpURLConnection httpURLConnection) {
mHttpURLConnection = httpURLConnection;
}
@Override
public int read() throws IOException {
getMoreDataIfNeeded();
if (hasUnreadData()) {
return mBuffer.get() & 0xFF;
}
return -1;
}
@Override
public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
if (byteOffset < 0 || byteCount < 0 || byteOffset + byteCount > buffer.length) {
throw new IndexOutOfBoundsException();
}
if (byteCount == 0) {
return 0;
}
getMoreDataIfNeeded();
if (hasUnreadData()) {
int bytesRead = Math.min(mBuffer.limit() - mBuffer.position(), byteCount);
mBuffer.get(buffer, byteOffset, bytesRead);
return bytesRead;
}
return -1;
}
@Override
public int available() throws IOException {
if (mResponseDataCompleted) {
if (mException != null) {
throw mException;
}
return 0;
}
if (hasUnreadData()) {
return mBuffer.remaining();
} else {
return 0;
}
}
/**
* Called by {@link CronetHttpURLConnection} to notify that the entire
* response body has been read.
* @param exception if not {@code null}, it is the exception to throw when caller
* tries to read more data.
*/
public void setResponseDataCompleted(IOException exception) {
mException = exception;
mResponseDataCompleted = true;
// Nothing else to read, so can free the buffer.
mBuffer = null;
}
private void getMoreDataIfNeeded() throws IOException {
if (mResponseDataCompleted) {
if (mException != null) {
throw mException;
}
return;
}
if (!hasUnreadData()) {
// Allocate read buffer if needed.
if (mBuffer == null) {
mBuffer = ByteBuffer.allocateDirect(READ_BUFFER_SIZE);
}
mBuffer.clear();
// Requests more data from CronetHttpURLConnection.
mHttpURLConnection.getMoreData(mBuffer);
if (mException != null) {
throw mException;
}
if (mBuffer != null) {
mBuffer.flip();
}
}
}
/**
* Returns whether {@link #mBuffer} has unread data.
*/
private boolean hasUnreadData() {
return mBuffer != null && mBuffer.hasRemaining();
}
}