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