1 // Copyright 2015 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 static com.google.common.truth.Truth.assertThat; 8 9 import static org.junit.Assert.assertThrows; 10 11 import androidx.test.ext.junit.runners.AndroidJUnit4; 12 import androidx.test.filters.SmallTest; 13 14 import org.junit.After; 15 import org.junit.Before; 16 import org.junit.Rule; 17 import org.junit.Test; 18 import org.junit.runner.RunWith; 19 20 import org.chromium.base.test.util.Batch; 21 import org.chromium.net.CronetEngine; 22 import org.chromium.net.CronetTestRule; 23 import org.chromium.net.CronetTestRule.CronetImplementation; 24 import org.chromium.net.CronetTestRule.IgnoreFor; 25 import org.chromium.net.NativeTestServer; 26 import org.chromium.net.NetworkException; 27 28 import java.io.IOException; 29 import java.io.OutputStream; 30 import java.net.HttpURLConnection; 31 import java.net.ProtocolException; 32 import java.net.URL; 33 34 /** Tests {@code getOutputStream} when {@code setChunkedStreamingMode} is enabled. */ 35 @Batch(Batch.UNIT_TESTS) 36 @IgnoreFor( 37 implementations = {CronetImplementation.FALLBACK}, 38 reason = "See crrev.com/c/4590329") 39 @RunWith(AndroidJUnit4.class) 40 public class CronetChunkedOutputStreamTest { 41 @Rule public final CronetTestRule mTestRule = CronetTestRule.withAutomaticEngineStartup(); 42 43 private static final String UPLOAD_DATA_STRING = "Nifty upload data!"; 44 private static final byte[] UPLOAD_DATA = UPLOAD_DATA_STRING.getBytes(); 45 private static final int REPEAT_COUNT = 100000; 46 47 private HttpURLConnection mConnection; 48 49 private CronetEngine mCronetEngine; 50 51 @Before setUp()52 public void setUp() throws Exception { 53 mCronetEngine = mTestRule.getTestFramework().getEngine(); 54 assertThat( 55 NativeTestServer.startNativeTestServer( 56 mTestRule.getTestFramework().getContext())) 57 .isTrue(); 58 } 59 60 @After tearDown()61 public void tearDown() throws Exception { 62 if (mConnection != null) { 63 mConnection.disconnect(); 64 } 65 NativeTestServer.shutdownNativeTestServer(); 66 } 67 68 @Test 69 @SmallTest testGetOutputStreamAfterConnectionMade()70 public void testGetOutputStreamAfterConnectionMade() throws Exception { 71 URL url = new URL(NativeTestServer.getEchoBodyURL()); 72 mConnection = (HttpURLConnection) mCronetEngine.openConnection(url); 73 mConnection.setDoOutput(true); 74 mConnection.setRequestMethod("POST"); 75 mConnection.setChunkedStreamingMode(0); 76 assertThat(mConnection.getResponseCode()).isEqualTo(200); 77 assertThrows(ProtocolException.class, mConnection::getOutputStream); 78 } 79 80 @Test 81 @SmallTest testWriteAfterReadingResponse()82 public void testWriteAfterReadingResponse() throws Exception { 83 URL url = new URL(NativeTestServer.getEchoBodyURL()); 84 mConnection = (HttpURLConnection) mCronetEngine.openConnection(url); 85 mConnection.setDoOutput(true); 86 mConnection.setRequestMethod("POST"); 87 mConnection.setChunkedStreamingMode(0); 88 OutputStream out = mConnection.getOutputStream(); 89 assertThat(mConnection.getResponseCode()).isEqualTo(200); 90 assertThrows(IOException.class, () -> out.write(UPLOAD_DATA)); 91 } 92 93 @Test 94 @SmallTest testWriteAfterRequestFailed()95 public void testWriteAfterRequestFailed() throws Exception { 96 URL url = new URL(NativeTestServer.getEchoBodyURL()); 97 mConnection = (HttpURLConnection) mCronetEngine.openConnection(url); 98 mConnection.setDoOutput(true); 99 mConnection.setRequestMethod("POST"); 100 mConnection.setChunkedStreamingMode(0); 101 OutputStream out = mConnection.getOutputStream(); 102 out.write(UPLOAD_DATA); 103 NativeTestServer.shutdownNativeTestServer(); 104 IOException e = assertThrows(IOException.class, () -> out.write(TestUtil.getLargeData())); 105 // TODO(crbug.com/1495774): Consider whether we should be checking this in the first place. 106 if (mTestRule.implementationUnderTest().equals(CronetImplementation.STATICALLY_LINKED)) { 107 assertThat(e).isInstanceOf(NetworkException.class); 108 NetworkException networkException = (NetworkException) e; 109 assertThat(networkException.getErrorCode()) 110 .isEqualTo(NetworkException.ERROR_CONNECTION_REFUSED); 111 } 112 } 113 114 @Test 115 @SmallTest testGetResponseAfterWriteFailed()116 public void testGetResponseAfterWriteFailed() throws Exception { 117 URL url = new URL(NativeTestServer.getEchoBodyURL()); 118 NativeTestServer.shutdownNativeTestServer(); 119 mConnection = (HttpURLConnection) mCronetEngine.openConnection(url); 120 mConnection.setDoOutput(true); 121 mConnection.setRequestMethod("POST"); 122 // Set 1 byte as chunk size so internally Cronet will try upload when 123 // 1 byte is filled. 124 mConnection.setChunkedStreamingMode(1); 125 OutputStream out = mConnection.getOutputStream(); 126 out.write(1); 127 IOException e = assertThrows(IOException.class, () -> out.write(1)); 128 // TODO(crbug.com/1495774): Consider whether we should be checking this in the first place. 129 if (mTestRule.implementationUnderTest().equals(CronetImplementation.STATICALLY_LINKED)) { 130 assertThat(e).isInstanceOf(NetworkException.class); 131 NetworkException networkException = (NetworkException) e; 132 assertThat(networkException.getErrorCode()) 133 .isEqualTo(NetworkException.ERROR_CONNECTION_REFUSED); 134 } 135 136 // Make sure IOException is reported again when trying to read response 137 // from the mConnection. 138 e = assertThrows(IOException.class, mConnection::getResponseCode); 139 // TODO(crbug.com/1495774): Consider whether we should be checking this in the first place. 140 if (mTestRule.implementationUnderTest().equals(CronetImplementation.STATICALLY_LINKED)) { 141 assertThat(e).isInstanceOf(NetworkException.class); 142 NetworkException networkException = (NetworkException) e; 143 assertThat(networkException.getErrorCode()) 144 .isEqualTo(NetworkException.ERROR_CONNECTION_REFUSED); 145 } 146 } 147 148 @Test 149 @SmallTest testPost()150 public void testPost() throws Exception { 151 URL url = new URL(NativeTestServer.getEchoBodyURL()); 152 mConnection = (HttpURLConnection) mCronetEngine.openConnection(url); 153 mConnection.setDoOutput(true); 154 mConnection.setRequestMethod("POST"); 155 mConnection.setChunkedStreamingMode(0); 156 OutputStream out = mConnection.getOutputStream(); 157 out.write(UPLOAD_DATA); 158 assertThat(mConnection.getResponseCode()).isEqualTo(200); 159 assertThat(mConnection.getResponseMessage()).isEqualTo("OK"); 160 assertThat(TestUtil.getResponseAsString(mConnection)).isEqualTo(UPLOAD_DATA_STRING); 161 } 162 163 @Test 164 @SmallTest testTransferEncodingHeaderSet()165 public void testTransferEncodingHeaderSet() throws Exception { 166 URL url = new URL(NativeTestServer.getEchoHeaderURL("Transfer-Encoding")); 167 mConnection = (HttpURLConnection) mCronetEngine.openConnection(url); 168 mConnection.setDoOutput(true); 169 mConnection.setRequestMethod("POST"); 170 mConnection.setChunkedStreamingMode(0); 171 OutputStream out = mConnection.getOutputStream(); 172 out.write(UPLOAD_DATA); 173 assertThat(mConnection.getResponseCode()).isEqualTo(200); 174 assertThat(mConnection.getResponseMessage()).isEqualTo("OK"); 175 assertThat(TestUtil.getResponseAsString(mConnection)).isEqualTo("chunked"); 176 } 177 178 @Test 179 @SmallTest testPostOneMassiveWrite()180 public void testPostOneMassiveWrite() throws Exception { 181 URL url = new URL(NativeTestServer.getEchoBodyURL()); 182 mConnection = (HttpURLConnection) mCronetEngine.openConnection(url); 183 mConnection.setDoOutput(true); 184 mConnection.setRequestMethod("POST"); 185 mConnection.setChunkedStreamingMode(0); 186 OutputStream out = mConnection.getOutputStream(); 187 byte[] largeData = TestUtil.getLargeData(); 188 out.write(largeData); 189 assertThat(mConnection.getResponseCode()).isEqualTo(200); 190 assertThat(mConnection.getResponseMessage()).isEqualTo("OK"); 191 TestUtil.checkLargeData(TestUtil.getResponseAsString(mConnection)); 192 } 193 194 @Test 195 @SmallTest testPostWriteOneByte()196 public void testPostWriteOneByte() throws Exception { 197 URL url = new URL(NativeTestServer.getEchoBodyURL()); 198 mConnection = (HttpURLConnection) mCronetEngine.openConnection(url); 199 mConnection.setDoOutput(true); 200 mConnection.setRequestMethod("POST"); 201 mConnection.setChunkedStreamingMode(0); 202 OutputStream out = mConnection.getOutputStream(); 203 for (int i = 0; i < UPLOAD_DATA.length; i++) { 204 out.write(UPLOAD_DATA[i]); 205 } 206 assertThat(mConnection.getResponseCode()).isEqualTo(200); 207 assertThat(mConnection.getResponseMessage()).isEqualTo("OK"); 208 assertThat(TestUtil.getResponseAsString(mConnection)).isEqualTo(UPLOAD_DATA_STRING); 209 } 210 211 @Test 212 @SmallTest testPostOneMassiveWriteWriteOneByte()213 public void testPostOneMassiveWriteWriteOneByte() throws Exception { 214 URL url = new URL(NativeTestServer.getEchoBodyURL()); 215 mConnection = (HttpURLConnection) mCronetEngine.openConnection(url); 216 mConnection.setDoOutput(true); 217 mConnection.setRequestMethod("POST"); 218 mConnection.setChunkedStreamingMode(0); 219 OutputStream out = mConnection.getOutputStream(); 220 byte[] largeData = TestUtil.getLargeData(); 221 for (int i = 0; i < largeData.length; i++) { 222 out.write(largeData[i]); 223 } 224 assertThat(mConnection.getResponseCode()).isEqualTo(200); 225 assertThat(mConnection.getResponseMessage()).isEqualTo("OK"); 226 TestUtil.checkLargeData(TestUtil.getResponseAsString(mConnection)); 227 } 228 229 @Test 230 @SmallTest testPostWholeNumberOfChunks()231 public void testPostWholeNumberOfChunks() throws Exception { 232 URL url = new URL(NativeTestServer.getEchoBodyURL()); 233 mConnection = (HttpURLConnection) mCronetEngine.openConnection(url); 234 mConnection.setDoOutput(true); 235 mConnection.setRequestMethod("POST"); 236 int totalSize = UPLOAD_DATA.length * REPEAT_COUNT; 237 int chunkSize = 18000; 238 // Ensure total data size is a multiple of chunk size, so no partial 239 // chunks will be used. 240 assertThat(totalSize % chunkSize).isEqualTo(0); 241 mConnection.setChunkedStreamingMode(chunkSize); 242 OutputStream out = mConnection.getOutputStream(); 243 byte[] largeData = TestUtil.getLargeData(); 244 out.write(largeData); 245 assertThat(mConnection.getResponseCode()).isEqualTo(200); 246 assertThat(mConnection.getResponseMessage()).isEqualTo("OK"); 247 TestUtil.checkLargeData(TestUtil.getResponseAsString(mConnection)); 248 } 249 250 @Test 251 @SmallTest 252 // Regression testing for crbug.com/618872. testOneMassiveWriteLargerThanInternalBuffer()253 public void testOneMassiveWriteLargerThanInternalBuffer() throws Exception { 254 URL url = new URL(NativeTestServer.getEchoBodyURL()); 255 mConnection = (HttpURLConnection) mCronetEngine.openConnection(url); 256 mConnection.setDoOutput(true); 257 mConnection.setRequestMethod("POST"); 258 // Use a super big chunk size so that it exceeds the UploadDataProvider 259 // read buffer size. 260 byte[] largeData = TestUtil.getLargeData(); 261 mConnection.setChunkedStreamingMode(largeData.length); 262 OutputStream out = mConnection.getOutputStream(); 263 out.write(largeData); 264 assertThat(mConnection.getResponseCode()).isEqualTo(200); 265 TestUtil.checkLargeData(TestUtil.getResponseAsString(mConnection)); 266 } 267 } 268