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 org.junit.Assert.assertEquals; 8 import static org.junit.Assert.assertTrue; 9 import static org.junit.Assert.fail; 10 11 import static org.chromium.net.CronetTestRule.getContext; 12 13 import androidx.test.ext.junit.runners.AndroidJUnit4; 14 import androidx.test.filters.SmallTest; 15 16 import org.junit.After; 17 import org.junit.Before; 18 import org.junit.Rule; 19 import org.junit.Test; 20 import org.junit.runner.RunWith; 21 22 import android.net.http.HttpEngine; 23 import org.chromium.net.CronetTestRule; 24 import org.chromium.net.CronetTestRule.CompareDefaultWithCronet; 25 import org.chromium.net.CronetTestRule.OnlyRunCronetHttpURLConnection; 26 import org.chromium.net.NativeTestServer; 27 import android.net.http.NetworkException; 28 29 import java.io.IOException; 30 import java.io.OutputStream; 31 import java.net.HttpURLConnection; 32 import java.net.ProtocolException; 33 import java.net.URL; 34 35 /** 36 * Tests {@code getOutputStream} when {@code setChunkedStreamingMode} is enabled. 37 * Tests annotated with {@code CompareDefaultWithCronet} will run once with the 38 * default HttpURLConnection implementation and then with Cronet's 39 * HttpURLConnection implementation. Tests annotated with 40 * {@code OnlyRunCronetHttpURLConnection} only run Cronet's implementation. 41 * See {@link CronetTestBase#runTest()} for details. 42 */ 43 @RunWith(AndroidJUnit4.class) 44 public class CronetChunkedOutputStreamTest { 45 @Rule 46 public final CronetTestRule mTestRule = new CronetTestRule(); 47 48 private static final String UPLOAD_DATA_STRING = "Nifty upload data!"; 49 private static final byte[] UPLOAD_DATA = UPLOAD_DATA_STRING.getBytes(); 50 private static final int REPEAT_COUNT = 100000; 51 52 @Before setUp()53 public void setUp() throws Exception { 54 mTestRule.setStreamHandlerFactory(new HttpEngine.Builder(getContext()).build()); 55 assertTrue(NativeTestServer.startNativeTestServer(getContext())); 56 } 57 58 @After tearDown()59 public void tearDown() throws Exception { 60 NativeTestServer.shutdownNativeTestServer(); 61 } 62 63 @Test 64 @SmallTest 65 @CompareDefaultWithCronet testGetOutputStreamAfterConnectionMade()66 public void testGetOutputStreamAfterConnectionMade() throws Exception { 67 URL url = new URL(NativeTestServer.getEchoBodyURL()); 68 HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 69 connection.setDoOutput(true); 70 connection.setRequestMethod("POST"); 71 connection.setChunkedStreamingMode(0); 72 assertEquals(200, connection.getResponseCode()); 73 try { 74 connection.getOutputStream(); 75 fail(); 76 } catch (ProtocolException e) { 77 // Expected. 78 } 79 } 80 81 @Test 82 @SmallTest 83 @CompareDefaultWithCronet testWriteAfterReadingResponse()84 public void testWriteAfterReadingResponse() throws Exception { 85 URL url = new URL(NativeTestServer.getEchoBodyURL()); 86 HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 87 connection.setDoOutput(true); 88 connection.setRequestMethod("POST"); 89 connection.setChunkedStreamingMode(0); 90 OutputStream out = connection.getOutputStream(); 91 assertEquals(200, connection.getResponseCode()); 92 try { 93 out.write(UPLOAD_DATA); 94 fail(); 95 } catch (IOException e) { 96 // Expected. 97 } 98 } 99 100 @Test 101 @SmallTest 102 @CompareDefaultWithCronet testWriteAfterRequestFailed()103 public void testWriteAfterRequestFailed() throws Exception { 104 URL url = new URL(NativeTestServer.getEchoBodyURL()); 105 HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 106 connection.setDoOutput(true); 107 connection.setRequestMethod("POST"); 108 connection.setChunkedStreamingMode(0); 109 OutputStream out = connection.getOutputStream(); 110 out.write(UPLOAD_DATA); 111 NativeTestServer.shutdownNativeTestServer(); 112 try { 113 out.write(TestUtil.getLargeData()); 114 connection.getResponseCode(); 115 fail(); 116 } catch (IOException e) { 117 if (!mTestRule.testingSystemHttpURLConnection()) { 118 NetworkException requestException = (NetworkException) e; 119 assertEquals( 120 NetworkException.ERROR_CONNECTION_REFUSED, requestException.getErrorCode()); 121 } 122 } 123 connection.disconnect(); 124 // Restarting server to run the test for a second time. 125 assertTrue(NativeTestServer.startNativeTestServer(getContext())); 126 } 127 128 @Test 129 @SmallTest 130 @CompareDefaultWithCronet testGetResponseAfterWriteFailed()131 public void testGetResponseAfterWriteFailed() throws Exception { 132 URL url = new URL(NativeTestServer.getEchoBodyURL()); 133 NativeTestServer.shutdownNativeTestServer(); 134 HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 135 connection.setDoOutput(true); 136 connection.setRequestMethod("POST"); 137 // Set 1 byte as chunk size so internally Cronet will try upload when 138 // 1 byte is filled. 139 connection.setChunkedStreamingMode(1); 140 try { 141 OutputStream out = connection.getOutputStream(); 142 out.write(1); 143 out.write(1); 144 // Forces OutputStream implementation to flush. crbug.com/653072 145 out.flush(); 146 // System's implementation is flaky see crbug.com/653072. 147 if (!mTestRule.testingSystemHttpURLConnection()) { 148 fail(); 149 } 150 } catch (IOException e) { 151 if (!mTestRule.testingSystemHttpURLConnection()) { 152 NetworkException requestException = (NetworkException) e; 153 assertEquals( 154 NetworkException.ERROR_CONNECTION_REFUSED, requestException.getErrorCode()); 155 } 156 } 157 // Make sure IOException is reported again when trying to read response 158 // from the connection. 159 try { 160 connection.getResponseCode(); 161 fail(); 162 } catch (IOException e) { 163 // Expected. 164 if (!mTestRule.testingSystemHttpURLConnection()) { 165 NetworkException requestException = (NetworkException) e; 166 assertEquals( 167 NetworkException.ERROR_CONNECTION_REFUSED, requestException.getErrorCode()); 168 } 169 } 170 // Restarting server to run the test for a second time. 171 assertTrue(NativeTestServer.startNativeTestServer(getContext())); 172 } 173 174 @Test 175 @SmallTest 176 @CompareDefaultWithCronet testPost()177 public void testPost() throws Exception { 178 URL url = new URL(NativeTestServer.getEchoBodyURL()); 179 HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 180 connection.setDoOutput(true); 181 connection.setRequestMethod("POST"); 182 connection.setChunkedStreamingMode(0); 183 OutputStream out = connection.getOutputStream(); 184 out.write(UPLOAD_DATA); 185 assertEquals(200, connection.getResponseCode()); 186 assertEquals("OK", connection.getResponseMessage()); 187 assertEquals(UPLOAD_DATA_STRING, TestUtil.getResponseAsString(connection)); 188 connection.disconnect(); 189 } 190 191 @Test 192 @SmallTest 193 @CompareDefaultWithCronet testTransferEncodingHeaderSet()194 public void testTransferEncodingHeaderSet() throws Exception { 195 URL url = new URL(NativeTestServer.getEchoHeaderURL("Transfer-Encoding")); 196 HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 197 connection.setDoOutput(true); 198 connection.setRequestMethod("POST"); 199 connection.setChunkedStreamingMode(0); 200 OutputStream out = connection.getOutputStream(); 201 out.write(UPLOAD_DATA); 202 assertEquals(200, connection.getResponseCode()); 203 assertEquals("OK", connection.getResponseMessage()); 204 assertEquals("chunked", TestUtil.getResponseAsString(connection)); 205 connection.disconnect(); 206 } 207 208 @Test 209 @SmallTest 210 @CompareDefaultWithCronet testPostOneMassiveWrite()211 public void testPostOneMassiveWrite() throws Exception { 212 URL url = new URL(NativeTestServer.getEchoBodyURL()); 213 HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 214 connection.setDoOutput(true); 215 connection.setRequestMethod("POST"); 216 connection.setChunkedStreamingMode(0); 217 OutputStream out = connection.getOutputStream(); 218 byte[] largeData = TestUtil.getLargeData(); 219 out.write(largeData); 220 assertEquals(200, connection.getResponseCode()); 221 assertEquals("OK", connection.getResponseMessage()); 222 TestUtil.checkLargeData(TestUtil.getResponseAsString(connection)); 223 connection.disconnect(); 224 } 225 226 @Test 227 @SmallTest 228 @CompareDefaultWithCronet testPostWriteOneByte()229 public void testPostWriteOneByte() throws Exception { 230 URL url = new URL(NativeTestServer.getEchoBodyURL()); 231 HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 232 connection.setDoOutput(true); 233 connection.setRequestMethod("POST"); 234 connection.setChunkedStreamingMode(0); 235 OutputStream out = connection.getOutputStream(); 236 for (int i = 0; i < UPLOAD_DATA.length; i++) { 237 out.write(UPLOAD_DATA[i]); 238 } 239 assertEquals(200, connection.getResponseCode()); 240 assertEquals("OK", connection.getResponseMessage()); 241 assertEquals(UPLOAD_DATA_STRING, TestUtil.getResponseAsString(connection)); 242 connection.disconnect(); 243 } 244 245 @Test 246 @SmallTest 247 @CompareDefaultWithCronet testPostOneMassiveWriteWriteOneByte()248 public void testPostOneMassiveWriteWriteOneByte() throws Exception { 249 URL url = new URL(NativeTestServer.getEchoBodyURL()); 250 HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 251 connection.setDoOutput(true); 252 connection.setRequestMethod("POST"); 253 connection.setChunkedStreamingMode(0); 254 OutputStream out = connection.getOutputStream(); 255 byte[] largeData = TestUtil.getLargeData(); 256 for (int i = 0; i < largeData.length; i++) { 257 out.write(largeData[i]); 258 } 259 assertEquals(200, connection.getResponseCode()); 260 assertEquals("OK", connection.getResponseMessage()); 261 TestUtil.checkLargeData(TestUtil.getResponseAsString(connection)); 262 connection.disconnect(); 263 } 264 265 @Test 266 @SmallTest 267 @CompareDefaultWithCronet testPostWholeNumberOfChunks()268 public void testPostWholeNumberOfChunks() throws Exception { 269 URL url = new URL(NativeTestServer.getEchoBodyURL()); 270 HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 271 connection.setDoOutput(true); 272 connection.setRequestMethod("POST"); 273 int totalSize = UPLOAD_DATA.length * REPEAT_COUNT; 274 int chunkSize = 18000; 275 // Ensure total data size is a multiple of chunk size, so no partial 276 // chunks will be used. 277 assertEquals(0, totalSize % chunkSize); 278 connection.setChunkedStreamingMode(chunkSize); 279 OutputStream out = connection.getOutputStream(); 280 byte[] largeData = TestUtil.getLargeData(); 281 out.write(largeData); 282 assertEquals(200, connection.getResponseCode()); 283 assertEquals("OK", connection.getResponseMessage()); 284 TestUtil.checkLargeData(TestUtil.getResponseAsString(connection)); 285 connection.disconnect(); 286 } 287 288 @Test 289 @SmallTest 290 @OnlyRunCronetHttpURLConnection 291 // Regression testing for crbug.com/618872. testOneMassiveWriteLargerThanInternalBuffer()292 public void testOneMassiveWriteLargerThanInternalBuffer() throws Exception { 293 URL url = new URL(NativeTestServer.getEchoBodyURL()); 294 HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 295 connection.setDoOutput(true); 296 connection.setRequestMethod("POST"); 297 // Use a super big chunk size so that it exceeds the UploadDataProvider 298 // read buffer size. 299 byte[] largeData = TestUtil.getLargeData(); 300 connection.setChunkedStreamingMode(largeData.length); 301 OutputStream out = connection.getOutputStream(); 302 out.write(largeData); 303 assertEquals(200, connection.getResponseCode()); 304 TestUtil.checkLargeData(TestUtil.getResponseAsString(connection)); 305 connection.disconnect(); 306 } 307 } 308