• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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