1 // Copyright 2014 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.urlconnection; 6 7 import androidx.annotation.VisibleForTesting; 8 9 import java.io.IOException; 10 import java.io.InputStream; 11 import java.nio.ByteBuffer; 12 13 /** 14 * An InputStream that is used by {@link CronetHttpURLConnection} to request 15 * data from the network stack as needed. 16 */ 17 @VisibleForTesting 18 public class CronetInputStream extends InputStream { 19 private final CronetHttpURLConnection mHttpURLConnection; 20 // Indicates whether listener's onSucceeded or onFailed callback is invoked. 21 private boolean mResponseDataCompleted; 22 private ByteBuffer mBuffer; 23 private IOException mException; 24 25 private static final int READ_BUFFER_SIZE = 32 * 1024; 26 27 /** 28 * Constructs a CronetInputStream. 29 * @param httpURLConnection the CronetHttpURLConnection that is associated 30 * with this InputStream. 31 */ CronetInputStream(CronetHttpURLConnection httpURLConnection)32 public CronetInputStream(CronetHttpURLConnection httpURLConnection) { 33 mHttpURLConnection = httpURLConnection; 34 } 35 36 @Override read()37 public int read() throws IOException { 38 getMoreDataIfNeeded(); 39 if (hasUnreadData()) { 40 return mBuffer.get() & 0xFF; 41 } 42 return -1; 43 } 44 45 @Override read(byte[] buffer, int byteOffset, int byteCount)46 public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException { 47 if (byteOffset < 0 || byteCount < 0 || byteOffset + byteCount > buffer.length) { 48 throw new IndexOutOfBoundsException(); 49 } 50 if (byteCount == 0) { 51 return 0; 52 } 53 getMoreDataIfNeeded(); 54 if (hasUnreadData()) { 55 int bytesRead = Math.min(mBuffer.limit() - mBuffer.position(), byteCount); 56 mBuffer.get(buffer, byteOffset, bytesRead); 57 return bytesRead; 58 } 59 return -1; 60 } 61 62 @Override available()63 public int available() throws IOException { 64 if (mResponseDataCompleted) { 65 if (mException != null) { 66 throw mException; 67 } 68 return 0; 69 } 70 if (hasUnreadData()) { 71 return mBuffer.remaining(); 72 } else { 73 return 0; 74 } 75 } 76 77 /** 78 * Called by {@link CronetHttpURLConnection} to notify that the entire 79 * response body has been read. 80 * @param exception if not {@code null}, it is the exception to throw when caller 81 * tries to read more data. 82 */ setResponseDataCompleted(IOException exception)83 public void setResponseDataCompleted(IOException exception) { 84 mException = exception; 85 mResponseDataCompleted = true; 86 // Nothing else to read, so can free the buffer. 87 mBuffer = null; 88 } 89 getMoreDataIfNeeded()90 private void getMoreDataIfNeeded() throws IOException { 91 if (mResponseDataCompleted) { 92 if (mException != null) { 93 throw mException; 94 } 95 return; 96 } 97 if (!hasUnreadData()) { 98 // Allocate read buffer if needed. 99 if (mBuffer == null) { 100 mBuffer = ByteBuffer.allocateDirect(READ_BUFFER_SIZE); 101 } 102 mBuffer.clear(); 103 104 // Requests more data from CronetHttpURLConnection. 105 mHttpURLConnection.getMoreData(mBuffer); 106 if (mException != null) { 107 throw mException; 108 } 109 if (mBuffer != null) { 110 mBuffer.flip(); 111 } 112 } 113 } 114 115 /** Returns whether {@link #mBuffer} has unread data. */ hasUnreadData()116 private boolean hasUnreadData() { 117 return mBuffer != null && mBuffer.hasRemaining(); 118 } 119 } 120