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