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