URLConnectionTest.java revision 1f8243e3d2b5a3f8e0398c304d1dea0395cbc368
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 final HostnameVerifier ALWAYS_TRUST_VERIFIER = new HostnameVerifier() { 74 public boolean verify(String hostname, SSLSession session) { 75 return true; 76 } 77 }; 78 79 private MockWebServer server = new MockWebServer(); 80 private String hostname; 81 82 @Override protected void setUp() throws Exception { 83 super.setUp(); 84 hostname = InetAddress.getLocalHost().getHostName(); 85 } 86 87 @Override protected void tearDown() throws Exception { 88 ResponseCache.setDefault(null); 89 Authenticator.setDefault(null); 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 testConnectViaProxy() throws IOException, InterruptedException { 462 MockResponse mockResponse = new MockResponse().setBody("this response comes via a proxy"); 463 server.enqueue(mockResponse); 464 server.play(); 465 466 URLConnection connection = new URL("http://android.com/foo").openConnection( 467 server.toProxyAddress()); 468 assertContent("this response comes via a proxy", connection); 469 470 RecordedRequest request = server.takeRequest(); 471 assertEquals("GET http://android.com/foo HTTP/1.1", request.getRequestLine()); 472 assertContains(request.getHeaders(), "Host: android.com"); 473 } 474 475 public void testContentDisagreesWithContentLengthHeader() throws IOException { 476 server.enqueue(new MockResponse() 477 .setBody("abc\r\nYOU SHOULD NOT SEE THIS") 478 .clearHeaders() 479 .addHeader("Content-Length: 3")); 480 server.play(); 481 482 assertContent("abc", server.getUrl("/").openConnection()); 483 } 484 485 public void testContentDisagreesWithChunkedHeader() throws IOException { 486 MockResponse mockResponse = new MockResponse(); 487 mockResponse.setChunkedBody("abc", 3); 488 ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); 489 bytesOut.write(mockResponse.getBody()); 490 bytesOut.write("\r\nYOU SHOULD NOT SEE THIS".getBytes()); 491 mockResponse.setBody(bytesOut.toByteArray()); 492 mockResponse.clearHeaders(); 493 mockResponse.addHeader("Transfer-encoding: chunked"); 494 495 server.enqueue(mockResponse); 496 server.play(); 497 498 assertContent("abc", server.getUrl("/").openConnection()); 499 } 500 501 public void testConnectViaHttpProxyToHttps() throws IOException, InterruptedException { 502 TestSSLContext testSSLContext = TestSSLContext.create(); 503 504 server.useHttps(testSSLContext.serverContext.getSocketFactory(), true); 505 server.enqueue(new MockResponse().clearHeaders()); // for CONNECT 506 server.enqueue(new MockResponse().setBody("this response comes via a secure proxy")); 507 server.play(); 508 509 URL url = new URL("https://android.com/foo"); 510 HttpsURLConnection connection = (HttpsURLConnection) url.openConnection( 511 server.toProxyAddress()); 512 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 513 connection.setHostnameVerifier(ALWAYS_TRUST_VERIFIER); 514 515 assertContent("this response comes via a secure proxy", connection); 516 517 RecordedRequest connect = server.takeRequest(); 518 assertEquals("Connect line failure on proxy", 519 "CONNECT android.com:443 HTTP/1.1", connect.getRequestLine()); 520 assertContains(connect.getHeaders(), "Host: android.com"); 521 522 RecordedRequest get = server.takeRequest(); 523 assertEquals("GET /foo HTTP/1.1", get.getRequestLine()); 524 assertContains(get.getHeaders(), "Host: android.com"); 525 } 526 527 /** 528 * Test which headers are sent unencrypted to the HTTP proxy. 529 */ 530 public void testProxyConnectIncludesProxyHeadersOnly() 531 throws IOException, InterruptedException { 532 TestSSLContext testSSLContext = TestSSLContext.create(); 533 534 server.useHttps(testSSLContext.serverContext.getSocketFactory(), true); 535 server.enqueue(new MockResponse().clearHeaders()); // for CONNECT 536 server.enqueue(new MockResponse().setBody("encrypted response from the origin server")); 537 server.play(); 538 539 URL url = new URL("https://android.com/foo"); 540 HttpsURLConnection connection = (HttpsURLConnection) url.openConnection( 541 server.toProxyAddress()); 542 connection.addRequestProperty("Private", "Secret"); 543 connection.addRequestProperty("Proxy-Authorization", "bar"); 544 connection.addRequestProperty("User-Agent", "baz"); 545 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 546 connection.setHostnameVerifier(ALWAYS_TRUST_VERIFIER); 547 assertContent("encrypted response from the origin server", connection); 548 549 RecordedRequest connect = server.takeRequest(); 550 assertContainsNoneMatching(connect.getHeaders(), "Private.*"); 551 assertContains(connect.getHeaders(), "Proxy-Authorization: bar"); 552 assertContains(connect.getHeaders(), "User-Agent: baz"); 553 assertContains(connect.getHeaders(), "Host: android.com"); 554 assertContains(connect.getHeaders(), "Proxy-Connection: Keep-Alive"); 555 556 RecordedRequest get = server.takeRequest(); 557 assertContains(get.getHeaders(), "Private: Secret"); 558 } 559 560 public void testDisconnectedConnection() throws IOException { 561 server.enqueue(new MockResponse().setBody("ABCDEFGHIJKLMNOPQR")); 562 server.play(); 563 564 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 565 InputStream in = connection.getInputStream(); 566 assertEquals('A', (char) in.read()); 567 connection.disconnect(); 568 try { 569 in.read(); 570 fail("Expected a connection closed exception"); 571 } catch (IOException expected) { 572 } 573 } 574 575 public void testResponseCachingAndInputStreamSkipWithFixedLength() throws IOException { 576 testResponseCaching(TransferKind.FIXED_LENGTH); 577 } 578 579 public void testResponseCachingAndInputStreamSkipWithChunkedEncoding() throws IOException { 580 testResponseCaching(TransferKind.CHUNKED); 581 } 582 583 public void testResponseCachingAndInputStreamSkipWithNoLengthHeaders() throws IOException { 584 testResponseCaching(TransferKind.END_OF_STREAM); 585 } 586 587 /** 588 * HttpURLConnection.getInputStream().skip(long) causes ResponseCache corruption 589 * http://code.google.com/p/android/issues/detail?id=8175 590 */ 591 private void testResponseCaching(TransferKind transferKind) throws IOException { 592 MockResponse response = new MockResponse(); 593 transferKind.setBody(response, "I love puppies but hate spiders", 1); 594 server.enqueue(response); 595 server.play(); 596 597 DefaultResponseCache cache = new DefaultResponseCache(); 598 ResponseCache.setDefault(cache); 599 600 // Make sure that calling skip() doesn't omit bytes from the cache. 601 URLConnection urlConnection = server.getUrl("/").openConnection(); 602 InputStream in = urlConnection.getInputStream(); 603 assertEquals("I love ", readAscii(in, "I love ".length())); 604 reliableSkip(in, "puppies but hate ".length()); 605 assertEquals("spiders", readAscii(in, "spiders".length())); 606 assertEquals(-1, in.read()); 607 in.close(); 608 assertEquals(1, cache.getSuccessCount()); 609 assertEquals(0, cache.getAbortCount()); 610 611 urlConnection = server.getUrl("/").openConnection(); // this response is cached! 612 in = urlConnection.getInputStream(); 613 assertEquals("I love puppies but hate spiders", 614 readAscii(in, "I love puppies but hate spiders".length())); 615 assertEquals(-1, in.read()); 616 assertEquals(1, cache.getMissCount()); 617 assertEquals(1, cache.getHitCount()); 618 assertEquals(1, cache.getSuccessCount()); 619 assertEquals(0, cache.getAbortCount()); 620 } 621 622 public void testResponseCacheRequestHeaders() throws IOException, URISyntaxException { 623 server.enqueue(new MockResponse().setBody("ABC")); 624 server.play(); 625 626 final AtomicReference<Map<String, List<String>>> requestHeadersRef 627 = new AtomicReference<Map<String, List<String>>>(); 628 ResponseCache.setDefault(new ResponseCache() { 629 @Override public CacheResponse get(URI uri, String requestMethod, 630 Map<String, List<String>> requestHeaders) throws IOException { 631 requestHeadersRef.set(requestHeaders); 632 return null; 633 } 634 @Override public CacheRequest put(URI uri, URLConnection conn) throws IOException { 635 return null; 636 } 637 }); 638 639 URL url = server.getUrl("/"); 640 URLConnection urlConnection = url.openConnection(); 641 urlConnection.addRequestProperty("A", "android"); 642 readAscii(urlConnection.getInputStream(), Integer.MAX_VALUE); 643 assertEquals(Arrays.asList("android"), requestHeadersRef.get().get("A")); 644 } 645 646 private void reliableSkip(InputStream in, int length) throws IOException { 647 while (length > 0) { 648 length -= in.skip(length); 649 } 650 } 651 652 /** 653 * Reads {@code count} characters from the stream. If the stream is 654 * exhausted before {@code count} characters can be read, the remaining 655 * characters are returned and the stream is closed. 656 */ 657 private String readAscii(InputStream in, int count) throws IOException { 658 StringBuilder result = new StringBuilder(); 659 for (int i = 0; i < count; i++) { 660 int value = in.read(); 661 if (value == -1) { 662 in.close(); 663 break; 664 } 665 result.append((char) value); 666 } 667 return result.toString(); 668 } 669 670 public void testServerDisconnectsPrematurelyWithContentLengthHeader() throws IOException { 671 testServerPrematureDisconnect(TransferKind.FIXED_LENGTH); 672 } 673 674 public void testServerDisconnectsPrematurelyWithChunkedEncoding() throws IOException { 675 testServerPrematureDisconnect(TransferKind.CHUNKED); 676 } 677 678 public void testServerDisconnectsPrematurelyWithNoLengthHeaders() throws IOException { 679 /* 680 * Intentionally empty. This case doesn't make sense because there's no 681 * such thing as a premature disconnect when the disconnect itself 682 * indicates the end of the data stream. 683 */ 684 } 685 686 private void testServerPrematureDisconnect(TransferKind transferKind) throws IOException { 687 MockResponse response = new MockResponse(); 688 transferKind.setBody(response, "ABCDE\nFGHIJKLMNOPQRSTUVWXYZ", 16); 689 server.enqueue(truncateViolently(response, 16)); 690 server.enqueue(new MockResponse().setBody("Request #2")); 691 server.play(); 692 693 DefaultResponseCache cache = new DefaultResponseCache(); 694 ResponseCache.setDefault(cache); 695 696 BufferedReader reader = new BufferedReader(new InputStreamReader( 697 server.getUrl("/").openConnection().getInputStream())); 698 assertEquals("ABCDE", reader.readLine()); 699 try { 700 reader.readLine(); 701 fail("This implementation silently ignored a truncated HTTP body."); 702 } catch (IOException expected) { 703 } 704 705 assertEquals(1, cache.getAbortCount()); 706 assertEquals(0, cache.getSuccessCount()); 707 assertContent("Request #2", server.getUrl("/").openConnection()); 708 assertEquals(1, cache.getAbortCount()); 709 assertEquals(1, cache.getSuccessCount()); 710 } 711 712 public void testClientPrematureDisconnectWithContentLengthHeader() throws IOException { 713 testClientPrematureDisconnect(TransferKind.FIXED_LENGTH); 714 } 715 716 public void testClientPrematureDisconnectWithChunkedEncoding() throws IOException { 717 testClientPrematureDisconnect(TransferKind.CHUNKED); 718 } 719 720 public void testClientPrematureDisconnectWithNoLengthHeaders() throws IOException { 721 testClientPrematureDisconnect(TransferKind.END_OF_STREAM); 722 } 723 724 private void testClientPrematureDisconnect(TransferKind transferKind) throws IOException { 725 MockResponse response = new MockResponse(); 726 transferKind.setBody(response, "ABCDE\nFGHIJKLMNOPQRSTUVWXYZ", 1024); 727 server.enqueue(response); 728 server.enqueue(new MockResponse().setBody("Request #2")); 729 server.play(); 730 731 DefaultResponseCache cache = new DefaultResponseCache(); 732 ResponseCache.setDefault(cache); 733 734 InputStream in = server.getUrl("/").openConnection().getInputStream(); 735 assertEquals("ABCDE", readAscii(in, 5)); 736 in.close(); 737 try { 738 in.read(); 739 fail("Expected an IOException because the stream is closed."); 740 } catch (IOException expected) { 741 } 742 743 assertEquals(1, cache.getAbortCount()); 744 assertEquals(0, cache.getSuccessCount()); 745 assertContent("Request #2", server.getUrl("/").openConnection()); 746 assertEquals(1, cache.getAbortCount()); 747 assertEquals(1, cache.getSuccessCount()); 748 } 749 750 /** 751 * Shortens the body of {@code response} but not the corresponding headers. 752 * Only useful to test how clients respond to the premature conclusion of 753 * the HTTP body. 754 */ 755 private MockResponse truncateViolently(MockResponse response, int numBytesToKeep) { 756 response.setDisconnectAtEnd(true); 757 List<String> headers = new ArrayList<String>(response.getHeaders()); 758 response.setBody(Arrays.copyOfRange(response.getBody(), 0, numBytesToKeep)); 759 response.getHeaders().clear(); 760 response.getHeaders().addAll(headers); 761 return response; 762 } 763 764 public void testMarkAndResetWithContentLengthHeader() throws IOException { 765 testMarkAndReset(TransferKind.FIXED_LENGTH); 766 } 767 768 public void testMarkAndResetWithChunkedEncoding() throws IOException { 769 testMarkAndReset(TransferKind.CHUNKED); 770 } 771 772 public void testMarkAndResetWithNoLengthHeaders() throws IOException { 773 testMarkAndReset(TransferKind.END_OF_STREAM); 774 } 775 776 public void testMarkAndReset(TransferKind transferKind) throws IOException { 777 MockResponse response = new MockResponse(); 778 transferKind.setBody(response, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1024); 779 server.enqueue(response); 780 server.play(); 781 782 DefaultResponseCache cache = new DefaultResponseCache(); 783 ResponseCache.setDefault(cache); 784 785 InputStream in = server.getUrl("/").openConnection().getInputStream(); 786 assertFalse("This implementation claims to support mark().", in.markSupported()); 787 in.mark(5); 788 assertEquals("ABCDE", readAscii(in, 5)); 789 try { 790 in.reset(); 791 fail(); 792 } catch (IOException expected) { 793 } 794 assertEquals("FGHIJKLMNOPQRSTUVWXYZ", readAscii(in, Integer.MAX_VALUE)); 795 796 assertContent("ABCDEFGHIJKLMNOPQRSTUVWXYZ", server.getUrl("/").openConnection()); 797 assertEquals(1, cache.getSuccessCount()); 798 assertEquals(1, cache.getHitCount()); 799 } 800 801 /** 802 * We've had a bug where we forget the HTTP response when we see response 803 * code 401. This causes a new HTTP request to be issued for every call into 804 * the URLConnection. 805 */ 806 public void testUnauthorizedResponseHandling() throws IOException { 807 MockResponse response = new MockResponse() 808 .addHeader("WWW-Authenticate: challenge") 809 .setResponseCode(401) // UNAUTHORIZED 810 .setBody("Unauthorized"); 811 server.enqueue(response); 812 server.enqueue(response); 813 server.enqueue(response); 814 server.play(); 815 816 URL url = server.getUrl("/"); 817 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 818 819 assertEquals(401, conn.getResponseCode()); 820 assertEquals(401, conn.getResponseCode()); 821 assertEquals(401, conn.getResponseCode()); 822 assertEquals(1, server.getRequestCount()); 823 } 824 825 public void testNonHexChunkSize() throws IOException { 826 server.enqueue(new MockResponse() 827 .setBody("5\r\nABCDE\r\nG\r\nFGHIJKLMNOPQRSTU\r\n0\r\n\r\n") 828 .clearHeaders() 829 .addHeader("Transfer-encoding: chunked")); 830 server.play(); 831 832 URLConnection connection = server.getUrl("/").openConnection(); 833 try { 834 readAscii(connection.getInputStream(), Integer.MAX_VALUE); 835 fail(); 836 } catch (IOException e) { 837 } 838 } 839 840 public void testMissingChunkBody() throws IOException { 841 server.enqueue(new MockResponse() 842 .setBody("5") 843 .clearHeaders() 844 .addHeader("Transfer-encoding: chunked") 845 .setDisconnectAtEnd(true)); 846 server.play(); 847 848 URLConnection connection = server.getUrl("/").openConnection(); 849 try { 850 readAscii(connection.getInputStream(), Integer.MAX_VALUE); 851 fail(); 852 } catch (IOException e) { 853 } 854 } 855 856 /** 857 * This test checks whether connections are gzipped by default. This 858 * behavior in not required by the API, so a failure of this test does not 859 * imply a bug in the implementation. 860 */ 861 public void testGzipEncodingEnabledByDefault() throws IOException, InterruptedException { 862 server.enqueue(new MockResponse() 863 .setBody(gzip("ABCABCABC".getBytes("UTF-8"))) 864 .addHeader("Content-Encoding: gzip")); 865 server.play(); 866 867 URLConnection connection = server.getUrl("/").openConnection(); 868 assertEquals("ABCABCABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 869 assertNull(connection.getContentEncoding()); 870 871 RecordedRequest request = server.takeRequest(); 872 assertContains(request.getHeaders(), "Accept-Encoding: gzip"); 873 } 874 875 public void testClientConfiguredGzipContentEncoding() throws Exception { 876 server.enqueue(new MockResponse() 877 .setBody(gzip("ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes("UTF-8"))) 878 .addHeader("Content-Encoding: gzip")); 879 server.play(); 880 881 URLConnection connection = server.getUrl("/").openConnection(); 882 connection.addRequestProperty("Accept-Encoding", "gzip"); 883 InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream()); 884 assertEquals("ABCDEFGHIJKLMNOPQRSTUVWXYZ", readAscii(gunzippedIn, Integer.MAX_VALUE)); 885 886 RecordedRequest request = server.takeRequest(); 887 assertContains(request.getHeaders(), "Accept-Encoding: gzip"); 888 } 889 890 public void testGzipAndConnectionReuseWithFixedLength() throws Exception { 891 testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.FIXED_LENGTH); 892 } 893 894 public void testGzipAndConnectionReuseWithChunkedEncoding() throws Exception { 895 testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.CHUNKED); 896 } 897 898 public void testClientConfiguredCustomContentEncoding() throws Exception { 899 server.enqueue(new MockResponse() 900 .setBody("ABCDE") 901 .addHeader("Content-Encoding: custom")); 902 server.play(); 903 904 URLConnection connection = server.getUrl("/").openConnection(); 905 connection.addRequestProperty("Accept-Encoding", "custom"); 906 assertEquals("ABCDE", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 907 908 RecordedRequest request = server.takeRequest(); 909 assertContains(request.getHeaders(), "Accept-Encoding: custom"); 910 } 911 912 /** 913 * Test a bug where gzip input streams weren't exhausting the input stream, 914 * which corrupted the request that followed. 915 * http://code.google.com/p/android/issues/detail?id=7059 916 */ 917 private void testClientConfiguredGzipContentEncodingAndConnectionReuse( 918 TransferKind transferKind) throws Exception { 919 MockResponse responseOne = new MockResponse(); 920 responseOne.addHeader("Content-Encoding: gzip"); 921 transferKind.setBody(responseOne, gzip("one (gzipped)".getBytes("UTF-8")), 5); 922 server.enqueue(responseOne); 923 MockResponse responseTwo = new MockResponse(); 924 transferKind.setBody(responseTwo, "two (identity)", 5); 925 server.enqueue(responseTwo); 926 server.play(); 927 928 URLConnection connection = server.getUrl("/").openConnection(); 929 connection.addRequestProperty("Accept-Encoding", "gzip"); 930 InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream()); 931 assertEquals("one (gzipped)", readAscii(gunzippedIn, Integer.MAX_VALUE)); 932 assertEquals(0, server.takeRequest().getSequenceNumber()); 933 934 connection = server.getUrl("/").openConnection(); 935 assertEquals("two (identity)", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 936 assertEquals(1, server.takeRequest().getSequenceNumber()); 937 } 938 939 /** 940 * Obnoxiously test that the chunk sizes transmitted exactly equal the 941 * requested data+chunk header size. Although setChunkedStreamingMode() 942 * isn't specific about whether the size applies to the data or the 943 * complete chunk, the RI interprets it as a complete chunk. 944 */ 945 public void testSetChunkedStreamingMode() throws IOException, InterruptedException { 946 server.enqueue(new MockResponse()); 947 server.play(); 948 949 HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); 950 urlConnection.setChunkedStreamingMode(8); 951 urlConnection.setDoOutput(true); 952 OutputStream outputStream = urlConnection.getOutputStream(); 953 outputStream.write("ABCDEFGHIJKLMNOPQ".getBytes("US-ASCII")); 954 assertEquals(200, urlConnection.getResponseCode()); 955 956 RecordedRequest request = server.takeRequest(); 957 assertEquals("ABCDEFGHIJKLMNOPQ", new String(request.getBody(), "US-ASCII")); 958 assertEquals(Arrays.asList(3, 3, 3, 3, 3, 2), request.getChunkSizes()); 959 } 960 961 public void testAuthenticateWithFixedLengthStreaming() throws Exception { 962 testAuthenticateWithStreamingPost(StreamingMode.FIXED_LENGTH); 963 } 964 965 public void testAuthenticateWithChunkedStreaming() throws Exception { 966 testAuthenticateWithStreamingPost(StreamingMode.CHUNKED); 967 } 968 969 private void testAuthenticateWithStreamingPost(StreamingMode streamingMode) throws Exception { 970 MockResponse pleaseAuthenticate = new MockResponse() 971 .setResponseCode(401) 972 .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") 973 .setBody("Please authenticate."); 974 server.enqueue(pleaseAuthenticate); 975 server.play(); 976 977 Authenticator.setDefault(SIMPLE_AUTHENTICATOR); 978 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 979 connection.setDoOutput(true); 980 byte[] requestBody = { 'A', 'B', 'C', 'D' }; 981 if (streamingMode == StreamingMode.FIXED_LENGTH) { 982 connection.setFixedLengthStreamingMode(requestBody.length); 983 } else if (streamingMode == StreamingMode.CHUNKED) { 984 connection.setChunkedStreamingMode(0); 985 } 986 OutputStream outputStream = connection.getOutputStream(); 987 outputStream.write(requestBody); 988 outputStream.close(); 989 try { 990 connection.getInputStream(); 991 fail(); 992 } catch (HttpRetryException expected) { 993 } 994 995 // no authorization header for the request... 996 RecordedRequest request = server.takeRequest(); 997 assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*"); 998 assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody())); 999 } 1000 1001 enum StreamingMode { 1002 FIXED_LENGTH, CHUNKED 1003 } 1004 1005 public void testAuthenticateWithPost() throws Exception { 1006 MockResponse pleaseAuthenticate = new MockResponse() 1007 .setResponseCode(401) 1008 .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") 1009 .setBody("Please authenticate."); 1010 // fail auth three times... 1011 server.enqueue(pleaseAuthenticate); 1012 server.enqueue(pleaseAuthenticate); 1013 server.enqueue(pleaseAuthenticate); 1014 // ...then succeed the fourth time 1015 server.enqueue(new MockResponse().setBody("Successful auth!")); 1016 server.play(); 1017 1018 Authenticator.setDefault(SIMPLE_AUTHENTICATOR); 1019 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1020 connection.setDoOutput(true); 1021 byte[] requestBody = { 'A', 'B', 'C', 'D' }; 1022 OutputStream outputStream = connection.getOutputStream(); 1023 outputStream.write(requestBody); 1024 outputStream.close(); 1025 assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1026 1027 // no authorization header for the first request... 1028 RecordedRequest request = server.takeRequest(); 1029 assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*"); 1030 1031 // ...but the three requests that follow include an authorization header 1032 for (int i = 0; i < 3; i++) { 1033 request = server.takeRequest(); 1034 assertEquals("POST / HTTP/1.1", request.getRequestLine()); 1035 assertContains(request.getHeaders(), "Authorization: Basic " 1036 + "dXNlcm5hbWU6cGFzc3dvcmQ="); // "dXNl..." == base64("username:password") 1037 assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody())); 1038 } 1039 } 1040 1041 public void testAuthenticateWithGet() throws Exception { 1042 MockResponse pleaseAuthenticate = new MockResponse() 1043 .setResponseCode(401) 1044 .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") 1045 .setBody("Please authenticate."); 1046 // fail auth three times... 1047 server.enqueue(pleaseAuthenticate); 1048 server.enqueue(pleaseAuthenticate); 1049 server.enqueue(pleaseAuthenticate); 1050 // ...then succeed the fourth time 1051 server.enqueue(new MockResponse().setBody("Successful auth!")); 1052 server.play(); 1053 1054 Authenticator.setDefault(SIMPLE_AUTHENTICATOR); 1055 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1056 assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1057 1058 // no authorization header for the first request... 1059 RecordedRequest request = server.takeRequest(); 1060 assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*"); 1061 1062 // ...but the three requests that follow requests include an authorization header 1063 for (int i = 0; i < 3; i++) { 1064 request = server.takeRequest(); 1065 assertEquals("GET / HTTP/1.1", request.getRequestLine()); 1066 assertContains(request.getHeaders(), "Authorization: Basic " 1067 + "dXNlcm5hbWU6cGFzc3dvcmQ="); // "dXNl..." == base64("username:password") 1068 } 1069 } 1070 1071 public void testRedirectedWithChunkedEncoding() throws Exception { 1072 testRedirected(TransferKind.CHUNKED, true); 1073 } 1074 1075 public void testRedirectedWithContentLengthHeader() throws Exception { 1076 testRedirected(TransferKind.FIXED_LENGTH, true); 1077 } 1078 1079 public void testRedirectedWithNoLengthHeaders() throws Exception { 1080 testRedirected(TransferKind.END_OF_STREAM, false); 1081 } 1082 1083 private void testRedirected(TransferKind transferKind, boolean reuse) throws Exception { 1084 MockResponse response = new MockResponse() 1085 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1086 .addHeader("Location: /foo"); 1087 transferKind.setBody(response, "This page has moved!", 10); 1088 server.enqueue(response); 1089 server.enqueue(new MockResponse().setBody("This is the new location!")); 1090 server.play(); 1091 1092 URLConnection connection = server.getUrl("/").openConnection(); 1093 assertEquals("This is the new location!", 1094 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1095 1096 RecordedRequest first = server.takeRequest(); 1097 assertEquals("GET / HTTP/1.1", first.getRequestLine()); 1098 RecordedRequest retry = server.takeRequest(); 1099 assertEquals("GET /foo HTTP/1.1", retry.getRequestLine()); 1100 if (reuse) { 1101 assertEquals("Expected connection reuse", 1, retry.getSequenceNumber()); 1102 } 1103 } 1104 1105 public void testRedirectedOnHttps() throws IOException, InterruptedException { 1106 TestSSLContext testSSLContext = TestSSLContext.create(); 1107 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 1108 server.enqueue(new MockResponse() 1109 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1110 .addHeader("Location: /foo") 1111 .setBody("This page has moved!")); 1112 server.enqueue(new MockResponse().setBody("This is the new location!")); 1113 server.play(); 1114 1115 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 1116 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 1117 assertEquals("This is the new location!", 1118 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1119 1120 RecordedRequest first = server.takeRequest(); 1121 assertEquals("GET / HTTP/1.1", first.getRequestLine()); 1122 RecordedRequest retry = server.takeRequest(); 1123 assertEquals("GET /foo HTTP/1.1", retry.getRequestLine()); 1124 assertEquals("Expected connection reuse", 1, retry.getSequenceNumber()); 1125 } 1126 1127 public void testNotRedirectedFromHttpsToHttp() throws IOException, InterruptedException { 1128 TestSSLContext testSSLContext = TestSSLContext.create(); 1129 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 1130 server.enqueue(new MockResponse() 1131 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1132 .addHeader("Location: http://anyhost/foo") 1133 .setBody("This page has moved!")); 1134 server.play(); 1135 1136 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 1137 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 1138 assertEquals("This page has moved!", 1139 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1140 } 1141 1142 public void testNotRedirectedFromHttpToHttps() throws IOException, InterruptedException { 1143 server.enqueue(new MockResponse() 1144 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1145 .addHeader("Location: https://anyhost/foo") 1146 .setBody("This page has moved!")); 1147 server.play(); 1148 1149 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1150 assertEquals("This page has moved!", 1151 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1152 } 1153 1154 public void testRedirectToAnotherOriginServer() throws Exception { 1155 MockWebServer server2 = new MockWebServer(); 1156 server2.enqueue(new MockResponse().setBody("This is the 2nd server!")); 1157 server2.play(); 1158 1159 server.enqueue(new MockResponse() 1160 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1161 .addHeader("Location: " + server2.getUrl("/").toString()) 1162 .setBody("This page has moved!")); 1163 server.enqueue(new MockResponse().setBody("This is the first server again!")); 1164 server.play(); 1165 1166 URLConnection connection = server.getUrl("/").openConnection(); 1167 assertEquals("This is the 2nd server!", 1168 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1169 assertEquals(server2.getUrl("/"), connection.getURL()); 1170 1171 // make sure the first server was careful to recycle the connection 1172 assertEquals("This is the first server again!", 1173 readAscii(server.getUrl("/").openStream(), Integer.MAX_VALUE)); 1174 1175 RecordedRequest first = server.takeRequest(); 1176 assertContains(first.getHeaders(), "Host: " + hostname + ":" + server.getPort()); 1177 RecordedRequest second = server2.takeRequest(); 1178 assertContains(second.getHeaders(), "Host: " + hostname + ":" + server2.getPort()); 1179 RecordedRequest third = server.takeRequest(); 1180 assertEquals("Expected connection reuse", 1, third.getSequenceNumber()); 1181 1182 server2.shutdown(); 1183 } 1184 1185 public void testHttpsWithCustomTrustManager() throws Exception { 1186 RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); 1187 RecordingTrustManager trustManager = new RecordingTrustManager(); 1188 SSLContext sc = SSLContext.getInstance("TLS"); 1189 sc.init(null, new TrustManager[] { trustManager }, new java.security.SecureRandom()); 1190 1191 HostnameVerifier defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier(); 1192 HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier); 1193 SSLSocketFactory defaultSSLSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory(); 1194 HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); 1195 try { 1196 TestSSLContext testSSLContext = TestSSLContext.create(); 1197 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 1198 server.enqueue(new MockResponse().setBody("ABC")); 1199 server.enqueue(new MockResponse().setBody("DEF")); 1200 server.enqueue(new MockResponse().setBody("GHI")); 1201 server.play(); 1202 1203 URL url = server.getUrl("/"); 1204 assertEquals("ABC", readAscii(url.openStream(), Integer.MAX_VALUE)); 1205 assertEquals("DEF", readAscii(url.openStream(), Integer.MAX_VALUE)); 1206 assertEquals("GHI", readAscii(url.openStream(), Integer.MAX_VALUE)); 1207 1208 assertEquals(Arrays.asList("verify " + hostname), hostnameVerifier.calls); 1209 assertEquals(Arrays.asList("checkServerTrusted [" 1210 + "CN=" + hostname + " 1, " 1211 + "CN=Test Intermediate Certificate Authority 1, " 1212 + "CN=Test Root Certificate Authority 1" 1213 + "] RSA"), 1214 trustManager.calls); 1215 } finally { 1216 HttpsURLConnection.setDefaultHostnameVerifier(defaultHostnameVerifier); 1217 HttpsURLConnection.setDefaultSSLSocketFactory(defaultSSLSocketFactory); 1218 } 1219 } 1220 1221 public void testConnectTimeouts() throws IOException { 1222 // 10.0.0.0 is non-routable and will time out on every network 1223 URLConnection urlConnection = new URL("http://10.0.0.0/").openConnection(); 1224 urlConnection.setConnectTimeout(1000); 1225 try { 1226 urlConnection.getInputStream(); 1227 fail(); 1228 } catch (SocketTimeoutException expected) { 1229 } 1230 } 1231 1232 public void testReadTimeouts() throws IOException { 1233 /* 1234 * This relies on the fact that MockWebServer doesn't close the 1235 * connection after a response has been sent. This causes the client to 1236 * try to read more bytes than are sent, which results in a timeout. 1237 */ 1238 MockResponse timeout = new MockResponse() 1239 .setBody("ABC") 1240 .clearHeaders() 1241 .addHeader("Content-Length: 4"); 1242 server.enqueue(timeout); 1243 server.play(); 1244 1245 URLConnection urlConnection = server.getUrl("/").openConnection(); 1246 urlConnection.setReadTimeout(1000); 1247 InputStream in = urlConnection.getInputStream(); 1248 assertEquals('A', in.read()); 1249 assertEquals('B', in.read()); 1250 assertEquals('C', in.read()); 1251 try { 1252 in.read(); // if Content-Length was accurate, this would return -1 immediately 1253 fail(); 1254 } catch (SocketTimeoutException expected) { 1255 } 1256 } 1257 1258 public void testSetChunkedEncodingAsRequestProperty() throws IOException, InterruptedException { 1259 server.enqueue(new MockResponse()); 1260 server.play(); 1261 1262 HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); 1263 urlConnection.setRequestProperty("Transfer-encoding", "chunked"); 1264 urlConnection.setDoOutput(true); 1265 urlConnection.getOutputStream().write("ABC".getBytes("UTF-8")); 1266 assertEquals(200, urlConnection.getResponseCode()); 1267 1268 RecordedRequest request = server.takeRequest(); 1269 assertEquals("ABC", new String(request.getBody(), "UTF-8")); 1270 } 1271 1272 public void testConnectionCloseInRequest() throws IOException, InterruptedException { 1273 server.enqueue(new MockResponse()); // server doesn't honor the connection: close header! 1274 server.enqueue(new MockResponse()); 1275 server.play(); 1276 1277 HttpURLConnection a = (HttpURLConnection) server.getUrl("/").openConnection(); 1278 a.setRequestProperty("Connection", "close"); 1279 assertEquals(200, a.getResponseCode()); 1280 1281 HttpURLConnection b = (HttpURLConnection) server.getUrl("/").openConnection(); 1282 assertEquals(200, b.getResponseCode()); 1283 1284 assertEquals(0, server.takeRequest().getSequenceNumber()); 1285 assertEquals("When connection: close is used, each request should get its own connection", 1286 0, server.takeRequest().getSequenceNumber()); 1287 } 1288 1289 public void testConnectionCloseInResponse() throws IOException, InterruptedException { 1290 server.enqueue(new MockResponse().addHeader("Connection: close")); 1291 server.enqueue(new MockResponse()); 1292 server.play(); 1293 1294 HttpURLConnection a = (HttpURLConnection) server.getUrl("/").openConnection(); 1295 assertEquals(200, a.getResponseCode()); 1296 1297 HttpURLConnection b = (HttpURLConnection) server.getUrl("/").openConnection(); 1298 assertEquals(200, b.getResponseCode()); 1299 1300 assertEquals(0, server.takeRequest().getSequenceNumber()); 1301 assertEquals("When connection: close is used, each request should get its own connection", 1302 0, server.takeRequest().getSequenceNumber()); 1303 } 1304 1305 public void testConnectionCloseWithRedirect() throws IOException, InterruptedException { 1306 MockResponse response = new MockResponse() 1307 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1308 .addHeader("Location: /foo") 1309 .addHeader("Connection: close"); 1310 server.enqueue(response); 1311 server.enqueue(new MockResponse().setBody("This is the new location!")); 1312 server.play(); 1313 1314 URLConnection connection = server.getUrl("/").openConnection(); 1315 assertEquals("This is the new location!", 1316 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1317 1318 assertEquals(0, server.takeRequest().getSequenceNumber()); 1319 assertEquals("When connection: close is used, each request should get its own connection", 1320 0, server.takeRequest().getSequenceNumber()); 1321 } 1322 1323 public void testResponseCodeDisagreesWithHeaders() throws IOException, InterruptedException { 1324 server.enqueue(new MockResponse() 1325 .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT) 1326 .setBody("This body is not allowed!")); 1327 server.play(); 1328 1329 URLConnection connection = server.getUrl("/").openConnection(); 1330 assertEquals("This body is not allowed!", 1331 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1332 } 1333 1334 public void testSingleByteReadIsSigned() throws IOException { 1335 server.enqueue(new MockResponse().setBody(new byte[] { -2, -1 })); 1336 server.play(); 1337 1338 URLConnection connection = server.getUrl("/").openConnection(); 1339 InputStream in = connection.getInputStream(); 1340 assertEquals(254, in.read()); 1341 assertEquals(255, in.read()); 1342 assertEquals(-1, in.read()); 1343 } 1344 1345 /** 1346 * Encodes the response body using GZIP and adds the corresponding header. 1347 */ 1348 public byte[] gzip(byte[] bytes) throws IOException { 1349 ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); 1350 OutputStream gzippedOut = new GZIPOutputStream(bytesOut); 1351 gzippedOut.write(bytes); 1352 gzippedOut.close(); 1353 return bytesOut.toByteArray(); 1354 } 1355 1356 /** 1357 * Reads at most {@code limit} characters from {@code in} and asserts that 1358 * content equals {@code expected}. 1359 */ 1360 private void assertContent(String expected, URLConnection connection, int limit) 1361 throws IOException { 1362 assertEquals(expected, readAscii(connection.getInputStream(), limit)); 1363 ((HttpURLConnection) connection).disconnect(); 1364 } 1365 1366 private void assertContent(String expected, URLConnection connection) throws IOException { 1367 assertContent(expected, connection, Integer.MAX_VALUE); 1368 } 1369 1370 private void assertContains(List<String> headers, String header) { 1371 assertTrue(headers.toString(), headers.contains(header)); 1372 } 1373 1374 private void assertContainsNoneMatching(List<String> headers, String pattern) { 1375 for (String header : headers) { 1376 if (header.matches(pattern)) { 1377 fail("Header " + header + " matches " + pattern); 1378 } 1379 } 1380 } 1381 1382 private Set<String> newSet(String... elements) { 1383 return new HashSet<String>(Arrays.asList(elements)); 1384 } 1385 1386 enum TransferKind { 1387 CHUNKED() { 1388 @Override void setBody(MockResponse response, byte[] content, int chunkSize) 1389 throws IOException { 1390 response.setChunkedBody(content, chunkSize); 1391 } 1392 }, 1393 FIXED_LENGTH() { 1394 @Override void setBody(MockResponse response, byte[] content, int chunkSize) { 1395 response.setBody(content); 1396 } 1397 }, 1398 END_OF_STREAM() { 1399 @Override void setBody(MockResponse response, byte[] content, int chunkSize) { 1400 response.setBody(content); 1401 response.setDisconnectAtEnd(true); 1402 for (Iterator<String> h = response.getHeaders().iterator(); h.hasNext(); ) { 1403 if (h.next().startsWith("Content-Length:")) { 1404 h.remove(); 1405 break; 1406 } 1407 } 1408 } 1409 }; 1410 1411 abstract void setBody(MockResponse response, byte[] content, int chunkSize) 1412 throws IOException; 1413 1414 void setBody(MockResponse response, String content, int chunkSize) throws IOException { 1415 setBody(response, content.getBytes("UTF-8"), chunkSize); 1416 } 1417 } 1418 1419 private static class RecordingTrustManager implements X509TrustManager { 1420 private final List<String> calls = new ArrayList<String>(); 1421 1422 public X509Certificate[] getAcceptedIssuers() { 1423 calls.add("getAcceptedIssuers"); 1424 return new X509Certificate[] {}; 1425 } 1426 1427 public void checkClientTrusted(X509Certificate[] chain, String authType) 1428 throws CertificateException { 1429 calls.add("checkClientTrusted " + certificatesToString(chain) + " " + authType); 1430 } 1431 1432 public void checkServerTrusted(X509Certificate[] chain, String authType) 1433 throws CertificateException { 1434 calls.add("checkServerTrusted " + certificatesToString(chain) + " " + authType); 1435 } 1436 1437 private String certificatesToString(X509Certificate[] certificates) { 1438 List<String> result = new ArrayList<String>(); 1439 for (X509Certificate certificate : certificates) { 1440 result.add(certificate.getSubjectDN() + " " + certificate.getSerialNumber()); 1441 } 1442 return result.toString(); 1443 } 1444 } 1445 1446 private static class RecordingHostnameVerifier implements HostnameVerifier { 1447 private final List<String> calls = new ArrayList<String>(); 1448 1449 public boolean verify(String hostname, SSLSession session) { 1450 calls.add("verify " + hostname); 1451 return true; 1452 } 1453 } 1454} 1455