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