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