URLConnectionTest.java revision dbc42ad94ce37b3178e14b50496914c267d6cea6
1/* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package libcore.java.net; 18 19import java.io.BufferedReader; 20import java.io.ByteArrayOutputStream; 21import java.io.IOException; 22import java.io.InputStream; 23import java.io.InputStreamReader; 24import java.io.OutputStream; 25import java.net.Authenticator; 26import java.net.CacheRequest; 27import java.net.CacheResponse; 28import java.net.HttpRetryException; 29import java.net.HttpURLConnection; 30import java.net.InetAddress; 31import java.net.PasswordAuthentication; 32import java.net.Proxy; 33import java.net.ResponseCache; 34import java.net.ServerSocket; 35import java.net.Socket; 36import java.net.SocketTimeoutException; 37import java.net.URI; 38import java.net.URISyntaxException; 39import java.net.URL; 40import java.net.URLConnection; 41import java.security.cert.CertificateException; 42import java.security.cert.X509Certificate; 43import java.util.ArrayList; 44import java.util.Arrays; 45import java.util.Collections; 46import java.util.HashSet; 47import java.util.Iterator; 48import java.util.List; 49import java.util.Map; 50import java.util.Set; 51import java.util.concurrent.atomic.AtomicReference; 52import java.util.zip.GZIPInputStream; 53import java.util.zip.GZIPOutputStream; 54import javax.net.ssl.HostnameVerifier; 55import javax.net.ssl.HttpsURLConnection; 56import javax.net.ssl.SSLContext; 57import javax.net.ssl.SSLException; 58import javax.net.ssl.SSLSession; 59import javax.net.ssl.SSLSocketFactory; 60import javax.net.ssl.TrustManager; 61import javax.net.ssl.X509TrustManager; 62import libcore.javax.net.ssl.TestSSLContext; 63import tests.http.DefaultResponseCache; 64import tests.http.MockResponse; 65import tests.http.MockWebServer; 66import tests.http.RecordedRequest; 67 68public class URLConnectionTest extends junit.framework.TestCase { 69 70 private static final Authenticator SIMPLE_AUTHENTICATOR = new Authenticator() { 71 protected PasswordAuthentication getPasswordAuthentication() { 72 return new PasswordAuthentication("username", "password".toCharArray()); 73 } 74 }; 75 76 private MockWebServer server = new MockWebServer(); 77 private String hostname; 78 79 @Override protected void setUp() throws Exception { 80 super.setUp(); 81 hostname = InetAddress.getLocalHost().getHostName(); 82 } 83 84 @Override protected void tearDown() throws Exception { 85 ResponseCache.setDefault(null); 86 Authenticator.setDefault(null); 87 System.clearProperty("proxyHost"); 88 System.clearProperty("proxyPort"); 89 System.clearProperty("http.proxyHost"); 90 System.clearProperty("http.proxyPort"); 91 System.clearProperty("https.proxyHost"); 92 System.clearProperty("https.proxyPort"); 93 server.shutdown(); 94 super.tearDown(); 95 } 96 97 public void testRequestHeaders() throws IOException, InterruptedException { 98 server.enqueue(new MockResponse()); 99 server.play(); 100 101 HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); 102 urlConnection.addRequestProperty("D", "e"); 103 urlConnection.addRequestProperty("D", "f"); 104 Map<String, List<String>> requestHeaders = urlConnection.getRequestProperties(); 105 assertEquals(newSet("e", "f"), new HashSet<String>(requestHeaders.get("D"))); 106 try { 107 requestHeaders.put("G", Arrays.asList("h")); 108 fail("Modified an unmodifiable view."); 109 } catch (UnsupportedOperationException expected) { 110 } 111 try { 112 requestHeaders.get("D").add("i"); 113 fail("Modified an unmodifiable view."); 114 } catch (UnsupportedOperationException expected) { 115 } 116 try { 117 urlConnection.setRequestProperty(null, "j"); 118 fail(); 119 } catch (NullPointerException expected) { 120 } 121 try { 122 urlConnection.addRequestProperty(null, "k"); 123 fail(); 124 } catch (NullPointerException expected) { 125 } 126 urlConnection.setRequestProperty("NullValue", null); // should fail silently! 127 urlConnection.addRequestProperty("AnotherNullValue", null); // should fail silently! 128 129 urlConnection.getResponseCode(); 130 RecordedRequest request = server.takeRequest(); 131 assertContains(request.getHeaders(), "D: e"); 132 assertContains(request.getHeaders(), "D: f"); 133 assertContainsNoneMatching(request.getHeaders(), "NullValue.*"); 134 assertContainsNoneMatching(request.getHeaders(), "AnotherNullValue.*"); 135 assertContainsNoneMatching(request.getHeaders(), "G:.*"); 136 assertContainsNoneMatching(request.getHeaders(), "null:.*"); 137 138 try { 139 urlConnection.addRequestProperty("N", "o"); 140 fail("Set header after connect"); 141 } catch (IllegalStateException expected) { 142 } 143 try { 144 urlConnection.setRequestProperty("P", "q"); 145 fail("Set header after connect"); 146 } catch (IllegalStateException expected) { 147 } 148 } 149 150 public void testResponseHeaders() throws IOException, InterruptedException { 151 server.enqueue(new MockResponse() 152 .setStatus("HTTP/1.0 200 Fantastic") 153 .addHeader("A: b") 154 .addHeader("A: c") 155 .setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8)); 156 server.play(); 157 158 HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); 159 assertEquals(200, urlConnection.getResponseCode()); 160 assertEquals("Fantastic", urlConnection.getResponseMessage()); 161 Map<String, List<String>> responseHeaders = urlConnection.getHeaderFields(); 162 assertEquals(newSet("b", "c"), new HashSet<String>(responseHeaders.get("A"))); 163 try { 164 responseHeaders.put("N", Arrays.asList("o")); 165 fail("Modified an unmodifiable view."); 166 } catch (UnsupportedOperationException expected) { 167 } 168 try { 169 responseHeaders.get("A").add("d"); 170 fail("Modified an unmodifiable view."); 171 } catch (UnsupportedOperationException expected) { 172 } 173 } 174 175 // Check that if we don't read to the end of a response, the next request on the 176 // recycled connection doesn't get the unread tail of the first request's response. 177 // http://code.google.com/p/android/issues/detail?id=2939 178 public void test_2939() throws Exception { 179 MockResponse response = new MockResponse().setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8); 180 181 server.enqueue(response); 182 server.enqueue(response); 183 server.play(); 184 185 assertContent("ABCDE", server.getUrl("/").openConnection(), 5); 186 assertContent("ABCDE", server.getUrl("/").openConnection(), 5); 187 } 188 189 // Check that we recognize a few basic mime types by extension. 190 // http://code.google.com/p/android/issues/detail?id=10100 191 public void test_10100() throws Exception { 192 assertEquals("image/jpeg", URLConnection.guessContentTypeFromName("someFile.jpg")); 193 assertEquals("application/pdf", URLConnection.guessContentTypeFromName("stuff.pdf")); 194 } 195 196 public void testConnectionsArePooled() throws Exception { 197 MockResponse response = new MockResponse().setBody("ABCDEFGHIJKLMNOPQR"); 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 public void testChunkedConnectionsArePooled() throws Exception { 213 MockResponse response = new MockResponse().setChunkedBody("ABCDEFGHIJKLMNOPQR", 5); 214 215 server.enqueue(response); 216 server.enqueue(response); 217 server.enqueue(response); 218 server.play(); 219 220 assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/").openConnection()); 221 assertEquals(0, server.takeRequest().getSequenceNumber()); 222 assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/").openConnection()); 223 assertEquals(1, server.takeRequest().getSequenceNumber()); 224 assertContent("ABCDEFGHIJKLMNOPQR", server.getUrl("/").openConnection()); 225 assertEquals(2, server.takeRequest().getSequenceNumber()); 226 } 227 228 enum WriteKind { BYTE_BY_BYTE, SMALL_BUFFERS, LARGE_BUFFERS } 229 230 public void test_chunkedUpload_byteByByte() throws Exception { 231 doUpload(TransferKind.CHUNKED, WriteKind.BYTE_BY_BYTE); 232 } 233 234 public void test_chunkedUpload_smallBuffers() throws Exception { 235 doUpload(TransferKind.CHUNKED, WriteKind.SMALL_BUFFERS); 236 } 237 238 public void test_chunkedUpload_largeBuffers() throws Exception { 239 doUpload(TransferKind.CHUNKED, WriteKind.LARGE_BUFFERS); 240 } 241 242 public void test_fixedLengthUpload_byteByByte() throws Exception { 243 doUpload(TransferKind.FIXED_LENGTH, WriteKind.BYTE_BY_BYTE); 244 } 245 246 public void test_fixedLengthUpload_smallBuffers() throws Exception { 247 doUpload(TransferKind.FIXED_LENGTH, WriteKind.SMALL_BUFFERS); 248 } 249 250 public void test_fixedLengthUpload_largeBuffers() throws Exception { 251 doUpload(TransferKind.FIXED_LENGTH, WriteKind.LARGE_BUFFERS); 252 } 253 254 private void doUpload(TransferKind uploadKind, WriteKind writeKind) throws Exception { 255 int n = 512*1024; 256 server.setBodyLimit(0); 257 server.enqueue(new MockResponse()); 258 server.play(); 259 260 HttpURLConnection conn = (HttpURLConnection) server.getUrl("/").openConnection(); 261 conn.setDoOutput(true); 262 conn.setRequestMethod("POST"); 263 if (uploadKind == TransferKind.CHUNKED) { 264 conn.setChunkedStreamingMode(-1); 265 } else { 266 conn.setFixedLengthStreamingMode(n); 267 } 268 OutputStream out = conn.getOutputStream(); 269 if (writeKind == WriteKind.BYTE_BY_BYTE) { 270 for (int i = 0; i < n; ++i) { 271 out.write('x'); 272 } 273 } else { 274 byte[] buf = new byte[writeKind == WriteKind.SMALL_BUFFERS ? 256 : 64*1024]; 275 Arrays.fill(buf, (byte) 'x'); 276 for (int i = 0; i < n; i += buf.length) { 277 out.write(buf, 0, Math.min(buf.length, n - i)); 278 } 279 } 280 out.close(); 281 assertEquals(200, conn.getResponseCode()); 282 RecordedRequest request = server.takeRequest(); 283 assertEquals(n, request.getBodySize()); 284 if (uploadKind == TransferKind.CHUNKED) { 285 assertTrue(request.getChunkSizes().size() > 0); 286 } else { 287 assertTrue(request.getChunkSizes().isEmpty()); 288 } 289 } 290 291 /** 292 * Test that response caching is consistent with the RI and the spec. 293 * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.4 294 */ 295 public void test_responseCaching() throws Exception { 296 // Test each documented HTTP/1.1 code, plus the first unused value in each range. 297 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html 298 299 // We can't test 100 because it's not really a response. 300 // assertCached(false, 100); 301 assertCached(false, 101); 302 assertCached(false, 102); 303 assertCached(true, 200); 304 assertCached(false, 201); 305 assertCached(false, 202); 306 assertCached(true, 203); 307 assertCached(false, 204); 308 assertCached(false, 205); 309 assertCached(true, 206); 310 assertCached(false, 207); 311 // (See test_responseCaching_300.) 312 assertCached(true, 301); 313 for (int i = 302; i <= 308; ++i) { 314 assertCached(false, i); 315 } 316 for (int i = 400; i <= 406; ++i) { 317 assertCached(false, i); 318 } 319 // (See test_responseCaching_407.) 320 assertCached(false, 408); 321 assertCached(false, 409); 322 // (See test_responseCaching_410.) 323 for (int i = 411; i <= 418; ++i) { 324 assertCached(false, i); 325 } 326 for (int i = 500; i <= 506; ++i) { 327 assertCached(false, i); 328 } 329 } 330 331 public void test_responseCaching_300() throws Exception { 332 // TODO: fix this for android 333 assertCached(false, 300); 334 } 335 336 /** 337 * Response code 407 should only come from proxy servers. Android's client 338 * throws if it is sent by an origin server. 339 */ 340 public void testOriginServerSends407() throws Exception { 341 server.enqueue(new MockResponse().setResponseCode(407)); 342 server.play(); 343 344 URL url = server.getUrl("/"); 345 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 346 try { 347 conn.getResponseCode(); 348 fail(); 349 } catch (IOException expected) { 350 } 351 } 352 353 public void test_responseCaching_410() throws Exception { 354 // the HTTP spec permits caching 410s, but the RI doesn't. 355 assertCached(false, 410); 356 } 357 358 private void assertCached(boolean shouldPut, int responseCode) throws Exception { 359 server = new MockWebServer(); 360 server.enqueue(new MockResponse() 361 .setResponseCode(responseCode) 362 .setBody("ABCDE") 363 .addHeader("WWW-Authenticate: challenge")); 364 server.play(); 365 366 DefaultResponseCache responseCache = new DefaultResponseCache(); 367 ResponseCache.setDefault(responseCache); 368 URL url = server.getUrl("/"); 369 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 370 assertEquals(responseCode, conn.getResponseCode()); 371 372 // exhaust the content stream 373 try { 374 // TODO: remove special case once testUnauthorizedResponseHandling() is fixed 375 if (responseCode != 401) { 376 readAscii(conn.getInputStream(), Integer.MAX_VALUE); 377 } 378 } catch (IOException ignored) { 379 } 380 381 Set<URI> expectedCachedUris = shouldPut 382 ? Collections.singleton(url.toURI()) 383 : Collections.<URI>emptySet(); 384 assertEquals(Integer.toString(responseCode), 385 expectedCachedUris, responseCache.getContents().keySet()); 386 server.shutdown(); // tearDown() isn't sufficient; this test starts multiple servers 387 } 388 389 public void testConnectViaHttps() throws IOException, InterruptedException { 390 TestSSLContext testSSLContext = TestSSLContext.create(); 391 392 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 393 server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); 394 server.play(); 395 396 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/foo").openConnection(); 397 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 398 399 assertContent("this response comes via HTTPS", connection); 400 401 RecordedRequest request = server.takeRequest(); 402 assertEquals("GET /foo HTTP/1.1", request.getRequestLine()); 403 } 404 405 public void testConnectViaHttpsReusingConnections() throws IOException, InterruptedException { 406 TestSSLContext testSSLContext = TestSSLContext.create(); 407 408 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 409 server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); 410 server.enqueue(new MockResponse().setBody("another response via HTTPS")); 411 server.play(); 412 413 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 414 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 415 assertContent("this response comes via HTTPS", connection); 416 417 connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 418 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 419 assertContent("another response via HTTPS", connection); 420 421 assertEquals(0, server.takeRequest().getSequenceNumber()); 422 assertEquals(1, server.takeRequest().getSequenceNumber()); 423 } 424 425 public void testConnectViaHttpsReusingConnectionsDifferentFactories() 426 throws IOException, InterruptedException { 427 TestSSLContext testSSLContext = TestSSLContext.create(); 428 429 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 430 server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); 431 server.enqueue(new MockResponse().setBody("another response via HTTPS")); 432 server.play(); 433 434 // install a custom SSL socket factory so the server can be authorized 435 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 436 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 437 assertContent("this response comes via HTTPS", connection); 438 439 connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 440 try { 441 readAscii(connection.getInputStream(), Integer.MAX_VALUE); 442 fail("without an SSL socket factory, the connection should fail"); 443 } catch (SSLException expected) { 444 } 445 } 446 447 public void testConnectViaHttpsWithSSLFallback() throws IOException, InterruptedException { 448 TestSSLContext testSSLContext = TestSSLContext.create(); 449 450 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 451 server.enqueue(new MockResponse().setDisconnectAtStart(true)); 452 server.enqueue(new MockResponse().setBody("this response comes via SSL")); 453 server.play(); 454 455 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/foo").openConnection(); 456 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 457 458 assertContent("this response comes via SSL", connection); 459 460 RecordedRequest request = server.takeRequest(); 461 assertEquals("GET /foo HTTP/1.1", request.getRequestLine()); 462 } 463 464 public void testConnectViaProxyUsingProxyArg() throws Exception { 465 testConnectViaProxy(ProxyConfig.CREATE_ARG); 466 } 467 468 public void testConnectViaProxyUsingProxySystemProperty() throws Exception { 469 testConnectViaProxy(ProxyConfig.PROXY_SYSTEM_PROPERTY); 470 } 471 472 public void testConnectViaProxyUsingHttpProxySystemProperty() throws Exception { 473 testConnectViaProxy(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY); 474 } 475 476 private void testConnectViaProxy(ProxyConfig proxyConfig) throws Exception { 477 MockResponse mockResponse = new MockResponse().setBody("this response comes via a proxy"); 478 server.enqueue(mockResponse); 479 server.play(); 480 481 URL url = new URL("http://android.com/foo"); 482 HttpURLConnection connection = proxyConfig.connect(server, url); 483 assertContent("this response comes via a proxy", connection); 484 485 RecordedRequest request = server.takeRequest(); 486 assertEquals("GET http://android.com/foo HTTP/1.1", request.getRequestLine()); 487 assertContains(request.getHeaders(), "Host: android.com"); 488 } 489 490 public void testContentDisagreesWithContentLengthHeader() throws IOException { 491 server.enqueue(new MockResponse() 492 .setBody("abc\r\nYOU SHOULD NOT SEE THIS") 493 .clearHeaders() 494 .addHeader("Content-Length: 3")); 495 server.play(); 496 497 assertContent("abc", server.getUrl("/").openConnection()); 498 } 499 500 public void testContentDisagreesWithChunkedHeader() throws IOException { 501 MockResponse mockResponse = new MockResponse(); 502 mockResponse.setChunkedBody("abc", 3); 503 ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); 504 bytesOut.write(mockResponse.getBody()); 505 bytesOut.write("\r\nYOU SHOULD NOT SEE THIS".getBytes()); 506 mockResponse.setBody(bytesOut.toByteArray()); 507 mockResponse.clearHeaders(); 508 mockResponse.addHeader("Transfer-encoding: chunked"); 509 510 server.enqueue(mockResponse); 511 server.play(); 512 513 assertContent("abc", server.getUrl("/").openConnection()); 514 } 515 516 public void testConnectViaHttpProxyToHttpsUsingProxyArgWithNoProxy() throws Exception { 517 testConnectViaDirectProxyToHttps(ProxyConfig.NO_PROXY); 518 } 519 520 public void testConnectViaHttpProxyToHttpsUsingHttpProxySystemProperty() throws Exception { 521 // https should not use http proxy 522 testConnectViaDirectProxyToHttps(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY); 523 } 524 525 private void testConnectViaDirectProxyToHttps(ProxyConfig proxyConfig) throws Exception { 526 TestSSLContext testSSLContext = TestSSLContext.create(); 527 528 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 529 server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); 530 server.play(); 531 532 URL url = server.getUrl("/foo"); 533 HttpsURLConnection connection = (HttpsURLConnection) proxyConfig.connect(server, url); 534 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 535 536 assertContent("this response comes via HTTPS", connection); 537 538 RecordedRequest request = server.takeRequest(); 539 assertEquals("GET /foo HTTP/1.1", request.getRequestLine()); 540 } 541 542 543 public void testConnectViaHttpProxyToHttpsUsingProxyArg() throws Exception { 544 testConnectViaHttpProxyToHttps(ProxyConfig.CREATE_ARG); 545 } 546 547 public void testConnectViaHttpProxyToHttpsUsingHttpsProxySystemProperty() throws Exception { 548 testConnectViaHttpProxyToHttps(ProxyConfig.HTTPS_PROXY_SYSTEM_PROPERTY); 549 } 550 551 /** 552 * We were verifying the wrong hostname when connecting to an HTTPS site 553 * through a proxy. http://b/3097277 554 */ 555 private void testConnectViaHttpProxyToHttps(ProxyConfig proxyConfig) throws Exception { 556 TestSSLContext testSSLContext = TestSSLContext.create(); 557 RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); 558 559 server.useHttps(testSSLContext.serverContext.getSocketFactory(), true); 560 server.enqueue(new MockResponse().clearHeaders()); // for CONNECT 561 server.enqueue(new MockResponse().setBody("this response comes via a secure proxy")); 562 server.play(); 563 564 URL url = new URL("https://android.com/foo"); 565 HttpsURLConnection connection = (HttpsURLConnection) proxyConfig.connect(server, url); 566 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 567 connection.setHostnameVerifier(hostnameVerifier); 568 569 assertContent("this response comes via a secure proxy", connection); 570 571 RecordedRequest connect = server.takeRequest(); 572 assertEquals("Connect line failure on proxy", 573 "CONNECT android.com:443 HTTP/1.1", connect.getRequestLine()); 574 assertContains(connect.getHeaders(), "Host: android.com"); 575 576 RecordedRequest get = server.takeRequest(); 577 assertEquals("GET /foo HTTP/1.1", get.getRequestLine()); 578 assertContains(get.getHeaders(), "Host: android.com"); 579 assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls); 580 } 581 582 /** 583 * Test which headers are sent unencrypted to the HTTP proxy. 584 */ 585 public void testProxyConnectIncludesProxyHeadersOnly() 586 throws IOException, InterruptedException { 587 RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); 588 TestSSLContext testSSLContext = TestSSLContext.create(); 589 590 server.useHttps(testSSLContext.serverContext.getSocketFactory(), true); 591 server.enqueue(new MockResponse().clearHeaders()); // for CONNECT 592 server.enqueue(new MockResponse().setBody("encrypted response from the origin server")); 593 server.play(); 594 595 URL url = new URL("https://android.com/foo"); 596 HttpsURLConnection connection = (HttpsURLConnection) url.openConnection( 597 server.toProxyAddress()); 598 connection.addRequestProperty("Private", "Secret"); 599 connection.addRequestProperty("Proxy-Authorization", "bar"); 600 connection.addRequestProperty("User-Agent", "baz"); 601 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 602 connection.setHostnameVerifier(hostnameVerifier); 603 assertContent("encrypted response from the origin server", connection); 604 605 RecordedRequest connect = server.takeRequest(); 606 assertContainsNoneMatching(connect.getHeaders(), "Private.*"); 607 assertContains(connect.getHeaders(), "Proxy-Authorization: bar"); 608 assertContains(connect.getHeaders(), "User-Agent: baz"); 609 assertContains(connect.getHeaders(), "Host: android.com"); 610 assertContains(connect.getHeaders(), "Proxy-Connection: Keep-Alive"); 611 612 RecordedRequest get = server.takeRequest(); 613 assertContains(get.getHeaders(), "Private: Secret"); 614 assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls); 615 } 616 617 public void testDisconnectedConnection() throws IOException { 618 server.enqueue(new MockResponse().setBody("ABCDEFGHIJKLMNOPQR")); 619 server.play(); 620 621 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 622 InputStream in = connection.getInputStream(); 623 assertEquals('A', (char) in.read()); 624 connection.disconnect(); 625 try { 626 in.read(); 627 fail("Expected a connection closed exception"); 628 } catch (IOException expected) { 629 } 630 } 631 632 public void testResponseCachingAndInputStreamSkipWithFixedLength() throws IOException { 633 testResponseCaching(TransferKind.FIXED_LENGTH); 634 } 635 636 public void testResponseCachingAndInputStreamSkipWithChunkedEncoding() throws IOException { 637 testResponseCaching(TransferKind.CHUNKED); 638 } 639 640 public void testResponseCachingAndInputStreamSkipWithNoLengthHeaders() throws IOException { 641 testResponseCaching(TransferKind.END_OF_STREAM); 642 } 643 644 /** 645 * HttpURLConnection.getInputStream().skip(long) causes ResponseCache corruption 646 * http://code.google.com/p/android/issues/detail?id=8175 647 */ 648 private void testResponseCaching(TransferKind transferKind) throws IOException { 649 MockResponse response = new MockResponse(); 650 transferKind.setBody(response, "I love puppies but hate spiders", 1); 651 server.enqueue(response); 652 server.play(); 653 654 DefaultResponseCache cache = new DefaultResponseCache(); 655 ResponseCache.setDefault(cache); 656 657 // Make sure that calling skip() doesn't omit bytes from the cache. 658 URLConnection urlConnection = server.getUrl("/").openConnection(); 659 InputStream in = urlConnection.getInputStream(); 660 assertEquals("I love ", readAscii(in, "I love ".length())); 661 reliableSkip(in, "puppies but hate ".length()); 662 assertEquals("spiders", readAscii(in, "spiders".length())); 663 assertEquals(-1, in.read()); 664 in.close(); 665 assertEquals(1, cache.getSuccessCount()); 666 assertEquals(0, cache.getAbortCount()); 667 668 urlConnection = server.getUrl("/").openConnection(); // this response is cached! 669 in = urlConnection.getInputStream(); 670 assertEquals("I love puppies but hate spiders", 671 readAscii(in, "I love puppies but hate spiders".length())); 672 assertEquals(-1, in.read()); 673 assertEquals(1, cache.getMissCount()); 674 assertEquals(1, cache.getHitCount()); 675 assertEquals(1, cache.getSuccessCount()); 676 assertEquals(0, cache.getAbortCount()); 677 } 678 679 public void testResponseCacheRequestHeaders() throws IOException, URISyntaxException { 680 server.enqueue(new MockResponse().setBody("ABC")); 681 server.play(); 682 683 final AtomicReference<Map<String, List<String>>> requestHeadersRef 684 = new AtomicReference<Map<String, List<String>>>(); 685 ResponseCache.setDefault(new ResponseCache() { 686 @Override public CacheResponse get(URI uri, String requestMethod, 687 Map<String, List<String>> requestHeaders) throws IOException { 688 requestHeadersRef.set(requestHeaders); 689 return null; 690 } 691 @Override public CacheRequest put(URI uri, URLConnection conn) throws IOException { 692 return null; 693 } 694 }); 695 696 URL url = server.getUrl("/"); 697 URLConnection urlConnection = url.openConnection(); 698 urlConnection.addRequestProperty("A", "android"); 699 readAscii(urlConnection.getInputStream(), Integer.MAX_VALUE); 700 assertEquals(Arrays.asList("android"), requestHeadersRef.get().get("A")); 701 } 702 703 private void reliableSkip(InputStream in, int length) throws IOException { 704 while (length > 0) { 705 length -= in.skip(length); 706 } 707 } 708 709 /** 710 * Reads {@code count} characters from the stream. If the stream is 711 * exhausted before {@code count} characters can be read, the remaining 712 * characters are returned and the stream is closed. 713 */ 714 private String readAscii(InputStream in, int count) throws IOException { 715 StringBuilder result = new StringBuilder(); 716 for (int i = 0; i < count; i++) { 717 int value = in.read(); 718 if (value == -1) { 719 in.close(); 720 break; 721 } 722 result.append((char) value); 723 } 724 return result.toString(); 725 } 726 727 public void testServerDisconnectsPrematurelyWithContentLengthHeader() throws IOException { 728 testServerPrematureDisconnect(TransferKind.FIXED_LENGTH); 729 } 730 731 public void testServerDisconnectsPrematurelyWithChunkedEncoding() throws IOException { 732 testServerPrematureDisconnect(TransferKind.CHUNKED); 733 } 734 735 public void testServerDisconnectsPrematurelyWithNoLengthHeaders() throws IOException { 736 /* 737 * Intentionally empty. This case doesn't make sense because there's no 738 * such thing as a premature disconnect when the disconnect itself 739 * indicates the end of the data stream. 740 */ 741 } 742 743 private void testServerPrematureDisconnect(TransferKind transferKind) throws IOException { 744 MockResponse response = new MockResponse(); 745 transferKind.setBody(response, "ABCDE\nFGHIJKLMNOPQRSTUVWXYZ", 16); 746 server.enqueue(truncateViolently(response, 16)); 747 server.enqueue(new MockResponse().setBody("Request #2")); 748 server.play(); 749 750 DefaultResponseCache cache = new DefaultResponseCache(); 751 ResponseCache.setDefault(cache); 752 753 BufferedReader reader = new BufferedReader(new InputStreamReader( 754 server.getUrl("/").openConnection().getInputStream())); 755 assertEquals("ABCDE", reader.readLine()); 756 try { 757 reader.readLine(); 758 fail("This implementation silently ignored a truncated HTTP body."); 759 } catch (IOException expected) { 760 } 761 762 assertEquals(1, cache.getAbortCount()); 763 assertEquals(0, cache.getSuccessCount()); 764 assertContent("Request #2", server.getUrl("/").openConnection()); 765 assertEquals(1, cache.getAbortCount()); 766 assertEquals(1, cache.getSuccessCount()); 767 } 768 769 public void testClientPrematureDisconnectWithContentLengthHeader() throws IOException { 770 testClientPrematureDisconnect(TransferKind.FIXED_LENGTH); 771 } 772 773 public void testClientPrematureDisconnectWithChunkedEncoding() throws IOException { 774 testClientPrematureDisconnect(TransferKind.CHUNKED); 775 } 776 777 public void testClientPrematureDisconnectWithNoLengthHeaders() throws IOException { 778 testClientPrematureDisconnect(TransferKind.END_OF_STREAM); 779 } 780 781 private void testClientPrematureDisconnect(TransferKind transferKind) throws IOException { 782 MockResponse response = new MockResponse(); 783 transferKind.setBody(response, "ABCDE\nFGHIJKLMNOPQRSTUVWXYZ", 1024); 784 server.enqueue(response); 785 server.enqueue(new MockResponse().setBody("Request #2")); 786 server.play(); 787 788 DefaultResponseCache cache = new DefaultResponseCache(); 789 ResponseCache.setDefault(cache); 790 791 InputStream in = server.getUrl("/").openConnection().getInputStream(); 792 assertEquals("ABCDE", readAscii(in, 5)); 793 in.close(); 794 try { 795 in.read(); 796 fail("Expected an IOException because the stream is closed."); 797 } catch (IOException expected) { 798 } 799 800 assertEquals(1, cache.getAbortCount()); 801 assertEquals(0, cache.getSuccessCount()); 802 assertContent("Request #2", server.getUrl("/").openConnection()); 803 assertEquals(1, cache.getAbortCount()); 804 assertEquals(1, cache.getSuccessCount()); 805 } 806 807 /** 808 * Shortens the body of {@code response} but not the corresponding headers. 809 * Only useful to test how clients respond to the premature conclusion of 810 * the HTTP body. 811 */ 812 private MockResponse truncateViolently(MockResponse response, int numBytesToKeep) { 813 response.setDisconnectAtEnd(true); 814 List<String> headers = new ArrayList<String>(response.getHeaders()); 815 response.setBody(Arrays.copyOfRange(response.getBody(), 0, numBytesToKeep)); 816 response.getHeaders().clear(); 817 response.getHeaders().addAll(headers); 818 return response; 819 } 820 821 public void testMarkAndResetWithContentLengthHeader() throws IOException { 822 testMarkAndReset(TransferKind.FIXED_LENGTH); 823 } 824 825 public void testMarkAndResetWithChunkedEncoding() throws IOException { 826 testMarkAndReset(TransferKind.CHUNKED); 827 } 828 829 public void testMarkAndResetWithNoLengthHeaders() throws IOException { 830 testMarkAndReset(TransferKind.END_OF_STREAM); 831 } 832 833 public void testMarkAndReset(TransferKind transferKind) throws IOException { 834 MockResponse response = new MockResponse(); 835 transferKind.setBody(response, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1024); 836 server.enqueue(response); 837 server.play(); 838 839 DefaultResponseCache cache = new DefaultResponseCache(); 840 ResponseCache.setDefault(cache); 841 842 InputStream in = server.getUrl("/").openConnection().getInputStream(); 843 assertFalse("This implementation claims to support mark().", in.markSupported()); 844 in.mark(5); 845 assertEquals("ABCDE", readAscii(in, 5)); 846 try { 847 in.reset(); 848 fail(); 849 } catch (IOException expected) { 850 } 851 assertEquals("FGHIJKLMNOPQRSTUVWXYZ", readAscii(in, Integer.MAX_VALUE)); 852 853 assertContent("ABCDEFGHIJKLMNOPQRSTUVWXYZ", server.getUrl("/").openConnection()); 854 assertEquals(1, cache.getSuccessCount()); 855 assertEquals(1, cache.getHitCount()); 856 } 857 858 /** 859 * We've had a bug where we forget the HTTP response when we see response 860 * code 401. This causes a new HTTP request to be issued for every call into 861 * the URLConnection. 862 */ 863 public void testUnauthorizedResponseHandling() throws IOException { 864 MockResponse response = new MockResponse() 865 .addHeader("WWW-Authenticate: challenge") 866 .setResponseCode(401) // UNAUTHORIZED 867 .setBody("Unauthorized"); 868 server.enqueue(response); 869 server.enqueue(response); 870 server.enqueue(response); 871 server.play(); 872 873 URL url = server.getUrl("/"); 874 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 875 876 assertEquals(401, conn.getResponseCode()); 877 assertEquals(401, conn.getResponseCode()); 878 assertEquals(401, conn.getResponseCode()); 879 assertEquals(1, server.getRequestCount()); 880 } 881 882 public void testNonHexChunkSize() throws IOException { 883 server.enqueue(new MockResponse() 884 .setBody("5\r\nABCDE\r\nG\r\nFGHIJKLMNOPQRSTU\r\n0\r\n\r\n") 885 .clearHeaders() 886 .addHeader("Transfer-encoding: chunked")); 887 server.play(); 888 889 URLConnection connection = server.getUrl("/").openConnection(); 890 try { 891 readAscii(connection.getInputStream(), Integer.MAX_VALUE); 892 fail(); 893 } catch (IOException e) { 894 } 895 } 896 897 public void testMissingChunkBody() throws IOException { 898 server.enqueue(new MockResponse() 899 .setBody("5") 900 .clearHeaders() 901 .addHeader("Transfer-encoding: chunked") 902 .setDisconnectAtEnd(true)); 903 server.play(); 904 905 URLConnection connection = server.getUrl("/").openConnection(); 906 try { 907 readAscii(connection.getInputStream(), Integer.MAX_VALUE); 908 fail(); 909 } catch (IOException e) { 910 } 911 } 912 913 /** 914 * This test checks whether connections are gzipped by default. This 915 * behavior in not required by the API, so a failure of this test does not 916 * imply a bug in the implementation. 917 */ 918 public void testGzipEncodingEnabledByDefault() throws IOException, InterruptedException { 919 server.enqueue(new MockResponse() 920 .setBody(gzip("ABCABCABC".getBytes("UTF-8"))) 921 .addHeader("Content-Encoding: gzip")); 922 server.play(); 923 924 URLConnection connection = server.getUrl("/").openConnection(); 925 assertEquals("ABCABCABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 926 assertNull(connection.getContentEncoding()); 927 928 RecordedRequest request = server.takeRequest(); 929 assertContains(request.getHeaders(), "Accept-Encoding: gzip"); 930 } 931 932 public void testClientConfiguredGzipContentEncoding() throws Exception { 933 server.enqueue(new MockResponse() 934 .setBody(gzip("ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes("UTF-8"))) 935 .addHeader("Content-Encoding: gzip")); 936 server.play(); 937 938 URLConnection connection = server.getUrl("/").openConnection(); 939 connection.addRequestProperty("Accept-Encoding", "gzip"); 940 InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream()); 941 assertEquals("ABCDEFGHIJKLMNOPQRSTUVWXYZ", readAscii(gunzippedIn, Integer.MAX_VALUE)); 942 943 RecordedRequest request = server.takeRequest(); 944 assertContains(request.getHeaders(), "Accept-Encoding: gzip"); 945 } 946 947 public void testGzipAndConnectionReuseWithFixedLength() throws Exception { 948 testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.FIXED_LENGTH); 949 } 950 951 public void testGzipAndConnectionReuseWithChunkedEncoding() throws Exception { 952 testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.CHUNKED); 953 } 954 955 public void testClientConfiguredCustomContentEncoding() throws Exception { 956 server.enqueue(new MockResponse() 957 .setBody("ABCDE") 958 .addHeader("Content-Encoding: custom")); 959 server.play(); 960 961 URLConnection connection = server.getUrl("/").openConnection(); 962 connection.addRequestProperty("Accept-Encoding", "custom"); 963 assertEquals("ABCDE", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 964 965 RecordedRequest request = server.takeRequest(); 966 assertContains(request.getHeaders(), "Accept-Encoding: custom"); 967 } 968 969 /** 970 * Test a bug where gzip input streams weren't exhausting the input stream, 971 * which corrupted the request that followed. 972 * http://code.google.com/p/android/issues/detail?id=7059 973 */ 974 private void testClientConfiguredGzipContentEncodingAndConnectionReuse( 975 TransferKind transferKind) throws Exception { 976 MockResponse responseOne = new MockResponse(); 977 responseOne.addHeader("Content-Encoding: gzip"); 978 transferKind.setBody(responseOne, gzip("one (gzipped)".getBytes("UTF-8")), 5); 979 server.enqueue(responseOne); 980 MockResponse responseTwo = new MockResponse(); 981 transferKind.setBody(responseTwo, "two (identity)", 5); 982 server.enqueue(responseTwo); 983 server.play(); 984 985 URLConnection connection = server.getUrl("/").openConnection(); 986 connection.addRequestProperty("Accept-Encoding", "gzip"); 987 InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream()); 988 assertEquals("one (gzipped)", readAscii(gunzippedIn, Integer.MAX_VALUE)); 989 assertEquals(0, server.takeRequest().getSequenceNumber()); 990 991 connection = server.getUrl("/").openConnection(); 992 assertEquals("two (identity)", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 993 assertEquals(1, server.takeRequest().getSequenceNumber()); 994 } 995 996 /** 997 * Obnoxiously test that the chunk sizes transmitted exactly equal the 998 * requested data+chunk header size. Although setChunkedStreamingMode() 999 * isn't specific about whether the size applies to the data or the 1000 * complete chunk, the RI interprets it as a complete chunk. 1001 */ 1002 public void testSetChunkedStreamingMode() throws IOException, InterruptedException { 1003 server.enqueue(new MockResponse()); 1004 server.play(); 1005 1006 HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); 1007 urlConnection.setChunkedStreamingMode(8); 1008 urlConnection.setDoOutput(true); 1009 OutputStream outputStream = urlConnection.getOutputStream(); 1010 outputStream.write("ABCDEFGHIJKLMNOPQ".getBytes("US-ASCII")); 1011 assertEquals(200, urlConnection.getResponseCode()); 1012 1013 RecordedRequest request = server.takeRequest(); 1014 assertEquals("ABCDEFGHIJKLMNOPQ", new String(request.getBody(), "US-ASCII")); 1015 assertEquals(Arrays.asList(3, 3, 3, 3, 3, 2), request.getChunkSizes()); 1016 } 1017 1018 public void testAuthenticateWithFixedLengthStreaming() throws Exception { 1019 testAuthenticateWithStreamingPost(StreamingMode.FIXED_LENGTH); 1020 } 1021 1022 public void testAuthenticateWithChunkedStreaming() throws Exception { 1023 testAuthenticateWithStreamingPost(StreamingMode.CHUNKED); 1024 } 1025 1026 private void testAuthenticateWithStreamingPost(StreamingMode streamingMode) throws Exception { 1027 MockResponse pleaseAuthenticate = new MockResponse() 1028 .setResponseCode(401) 1029 .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") 1030 .setBody("Please authenticate."); 1031 server.enqueue(pleaseAuthenticate); 1032 server.play(); 1033 1034 Authenticator.setDefault(SIMPLE_AUTHENTICATOR); 1035 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1036 connection.setDoOutput(true); 1037 byte[] requestBody = { 'A', 'B', 'C', 'D' }; 1038 if (streamingMode == StreamingMode.FIXED_LENGTH) { 1039 connection.setFixedLengthStreamingMode(requestBody.length); 1040 } else if (streamingMode == StreamingMode.CHUNKED) { 1041 connection.setChunkedStreamingMode(0); 1042 } 1043 OutputStream outputStream = connection.getOutputStream(); 1044 outputStream.write(requestBody); 1045 outputStream.close(); 1046 try { 1047 connection.getInputStream(); 1048 fail(); 1049 } catch (HttpRetryException expected) { 1050 } 1051 1052 // no authorization header for the request... 1053 RecordedRequest request = server.takeRequest(); 1054 assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*"); 1055 assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody())); 1056 } 1057 1058 enum StreamingMode { 1059 FIXED_LENGTH, CHUNKED 1060 } 1061 1062 public void testAuthenticateWithPost() throws Exception { 1063 MockResponse pleaseAuthenticate = new MockResponse() 1064 .setResponseCode(401) 1065 .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") 1066 .setBody("Please authenticate."); 1067 // fail auth three times... 1068 server.enqueue(pleaseAuthenticate); 1069 server.enqueue(pleaseAuthenticate); 1070 server.enqueue(pleaseAuthenticate); 1071 // ...then succeed the fourth time 1072 server.enqueue(new MockResponse().setBody("Successful auth!")); 1073 server.play(); 1074 1075 Authenticator.setDefault(SIMPLE_AUTHENTICATOR); 1076 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1077 connection.setDoOutput(true); 1078 byte[] requestBody = { 'A', 'B', 'C', 'D' }; 1079 OutputStream outputStream = connection.getOutputStream(); 1080 outputStream.write(requestBody); 1081 outputStream.close(); 1082 assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1083 1084 // no authorization header for the first request... 1085 RecordedRequest request = server.takeRequest(); 1086 assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*"); 1087 1088 // ...but the three requests that follow include an authorization header 1089 for (int i = 0; i < 3; i++) { 1090 request = server.takeRequest(); 1091 assertEquals("POST / HTTP/1.1", request.getRequestLine()); 1092 assertContains(request.getHeaders(), "Authorization: Basic " 1093 + "dXNlcm5hbWU6cGFzc3dvcmQ="); // "dXNl..." == base64("username:password") 1094 assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody())); 1095 } 1096 } 1097 1098 public void testAuthenticateWithGet() throws Exception { 1099 MockResponse pleaseAuthenticate = new MockResponse() 1100 .setResponseCode(401) 1101 .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") 1102 .setBody("Please authenticate."); 1103 // fail auth three times... 1104 server.enqueue(pleaseAuthenticate); 1105 server.enqueue(pleaseAuthenticate); 1106 server.enqueue(pleaseAuthenticate); 1107 // ...then succeed the fourth time 1108 server.enqueue(new MockResponse().setBody("Successful auth!")); 1109 server.play(); 1110 1111 Authenticator.setDefault(SIMPLE_AUTHENTICATOR); 1112 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1113 assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1114 1115 // no authorization header for the first request... 1116 RecordedRequest request = server.takeRequest(); 1117 assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*"); 1118 1119 // ...but the three requests that follow requests include an authorization header 1120 for (int i = 0; i < 3; i++) { 1121 request = server.takeRequest(); 1122 assertEquals("GET / HTTP/1.1", request.getRequestLine()); 1123 assertContains(request.getHeaders(), "Authorization: Basic " 1124 + "dXNlcm5hbWU6cGFzc3dvcmQ="); // "dXNl..." == base64("username:password") 1125 } 1126 } 1127 1128 public void testRedirectedWithChunkedEncoding() throws Exception { 1129 testRedirected(TransferKind.CHUNKED, true); 1130 } 1131 1132 public void testRedirectedWithContentLengthHeader() throws Exception { 1133 testRedirected(TransferKind.FIXED_LENGTH, true); 1134 } 1135 1136 public void testRedirectedWithNoLengthHeaders() throws Exception { 1137 testRedirected(TransferKind.END_OF_STREAM, false); 1138 } 1139 1140 private void testRedirected(TransferKind transferKind, boolean reuse) throws Exception { 1141 MockResponse response = new MockResponse() 1142 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1143 .addHeader("Location: /foo"); 1144 transferKind.setBody(response, "This page has moved!", 10); 1145 server.enqueue(response); 1146 server.enqueue(new MockResponse().setBody("This is the new location!")); 1147 server.play(); 1148 1149 URLConnection connection = server.getUrl("/").openConnection(); 1150 assertEquals("This is the new location!", 1151 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1152 1153 RecordedRequest first = server.takeRequest(); 1154 assertEquals("GET / HTTP/1.1", first.getRequestLine()); 1155 RecordedRequest retry = server.takeRequest(); 1156 assertEquals("GET /foo HTTP/1.1", retry.getRequestLine()); 1157 if (reuse) { 1158 assertEquals("Expected connection reuse", 1, retry.getSequenceNumber()); 1159 } 1160 } 1161 1162 public void testRedirectedOnHttps() throws IOException, InterruptedException { 1163 TestSSLContext testSSLContext = TestSSLContext.create(); 1164 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 1165 server.enqueue(new MockResponse() 1166 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1167 .addHeader("Location: /foo") 1168 .setBody("This page has moved!")); 1169 server.enqueue(new MockResponse().setBody("This is the new location!")); 1170 server.play(); 1171 1172 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 1173 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 1174 assertEquals("This is the new location!", 1175 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1176 1177 RecordedRequest first = server.takeRequest(); 1178 assertEquals("GET / HTTP/1.1", first.getRequestLine()); 1179 RecordedRequest retry = server.takeRequest(); 1180 assertEquals("GET /foo HTTP/1.1", retry.getRequestLine()); 1181 assertEquals("Expected connection reuse", 1, retry.getSequenceNumber()); 1182 } 1183 1184 public void testNotRedirectedFromHttpsToHttp() throws IOException, InterruptedException { 1185 TestSSLContext testSSLContext = TestSSLContext.create(); 1186 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 1187 server.enqueue(new MockResponse() 1188 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1189 .addHeader("Location: http://anyhost/foo") 1190 .setBody("This page has moved!")); 1191 server.play(); 1192 1193 HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); 1194 connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); 1195 assertEquals("This page has moved!", 1196 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1197 } 1198 1199 public void testNotRedirectedFromHttpToHttps() throws IOException, InterruptedException { 1200 server.enqueue(new MockResponse() 1201 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1202 .addHeader("Location: https://anyhost/foo") 1203 .setBody("This page has moved!")); 1204 server.play(); 1205 1206 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1207 assertEquals("This page has moved!", 1208 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1209 } 1210 1211 public void testRedirectToAnotherOriginServer() throws Exception { 1212 MockWebServer server2 = new MockWebServer(); 1213 server2.enqueue(new MockResponse().setBody("This is the 2nd server!")); 1214 server2.play(); 1215 1216 server.enqueue(new MockResponse() 1217 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1218 .addHeader("Location: " + server2.getUrl("/").toString()) 1219 .setBody("This page has moved!")); 1220 server.enqueue(new MockResponse().setBody("This is the first server again!")); 1221 server.play(); 1222 1223 URLConnection connection = server.getUrl("/").openConnection(); 1224 assertEquals("This is the 2nd server!", 1225 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1226 assertEquals(server2.getUrl("/"), connection.getURL()); 1227 1228 // make sure the first server was careful to recycle the connection 1229 assertEquals("This is the first server again!", 1230 readAscii(server.getUrl("/").openStream(), Integer.MAX_VALUE)); 1231 1232 RecordedRequest first = server.takeRequest(); 1233 assertContains(first.getHeaders(), "Host: " + hostname + ":" + server.getPort()); 1234 RecordedRequest second = server2.takeRequest(); 1235 assertContains(second.getHeaders(), "Host: " + hostname + ":" + server2.getPort()); 1236 RecordedRequest third = server.takeRequest(); 1237 assertEquals("Expected connection reuse", 1, third.getSequenceNumber()); 1238 1239 server2.shutdown(); 1240 } 1241 1242 public void testHttpsWithCustomTrustManager() throws Exception { 1243 RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); 1244 RecordingTrustManager trustManager = new RecordingTrustManager(); 1245 SSLContext sc = SSLContext.getInstance("TLS"); 1246 sc.init(null, new TrustManager[] { trustManager }, new java.security.SecureRandom()); 1247 1248 HostnameVerifier defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier(); 1249 HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier); 1250 SSLSocketFactory defaultSSLSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory(); 1251 HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); 1252 try { 1253 TestSSLContext testSSLContext = TestSSLContext.create(); 1254 server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); 1255 server.enqueue(new MockResponse().setBody("ABC")); 1256 server.enqueue(new MockResponse().setBody("DEF")); 1257 server.enqueue(new MockResponse().setBody("GHI")); 1258 server.play(); 1259 1260 URL url = server.getUrl("/"); 1261 assertEquals("ABC", readAscii(url.openStream(), Integer.MAX_VALUE)); 1262 assertEquals("DEF", readAscii(url.openStream(), Integer.MAX_VALUE)); 1263 assertEquals("GHI", readAscii(url.openStream(), Integer.MAX_VALUE)); 1264 1265 assertEquals(Arrays.asList("verify " + hostname), hostnameVerifier.calls); 1266 assertEquals(Arrays.asList("checkServerTrusted [" 1267 + "CN=" + hostname + " 1, " 1268 + "CN=Test Intermediate Certificate Authority 1, " 1269 + "CN=Test Root Certificate Authority 1" 1270 + "] RSA"), 1271 trustManager.calls); 1272 } finally { 1273 HttpsURLConnection.setDefaultHostnameVerifier(defaultHostnameVerifier); 1274 HttpsURLConnection.setDefaultSSLSocketFactory(defaultSSLSocketFactory); 1275 } 1276 } 1277 1278 public void testConnectTimeouts() throws IOException { 1279 // Set a backlog and use it up so that we can expect the 1280 // URLConnection to properly timeout. According to Steven's 1281 // 4.5 "listen function", linux adds 3 to the specified 1282 // backlog, so we need to connect 4 times before it will hang. 1283 ServerSocket serverSocket = new ServerSocket(0, 1); 1284 int serverPort = serverSocket.getLocalPort(); 1285 Socket[] sockets = new Socket[4]; 1286 for (int i = 0; i < sockets.length; i++) { 1287 sockets[i] = new Socket("localhost", serverPort); 1288 } 1289 1290 URLConnection urlConnection = new URL("http://localhost:" + serverPort).openConnection(); 1291 urlConnection.setConnectTimeout(1000); 1292 try { 1293 urlConnection.getInputStream(); 1294 fail(); 1295 } catch (SocketTimeoutException expected) { 1296 } 1297 1298 for (Socket s : sockets) { 1299 s.close(); 1300 } 1301 } 1302 1303 public void testReadTimeouts() throws IOException { 1304 /* 1305 * This relies on the fact that MockWebServer doesn't close the 1306 * connection after a response has been sent. This causes the client to 1307 * try to read more bytes than are sent, which results in a timeout. 1308 */ 1309 MockResponse timeout = new MockResponse() 1310 .setBody("ABC") 1311 .clearHeaders() 1312 .addHeader("Content-Length: 4"); 1313 server.enqueue(timeout); 1314 server.play(); 1315 1316 URLConnection urlConnection = server.getUrl("/").openConnection(); 1317 urlConnection.setReadTimeout(1000); 1318 InputStream in = urlConnection.getInputStream(); 1319 assertEquals('A', in.read()); 1320 assertEquals('B', in.read()); 1321 assertEquals('C', in.read()); 1322 try { 1323 in.read(); // if Content-Length was accurate, this would return -1 immediately 1324 fail(); 1325 } catch (SocketTimeoutException expected) { 1326 } 1327 } 1328 1329 public void testSetChunkedEncodingAsRequestProperty() throws IOException, InterruptedException { 1330 server.enqueue(new MockResponse()); 1331 server.play(); 1332 1333 HttpURLConnection urlConnection = (HttpURLConnection) server.getUrl("/").openConnection(); 1334 urlConnection.setRequestProperty("Transfer-encoding", "chunked"); 1335 urlConnection.setDoOutput(true); 1336 urlConnection.getOutputStream().write("ABC".getBytes("UTF-8")); 1337 assertEquals(200, urlConnection.getResponseCode()); 1338 1339 RecordedRequest request = server.takeRequest(); 1340 assertEquals("ABC", new String(request.getBody(), "UTF-8")); 1341 } 1342 1343 public void testConnectionCloseInRequest() throws IOException, InterruptedException { 1344 server.enqueue(new MockResponse()); // server doesn't honor the connection: close header! 1345 server.enqueue(new MockResponse()); 1346 server.play(); 1347 1348 HttpURLConnection a = (HttpURLConnection) server.getUrl("/").openConnection(); 1349 a.setRequestProperty("Connection", "close"); 1350 assertEquals(200, a.getResponseCode()); 1351 1352 HttpURLConnection b = (HttpURLConnection) server.getUrl("/").openConnection(); 1353 assertEquals(200, b.getResponseCode()); 1354 1355 assertEquals(0, server.takeRequest().getSequenceNumber()); 1356 assertEquals("When connection: close is used, each request should get its own connection", 1357 0, server.takeRequest().getSequenceNumber()); 1358 } 1359 1360 public void testConnectionCloseInResponse() throws IOException, InterruptedException { 1361 server.enqueue(new MockResponse().addHeader("Connection: close")); 1362 server.enqueue(new MockResponse()); 1363 server.play(); 1364 1365 HttpURLConnection a = (HttpURLConnection) server.getUrl("/").openConnection(); 1366 assertEquals(200, a.getResponseCode()); 1367 1368 HttpURLConnection b = (HttpURLConnection) server.getUrl("/").openConnection(); 1369 assertEquals(200, b.getResponseCode()); 1370 1371 assertEquals(0, server.takeRequest().getSequenceNumber()); 1372 assertEquals("When connection: close is used, each request should get its own connection", 1373 0, server.takeRequest().getSequenceNumber()); 1374 } 1375 1376 public void testConnectionCloseWithRedirect() throws IOException, InterruptedException { 1377 MockResponse response = new MockResponse() 1378 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1379 .addHeader("Location: /foo") 1380 .addHeader("Connection: close"); 1381 server.enqueue(response); 1382 server.enqueue(new MockResponse().setBody("This is the new location!")); 1383 server.play(); 1384 1385 URLConnection connection = server.getUrl("/").openConnection(); 1386 assertEquals("This is the new location!", 1387 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1388 1389 assertEquals(0, server.takeRequest().getSequenceNumber()); 1390 assertEquals("When connection: close is used, each request should get its own connection", 1391 0, server.takeRequest().getSequenceNumber()); 1392 } 1393 1394 public void testResponseCodeDisagreesWithHeaders() throws IOException, InterruptedException { 1395 server.enqueue(new MockResponse() 1396 .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT) 1397 .setBody("This body is not allowed!")); 1398 server.play(); 1399 1400 URLConnection connection = server.getUrl("/").openConnection(); 1401 assertEquals("This body is not allowed!", 1402 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1403 } 1404 1405 public void testSingleByteReadIsSigned() throws IOException { 1406 server.enqueue(new MockResponse().setBody(new byte[] { -2, -1 })); 1407 server.play(); 1408 1409 URLConnection connection = server.getUrl("/").openConnection(); 1410 InputStream in = connection.getInputStream(); 1411 assertEquals(254, in.read()); 1412 assertEquals(255, in.read()); 1413 assertEquals(-1, in.read()); 1414 } 1415 1416 public void testFlushAfterStreamTransmittedWithChunkedEncoding() throws IOException { 1417 testFlushAfterStreamTransmitted(TransferKind.CHUNKED); 1418 } 1419 1420 public void testFlushAfterStreamTransmittedWithFixedLength() throws IOException { 1421 testFlushAfterStreamTransmitted(TransferKind.FIXED_LENGTH); 1422 } 1423 1424 public void testFlushAfterStreamTransmittedWithNoLengthHeaders() throws IOException { 1425 testFlushAfterStreamTransmitted(TransferKind.END_OF_STREAM); 1426 } 1427 1428 /** 1429 * We explicitly permit apps to close the upload stream even after it has 1430 * been transmitted. We also permit flush so that buffered streams can 1431 * do a no-op flush when they are closed. http://b/3038470 1432 */ 1433 private void testFlushAfterStreamTransmitted(TransferKind transferKind) throws IOException { 1434 server.enqueue(new MockResponse().setBody("abc")); 1435 server.play(); 1436 1437 HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); 1438 connection.setDoOutput(true); 1439 byte[] upload = "def".getBytes("UTF-8"); 1440 1441 if (transferKind == TransferKind.CHUNKED) { 1442 connection.setChunkedStreamingMode(0); 1443 } else if (transferKind == TransferKind.FIXED_LENGTH) { 1444 connection.setFixedLengthStreamingMode(upload.length); 1445 } 1446 1447 OutputStream out = connection.getOutputStream(); 1448 out.write(upload); 1449 assertEquals("abc", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1450 1451 out.flush(); // dubious but permitted 1452 try { 1453 out.write("ghi".getBytes("UTF-8")); 1454 fail(); 1455 } catch (IOException expected) { 1456 } 1457 } 1458 1459 /** 1460 * Encodes the response body using GZIP and adds the corresponding header. 1461 */ 1462 public byte[] gzip(byte[] bytes) throws IOException { 1463 ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); 1464 OutputStream gzippedOut = new GZIPOutputStream(bytesOut); 1465 gzippedOut.write(bytes); 1466 gzippedOut.close(); 1467 return bytesOut.toByteArray(); 1468 } 1469 1470 /** 1471 * Reads at most {@code limit} characters from {@code in} and asserts that 1472 * content equals {@code expected}. 1473 */ 1474 private void assertContent(String expected, URLConnection connection, int limit) 1475 throws IOException { 1476 connection.connect(); 1477 assertEquals(expected, readAscii(connection.getInputStream(), limit)); 1478 ((HttpURLConnection) connection).disconnect(); 1479 } 1480 1481 private void assertContent(String expected, URLConnection connection) throws IOException { 1482 assertContent(expected, connection, Integer.MAX_VALUE); 1483 } 1484 1485 private void assertContains(List<String> headers, String header) { 1486 assertTrue(headers.toString(), headers.contains(header)); 1487 } 1488 1489 private void assertContainsNoneMatching(List<String> headers, String pattern) { 1490 for (String header : headers) { 1491 if (header.matches(pattern)) { 1492 fail("Header " + header + " matches " + pattern); 1493 } 1494 } 1495 } 1496 1497 private Set<String> newSet(String... elements) { 1498 return new HashSet<String>(Arrays.asList(elements)); 1499 } 1500 1501 enum TransferKind { 1502 CHUNKED() { 1503 @Override void setBody(MockResponse response, byte[] content, int chunkSize) 1504 throws IOException { 1505 response.setChunkedBody(content, chunkSize); 1506 } 1507 }, 1508 FIXED_LENGTH() { 1509 @Override void setBody(MockResponse response, byte[] content, int chunkSize) { 1510 response.setBody(content); 1511 } 1512 }, 1513 END_OF_STREAM() { 1514 @Override void setBody(MockResponse response, byte[] content, int chunkSize) { 1515 response.setBody(content); 1516 response.setDisconnectAtEnd(true); 1517 for (Iterator<String> h = response.getHeaders().iterator(); h.hasNext(); ) { 1518 if (h.next().startsWith("Content-Length:")) { 1519 h.remove(); 1520 break; 1521 } 1522 } 1523 } 1524 }; 1525 1526 abstract void setBody(MockResponse response, byte[] content, int chunkSize) 1527 throws IOException; 1528 1529 void setBody(MockResponse response, String content, int chunkSize) throws IOException { 1530 setBody(response, content.getBytes("UTF-8"), chunkSize); 1531 } 1532 } 1533 1534 enum ProxyConfig { 1535 NO_PROXY() { 1536 @Override public HttpURLConnection connect(MockWebServer server, URL url) 1537 throws IOException { 1538 return (HttpURLConnection) url.openConnection(Proxy.NO_PROXY); 1539 } 1540 }, 1541 1542 CREATE_ARG() { 1543 @Override public HttpURLConnection connect(MockWebServer server, URL url) 1544 throws IOException { 1545 return (HttpURLConnection) url.openConnection(server.toProxyAddress()); 1546 } 1547 }, 1548 1549 PROXY_SYSTEM_PROPERTY() { 1550 @Override public HttpURLConnection connect(MockWebServer server, URL url) 1551 throws IOException { 1552 System.setProperty("proxyHost", "localhost"); 1553 System.setProperty("proxyPort", Integer.toString(server.getPort())); 1554 return (HttpURLConnection) url.openConnection(); 1555 } 1556 }, 1557 1558 HTTP_PROXY_SYSTEM_PROPERTY() { 1559 @Override public HttpURLConnection connect(MockWebServer server, URL url) 1560 throws IOException { 1561 System.setProperty("http.proxyHost", "localhost"); 1562 System.setProperty("http.proxyPort", Integer.toString(server.getPort())); 1563 return (HttpURLConnection) url.openConnection(); 1564 } 1565 }, 1566 1567 HTTPS_PROXY_SYSTEM_PROPERTY() { 1568 @Override public HttpURLConnection connect(MockWebServer server, URL url) 1569 throws IOException { 1570 System.setProperty("https.proxyHost", "localhost"); 1571 System.setProperty("https.proxyPort", Integer.toString(server.getPort())); 1572 return (HttpURLConnection) url.openConnection(); 1573 } 1574 }; 1575 1576 public abstract HttpURLConnection connect(MockWebServer server, URL url) throws IOException; 1577 } 1578 1579 private static class RecordingTrustManager implements X509TrustManager { 1580 private final List<String> calls = new ArrayList<String>(); 1581 1582 public X509Certificate[] getAcceptedIssuers() { 1583 calls.add("getAcceptedIssuers"); 1584 return new X509Certificate[] {}; 1585 } 1586 1587 public void checkClientTrusted(X509Certificate[] chain, String authType) 1588 throws CertificateException { 1589 calls.add("checkClientTrusted " + certificatesToString(chain) + " " + authType); 1590 } 1591 1592 public void checkServerTrusted(X509Certificate[] chain, String authType) 1593 throws CertificateException { 1594 calls.add("checkServerTrusted " + certificatesToString(chain) + " " + authType); 1595 } 1596 1597 private String certificatesToString(X509Certificate[] certificates) { 1598 List<String> result = new ArrayList<String>(); 1599 for (X509Certificate certificate : certificates) { 1600 result.add(certificate.getSubjectDN() + " " + certificate.getSerialNumber()); 1601 } 1602 return result.toString(); 1603 } 1604 } 1605 1606 private static class RecordingHostnameVerifier implements HostnameVerifier { 1607 private final List<String> calls = new ArrayList<String>(); 1608 1609 public boolean verify(String hostname, SSLSession session) { 1610 calls.add("verify " + hostname); 1611 return true; 1612 } 1613 } 1614} 1615