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