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