URLConnectionTest.java revision 8116f7e97e00d223e7fbe5c950c9a5e3277de124
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 testConnectViaHttpsReusingConnectionsDifferentFactories() 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 assertNull(connection.getContentEncoding()); 801 802 RecordedRequest request = server.takeRequest(); 803 assertContains(request.getHeaders(), "Accept-Encoding: gzip"); 804 } 805 806 public void testClientConfiguredGzipContentEncoding() throws Exception { 807 server.enqueue(new MockResponse() 808 .setBody(gzip("ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes("UTF-8"))) 809 .addHeader("Content-Encoding: gzip")); 810 server.play(); 811 812 URLConnection connection = server.getUrl("/").openConnection(); 813 connection.addRequestProperty("Accept-Encoding", "gzip"); 814 InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream()); 815 assertEquals("ABCDEFGHIJKLMNOPQRSTUVWXYZ", readAscii(gunzippedIn, Integer.MAX_VALUE)); 816 817 RecordedRequest request = server.takeRequest(); 818 assertContains(request.getHeaders(), "Accept-Encoding: gzip"); 819 } 820 821 public void testGzipAndConnectionReuseWithFixedLength() throws Exception { 822 testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.FIXED_LENGTH); 823 } 824 825 public void testGzipAndConnectionReuseWithChunkedEncoding() throws Exception { 826 testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.CHUNKED); 827 } 828 829 public void testClientConfiguredCustomContentEncoding() throws Exception { 830 server.enqueue(new MockResponse() 831 .setBody("ABCDE") 832 .addHeader("Content-Encoding: custom")); 833 server.play(); 834 835 URLConnection connection = server.getUrl("/").openConnection(); 836 connection.addRequestProperty("Accept-Encoding", "custom"); 837 assertEquals("ABCDE", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 838 839 RecordedRequest request = server.takeRequest(); 840 assertContains(request.getHeaders(), "Accept-Encoding: custom"); 841 } 842 843 /** 844 * Test a bug where gzip input streams weren't exhausting the input stream, 845 * which corrupted the request that followed. 846 * http://code.google.com/p/android/issues/detail?id=7059 847 */ 848 private void testClientConfiguredGzipContentEncodingAndConnectionReuse( 849 TransferKind transferKind) throws Exception { 850 MockResponse responseOne = new MockResponse(); 851 responseOne.addHeader("Content-Encoding: gzip"); 852 transferKind.setBody(responseOne, gzip("one (gzipped)".getBytes("UTF-8")), 5); 853 server.enqueue(responseOne); 854 MockResponse responseTwo = new MockResponse(); 855 transferKind.setBody(responseTwo, "two (identity)", 5); 856 server.enqueue(responseTwo); 857 server.play(); 858 859 URLConnection connection = server.getUrl("/").openConnection(); 860 connection.addRequestProperty("Accept-Encoding", "gzip"); 861 InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream()); 862 assertEquals("one (gzipped)", readAscii(gunzippedIn, Integer.MAX_VALUE)); 863 assertEquals(0, server.takeRequest().getSequenceNumber()); 864 865 connection = server.getUrl("/").openConnection(); 866 assertEquals("two (identity)", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 867 assertEquals(1, server.takeRequest().getSequenceNumber()); 868 } 869 870 /** 871 * Obnoxiously test that the chunk sizes transmitted exactly equal the 872 * requested data+chunk header size. Although setChunkedStreamingMode() 873 * isn't specific about whether the size applies to the data or the 874 * complete chunk, the RI interprets it as a complete chunk. 875 */ 876 public void testSetChunkedStreamingMode() throws IOException, InterruptedException { 877 server.enqueue(new MockResponse()); 878 server.play(); 879 880 HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); 881 urlConnection.setChunkedStreamingMode(8); 882 urlConnection.setDoOutput(true); 883 OutputStream outputStream = urlConnection.getOutputStream(); 884 outputStream.write("ABCDEFGHIJKLMNOPQ".getBytes("US-ASCII")); 885 assertEquals(200, urlConnection.getResponseCode()); 886 887 RecordedRequest request = server.takeRequest(); 888 assertEquals("ABCDEFGHIJKLMNOPQ", new String(request.getBody(), "US-ASCII")); 889 assertEquals(Arrays.asList(3, 3, 3, 3, 3, 2), request.getChunkSizes()); 890 } 891 892 public void testAuthenticateWithFixedLengthStreaming() throws Exception { 893 testAuthenticateWithStreamingPost(StreamingMode.FIXED_LENGTH); 894 } 895 896 public void testAuthenticateWithChunkedStreaming() throws Exception { 897 testAuthenticateWithStreamingPost(StreamingMode.CHUNKED); 898 } 899 900 private void testAuthenticateWithStreamingPost(StreamingMode streamingMode) throws Exception { 901 MockResponse pleaseAuthenticate = new MockResponse() 902 .setResponseCode(401) 903 .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") 904 .setBody("Please authenticate."); 905 server.enqueue(pleaseAuthenticate); 906 server.play(); 907 908 Authenticator.setDefault(SIMPLE_AUTHENTICATOR); 909 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 910 connection.setDoOutput(true); 911 byte[] requestBody = { 'A', 'B', 'C', 'D' }; 912 if (streamingMode == StreamingMode.FIXED_LENGTH) { 913 connection.setFixedLengthStreamingMode(requestBody.length); 914 } else if (streamingMode == StreamingMode.CHUNKED) { 915 connection.setChunkedStreamingMode(0); 916 } 917 OutputStream outputStream = connection.getOutputStream(); 918 outputStream.write(requestBody); 919 outputStream.close(); 920 try { 921 connection.getInputStream(); 922 fail(); 923 } catch (HttpRetryException expected) { 924 } 925 926 // no authorization header for the request... 927 RecordedRequest request = server.takeRequest(); 928 assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*"); 929 assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody())); 930 } 931 932 enum StreamingMode { 933 FIXED_LENGTH, CHUNKED 934 } 935 936 public void testAuthenticateWithPost() throws Exception { 937 MockResponse pleaseAuthenticate = new MockResponse() 938 .setResponseCode(401) 939 .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") 940 .setBody("Please authenticate."); 941 // fail auth three times... 942 server.enqueue(pleaseAuthenticate); 943 server.enqueue(pleaseAuthenticate); 944 server.enqueue(pleaseAuthenticate); 945 // ...then succeed the fourth time 946 server.enqueue(new MockResponse().setBody("Successful auth!")); 947 server.play(); 948 949 Authenticator.setDefault(SIMPLE_AUTHENTICATOR); 950 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 951 connection.setDoOutput(true); 952 byte[] requestBody = { 'A', 'B', 'C', 'D' }; 953 OutputStream outputStream = connection.getOutputStream(); 954 outputStream.write(requestBody); 955 outputStream.close(); 956 assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 957 958 // no authorization header for the first request... 959 RecordedRequest request = server.takeRequest(); 960 assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*"); 961 962 // ...but the three requests that follow include an authorization header 963 for (int i = 0; i < 3; i++) { 964 request = server.takeRequest(); 965 assertEquals("POST / HTTP/1.1", request.getRequestLine()); 966 assertContains(request.getHeaders(), "Authorization: Basic " 967 + "dXNlcm5hbWU6cGFzc3dvcmQ="); // "dXNl..." == base64("username:password") 968 assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody())); 969 } 970 } 971 972 public void testAuthenticateWithGet() throws Exception { 973 MockResponse pleaseAuthenticate = new MockResponse() 974 .setResponseCode(401) 975 .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") 976 .setBody("Please authenticate."); 977 // fail auth three times... 978 server.enqueue(pleaseAuthenticate); 979 server.enqueue(pleaseAuthenticate); 980 server.enqueue(pleaseAuthenticate); 981 // ...then succeed the fourth time 982 server.enqueue(new MockResponse().setBody("Successful auth!")); 983 server.play(); 984 985 Authenticator.setDefault(SIMPLE_AUTHENTICATOR); 986 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 987 assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 988 989 // no authorization header for the first request... 990 RecordedRequest request = server.takeRequest(); 991 assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*"); 992 993 // ...but the three requests that follow requests include an authorization header 994 for (int i = 0; i < 3; i++) { 995 request = server.takeRequest(); 996 assertEquals("GET / HTTP/1.1", request.getRequestLine()); 997 assertContains(request.getHeaders(), "Authorization: Basic " 998 + "dXNlcm5hbWU6cGFzc3dvcmQ="); // "dXNl..." == base64("username:password") 999 } 1000 } 1001 1002 public void testRedirectedWithChunkedEncoding() throws Exception { 1003 testRedirected(TransferKind.CHUNKED, true); 1004 } 1005 1006 public void testRedirectedWithContentLengthHeader() throws Exception { 1007 testRedirected(TransferKind.FIXED_LENGTH, true); 1008 } 1009 1010 public void testRedirectedWithNoLengthHeaders() throws Exception { 1011 testRedirected(TransferKind.END_OF_STREAM, false); 1012 } 1013 1014 private void testRedirected(TransferKind transferKind, boolean reuse) throws Exception { 1015 MockResponse response = new MockResponse() 1016 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1017 .addHeader("Location: /foo"); 1018 transferKind.setBody(response, "This page has moved!", 10); 1019 server.enqueue(response); 1020 server.enqueue(new MockResponse().setBody("This is the new location!")); 1021 server.play(); 1022 1023 URLConnection connection = server.getUrl("/").openConnection(); 1024 assertEquals("This is the new location!", 1025 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1026 1027 RecordedRequest first = server.takeRequest(); 1028 assertEquals("GET / HTTP/1.1", first.getRequestLine()); 1029 RecordedRequest retry = server.takeRequest(); 1030 assertEquals("GET /foo HTTP/1.1", retry.getRequestLine()); 1031 if (reuse) { 1032 assertEquals("Expected connection reuse", 1, retry.getSequenceNumber()); 1033 } 1034 } 1035 1036 public void testRedirectedOnHttps() throws IOException, InterruptedException { 1037 TestSSLContext testSSLContext = TestSSLContext.create(); 1038 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 1039 server.enqueue(new MockResponse() 1040 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1041 .addHeader("Location: /foo") 1042 .setBody("This page has moved!")); 1043 server.enqueue(new MockResponse().setBody("This is the new location!")); 1044 server.play(); 1045 1046 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 1047 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 1048 assertEquals("This is the new location!", 1049 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1050 1051 RecordedRequest first = server.takeRequest(); 1052 assertEquals("GET / HTTP/1.1", first.getRequestLine()); 1053 RecordedRequest retry = server.takeRequest(); 1054 assertEquals("GET /foo HTTP/1.1", retry.getRequestLine()); 1055 assertEquals("Expected connection reuse", 1, retry.getSequenceNumber()); 1056 } 1057 1058 public void testNotRedirectedFromHttpsToHttp() throws IOException, InterruptedException { 1059 TestSSLContext testSSLContext = TestSSLContext.create(); 1060 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 1061 server.enqueue(new MockResponse() 1062 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1063 .addHeader("Location: http://anyhost/foo") 1064 .setBody("This page has moved!")); 1065 server.play(); 1066 1067 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 1068 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 1069 assertEquals("This page has moved!", 1070 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1071 } 1072 1073 public void testNotRedirectedFromHttpToHttps() throws IOException, InterruptedException { 1074 server.enqueue(new MockResponse() 1075 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1076 .addHeader("Location: https://anyhost/foo") 1077 .setBody("This page has moved!")); 1078 server.play(); 1079 1080 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1081 assertEquals("This page has moved!", 1082 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1083 } 1084 1085 public void testRedirectToAnotherOriginServer() throws Exception { 1086 MockWebServer server2 = new MockWebServer(); 1087 server2.enqueue(new MockResponse().setBody("This is the 2nd server!")); 1088 server2.play(); 1089 1090 server.enqueue(new MockResponse() 1091 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1092 .addHeader("Location: " + server2.getUrl("/").toString()) 1093 .setBody("This page has moved!")); 1094 server.enqueue(new MockResponse().setBody("This is the first server again!")); 1095 server.play(); 1096 1097 URLConnection connection = server.getUrl("/").openConnection(); 1098 assertEquals("This is the 2nd server!", 1099 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1100 assertEquals(server2.getUrl("/"), connection.getURL()); 1101 1102 // make sure the first server was careful to recycle the connection 1103 assertEquals("This is the first server again!", 1104 readAscii(server.getUrl("/").openStream(), Integer.MAX_VALUE)); 1105 1106 RecordedRequest first = server.takeRequest(); 1107 assertContains(first.getHeaders(), "Host: localhost:" + server.getPort()); 1108 RecordedRequest second = server2.takeRequest(); 1109 assertContains(second.getHeaders(), "Host: localhost:" + server2.getPort()); 1110 RecordedRequest third = server.takeRequest(); 1111 assertEquals("Expected connection reuse", 1, third.getSequenceNumber()); 1112 1113 server2.shutdown(); 1114 } 1115 1116 public void testHttpsWithCustomTrustManager() throws Exception { 1117 RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); 1118 RecordingTrustManager trustManager = new RecordingTrustManager(); 1119 SSLContext sc = SSLContext.getInstance("TLS"); 1120 sc.init(null, new TrustManager[] { trustManager }, new java.security.SecureRandom()); 1121 1122 HostnameVerifier defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier(); 1123 HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier); 1124 SSLSocketFactory defaultSSLSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory(); 1125 HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); 1126 try { 1127 TestSSLContext testSSLContext = TestSSLContext.create(); 1128 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 1129 server.enqueue(new MockResponse().setBody("ABC")); 1130 server.enqueue(new MockResponse().setBody("DEF")); 1131 server.enqueue(new MockResponse().setBody("GHI")); 1132 server.play(); 1133 1134 URL url = server.getUrl("/"); 1135 assertEquals("ABC", readAscii(url.openStream(), Integer.MAX_VALUE)); 1136 assertEquals("DEF", readAscii(url.openStream(), Integer.MAX_VALUE)); 1137 assertEquals("GHI", readAscii(url.openStream(), Integer.MAX_VALUE)); 1138 1139 assertEquals(Arrays.asList("verify localhost"), hostnameVerifier.calls); 1140 assertEquals(Arrays.asList("checkServerTrusted [" 1141 + "CN=localhost 1, " 1142 + "CN=Test Intermediate Certificate Authority 1, " 1143 + "CN=Test Root Certificate Authority 1" 1144 + "] RSA"), 1145 trustManager.calls); 1146 } finally { 1147 HttpsURLConnection.setDefaultHostnameVerifier(defaultHostnameVerifier); 1148 HttpsURLConnection.setDefaultSSLSocketFactory(defaultSSLSocketFactory); 1149 } 1150 } 1151 1152 public void testConnectTimeouts() throws IOException { 1153 // 10.0.0.0 is non-routable and will time out on every network 1154 URLConnection urlConnection = new URL("http://10.0.0.0/").openConnection(); 1155 urlConnection.setConnectTimeout(1000); 1156 try { 1157 urlConnection.getInputStream(); 1158 fail(); 1159 } catch (SocketTimeoutException expected) { 1160 } 1161 } 1162 1163 public void testReadTimeouts() throws IOException { 1164 /* 1165 * This relies on the fact that MockWebServer doesn't close the 1166 * connection after a response has been sent. This causes the client to 1167 * try to read more bytes than are sent, which results in a timeout. 1168 */ 1169 MockResponse timeout = new MockResponse() 1170 .setBody("ABC") 1171 .clearHeaders() 1172 .addHeader("Content-Length: 4"); 1173 server.enqueue(timeout); 1174 server.play(); 1175 1176 URLConnection urlConnection = server.getUrl("/").openConnection(); 1177 urlConnection.setReadTimeout(1000); 1178 InputStream in = urlConnection.getInputStream(); 1179 assertEquals('A', in.read()); 1180 assertEquals('B', in.read()); 1181 assertEquals('C', in.read()); 1182 try { 1183 in.read(); // if Content-Length was accurate, this would return -1 immediately 1184 fail(); 1185 } catch (SocketTimeoutException expected) { 1186 } 1187 } 1188 1189 public void testSetChunkedEncodingAsRequestProperty() throws IOException, InterruptedException { 1190 server.enqueue(new MockResponse()); 1191 server.play(); 1192 1193 HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); 1194 urlConnection.setRequestProperty("Transfer-encoding", "chunked"); 1195 urlConnection.setDoOutput(true); 1196 urlConnection.getOutputStream().write("ABC".getBytes("UTF-8")); 1197 assertEquals(200, urlConnection.getResponseCode()); 1198 1199 RecordedRequest request = server.takeRequest(); 1200 assertEquals("ABC", new String(request.getBody(), "UTF-8")); 1201 } 1202 1203 public void testConnectionCloseInRequest() throws IOException, InterruptedException { 1204 server.enqueue(new MockResponse()); // server doesn't honor the connection: close header! 1205 server.enqueue(new MockResponse()); 1206 server.play(); 1207 1208 HttpURLConnection a = (HttpURLConnection) server.getUrl("/").openConnection(); 1209 a.setRequestProperty("Connection", "close"); 1210 assertEquals(200, a.getResponseCode()); 1211 1212 HttpURLConnection b = (HttpURLConnection) server.getUrl("/").openConnection(); 1213 assertEquals(200, b.getResponseCode()); 1214 1215 assertEquals(0, server.takeRequest().getSequenceNumber()); 1216 assertEquals("When connection: close is used, each request should get its own connection", 1217 0, server.takeRequest().getSequenceNumber()); 1218 } 1219 1220 public void testConnectionCloseInResponse() throws IOException, InterruptedException { 1221 server.enqueue(new MockResponse().addHeader("Connection: close")); 1222 server.enqueue(new MockResponse()); 1223 server.play(); 1224 1225 HttpURLConnection a = (HttpURLConnection) server.getUrl("/").openConnection(); 1226 assertEquals(200, a.getResponseCode()); 1227 1228 HttpURLConnection b = (HttpURLConnection) server.getUrl("/").openConnection(); 1229 assertEquals(200, b.getResponseCode()); 1230 1231 assertEquals(0, server.takeRequest().getSequenceNumber()); 1232 assertEquals("When connection: close is used, each request should get its own connection", 1233 0, server.takeRequest().getSequenceNumber()); 1234 } 1235 1236 public void testConnectionCloseWithRedirect() throws IOException, InterruptedException { 1237 MockResponse response = new MockResponse() 1238 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1239 .addHeader("Location: /foo") 1240 .addHeader("Connection: close"); 1241 server.enqueue(response); 1242 server.enqueue(new MockResponse().setBody("This is the new location!")); 1243 server.play(); 1244 1245 URLConnection connection = server.getUrl("/").openConnection(); 1246 assertEquals("This is the new location!", 1247 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1248 1249 assertEquals(0, server.takeRequest().getSequenceNumber()); 1250 assertEquals("When connection: close is used, each request should get its own connection", 1251 0, server.takeRequest().getSequenceNumber()); 1252 } 1253 1254 public void testResponseCodeDisagreesWithHeaders() throws IOException, InterruptedException { 1255 server.enqueue(new MockResponse() 1256 .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT) 1257 .setBody("This body is not allowed!")); 1258 server.play(); 1259 1260 URLConnection connection = server.getUrl("/").openConnection(); 1261 assertEquals("This body is not allowed!", 1262 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1263 } 1264 1265 /** 1266 * Encodes the response body using GZIP and adds the corresponding header. 1267 */ 1268 public byte[] gzip(byte[] bytes) throws IOException { 1269 ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); 1270 OutputStream gzippedOut = new GZIPOutputStream(bytesOut); 1271 gzippedOut.write(bytes); 1272 gzippedOut.close(); 1273 return bytesOut.toByteArray(); 1274 } 1275 1276 /** 1277 * Reads at most {@code limit} characters from {@code in} and asserts that 1278 * content equals {@code expected}. 1279 */ 1280 private void assertContent(String expected, URLConnection connection, int limit) 1281 throws IOException { 1282 assertEquals(expected, readAscii(connection.getInputStream(), limit)); 1283 ((HttpURLConnection) connection).disconnect(); 1284 } 1285 1286 private void assertContent(String expected, URLConnection connection) throws IOException { 1287 assertContent(expected, connection, Integer.MAX_VALUE); 1288 } 1289 1290 private void assertContains(List<String> headers, String header) { 1291 assertTrue(headers.toString(), headers.contains(header)); 1292 } 1293 1294 private void assertContainsNoneMatching(List<String> headers, String pattern) { 1295 for (String header : headers) { 1296 if (header.matches(pattern)) { 1297 fail("Header " + header + " matches " + pattern); 1298 } 1299 } 1300 } 1301 1302 private Set<String> newSet(String... elements) { 1303 return new HashSet<String>(Arrays.asList(elements)); 1304 } 1305 1306 enum TransferKind { 1307 CHUNKED() { 1308 @Override void setBody(MockResponse response, byte[] content, int chunkSize) 1309 throws IOException { 1310 response.setChunkedBody(content, chunkSize); 1311 } 1312 }, 1313 FIXED_LENGTH() { 1314 @Override void setBody(MockResponse response, byte[] content, int chunkSize) { 1315 response.setBody(content); 1316 } 1317 }, 1318 END_OF_STREAM() { 1319 @Override void setBody(MockResponse response, byte[] content, int chunkSize) { 1320 response.setBody(content); 1321 response.setDisconnectAtEnd(true); 1322 for (Iterator<String> h = response.getHeaders().iterator(); h.hasNext(); ) { 1323 if (h.next().startsWith("Content-Length:")) { 1324 h.remove(); 1325 break; 1326 } 1327 } 1328 } 1329 }; 1330 1331 abstract void setBody(MockResponse response, byte[] content, int chunkSize) 1332 throws IOException; 1333 1334 void setBody(MockResponse response, String content, int chunkSize) throws IOException { 1335 setBody(response, content.getBytes("UTF-8"), chunkSize); 1336 } 1337 } 1338 1339 private static class RecordingTrustManager implements X509TrustManager { 1340 private final List<String> calls = new ArrayList<String>(); 1341 1342 public X509Certificate[] getAcceptedIssuers() { 1343 calls.add("getAcceptedIssuers"); 1344 return new X509Certificate[] {}; 1345 } 1346 1347 public void checkClientTrusted(X509Certificate[] chain, String authType) 1348 throws CertificateException { 1349 calls.add("checkClientTrusted " + certificatesToString(chain) + " " + authType); 1350 } 1351 1352 public void checkServerTrusted(X509Certificate[] chain, String authType) 1353 throws CertificateException { 1354 calls.add("checkServerTrusted " + certificatesToString(chain) + " " + authType); 1355 } 1356 1357 private String certificatesToString(X509Certificate[] certificates) { 1358 List<String> result = new ArrayList<String>(); 1359 for (X509Certificate certificate : certificates) { 1360 result.add(certificate.getSubjectDN() + " " + certificate.getSerialNumber()); 1361 } 1362 return result.toString(); 1363 } 1364 } 1365 1366 private static class RecordingHostnameVerifier implements HostnameVerifier { 1367 private final List<String> calls = new ArrayList<String>(); 1368 1369 public boolean verify(String hostname, SSLSession session) { 1370 calls.add("verify " + hostname); 1371 return true; 1372 } 1373 } 1374} 1375