URLConnectionTest.java revision f14bee4dbf9f724a177a85a7b73f7b0bc6df1ec0
1/* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package libcore.java.net; 18 19import java.io.BufferedReader; 20import java.io.ByteArrayOutputStream; 21import java.io.IOException; 22import java.io.InputStream; 23import java.io.InputStreamReader; 24import java.io.OutputStream; 25import java.net.Authenticator; 26import java.net.CacheRequest; 27import java.net.CacheResponse; 28import java.net.HttpRetryException; 29import java.net.HttpURLConnection; 30import java.net.InetAddress; 31import java.net.PasswordAuthentication; 32import java.net.ProtocolException; 33import java.net.Proxy; 34import java.net.ResponseCache; 35import java.net.SecureCacheResponse; 36import java.net.SocketTimeoutException; 37import java.net.URI; 38import java.net.URISyntaxException; 39import java.net.URL; 40import java.net.URLConnection; 41import java.security.Principal; 42import java.security.cert.Certificate; 43import java.security.cert.CertificateException; 44import java.security.cert.X509Certificate; 45import java.util.ArrayList; 46import java.util.Arrays; 47import java.util.Collections; 48import java.util.HashSet; 49import java.util.Iterator; 50import java.util.List; 51import java.util.Map; 52import java.util.Set; 53import java.util.concurrent.atomic.AtomicInteger; 54import java.util.concurrent.atomic.AtomicReference; 55import java.util.zip.GZIPInputStream; 56import java.util.zip.GZIPOutputStream; 57import javax.net.ssl.HostnameVerifier; 58import javax.net.ssl.HttpsURLConnection; 59import javax.net.ssl.SSLContext; 60import javax.net.ssl.SSLException; 61import javax.net.ssl.SSLHandshakeException; 62import javax.net.ssl.SSLSession; 63import javax.net.ssl.SSLSocketFactory; 64import javax.net.ssl.TrustManager; 65import javax.net.ssl.X509TrustManager; 66import libcore.java.security.TestKeyStore; 67import libcore.javax.net.ssl.TestSSLContext; 68import tests.http.DefaultResponseCache; 69import tests.http.MockResponse; 70import tests.http.MockWebServer; 71import tests.http.RecordedRequest; 72import tests.http.SocketPolicy; 73import static tests.http.SocketPolicy.DISCONNECT_AT_END; 74import static tests.http.SocketPolicy.DISCONNECT_AT_START; 75import static tests.http.SocketPolicy.SHUTDOWN_INPUT_AT_END; 76import static tests.http.SocketPolicy.SHUTDOWN_OUTPUT_AT_END; 77import tests.net.StuckServer; 78 79public class URLConnectionTest extends junit.framework.TestCase { 80 81 private static final Authenticator SIMPLE_AUTHENTICATOR = new Authenticator() { 82 protected PasswordAuthentication getPasswordAuthentication() { 83 return new PasswordAuthentication("username", "password".toCharArray()); 84 } 85 }; 86 87 private MockWebServer server = new MockWebServer(); 88 private String hostname; 89 90 @Override protected void setUp() throws Exception { 91 super.setUp(); 92 hostname = InetAddress.getLocalHost().getHostName(); 93 } 94 95 @Override protected void tearDown() throws Exception { 96 ResponseCache.setDefault(null); 97 Authenticator.setDefault(null); 98 System.clearProperty("proxyHost"); 99 System.clearProperty("proxyPort"); 100 System.clearProperty("http.proxyHost"); 101 System.clearProperty("http.proxyPort"); 102 System.clearProperty("https.proxyHost"); 103 System.clearProperty("https.proxyPort"); 104 server.shutdown(); 105 super.tearDown(); 106 } 107 108 public void testRequestHeaders() throws IOException, InterruptedException { 109 server.enqueue(new MockResponse()); 110 server.play(); 111 112 HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); 113 urlConnection.addRequestProperty("D", "e"); 114 urlConnection.addRequestProperty("D", "f"); 115 Map<String, List<String>> requestHeaders = urlConnection.getRequestProperties(); 116 assertEquals(newSet("e", "f"), new HashSet<String>(requestHeaders.get("D"))); 117 try { 118 requestHeaders.put("G", Arrays.asList("h")); 119 fail("Modified an unmodifiable view."); 120 } catch (UnsupportedOperationException expected) { 121 } 122 try { 123 requestHeaders.get("D").add("i"); 124 fail("Modified an unmodifiable view."); 125 } catch (UnsupportedOperationException expected) { 126 } 127 try { 128 urlConnection.setRequestProperty(null, "j"); 129 fail(); 130 } catch (NullPointerException expected) { 131 } 132 try { 133 urlConnection.addRequestProperty(null, "k"); 134 fail(); 135 } catch (NullPointerException expected) { 136 } 137 urlConnection.setRequestProperty("NullValue", null); // should fail silently! 138 urlConnection.addRequestProperty("AnotherNullValue", null); // should fail silently! 139 140 urlConnection.getResponseCode(); 141 RecordedRequest request = server.takeRequest(); 142 assertContains(request.getHeaders(), "D: e"); 143 assertContains(request.getHeaders(), "D: f"); 144 assertContainsNoneMatching(request.getHeaders(), "NullValue.*"); 145 assertContainsNoneMatching(request.getHeaders(), "AnotherNullValue.*"); 146 assertContainsNoneMatching(request.getHeaders(), "G:.*"); 147 assertContainsNoneMatching(request.getHeaders(), "null:.*"); 148 149 try { 150 urlConnection.addRequestProperty("N", "o"); 151 fail("Set header after connect"); 152 } catch (IllegalStateException expected) { 153 } 154 try { 155 urlConnection.setRequestProperty("P", "q"); 156 fail("Set header after connect"); 157 } catch (IllegalStateException expected) { 158 } 159 } 160 161 public void testResponseHeaders() throws IOException, InterruptedException { 162 server.enqueue(new MockResponse() 163 .setStatus("HTTP/1.0 200 Fantastic") 164 .addHeader("A: b") 165 .addHeader("A: c") 166 .setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8)); 167 server.play(); 168 169 HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); 170 assertEquals(200, urlConnection.getResponseCode()); 171 assertEquals("Fantastic", urlConnection.getResponseMessage()); 172 assertEquals("HTTP/1.0 200 Fantastic", urlConnection.getHeaderField(null)); 173 Map<String, List<String>> responseHeaders = urlConnection.getHeaderFields(); 174 assertEquals(Arrays.asList("HTTP/1.0 200 Fantastic"), responseHeaders.get(null)); 175 assertEquals(newSet("b", "c"), new HashSet<String>(responseHeaders.get("A"))); 176 try { 177 responseHeaders.put("N", Arrays.asList("o")); 178 fail("Modified an unmodifiable view."); 179 } catch (UnsupportedOperationException expected) { 180 } 181 try { 182 responseHeaders.get("A").add("d"); 183 fail("Modified an unmodifiable view."); 184 } catch (UnsupportedOperationException expected) { 185 } 186 } 187 188 // Check that if we don't read to the end of a response, the next request on the 189 // recycled connection doesn't get the unread tail of the first request's response. 190 // http://code.google.com/p/android/issues/detail?id=2939 191 public void test_2939() throws Exception { 192 MockResponse response = new MockResponse().setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8); 193 194 server.enqueue(response); 195 server.enqueue(response); 196 server.play(); 197 198 assertContent("ABCDE", server.getUrl("/").openConnection(), 5); 199 assertContent("ABCDE", server.getUrl("/").openConnection(), 5); 200 } 201 202 // Check that we recognize a few basic mime types by extension. 203 // http://code.google.com/p/android/issues/detail?id=10100 204 public void test_10100() throws Exception { 205 assertEquals("image/jpeg", URLConnection.guessContentTypeFromName("someFile.jpg")); 206 assertEquals("application/pdf", URLConnection.guessContentTypeFromName("stuff.pdf")); 207 } 208 209 public void testConnectionsArePooled() throws Exception { 210 MockResponse response = new MockResponse().setBody("ABCDEFGHIJKLMNOPQR"); 211 212 server.enqueue(response); 213 server.enqueue(response); 214 server.enqueue(response); 215 server.play(); 216 217 assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/foo").openConnection()); 218 assertEquals(0, server.takeRequest().getSequenceNumber()); 219 assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/bar?baz=quux").openConnection()); 220 assertEquals(1, server.takeRequest().getSequenceNumber()); 221 assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/z").openConnection()); 222 assertEquals(2, server.takeRequest().getSequenceNumber()); 223 } 224 225 public void testChunkedConnectionsArePooled() throws Exception { 226 MockResponse response = new MockResponse().setChunkedBody("ABCDEFGHIJKLMNOPQR", 5); 227 228 server.enqueue(response); 229 server.enqueue(response); 230 server.enqueue(response); 231 server.play(); 232 233 assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/foo").openConnection()); 234 assertEquals(0, server.takeRequest().getSequenceNumber()); 235 assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/bar?baz=quux").openConnection()); 236 assertEquals(1, server.takeRequest().getSequenceNumber()); 237 assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/z").openConnection()); 238 assertEquals(2, server.takeRequest().getSequenceNumber()); 239 } 240 241 public void testServerClosesSocket() throws Exception { 242 testServerClosesOutput(DISCONNECT_AT_END); 243 } 244 245 public void testServerShutdownInput() throws Exception { 246 testServerClosesOutput(SHUTDOWN_INPUT_AT_END); 247 } 248 249 public void testServerShutdownOutput() throws Exception { 250 testServerClosesOutput(SHUTDOWN_OUTPUT_AT_END); 251 } 252 253 private void testServerClosesOutput(SocketPolicy socketPolicy) throws Exception { 254 server.enqueue(new MockResponse() 255 .setBody("This connection won't pool properly") 256 .setSocketPolicy(socketPolicy)); 257 server.enqueue(new MockResponse() 258 .setBody("This comes after a busted connection")); 259 server.play(); 260 261 assertContent("This connection won't pool properly", server.getUrl("/a").openConnection()); 262 assertEquals(0, server.takeRequest().getSequenceNumber()); 263 assertContent("This comes after a busted connection", server.getUrl("/b").openConnection()); 264 // sequence number 0 means the HTTP socket connection was not reused 265 assertEquals(0, server.takeRequest().getSequenceNumber()); 266 } 267 268 enum WriteKind { BYTE_BY_BYTE, SMALL_BUFFERS, LARGE_BUFFERS } 269 270 public void test_chunkedUpload_byteByByte() throws Exception { 271 doUpload(TransferKind.CHUNKED, WriteKind.BYTE_BY_BYTE); 272 } 273 274 public void test_chunkedUpload_smallBuffers() throws Exception { 275 doUpload(TransferKind.CHUNKED, WriteKind.SMALL_BUFFERS); 276 } 277 278 public void test_chunkedUpload_largeBuffers() throws Exception { 279 doUpload(TransferKind.CHUNKED, WriteKind.LARGE_BUFFERS); 280 } 281 282 public void test_fixedLengthUpload_byteByByte() throws Exception { 283 doUpload(TransferKind.FIXED_LENGTH, WriteKind.BYTE_BY_BYTE); 284 } 285 286 public void test_fixedLengthUpload_smallBuffers() throws Exception { 287 doUpload(TransferKind.FIXED_LENGTH, WriteKind.SMALL_BUFFERS); 288 } 289 290 public void test_fixedLengthUpload_largeBuffers() throws Exception { 291 doUpload(TransferKind.FIXED_LENGTH, WriteKind.LARGE_BUFFERS); 292 } 293 294 private void doUpload(TransferKind uploadKind, WriteKind writeKind) throws Exception { 295 int n = 512*1024; 296 server.setBodyLimit(0); 297 server.enqueue(new MockResponse()); 298 server.play(); 299 300 HttpURLConnection conn = (HttpURLConnection) server.getUrl("/").openConnection(); 301 conn.setDoOutput(true); 302 conn.setRequestMethod("POST"); 303 if (uploadKind == TransferKind.CHUNKED) { 304 conn.setChunkedStreamingMode(-1); 305 } else { 306 conn.setFixedLengthStreamingMode(n); 307 } 308 OutputStream out = conn.getOutputStream(); 309 if (writeKind == WriteKind.BYTE_BY_BYTE) { 310 for (int i = 0; i < n; ++i) { 311 out.write('x'); 312 } 313 } else { 314 byte[] buf = new byte[writeKind == WriteKind.SMALL_BUFFERS ? 256 : 64*1024]; 315 Arrays.fill(buf, (byte) 'x'); 316 for (int i = 0; i < n; i += buf.length) { 317 out.write(buf, 0, Math.min(buf.length, n - i)); 318 } 319 } 320 out.close(); 321 assertEquals(200, conn.getResponseCode()); 322 RecordedRequest request = server.takeRequest(); 323 assertEquals(n, request.getBodySize()); 324 if (uploadKind == TransferKind.CHUNKED) { 325 assertTrue(request.getChunkSizes().size() > 0); 326 } else { 327 assertTrue(request.getChunkSizes().isEmpty()); 328 } 329 } 330 331 /** 332 * Test that response caching is consistent with the RI and the spec. 333 * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.4 334 */ 335 public void test_responseCaching() throws Exception { 336 // Test each documented HTTP/1.1 code, plus the first unused value in each range. 337 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html 338 339 // We can't test 100 because it's not really a response. 340 // assertCached(false, 100); 341 assertCached(false, 101); 342 assertCached(false, 102); 343 assertCached(true, 200); 344 assertCached(false, 201); 345 assertCached(false, 202); 346 assertCached(true, 203); 347 assertCached(false, 204); 348 assertCached(false, 205); 349 assertCached(true, 206); 350 assertCached(false, 207); 351 // (See test_responseCaching_300.) 352 assertCached(true, 301); 353 for (int i = 302; i <= 308; ++i) { 354 assertCached(false, i); 355 } 356 for (int i = 400; i <= 406; ++i) { 357 assertCached(false, i); 358 } 359 // (See test_responseCaching_407.) 360 assertCached(false, 408); 361 assertCached(false, 409); 362 // (See test_responseCaching_410.) 363 for (int i = 411; i <= 418; ++i) { 364 assertCached(false, i); 365 } 366 for (int i = 500; i <= 506; ++i) { 367 assertCached(false, i); 368 } 369 } 370 371 public void test_responseCaching_300() throws Exception { 372 // TODO: fix this for android 373 assertCached(false, 300); 374 } 375 376 /** 377 * Response code 407 should only come from proxy servers. Android's client 378 * throws if it is sent by an origin server. 379 */ 380 public void testOriginServerSends407() throws Exception { 381 server.enqueue(new MockResponse().setResponseCode(407)); 382 server.play(); 383 384 URL url = server.getUrl("/"); 385 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 386 try { 387 conn.getResponseCode(); 388 fail(); 389 } catch (IOException expected) { 390 } 391 } 392 393 public void test_responseCaching_410() throws Exception { 394 // the HTTP spec permits caching 410s, but the RI doesn't. 395 assertCached(false, 410); 396 } 397 398 private void assertCached(boolean shouldPut, int responseCode) throws Exception { 399 server = new MockWebServer(); 400 server.enqueue(new MockResponse() 401 .setResponseCode(responseCode) 402 .setBody("ABCDE") 403 .addHeader("WWW-Authenticate: challenge")); 404 server.play(); 405 406 DefaultResponseCache responseCache = new DefaultResponseCache(); 407 ResponseCache.setDefault(responseCache); 408 URL url = server.getUrl("/"); 409 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 410 assertEquals(responseCode, conn.getResponseCode()); 411 412 // exhaust the content stream 413 try { 414 // TODO: remove special case once testUnauthorizedResponseHandling() is fixed 415 if (responseCode != 401) { 416 readAscii(conn.getInputStream(), Integer.MAX_VALUE); 417 } 418 } catch (IOException ignored) { 419 } 420 421 Set<URI> expectedCachedUris = shouldPut 422 ? Collections.singleton(url.toURI()) 423 : Collections.<URI>emptySet(); 424 assertEquals(Integer.toString(responseCode), 425 expectedCachedUris, responseCache.getContents().keySet()); 426 server.shutdown(); // tearDown() isn't sufficient; this test starts multiple servers 427 } 428 429 /** 430 * Test that we can interrogate the response when the cache is being 431 * populated. http://code.google.com/p/android/issues/detail?id=7787 432 */ 433 public void testResponseCacheCallbackApis() throws Exception { 434 final String body = "ABCDE"; 435 final AtomicInteger cacheCount = new AtomicInteger(); 436 437 server.enqueue(new MockResponse() 438 .setStatus("HTTP/1.1 200 Fantastic") 439 .addHeader("fgh: ijk") 440 .setBody(body)); 441 server.play(); 442 443 ResponseCache.setDefault(new ResponseCache() { 444 @Override public CacheResponse get(URI uri, String requestMethod, 445 Map<String, List<String>> requestHeaders) throws IOException { 446 return null; 447 } 448 @Override public CacheRequest put(URI uri, URLConnection conn) throws IOException { 449 HttpURLConnection httpConnection = (HttpURLConnection) conn; 450 assertEquals("HTTP/1.1 200 Fantastic", httpConnection.getHeaderField(null)); 451 assertEquals(Arrays.asList("HTP/1.1 200 Fantastic"), 452 httpConnection.getHeaderFields().get(null)); 453 assertEquals(200, httpConnection.getResponseCode()); 454 assertEquals("Fantastic", httpConnection.getResponseMessage()); 455 assertEquals(body.length(), httpConnection.getContentLength()); 456 assertEquals("ijk", httpConnection.getHeaderField("fgh")); 457 try { 458 httpConnection.getInputStream(); // the RI doesn't forbid this, but it should 459 fail(); 460 } catch (IOException expected) { 461 } 462 cacheCount.incrementAndGet(); 463 return null; 464 } 465 }); 466 467 URL url = server.getUrl("/"); 468 HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 469 assertEquals(body, readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 470 assertEquals(1, cacheCount.get()); 471 } 472 473 public void testGetResponseCodeNoResponseBody() throws Exception { 474 server.enqueue(new MockResponse() 475 .addHeader("abc: def")); 476 server.play(); 477 478 URL url = server.getUrl("/"); 479 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 480 conn.setDoInput(false); 481 assertEquals("def", conn.getHeaderField("abc")); 482 assertEquals(200, conn.getResponseCode()); 483 try { 484 conn.getInputStream(); 485 fail(); 486 } catch (ProtocolException expected) { 487 } 488 } 489 490 public void testConnectViaHttps() throws IOException, InterruptedException { 491 TestSSLContext testSSLContext = TestSSLContext.create(); 492 493 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 494 server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); 495 server.play(); 496 497 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/foo").openConnection(); 498 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 499 500 assertContent("this response comes via HTTPS", connection); 501 502 RecordedRequest request = server.takeRequest(); 503 assertEquals("GET /foo HTTP/1.1", request.getRequestLine()); 504 } 505 506 public void testConnectViaHttpsReusingConnections() throws IOException, InterruptedException { 507 TestSSLContext testSSLContext = TestSSLContext.create(); 508 509 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 510 server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); 511 server.enqueue(new MockResponse().setBody("another response via HTTPS")); 512 server.play(); 513 514 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 515 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 516 assertContent("this response comes via HTTPS", connection); 517 518 connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 519 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 520 assertContent("another response via HTTPS", connection); 521 522 assertEquals(0, server.takeRequest().getSequenceNumber()); 523 assertEquals(1, server.takeRequest().getSequenceNumber()); 524 } 525 526 public void testConnectViaHttpsReusingConnectionsDifferentFactories() 527 throws IOException, InterruptedException { 528 TestSSLContext testSSLContext = TestSSLContext.create(); 529 530 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 531 server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); 532 server.enqueue(new MockResponse().setBody("another response via HTTPS")); 533 server.play(); 534 535 // install a custom SSL socket factory so the server can be authorized 536 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 537 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 538 assertContent("this response comes via HTTPS", connection); 539 540 connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 541 try { 542 readAscii(connection.getInputStream(), Integer.MAX_VALUE); 543 fail("without an SSL socket factory, the connection should fail"); 544 } catch (SSLException expected) { 545 } 546 } 547 548 public void testConnectViaHttpsWithSSLFallback() throws IOException, InterruptedException { 549 TestSSLContext testSSLContext = TestSSLContext.create(); 550 551 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 552 server.enqueue(new MockResponse().setSocketPolicy(DISCONNECT_AT_START)); 553 server.enqueue(new MockResponse().setBody("this response comes via SSL")); 554 server.play(); 555 556 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/foo").openConnection(); 557 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 558 559 assertContent("this response comes via SSL", connection); 560 561 RecordedRequest request = server.takeRequest(); 562 assertEquals("GET /foo HTTP/1.1", request.getRequestLine()); 563 } 564 565 /** 566 * Verify that we don't retry connections on certificate verification errors. 567 * 568 * http://code.google.com/p/android/issues/detail?id=13178 569 */ 570 public void testConnectViaHttpsToUntrustedServer() throws IOException, InterruptedException { 571 TestSSLContext testSSLContext = TestSSLContext.create(TestKeyStore.getClientCA2(), 572 TestKeyStore.getServer()); 573 574 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 575 server.enqueue(new MockResponse()); // unused 576 server.play(); 577 578 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/foo").openConnection(); 579 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 580 try { 581 connection.getInputStream(); 582 fail(); 583 } catch (SSLHandshakeException expected) { 584 assertTrue(expected.getCause() instanceof CertificateException); 585 } 586 assertEquals(0, server.getRequestCount()); 587 } 588 589 public void testConnectViaProxyUsingProxyArg() throws Exception { 590 testConnectViaProxy(ProxyConfig.CREATE_ARG); 591 } 592 593 public void testConnectViaProxyUsingProxySystemProperty() throws Exception { 594 testConnectViaProxy(ProxyConfig.PROXY_SYSTEM_PROPERTY); 595 } 596 597 public void testConnectViaProxyUsingHttpProxySystemProperty() throws Exception { 598 testConnectViaProxy(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY); 599 } 600 601 private void testConnectViaProxy(ProxyConfig proxyConfig) throws Exception { 602 MockResponse mockResponse = new MockResponse().setBody("this response comes via a proxy"); 603 server.enqueue(mockResponse); 604 server.play(); 605 606 URL url = new URL("http://android.com/foo"); 607 HttpURLConnection connection = proxyConfig.connect(server, url); 608 assertContent("this response comes via a proxy", connection); 609 610 RecordedRequest request = server.takeRequest(); 611 assertEquals("GET http://android.com/foo HTTP/1.1", request.getRequestLine()); 612 assertContains(request.getHeaders(), "Host: android.com"); 613 } 614 615 public void testContentDisagreesWithContentLengthHeader() throws IOException { 616 server.enqueue(new MockResponse() 617 .setBody("abc\r\nYOU SHOULD NOT SEE THIS") 618 .clearHeaders() 619 .addHeader("Content-Length: 3")); 620 server.play(); 621 622 assertContent("abc", server.getUrl("/").openConnection()); 623 } 624 625 public void testContentDisagreesWithChunkedHeader() throws IOException { 626 MockResponse mockResponse = new MockResponse(); 627 mockResponse.setChunkedBody("abc", 3); 628 ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); 629 bytesOut.write(mockResponse.getBody()); 630 bytesOut.write("\r\nYOU SHOULD NOT SEE THIS".getBytes()); 631 mockResponse.setBody(bytesOut.toByteArray()); 632 mockResponse.clearHeaders(); 633 mockResponse.addHeader("Transfer-encoding: chunked"); 634 635 server.enqueue(mockResponse); 636 server.play(); 637 638 assertContent("abc", server.getUrl("/").openConnection()); 639 } 640 641 public void testConnectViaHttpProxyToHttpsUsingProxyArgWithNoProxy() throws Exception { 642 testConnectViaDirectProxyToHttps(ProxyConfig.NO_PROXY); 643 } 644 645 public void testConnectViaHttpProxyToHttpsUsingHttpProxySystemProperty() throws Exception { 646 // https should not use http proxy 647 testConnectViaDirectProxyToHttps(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY); 648 } 649 650 private void testConnectViaDirectProxyToHttps(ProxyConfig proxyConfig) throws Exception { 651 TestSSLContext testSSLContext = TestSSLContext.create(); 652 653 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 654 server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); 655 server.play(); 656 657 URL url = server.getUrl("/foo"); 658 HttpsURLConnection connection = (HttpsURLConnection) proxyConfig.connect(server, url); 659 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 660 661 assertContent("this response comes via HTTPS", connection); 662 663 RecordedRequest request = server.takeRequest(); 664 assertEquals("GET /foo HTTP/1.1", request.getRequestLine()); 665 } 666 667 668 public void testConnectViaHttpProxyToHttpsUsingProxyArg() throws Exception { 669 testConnectViaHttpProxyToHttps(ProxyConfig.CREATE_ARG); 670 } 671 672 /** 673 * We weren't honoring all of the appropriate proxy system properties when 674 * connecting via HTTPS. http://b/3097518 675 */ 676 public void testConnectViaHttpProxyToHttpsUsingProxySystemProperty() throws Exception { 677 testConnectViaHttpProxyToHttps(ProxyConfig.PROXY_SYSTEM_PROPERTY); 678 } 679 680 public void testConnectViaHttpProxyToHttpsUsingHttpsProxySystemProperty() throws Exception { 681 testConnectViaHttpProxyToHttps(ProxyConfig.HTTPS_PROXY_SYSTEM_PROPERTY); 682 } 683 684 /** 685 * We were verifying the wrong hostname when connecting to an HTTPS site 686 * through a proxy. http://b/3097277 687 */ 688 private void testConnectViaHttpProxyToHttps(ProxyConfig proxyConfig) throws Exception { 689 TestSSLContext testSSLContext = TestSSLContext.create(); 690 RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); 691 692 server.useHttps(testSSLContext.serverContext.getSocketFactory(), true); 693 server.enqueue(new MockResponse().clearHeaders()); // for CONNECT 694 server.enqueue(new MockResponse().setBody("this response comes via a secure proxy")); 695 server.play(); 696 697 URL url = new URL("https://android.com/foo"); 698 HttpsURLConnection connection = (HttpsURLConnection) proxyConfig.connect(server, url); 699 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 700 connection.setHostnameVerifier(hostnameVerifier); 701 702 assertContent("this response comes via a secure proxy", connection); 703 704 RecordedRequest connect = server.takeRequest(); 705 assertEquals("Connect line failure on proxy", 706 "CONNECT android.com:443 HTTP/1.1", connect.getRequestLine()); 707 assertContains(connect.getHeaders(), "Host: android.com"); 708 709 RecordedRequest get = server.takeRequest(); 710 assertEquals("GET /foo HTTP/1.1", get.getRequestLine()); 711 assertContains(get.getHeaders(), "Host: android.com"); 712 assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls); 713 } 714 715 /** 716 * Test which headers are sent unencrypted to the HTTP proxy. 717 */ 718 public void testProxyConnectIncludesProxyHeadersOnly() 719 throws IOException, InterruptedException { 720 RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); 721 TestSSLContext testSSLContext = TestSSLContext.create(); 722 723 server.useHttps(testSSLContext.serverContext.getSocketFactory(), true); 724 server.enqueue(new MockResponse().clearHeaders()); // for CONNECT 725 server.enqueue(new MockResponse().setBody("encrypted response from the origin server")); 726 server.play(); 727 728 URL url = new URL("https://android.com/foo"); 729 HttpsURLConnection connection = (HttpsURLConnection) url.openConnection( 730 server.toProxyAddress()); 731 connection.addRequestProperty("Private", "Secret"); 732 connection.addRequestProperty("Proxy-Authorization", "bar"); 733 connection.addRequestProperty("User-Agent", "baz"); 734 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 735 connection.setHostnameVerifier(hostnameVerifier); 736 assertContent("encrypted response from the origin server", connection); 737 738 RecordedRequest connect = server.takeRequest(); 739 assertContainsNoneMatching(connect.getHeaders(), "Private.*"); 740 assertContains(connect.getHeaders(), "Proxy-Authorization: bar"); 741 assertContains(connect.getHeaders(), "User-Agent: baz"); 742 assertContains(connect.getHeaders(), "Host: android.com"); 743 assertContains(connect.getHeaders(), "Proxy-Connection: Keep-Alive"); 744 745 RecordedRequest get = server.takeRequest(); 746 assertContains(get.getHeaders(), "Private: Secret"); 747 assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls); 748 } 749 750 public void testDisconnectedConnection() throws IOException { 751 server.enqueue(new MockResponse().setBody("ABCDEFGHIJKLMNOPQR")); 752 server.play(); 753 754 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 755 InputStream in = connection.getInputStream(); 756 assertEquals('A', (char) in.read()); 757 connection.disconnect(); 758 try { 759 in.read(); 760 fail("Expected a connection closed exception"); 761 } catch (IOException expected) { 762 } 763 } 764 765 public void testResponseCachingAndInputStreamSkipWithFixedLength() throws IOException { 766 testResponseCaching(TransferKind.FIXED_LENGTH); 767 } 768 769 public void testResponseCachingAndInputStreamSkipWithChunkedEncoding() throws IOException { 770 testResponseCaching(TransferKind.CHUNKED); 771 } 772 773 public void testResponseCachingAndInputStreamSkipWithNoLengthHeaders() throws IOException { 774 testResponseCaching(TransferKind.END_OF_STREAM); 775 } 776 777 /** 778 * HttpURLConnection.getInputStream().skip(long) causes ResponseCache corruption 779 * http://code.google.com/p/android/issues/detail?id=8175 780 */ 781 private void testResponseCaching(TransferKind transferKind) throws IOException { 782 MockResponse response = new MockResponse() 783 .setStatus("HTTP/1.1 200 Fantastic"); 784 transferKind.setBody(response, "I love puppies but hate spiders", 1); 785 server.enqueue(response); 786 server.play(); 787 788 DefaultResponseCache cache = new DefaultResponseCache(); 789 ResponseCache.setDefault(cache); 790 791 // Make sure that calling skip() doesn't omit bytes from the cache. 792 HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); 793 InputStream in = urlConnection.getInputStream(); 794 assertEquals("I love ", readAscii(in, "I love ".length())); 795 reliableSkip(in, "puppies but hate ".length()); 796 assertEquals("spiders", readAscii(in, "spiders".length())); 797 assertEquals(-1, in.read()); 798 in.close(); 799 assertEquals(1, cache.getSuccessCount()); 800 assertEquals(0, cache.getAbortCount()); 801 802 urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); // cached! 803 in = urlConnection.getInputStream(); 804 assertEquals("I love puppies but hate spiders", 805 readAscii(in, "I love puppies but hate spiders".length())); 806 assertEquals(200, urlConnection.getResponseCode()); 807 assertEquals("Fantastic", urlConnection.getResponseMessage()); 808 809 assertEquals(-1, in.read()); 810 assertEquals(1, cache.getMissCount()); 811 assertEquals(1, cache.getHitCount()); 812 assertEquals(1, cache.getSuccessCount()); 813 assertEquals(0, cache.getAbortCount()); 814 } 815 816 public void testSecureResponseCaching() throws IOException { 817 TestSSLContext testSSLContext = TestSSLContext.create(); 818 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 819 server.enqueue(new MockResponse().setBody("ABC")); 820 server.play(); 821 822 DefaultResponseCache cache = new DefaultResponseCache(); 823 ResponseCache.setDefault(cache); 824 825 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 826 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 827 assertEquals("ABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 828 829 // OpenJDK 6 fails on this line, complaining that the connection isn't open yet 830 String suite = connection.getCipherSuite(); 831 List<Certificate> localCerts = toListOrNull(connection.getLocalCertificates()); 832 List<Certificate> serverCerts = toListOrNull(connection.getServerCertificates()); 833 Principal peerPrincipal = connection.getPeerPrincipal(); 834 Principal localPrincipal = connection.getLocalPrincipal(); 835 836 connection = (HttpsURLConnection) server.getUrl("/").openConnection(); // cached! 837 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 838 assertEquals("ABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 839 840 assertEquals(1, cache.getMissCount()); 841 assertEquals(1, cache.getHitCount()); 842 843 assertEquals(suite, connection.getCipherSuite()); 844 assertEquals(localCerts, toListOrNull(connection.getLocalCertificates())); 845 assertEquals(serverCerts, toListOrNull(connection.getServerCertificates())); 846 assertEquals(peerPrincipal, connection.getPeerPrincipal()); 847 assertEquals(localPrincipal, connection.getLocalPrincipal()); 848 } 849 850 public void testCacheReturnsInsecureResponseForSecureRequest() throws IOException { 851 TestSSLContext testSSLContext = TestSSLContext.create(); 852 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 853 server.enqueue(new MockResponse().setBody("ABC")); 854 server.enqueue(new MockResponse().setBody("DEF")); 855 server.play(); 856 857 ResponseCache insecureResponseCache = new InsecureResponseCache(); 858 ResponseCache.setDefault(insecureResponseCache); 859 860 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 861 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 862 assertEquals("ABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 863 864 connection = (HttpsURLConnection) server.getUrl("/").openConnection(); // not cached! 865 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 866 assertEquals("DEF", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 867 } 868 869 public void testResponseCachingAndRedirects() throws IOException { 870 server.enqueue(new MockResponse() 871 .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM) 872 .addHeader("Location: /foo")); 873 server.enqueue(new MockResponse().setBody("ABC")); 874 server.enqueue(new MockResponse().setBody("DEF")); 875 server.play(); 876 877 DefaultResponseCache cache = new DefaultResponseCache(); 878 ResponseCache.setDefault(cache); 879 880 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 881 assertEquals("ABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 882 883 connection = (HttpURLConnection) server.getUrl("/").openConnection(); // cached! 884 assertEquals("ABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 885 886 assertEquals(2, cache.getMissCount()); // 1 redirect + 1 final response = 2 887 assertEquals(2, cache.getHitCount()); 888 } 889 890 public void testSecureResponseCachingAndRedirects() throws IOException { 891 TestSSLContext testSSLContext = TestSSLContext.create(); 892 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 893 server.enqueue(new MockResponse() 894 .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM) 895 .addHeader("Location: /foo")); 896 server.enqueue(new MockResponse().setBody("ABC")); 897 server.enqueue(new MockResponse().setBody("DEF")); 898 server.play(); 899 900 DefaultResponseCache cache = new DefaultResponseCache(); 901 ResponseCache.setDefault(cache); 902 903 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 904 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 905 assertEquals("ABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 906 907 connection = (HttpsURLConnection) server.getUrl("/").openConnection(); // cached! 908 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 909 assertEquals("ABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 910 911 assertEquals(2, cache.getMissCount()); // 1 redirect + 1 final response = 2 912 assertEquals(2, cache.getHitCount()); 913 } 914 915 public void testResponseCacheRequestHeaders() throws IOException, URISyntaxException { 916 server.enqueue(new MockResponse().setBody("ABC")); 917 server.play(); 918 919 final AtomicReference<Map<String, List<String>>> requestHeadersRef 920 = new AtomicReference<Map<String, List<String>>>(); 921 ResponseCache.setDefault(new ResponseCache() { 922 @Override public CacheResponse get(URI uri, String requestMethod, 923 Map<String, List<String>> requestHeaders) throws IOException { 924 requestHeadersRef.set(requestHeaders); 925 return null; 926 } 927 @Override public CacheRequest put(URI uri, URLConnection conn) throws IOException { 928 return null; 929 } 930 }); 931 932 URL url = server.getUrl("/"); 933 URLConnection urlConnection = url.openConnection(); 934 urlConnection.addRequestProperty("A", "android"); 935 readAscii(urlConnection.getInputStream(), Integer.MAX_VALUE); 936 assertEquals(Arrays.asList("android"), requestHeadersRef.get().get("A")); 937 } 938 939 private void reliableSkip(InputStream in, int length) throws IOException { 940 while (length > 0) { 941 length -= in.skip(length); 942 } 943 } 944 945 /** 946 * Reads {@code count} characters from the stream. If the stream is 947 * exhausted before {@code count} characters can be read, the remaining 948 * characters are returned and the stream is closed. 949 */ 950 private String readAscii(InputStream in, int count) throws IOException { 951 StringBuilder result = new StringBuilder(); 952 for (int i = 0; i < count; i++) { 953 int value = in.read(); 954 if (value == -1) { 955 in.close(); 956 break; 957 } 958 result.append((char) value); 959 } 960 return result.toString(); 961 } 962 963 public void testServerDisconnectsPrematurelyWithContentLengthHeader() throws IOException { 964 testServerPrematureDisconnect(TransferKind.FIXED_LENGTH); 965 } 966 967 public void testServerDisconnectsPrematurelyWithChunkedEncoding() throws IOException { 968 testServerPrematureDisconnect(TransferKind.CHUNKED); 969 } 970 971 public void testServerDisconnectsPrematurelyWithNoLengthHeaders() throws IOException { 972 /* 973 * Intentionally empty. This case doesn't make sense because there's no 974 * such thing as a premature disconnect when the disconnect itself 975 * indicates the end of the data stream. 976 */ 977 } 978 979 private void testServerPrematureDisconnect(TransferKind transferKind) throws IOException { 980 MockResponse response = new MockResponse(); 981 transferKind.setBody(response, "ABCDE\nFGHIJKLMNOPQRSTUVWXYZ", 16); 982 server.enqueue(truncateViolently(response, 16)); 983 server.enqueue(new MockResponse().setBody("Request #2")); 984 server.play(); 985 986 DefaultResponseCache cache = new DefaultResponseCache(); 987 ResponseCache.setDefault(cache); 988 989 BufferedReader reader = new BufferedReader(new InputStreamReader( 990 server.getUrl("/").openConnection().getInputStream())); 991 assertEquals("ABCDE", reader.readLine()); 992 try { 993 reader.readLine(); 994 fail("This implementation silently ignored a truncated HTTP body."); 995 } catch (IOException expected) { 996 } 997 998 assertEquals(1, cache.getAbortCount()); 999 assertEquals(0, cache.getSuccessCount()); 1000 assertContent("Request #2", server.getUrl("/").openConnection()); 1001 assertEquals(1, cache.getAbortCount()); 1002 assertEquals(1, cache.getSuccessCount()); 1003 } 1004 1005 public void testClientPrematureDisconnectWithContentLengthHeader() throws IOException { 1006 testClientPrematureDisconnect(TransferKind.FIXED_LENGTH); 1007 } 1008 1009 public void testClientPrematureDisconnectWithChunkedEncoding() throws IOException { 1010 testClientPrematureDisconnect(TransferKind.CHUNKED); 1011 } 1012 1013 public void testClientPrematureDisconnectWithNoLengthHeaders() throws IOException { 1014 testClientPrematureDisconnect(TransferKind.END_OF_STREAM); 1015 } 1016 1017 private void testClientPrematureDisconnect(TransferKind transferKind) throws IOException { 1018 MockResponse response = new MockResponse(); 1019 transferKind.setBody(response, "ABCDE\nFGHIJKLMNOPQRSTUVWXYZ", 1024); 1020 server.enqueue(response); 1021 server.enqueue(new MockResponse().setBody("Request #2")); 1022 server.play(); 1023 1024 DefaultResponseCache cache = new DefaultResponseCache(); 1025 ResponseCache.setDefault(cache); 1026 1027 InputStream in = server.getUrl("/").openConnection().getInputStream(); 1028 assertEquals("ABCDE", readAscii(in, 5)); 1029 in.close(); 1030 try { 1031 in.read(); 1032 fail("Expected an IOException because the stream is closed."); 1033 } catch (IOException expected) { 1034 } 1035 1036 assertEquals(1, cache.getAbortCount()); 1037 assertEquals(0, cache.getSuccessCount()); 1038 assertContent("Request #2", server.getUrl("/").openConnection()); 1039 assertEquals(1, cache.getAbortCount()); 1040 assertEquals(1, cache.getSuccessCount()); 1041 } 1042 1043 /** 1044 * Shortens the body of {@code response} but not the corresponding headers. 1045 * Only useful to test how clients respond to the premature conclusion of 1046 * the HTTP body. 1047 */ 1048 private MockResponse truncateViolently(MockResponse response, int numBytesToKeep) { 1049 response.setSocketPolicy(DISCONNECT_AT_END); 1050 List<String> headers = new ArrayList<String>(response.getHeaders()); 1051 response.setBody(Arrays.copyOfRange(response.getBody(), 0, numBytesToKeep)); 1052 response.getHeaders().clear(); 1053 response.getHeaders().addAll(headers); 1054 return response; 1055 } 1056 1057 public void testMarkAndResetWithContentLengthHeader() throws IOException { 1058 testMarkAndReset(TransferKind.FIXED_LENGTH); 1059 } 1060 1061 public void testMarkAndResetWithChunkedEncoding() throws IOException { 1062 testMarkAndReset(TransferKind.CHUNKED); 1063 } 1064 1065 public void testMarkAndResetWithNoLengthHeaders() throws IOException { 1066 testMarkAndReset(TransferKind.END_OF_STREAM); 1067 } 1068 1069 public void testMarkAndReset(TransferKind transferKind) throws IOException { 1070 MockResponse response = new MockResponse(); 1071 transferKind.setBody(response, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1024); 1072 server.enqueue(response); 1073 server.play(); 1074 1075 DefaultResponseCache cache = new DefaultResponseCache(); 1076 ResponseCache.setDefault(cache); 1077 1078 InputStream in = server.getUrl("/").openConnection().getInputStream(); 1079 assertFalse("This implementation claims to support mark().", in.markSupported()); 1080 in.mark(5); 1081 assertEquals("ABCDE", readAscii(in, 5)); 1082 try { 1083 in.reset(); 1084 fail(); 1085 } catch (IOException expected) { 1086 } 1087 assertEquals("FGHIJKLMNOPQRSTUVWXYZ", readAscii(in, Integer.MAX_VALUE)); 1088 1089 assertContent("ABCDEFGHIJKLMNOPQRSTUVWXYZ", server.getUrl("/").openConnection()); 1090 assertEquals(1, cache.getSuccessCount()); 1091 assertEquals(1, cache.getHitCount()); 1092 } 1093 1094 /** 1095 * We've had a bug where we forget the HTTP response when we see response 1096 * code 401. This causes a new HTTP request to be issued for every call into 1097 * the URLConnection. 1098 */ 1099 public void testUnauthorizedResponseHandling() throws IOException { 1100 MockResponse response = new MockResponse() 1101 .addHeader("WWW-Authenticate: challenge") 1102 .setResponseCode(401) // UNAUTHORIZED 1103 .setBody("Unauthorized"); 1104 server.enqueue(response); 1105 server.enqueue(response); 1106 server.enqueue(response); 1107 server.play(); 1108 1109 URL url = server.getUrl("/"); 1110 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 1111 1112 assertEquals(401, conn.getResponseCode()); 1113 assertEquals(401, conn.getResponseCode()); 1114 assertEquals(401, conn.getResponseCode()); 1115 assertEquals(1, server.getRequestCount()); 1116 } 1117 1118 public void testNonHexChunkSize() throws IOException { 1119 server.enqueue(new MockResponse() 1120 .setBody("5\r\nABCDE\r\nG\r\nFGHIJKLMNOPQRSTU\r\n0\r\n\r\n") 1121 .clearHeaders() 1122 .addHeader("Transfer-encoding: chunked")); 1123 server.play(); 1124 1125 URLConnection connection = server.getUrl("/").openConnection(); 1126 try { 1127 readAscii(connection.getInputStream(), Integer.MAX_VALUE); 1128 fail(); 1129 } catch (IOException e) { 1130 } 1131 } 1132 1133 public void testMissingChunkBody() throws IOException { 1134 server.enqueue(new MockResponse() 1135 .setBody("5") 1136 .clearHeaders() 1137 .addHeader("Transfer-encoding: chunked") 1138 .setSocketPolicy(DISCONNECT_AT_END)); 1139 server.play(); 1140 1141 URLConnection connection = server.getUrl("/").openConnection(); 1142 try { 1143 readAscii(connection.getInputStream(), Integer.MAX_VALUE); 1144 fail(); 1145 } catch (IOException e) { 1146 } 1147 } 1148 1149 /** 1150 * This test checks whether connections are gzipped by default. This 1151 * behavior in not required by the API, so a failure of this test does not 1152 * imply a bug in the implementation. 1153 */ 1154 public void testGzipEncodingEnabledByDefault() throws IOException, InterruptedException { 1155 server.enqueue(new MockResponse() 1156 .setBody(gzip("ABCABCABC".getBytes("UTF-8"))) 1157 .addHeader("Content-Encoding: gzip")); 1158 server.play(); 1159 1160 URLConnection connection = server.getUrl("/").openConnection(); 1161 assertEquals("ABCABCABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1162 assertNull(connection.getContentEncoding()); 1163 1164 RecordedRequest request = server.takeRequest(); 1165 assertContains(request.getHeaders(), "Accept-Encoding: gzip"); 1166 } 1167 1168 public void testClientConfiguredGzipContentEncoding() throws Exception { 1169 server.enqueue(new MockResponse() 1170 .setBody(gzip("ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes("UTF-8"))) 1171 .addHeader("Content-Encoding: gzip")); 1172 server.play(); 1173 1174 URLConnection connection = server.getUrl("/").openConnection(); 1175 connection.addRequestProperty("Accept-Encoding", "gzip"); 1176 InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream()); 1177 assertEquals("ABCDEFGHIJKLMNOPQRSTUVWXYZ", readAscii(gunzippedIn, Integer.MAX_VALUE)); 1178 1179 RecordedRequest request = server.takeRequest(); 1180 assertContains(request.getHeaders(), "Accept-Encoding: gzip"); 1181 } 1182 1183 public void testGzipAndConnectionReuseWithFixedLength() throws Exception { 1184 testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.FIXED_LENGTH); 1185 } 1186 1187 public void testGzipAndConnectionReuseWithChunkedEncoding() throws Exception { 1188 testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.CHUNKED); 1189 } 1190 1191 public void testClientConfiguredCustomContentEncoding() throws Exception { 1192 server.enqueue(new MockResponse() 1193 .setBody("ABCDE") 1194 .addHeader("Content-Encoding: custom")); 1195 server.play(); 1196 1197 URLConnection connection = server.getUrl("/").openConnection(); 1198 connection.addRequestProperty("Accept-Encoding", "custom"); 1199 assertEquals("ABCDE", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1200 1201 RecordedRequest request = server.takeRequest(); 1202 assertContains(request.getHeaders(), "Accept-Encoding: custom"); 1203 } 1204 1205 /** 1206 * Test a bug where gzip input streams weren't exhausting the input stream, 1207 * which corrupted the request that followed. 1208 * http://code.google.com/p/android/issues/detail?id=7059 1209 */ 1210 private void testClientConfiguredGzipContentEncodingAndConnectionReuse( 1211 TransferKind transferKind) throws Exception { 1212 MockResponse responseOne = new MockResponse(); 1213 responseOne.addHeader("Content-Encoding: gzip"); 1214 transferKind.setBody(responseOne, gzip("one (gzipped)".getBytes("UTF-8")), 5); 1215 server.enqueue(responseOne); 1216 MockResponse responseTwo = new MockResponse(); 1217 transferKind.setBody(responseTwo, "two (identity)", 5); 1218 server.enqueue(responseTwo); 1219 server.play(); 1220 1221 URLConnection connection = server.getUrl("/").openConnection(); 1222 connection.addRequestProperty("Accept-Encoding", "gzip"); 1223 InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream()); 1224 assertEquals("one (gzipped)", readAscii(gunzippedIn, Integer.MAX_VALUE)); 1225 assertEquals(0, server.takeRequest().getSequenceNumber()); 1226 1227 connection = server.getUrl("/").openConnection(); 1228 assertEquals("two (identity)", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1229 assertEquals(1, server.takeRequest().getSequenceNumber()); 1230 } 1231 1232 /** 1233 * Obnoxiously test that the chunk sizes transmitted exactly equal the 1234 * requested data+chunk header size. Although setChunkedStreamingMode() 1235 * isn't specific about whether the size applies to the data or the 1236 * complete chunk, the RI interprets it as a complete chunk. 1237 */ 1238 public void testSetChunkedStreamingMode() throws IOException, InterruptedException { 1239 server.enqueue(new MockResponse()); 1240 server.play(); 1241 1242 HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); 1243 urlConnection.setChunkedStreamingMode(8); 1244 urlConnection.setDoOutput(true); 1245 OutputStream outputStream = urlConnection.getOutputStream(); 1246 outputStream.write("ABCDEFGHIJKLMNOPQ".getBytes("US-ASCII")); 1247 assertEquals(200, urlConnection.getResponseCode()); 1248 1249 RecordedRequest request = server.takeRequest(); 1250 assertEquals("ABCDEFGHIJKLMNOPQ", new String(request.getBody(), "US-ASCII")); 1251 assertEquals(Arrays.asList(3, 3, 3, 3, 3, 2), request.getChunkSizes()); 1252 } 1253 1254 public void testAuthenticateWithFixedLengthStreaming() throws Exception { 1255 testAuthenticateWithStreamingPost(StreamingMode.FIXED_LENGTH); 1256 } 1257 1258 public void testAuthenticateWithChunkedStreaming() throws Exception { 1259 testAuthenticateWithStreamingPost(StreamingMode.CHUNKED); 1260 } 1261 1262 private void testAuthenticateWithStreamingPost(StreamingMode streamingMode) throws Exception { 1263 MockResponse pleaseAuthenticate = new MockResponse() 1264 .setResponseCode(401) 1265 .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") 1266 .setBody("Please authenticate."); 1267 server.enqueue(pleaseAuthenticate); 1268 server.play(); 1269 1270 Authenticator.setDefault(SIMPLE_AUTHENTICATOR); 1271 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1272 connection.setDoOutput(true); 1273 byte[] requestBody = { 'A', 'B', 'C', 'D' }; 1274 if (streamingMode == StreamingMode.FIXED_LENGTH) { 1275 connection.setFixedLengthStreamingMode(requestBody.length); 1276 } else if (streamingMode == StreamingMode.CHUNKED) { 1277 connection.setChunkedStreamingMode(0); 1278 } 1279 OutputStream outputStream = connection.getOutputStream(); 1280 outputStream.write(requestBody); 1281 outputStream.close(); 1282 try { 1283 connection.getInputStream(); 1284 fail(); 1285 } catch (HttpRetryException expected) { 1286 } 1287 1288 // no authorization header for the request... 1289 RecordedRequest request = server.takeRequest(); 1290 assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*"); 1291 assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody())); 1292 } 1293 1294 public void testSecureFixedLengthStreaming() throws Exception { 1295 testSecureStreamingPost(StreamingMode.FIXED_LENGTH); 1296 } 1297 1298 public void testSecureChunkedStreaming() throws Exception { 1299 testSecureStreamingPost(StreamingMode.CHUNKED); 1300 } 1301 1302 /** 1303 * Users have reported problems using HTTPS with streaming request bodies. 1304 * http://code.google.com/p/android/issues/detail?id=12860 1305 */ 1306 private void testSecureStreamingPost(StreamingMode streamingMode) throws Exception { 1307 TestSSLContext testSSLContext = TestSSLContext.create(); 1308 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 1309 server.enqueue(new MockResponse().setBody("Success!")); 1310 server.play(); 1311 1312 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 1313 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 1314 connection.setDoOutput(true); 1315 byte[] requestBody = { 'A', 'B', 'C', 'D' }; 1316 if (streamingMode == StreamingMode.FIXED_LENGTH) { 1317 connection.setFixedLengthStreamingMode(requestBody.length); 1318 } else if (streamingMode == StreamingMode.CHUNKED) { 1319 connection.setChunkedStreamingMode(0); 1320 } 1321 OutputStream outputStream = connection.getOutputStream(); 1322 outputStream.write(requestBody); 1323 outputStream.close(); 1324 assertEquals("Success!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1325 1326 RecordedRequest request = server.takeRequest(); 1327 assertEquals("POST / HTTP/1.1", request.getRequestLine()); 1328 if (streamingMode == StreamingMode.FIXED_LENGTH) { 1329 assertEquals(Collections.<Integer>emptyList(), request.getChunkSizes()); 1330 } else if (streamingMode == StreamingMode.CHUNKED) { 1331 assertEquals(Arrays.asList(4), request.getChunkSizes()); 1332 } 1333 assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody())); 1334 } 1335 1336 enum StreamingMode { 1337 FIXED_LENGTH, CHUNKED 1338 } 1339 1340 public void testAuthenticateWithPost() throws Exception { 1341 MockResponse pleaseAuthenticate = new MockResponse() 1342 .setResponseCode(401) 1343 .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") 1344 .setBody("Please authenticate."); 1345 // fail auth three times... 1346 server.enqueue(pleaseAuthenticate); 1347 server.enqueue(pleaseAuthenticate); 1348 server.enqueue(pleaseAuthenticate); 1349 // ...then succeed the fourth time 1350 server.enqueue(new MockResponse().setBody("Successful auth!")); 1351 server.play(); 1352 1353 Authenticator.setDefault(SIMPLE_AUTHENTICATOR); 1354 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1355 connection.setDoOutput(true); 1356 byte[] requestBody = { 'A', 'B', 'C', 'D' }; 1357 OutputStream outputStream = connection.getOutputStream(); 1358 outputStream.write(requestBody); 1359 outputStream.close(); 1360 assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1361 1362 // no authorization header for the first request... 1363 RecordedRequest request = server.takeRequest(); 1364 assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*"); 1365 1366 // ...but the three requests that follow include an authorization header 1367 for (int i = 0; i < 3; i++) { 1368 request = server.takeRequest(); 1369 assertEquals("POST / HTTP/1.1", request.getRequestLine()); 1370 assertContains(request.getHeaders(), "Authorization: Basic " 1371 + "dXNlcm5hbWU6cGFzc3dvcmQ="); // "dXNl..." == base64("username:password") 1372 assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody())); 1373 } 1374 } 1375 1376 public void testAuthenticateWithGet() throws Exception { 1377 MockResponse pleaseAuthenticate = new MockResponse() 1378 .setResponseCode(401) 1379 .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") 1380 .setBody("Please authenticate."); 1381 // fail auth three times... 1382 server.enqueue(pleaseAuthenticate); 1383 server.enqueue(pleaseAuthenticate); 1384 server.enqueue(pleaseAuthenticate); 1385 // ...then succeed the fourth time 1386 server.enqueue(new MockResponse().setBody("Successful auth!")); 1387 server.play(); 1388 1389 Authenticator.setDefault(SIMPLE_AUTHENTICATOR); 1390 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1391 assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1392 1393 // no authorization header for the first request... 1394 RecordedRequest request = server.takeRequest(); 1395 assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*"); 1396 1397 // ...but the three requests that follow requests include an authorization header 1398 for (int i = 0; i < 3; i++) { 1399 request = server.takeRequest(); 1400 assertEquals("GET / HTTP/1.1", request.getRequestLine()); 1401 assertContains(request.getHeaders(), "Authorization: Basic " 1402 + "dXNlcm5hbWU6cGFzc3dvcmQ="); // "dXNl..." == base64("username:password") 1403 } 1404 } 1405 1406 public void testRedirectedWithChunkedEncoding() throws Exception { 1407 testRedirected(TransferKind.CHUNKED, true); 1408 } 1409 1410 public void testRedirectedWithContentLengthHeader() throws Exception { 1411 testRedirected(TransferKind.FIXED_LENGTH, true); 1412 } 1413 1414 public void testRedirectedWithNoLengthHeaders() throws Exception { 1415 testRedirected(TransferKind.END_OF_STREAM, false); 1416 } 1417 1418 private void testRedirected(TransferKind transferKind, boolean reuse) throws Exception { 1419 MockResponse response = new MockResponse() 1420 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1421 .addHeader("Location: /foo"); 1422 transferKind.setBody(response, "This page has moved!", 10); 1423 server.enqueue(response); 1424 server.enqueue(new MockResponse().setBody("This is the new location!")); 1425 server.play(); 1426 1427 URLConnection connection = server.getUrl("/").openConnection(); 1428 assertEquals("This is the new location!", 1429 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1430 1431 RecordedRequest first = server.takeRequest(); 1432 assertEquals("GET / HTTP/1.1", first.getRequestLine()); 1433 RecordedRequest retry = server.takeRequest(); 1434 assertEquals("GET /foo HTTP/1.1", retry.getRequestLine()); 1435 if (reuse) { 1436 assertEquals("Expected connection reuse", 1, retry.getSequenceNumber()); 1437 } 1438 } 1439 1440 public void testRedirectedOnHttps() throws IOException, InterruptedException { 1441 TestSSLContext testSSLContext = TestSSLContext.create(); 1442 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 1443 server.enqueue(new MockResponse() 1444 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1445 .addHeader("Location: /foo") 1446 .setBody("This page has moved!")); 1447 server.enqueue(new MockResponse().setBody("This is the new location!")); 1448 server.play(); 1449 1450 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 1451 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 1452 assertEquals("This is the new location!", 1453 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1454 1455 RecordedRequest first = server.takeRequest(); 1456 assertEquals("GET / HTTP/1.1", first.getRequestLine()); 1457 RecordedRequest retry = server.takeRequest(); 1458 assertEquals("GET /foo HTTP/1.1", retry.getRequestLine()); 1459 assertEquals("Expected connection reuse", 1, retry.getSequenceNumber()); 1460 } 1461 1462 public void testNotRedirectedFromHttpsToHttp() throws IOException, InterruptedException { 1463 TestSSLContext testSSLContext = TestSSLContext.create(); 1464 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 1465 server.enqueue(new MockResponse() 1466 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1467 .addHeader("Location: http://anyhost/foo") 1468 .setBody("This page has moved!")); 1469 server.play(); 1470 1471 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 1472 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 1473 assertEquals("This page has moved!", 1474 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1475 } 1476 1477 public void testNotRedirectedFromHttpToHttps() throws IOException, InterruptedException { 1478 server.enqueue(new MockResponse() 1479 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1480 .addHeader("Location: https://anyhost/foo") 1481 .setBody("This page has moved!")); 1482 server.play(); 1483 1484 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1485 assertEquals("This page has moved!", 1486 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1487 } 1488 1489 public void testRedirectToAnotherOriginServer() throws Exception { 1490 MockWebServer server2 = new MockWebServer(); 1491 server2.enqueue(new MockResponse().setBody("This is the 2nd server!")); 1492 server2.play(); 1493 1494 server.enqueue(new MockResponse() 1495 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1496 .addHeader("Location: " + server2.getUrl("/").toString()) 1497 .setBody("This page has moved!")); 1498 server.enqueue(new MockResponse().setBody("This is the first server again!")); 1499 server.play(); 1500 1501 URLConnection connection = server.getUrl("/").openConnection(); 1502 assertEquals("This is the 2nd server!", 1503 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1504 assertEquals(server2.getUrl("/"), connection.getURL()); 1505 1506 // make sure the first server was careful to recycle the connection 1507 assertEquals("This is the first server again!", 1508 readAscii(server.getUrl("/").openStream(), Integer.MAX_VALUE)); 1509 1510 RecordedRequest first = server.takeRequest(); 1511 assertContains(first.getHeaders(), "Host: " + hostname + ":" + server.getPort()); 1512 RecordedRequest second = server2.takeRequest(); 1513 assertContains(second.getHeaders(), "Host: " + hostname + ":" + server2.getPort()); 1514 RecordedRequest third = server.takeRequest(); 1515 assertEquals("Expected connection reuse", 1, third.getSequenceNumber()); 1516 1517 server2.shutdown(); 1518 } 1519 1520 public void testHttpsWithCustomTrustManager() throws Exception { 1521 RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); 1522 RecordingTrustManager trustManager = new RecordingTrustManager(); 1523 SSLContext sc = SSLContext.getInstance("TLS"); 1524 sc.init(null, new TrustManager[] { trustManager }, new java.security.SecureRandom()); 1525 1526 HostnameVerifier defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier(); 1527 HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier); 1528 SSLSocketFactory defaultSSLSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory(); 1529 HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); 1530 try { 1531 TestSSLContext testSSLContext = TestSSLContext.create(); 1532 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 1533 server.enqueue(new MockResponse().setBody("ABC")); 1534 server.enqueue(new MockResponse().setBody("DEF")); 1535 server.enqueue(new MockResponse().setBody("GHI")); 1536 server.play(); 1537 1538 URL url = server.getUrl("/"); 1539 assertEquals("ABC", readAscii(url.openStream(), Integer.MAX_VALUE)); 1540 assertEquals("DEF", readAscii(url.openStream(), Integer.MAX_VALUE)); 1541 assertEquals("GHI", readAscii(url.openStream(), Integer.MAX_VALUE)); 1542 1543 assertEquals(Arrays.asList("verify " + hostname), hostnameVerifier.calls); 1544 assertEquals(Arrays.asList("checkServerTrusted [" 1545 + "CN=" + hostname + " 1, " 1546 + "CN=Test Intermediate Certificate Authority 1, " 1547 + "CN=Test Root Certificate Authority 1" 1548 + "] RSA"), 1549 trustManager.calls); 1550 } finally { 1551 HttpsURLConnection.setDefaultHostnameVerifier(defaultHostnameVerifier); 1552 HttpsURLConnection.setDefaultSSLSocketFactory(defaultSSLSocketFactory); 1553 } 1554 } 1555 1556 public void testConnectTimeouts() throws IOException { 1557 StuckServer ss = new StuckServer(); 1558 int serverPort = ss.getLocalPort(); 1559 URLConnection urlConnection = new URL("http://localhost:" + serverPort).openConnection(); 1560 urlConnection.setConnectTimeout(1000); 1561 try { 1562 urlConnection.getInputStream(); 1563 fail(); 1564 } catch (SocketTimeoutException expected) { 1565 } finally { 1566 ss.close(); 1567 } 1568 } 1569 1570 public void testReadTimeouts() throws IOException { 1571 /* 1572 * This relies on the fact that MockWebServer doesn't close the 1573 * connection after a response has been sent. This causes the client to 1574 * try to read more bytes than are sent, which results in a timeout. 1575 */ 1576 MockResponse timeout = new MockResponse() 1577 .setBody("ABC") 1578 .clearHeaders() 1579 .addHeader("Content-Length: 4"); 1580 server.enqueue(timeout); 1581 server.play(); 1582 1583 URLConnection urlConnection = server.getUrl("/").openConnection(); 1584 urlConnection.setReadTimeout(1000); 1585 InputStream in = urlConnection.getInputStream(); 1586 assertEquals('A', in.read()); 1587 assertEquals('B', in.read()); 1588 assertEquals('C', in.read()); 1589 try { 1590 in.read(); // if Content-Length was accurate, this would return -1 immediately 1591 fail(); 1592 } catch (SocketTimeoutException expected) { 1593 } 1594 } 1595 1596 public void testSetChunkedEncodingAsRequestProperty() throws IOException, InterruptedException { 1597 server.enqueue(new MockResponse()); 1598 server.play(); 1599 1600 HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); 1601 urlConnection.setRequestProperty("Transfer-encoding", "chunked"); 1602 urlConnection.setDoOutput(true); 1603 urlConnection.getOutputStream().write("ABC".getBytes("UTF-8")); 1604 assertEquals(200, urlConnection.getResponseCode()); 1605 1606 RecordedRequest request = server.takeRequest(); 1607 assertEquals("ABC", new String(request.getBody(), "UTF-8")); 1608 } 1609 1610 public void testConnectionCloseInRequest() throws IOException, InterruptedException { 1611 server.enqueue(new MockResponse()); // server doesn't honor the connection: close header! 1612 server.enqueue(new MockResponse()); 1613 server.play(); 1614 1615 HttpURLConnection a = (HttpURLConnection) server.getUrl("/").openConnection(); 1616 a.setRequestProperty("Connection", "close"); 1617 assertEquals(200, a.getResponseCode()); 1618 1619 HttpURLConnection b = (HttpURLConnection) server.getUrl("/").openConnection(); 1620 assertEquals(200, b.getResponseCode()); 1621 1622 assertEquals(0, server.takeRequest().getSequenceNumber()); 1623 assertEquals("When connection: close is used, each request should get its own connection", 1624 0, server.takeRequest().getSequenceNumber()); 1625 } 1626 1627 public void testConnectionCloseInResponse() throws IOException, InterruptedException { 1628 server.enqueue(new MockResponse().addHeader("Connection: close")); 1629 server.enqueue(new MockResponse()); 1630 server.play(); 1631 1632 HttpURLConnection a = (HttpURLConnection) server.getUrl("/").openConnection(); 1633 assertEquals(200, a.getResponseCode()); 1634 1635 HttpURLConnection b = (HttpURLConnection) server.getUrl("/").openConnection(); 1636 assertEquals(200, b.getResponseCode()); 1637 1638 assertEquals(0, server.takeRequest().getSequenceNumber()); 1639 assertEquals("When connection: close is used, each request should get its own connection", 1640 0, server.takeRequest().getSequenceNumber()); 1641 } 1642 1643 public void testConnectionCloseWithRedirect() throws IOException, InterruptedException { 1644 MockResponse response = new MockResponse() 1645 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1646 .addHeader("Location: /foo") 1647 .addHeader("Connection: close"); 1648 server.enqueue(response); 1649 server.enqueue(new MockResponse().setBody("This is the new location!")); 1650 server.play(); 1651 1652 URLConnection connection = server.getUrl("/").openConnection(); 1653 assertEquals("This is the new location!", 1654 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1655 1656 assertEquals(0, server.takeRequest().getSequenceNumber()); 1657 assertEquals("When connection: close is used, each request should get its own connection", 1658 0, server.takeRequest().getSequenceNumber()); 1659 } 1660 1661 public void testResponseCodeDisagreesWithHeaders() throws IOException, InterruptedException { 1662 server.enqueue(new MockResponse() 1663 .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT) 1664 .setBody("This body is not allowed!")); 1665 server.play(); 1666 1667 URLConnection connection = server.getUrl("/").openConnection(); 1668 assertEquals("This body is not allowed!", 1669 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1670 } 1671 1672 public void testSingleByteReadIsSigned() throws IOException { 1673 server.enqueue(new MockResponse().setBody(new byte[] { -2, -1 })); 1674 server.play(); 1675 1676 URLConnection connection = server.getUrl("/").openConnection(); 1677 InputStream in = connection.getInputStream(); 1678 assertEquals(254, in.read()); 1679 assertEquals(255, in.read()); 1680 assertEquals(-1, in.read()); 1681 } 1682 1683 public void testFlushAfterStreamTransmittedWithChunkedEncoding() throws IOException { 1684 testFlushAfterStreamTransmitted(TransferKind.CHUNKED); 1685 } 1686 1687 public void testFlushAfterStreamTransmittedWithFixedLength() throws IOException { 1688 testFlushAfterStreamTransmitted(TransferKind.FIXED_LENGTH); 1689 } 1690 1691 public void testFlushAfterStreamTransmittedWithNoLengthHeaders() throws IOException { 1692 testFlushAfterStreamTransmitted(TransferKind.END_OF_STREAM); 1693 } 1694 1695 /** 1696 * We explicitly permit apps to close the upload stream even after it has 1697 * been transmitted. We also permit flush so that buffered streams can 1698 * do a no-op flush when they are closed. http://b/3038470 1699 */ 1700 private void testFlushAfterStreamTransmitted(TransferKind transferKind) throws IOException { 1701 server.enqueue(new MockResponse().setBody("abc")); 1702 server.play(); 1703 1704 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1705 connection.setDoOutput(true); 1706 byte[] upload = "def".getBytes("UTF-8"); 1707 1708 if (transferKind == TransferKind.CHUNKED) { 1709 connection.setChunkedStreamingMode(0); 1710 } else if (transferKind == TransferKind.FIXED_LENGTH) { 1711 connection.setFixedLengthStreamingMode(upload.length); 1712 } 1713 1714 OutputStream out = connection.getOutputStream(); 1715 out.write(upload); 1716 assertEquals("abc", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1717 1718 out.flush(); // dubious but permitted 1719 try { 1720 out.write("ghi".getBytes("UTF-8")); 1721 fail(); 1722 } catch (IOException expected) { 1723 } 1724 } 1725 1726 public void testGetHeadersThrows() throws IOException { 1727 server.enqueue(new MockResponse().setSocketPolicy(DISCONNECT_AT_START)); 1728 server.play(); 1729 1730 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1731 try { 1732 connection.getInputStream(); 1733 fail(); 1734 } catch (IOException expected) { 1735 } 1736 1737 try { 1738 connection.getInputStream(); 1739 fail(); 1740 } catch (IOException expected) { 1741 } 1742 } 1743 1744 /** 1745 * Encodes the response body using GZIP and adds the corresponding header. 1746 */ 1747 public byte[] gzip(byte[] bytes) throws IOException { 1748 ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); 1749 OutputStream gzippedOut = new GZIPOutputStream(bytesOut); 1750 gzippedOut.write(bytes); 1751 gzippedOut.close(); 1752 return bytesOut.toByteArray(); 1753 } 1754 1755 private <T> List<T> toListOrNull(T[] arrayOrNull) { 1756 return arrayOrNull != null ? Arrays.asList(arrayOrNull) : null; 1757 } 1758 1759 /** 1760 * Reads at most {@code limit} characters from {@code in} and asserts that 1761 * content equals {@code expected}. 1762 */ 1763 private void assertContent(String expected, URLConnection connection, int limit) 1764 throws IOException { 1765 connection.connect(); 1766 assertEquals(expected, readAscii(connection.getInputStream(), limit)); 1767 ((HttpURLConnection) connection).disconnect(); 1768 } 1769 1770 private void assertContent(String expected, URLConnection connection) throws IOException { 1771 assertContent(expected, connection, Integer.MAX_VALUE); 1772 } 1773 1774 private void assertContains(List<String> headers, String header) { 1775 assertTrue(headers.toString(), headers.contains(header)); 1776 } 1777 1778 private void assertContainsNoneMatching(List<String> headers, String pattern) { 1779 for (String header : headers) { 1780 if (header.matches(pattern)) { 1781 fail("Header " + header + " matches " + pattern); 1782 } 1783 } 1784 } 1785 1786 private Set<String> newSet(String... elements) { 1787 return new HashSet<String>(Arrays.asList(elements)); 1788 } 1789 1790 enum TransferKind { 1791 CHUNKED() { 1792 @Override void setBody(MockResponse response, byte[] content, int chunkSize) 1793 throws IOException { 1794 response.setChunkedBody(content, chunkSize); 1795 } 1796 }, 1797 FIXED_LENGTH() { 1798 @Override void setBody(MockResponse response, byte[] content, int chunkSize) { 1799 response.setBody(content); 1800 } 1801 }, 1802 END_OF_STREAM() { 1803 @Override void setBody(MockResponse response, byte[] content, int chunkSize) { 1804 response.setBody(content); 1805 response.setSocketPolicy(DISCONNECT_AT_END); 1806 for (Iterator<String> h = response.getHeaders().iterator(); h.hasNext(); ) { 1807 if (h.next().startsWith("Content-Length:")) { 1808 h.remove(); 1809 break; 1810 } 1811 } 1812 } 1813 }; 1814 1815 abstract void setBody(MockResponse response, byte[] content, int chunkSize) 1816 throws IOException; 1817 1818 void setBody(MockResponse response, String content, int chunkSize) throws IOException { 1819 setBody(response, content.getBytes("UTF-8"), chunkSize); 1820 } 1821 } 1822 1823 enum ProxyConfig { 1824 NO_PROXY() { 1825 @Override public HttpURLConnection connect(MockWebServer server, URL url) 1826 throws IOException { 1827 return (HttpURLConnection) url.openConnection(Proxy.NO_PROXY); 1828 } 1829 }, 1830 1831 CREATE_ARG() { 1832 @Override public HttpURLConnection connect(MockWebServer server, URL url) 1833 throws IOException { 1834 return (HttpURLConnection) url.openConnection(server.toProxyAddress()); 1835 } 1836 }, 1837 1838 PROXY_SYSTEM_PROPERTY() { 1839 @Override public HttpURLConnection connect(MockWebServer server, URL url) 1840 throws IOException { 1841 System.setProperty("proxyHost", "localhost"); 1842 System.setProperty("proxyPort", Integer.toString(server.getPort())); 1843 return (HttpURLConnection) url.openConnection(); 1844 } 1845 }, 1846 1847 HTTP_PROXY_SYSTEM_PROPERTY() { 1848 @Override public HttpURLConnection connect(MockWebServer server, URL url) 1849 throws IOException { 1850 System.setProperty("http.proxyHost", "localhost"); 1851 System.setProperty("http.proxyPort", Integer.toString(server.getPort())); 1852 return (HttpURLConnection) url.openConnection(); 1853 } 1854 }, 1855 1856 HTTPS_PROXY_SYSTEM_PROPERTY() { 1857 @Override public HttpURLConnection connect(MockWebServer server, URL url) 1858 throws IOException { 1859 System.setProperty("https.proxyHost", "localhost"); 1860 System.setProperty("https.proxyPort", Integer.toString(server.getPort())); 1861 return (HttpURLConnection) url.openConnection(); 1862 } 1863 }; 1864 1865 public abstract HttpURLConnection connect(MockWebServer server, URL url) throws IOException; 1866 } 1867 1868 private static class RecordingTrustManager implements X509TrustManager { 1869 private final List<String> calls = new ArrayList<String>(); 1870 1871 public X509Certificate[] getAcceptedIssuers() { 1872 calls.add("getAcceptedIssuers"); 1873 return new X509Certificate[] {}; 1874 } 1875 1876 public void checkClientTrusted(X509Certificate[] chain, String authType) 1877 throws CertificateException { 1878 calls.add("checkClientTrusted " + certificatesToString(chain) + " " + authType); 1879 } 1880 1881 public void checkServerTrusted(X509Certificate[] chain, String authType) 1882 throws CertificateException { 1883 calls.add("checkServerTrusted " + certificatesToString(chain) + " " + authType); 1884 } 1885 1886 private String certificatesToString(X509Certificate[] certificates) { 1887 List<String> result = new ArrayList<String>(); 1888 for (X509Certificate certificate : certificates) { 1889 result.add(certificate.getSubjectDN() + " " + certificate.getSerialNumber()); 1890 } 1891 return result.toString(); 1892 } 1893 } 1894 1895 private static class RecordingHostnameVerifier implements HostnameVerifier { 1896 private final List<String> calls = new ArrayList<String>(); 1897 1898 public boolean verify(String hostname, SSLSession session) { 1899 calls.add("verify " + hostname); 1900 return true; 1901 } 1902 } 1903 1904 private static class InsecureResponseCache extends ResponseCache { 1905 private final DefaultResponseCache delegate = new DefaultResponseCache(); 1906 1907 @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException { 1908 return delegate.put(uri, connection); 1909 } 1910 1911 @Override public CacheResponse get(URI uri, String requestMethod, 1912 Map<String, List<String>> requestHeaders) throws IOException { 1913 final CacheResponse response = delegate.get(uri, requestMethod, requestHeaders); 1914 if (response instanceof SecureCacheResponse) { 1915 return new CacheResponse() { 1916 @Override public InputStream getBody() throws IOException { 1917 return response.getBody(); 1918 } 1919 @Override public Map<String, List<String>> getHeaders() throws IOException { 1920 return response.getHeaders(); 1921 } 1922 }; 1923 } 1924 return response; 1925 } 1926 } 1927} 1928