URLConnectionTest.java revision c6bd683320121544811f481709b3fdbcbe9b3866
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 com.squareup.okhttp.internal.http; 18 19import com.squareup.okhttp.ConnectionPool; 20import com.squareup.okhttp.HttpResponseCache; 21import com.squareup.okhttp.OkAuthenticator.Credential; 22import com.squareup.okhttp.OkHttpClient; 23import com.squareup.okhttp.Protocol; 24import com.squareup.okhttp.internal.RecordingAuthenticator; 25import com.squareup.okhttp.internal.RecordingHostnameVerifier; 26import com.squareup.okhttp.internal.RecordingOkAuthenticator; 27import com.squareup.okhttp.internal.SslContextBuilder; 28import com.squareup.okhttp.mockwebserver.MockResponse; 29import com.squareup.okhttp.mockwebserver.MockWebServer; 30import com.squareup.okhttp.mockwebserver.RecordedRequest; 31import com.squareup.okhttp.mockwebserver.SocketPolicy; 32import java.io.ByteArrayOutputStream; 33import java.io.File; 34import java.io.IOException; 35import java.io.InputStream; 36import java.io.OutputStream; 37import java.net.Authenticator; 38import java.net.CacheRequest; 39import java.net.CacheResponse; 40import java.net.ConnectException; 41import java.net.HttpRetryException; 42import java.net.HttpURLConnection; 43import java.net.InetAddress; 44import java.net.ProtocolException; 45import java.net.Proxy; 46import java.net.ProxySelector; 47import java.net.ResponseCache; 48import java.net.SocketAddress; 49import java.net.SocketTimeoutException; 50import java.net.URI; 51import java.net.URL; 52import java.net.URLConnection; 53import java.net.UnknownHostException; 54import java.security.cert.CertificateException; 55import java.security.cert.X509Certificate; 56import java.util.ArrayList; 57import java.util.Arrays; 58import java.util.Collections; 59import java.util.HashSet; 60import java.util.Iterator; 61import java.util.List; 62import java.util.Map; 63import java.util.Random; 64import java.util.Set; 65import java.util.UUID; 66import java.util.concurrent.atomic.AtomicBoolean; 67import java.util.zip.GZIPInputStream; 68import java.util.zip.GZIPOutputStream; 69import javax.net.ssl.HttpsURLConnection; 70import javax.net.ssl.SSLContext; 71import javax.net.ssl.SSLException; 72import javax.net.ssl.SSLHandshakeException; 73import javax.net.ssl.SSLSocketFactory; 74import javax.net.ssl.TrustManager; 75import javax.net.ssl.X509TrustManager; 76import org.junit.After; 77import org.junit.Before; 78import org.junit.Ignore; 79import org.junit.Test; 80 81import static com.squareup.okhttp.internal.Util.UTF_8; 82import static com.squareup.okhttp.internal.http.OkHeaders.SELECTED_PROTOCOL; 83import static com.squareup.okhttp.internal.http.StatusLine.HTTP_TEMP_REDIRECT; 84import static com.squareup.okhttp.mockwebserver.SocketPolicy.DISCONNECT_AT_END; 85import static com.squareup.okhttp.mockwebserver.SocketPolicy.DISCONNECT_AT_START; 86import static com.squareup.okhttp.mockwebserver.SocketPolicy.SHUTDOWN_INPUT_AT_END; 87import static com.squareup.okhttp.mockwebserver.SocketPolicy.SHUTDOWN_OUTPUT_AT_END; 88import static java.util.concurrent.TimeUnit.MILLISECONDS; 89import static java.util.concurrent.TimeUnit.NANOSECONDS; 90import static org.junit.Assert.assertEquals; 91import static org.junit.Assert.assertFalse; 92import static org.junit.Assert.assertNotNull; 93import static org.junit.Assert.assertNull; 94import static org.junit.Assert.assertTrue; 95import static org.junit.Assert.fail; 96 97/** Android's URLConnectionTest. */ 98public final class URLConnectionTest { 99 private static final SSLContext sslContext = SslContextBuilder.localhost(); 100 101 private MockWebServer server = new MockWebServer(); 102 private MockWebServer server2 = new MockWebServer(); 103 104 private final OkHttpClient client = new OkHttpClient(); 105 private HttpURLConnection connection; 106 private HttpResponseCache cache; 107 private String hostName; 108 109 @Before public void setUp() throws Exception { 110 hostName = server.getHostName(); 111 server.setNpnEnabled(false); 112 } 113 114 @After public void tearDown() throws Exception { 115 Authenticator.setDefault(null); 116 System.clearProperty("proxyHost"); 117 System.clearProperty("proxyPort"); 118 System.clearProperty("http.proxyHost"); 119 System.clearProperty("http.proxyPort"); 120 System.clearProperty("https.proxyHost"); 121 System.clearProperty("https.proxyPort"); 122 server.shutdown(); 123 server2.shutdown(); 124 if (cache != null) { 125 cache.delete(); 126 } 127 } 128 129 @Test public void requestHeaders() throws IOException, InterruptedException { 130 server.enqueue(new MockResponse()); 131 server.play(); 132 133 connection = client.open(server.getUrl("/")); 134 connection.addRequestProperty("D", "e"); 135 connection.addRequestProperty("D", "f"); 136 assertEquals("f", connection.getRequestProperty("D")); 137 assertEquals("f", connection.getRequestProperty("d")); 138 Map<String, List<String>> requestHeaders = connection.getRequestProperties(); 139 assertEquals(newSet("e", "f"), new HashSet<String>(requestHeaders.get("D"))); 140 assertEquals(newSet("e", "f"), new HashSet<String>(requestHeaders.get("d"))); 141 try { 142 requestHeaders.put("G", Arrays.asList("h")); 143 fail("Modified an unmodifiable view."); 144 } catch (UnsupportedOperationException expected) { 145 } 146 try { 147 requestHeaders.get("D").add("i"); 148 fail("Modified an unmodifiable view."); 149 } catch (UnsupportedOperationException expected) { 150 } 151 try { 152 connection.setRequestProperty(null, "j"); 153 fail(); 154 } catch (NullPointerException expected) { 155 } 156 try { 157 connection.addRequestProperty(null, "k"); 158 fail(); 159 } catch (NullPointerException expected) { 160 } 161 connection.setRequestProperty("NullValue", null); 162 assertNull(connection.getRequestProperty("NullValue")); 163 connection.addRequestProperty("AnotherNullValue", null); 164 assertNull(connection.getRequestProperty("AnotherNullValue")); 165 166 connection.getResponseCode(); 167 RecordedRequest request = server.takeRequest(); 168 assertContains(request.getHeaders(), "D: e"); 169 assertContains(request.getHeaders(), "D: f"); 170 assertContainsNoneMatching(request.getHeaders(), "NullValue.*"); 171 assertContainsNoneMatching(request.getHeaders(), "AnotherNullValue.*"); 172 assertContainsNoneMatching(request.getHeaders(), "G:.*"); 173 assertContainsNoneMatching(request.getHeaders(), "null:.*"); 174 175 try { 176 connection.addRequestProperty("N", "o"); 177 fail("Set header after connect"); 178 } catch (IllegalStateException expected) { 179 } 180 try { 181 connection.setRequestProperty("P", "q"); 182 fail("Set header after connect"); 183 } catch (IllegalStateException expected) { 184 } 185 try { 186 connection.getRequestProperties(); 187 fail(); 188 } catch (IllegalStateException expected) { 189 } 190 } 191 192 @Test public void getRequestPropertyReturnsLastValue() throws Exception { 193 server.play(); 194 connection = client.open(server.getUrl("/")); 195 connection.addRequestProperty("A", "value1"); 196 connection.addRequestProperty("A", "value2"); 197 assertEquals("value2", connection.getRequestProperty("A")); 198 } 199 200 @Test public void responseHeaders() throws IOException, InterruptedException { 201 server.enqueue(new MockResponse().setStatus("HTTP/1.0 200 Fantastic") 202 .addHeader("A: c") 203 .addHeader("B: d") 204 .addHeader("A: e") 205 .setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8)); 206 server.play(); 207 208 connection = client.open(server.getUrl("/")); 209 assertEquals(200, connection.getResponseCode()); 210 assertEquals("Fantastic", connection.getResponseMessage()); 211 assertEquals("HTTP/1.0 200 Fantastic", connection.getHeaderField(null)); 212 Map<String, List<String>> responseHeaders = connection.getHeaderFields(); 213 assertEquals(Arrays.asList("HTTP/1.0 200 Fantastic"), responseHeaders.get(null)); 214 assertEquals(newSet("c", "e"), new HashSet<String>(responseHeaders.get("A"))); 215 assertEquals(newSet("c", "e"), new HashSet<String>(responseHeaders.get("a"))); 216 try { 217 responseHeaders.put("N", Arrays.asList("o")); 218 fail("Modified an unmodifiable view."); 219 } catch (UnsupportedOperationException expected) { 220 } 221 try { 222 responseHeaders.get("A").add("f"); 223 fail("Modified an unmodifiable view."); 224 } catch (UnsupportedOperationException expected) { 225 } 226 assertEquals("A", connection.getHeaderFieldKey(0)); 227 assertEquals("c", connection.getHeaderField(0)); 228 assertEquals("B", connection.getHeaderFieldKey(1)); 229 assertEquals("d", connection.getHeaderField(1)); 230 assertEquals("A", connection.getHeaderFieldKey(2)); 231 assertEquals("e", connection.getHeaderField(2)); 232 } 233 234 @Test public void serverSendsInvalidResponseHeaders() throws Exception { 235 server.enqueue(new MockResponse().setStatus("HTP/1.1 200 OK")); 236 server.play(); 237 238 connection = client.open(server.getUrl("/")); 239 try { 240 connection.getResponseCode(); 241 fail(); 242 } catch (IOException expected) { 243 } 244 } 245 246 @Test public void serverSendsInvalidCodeTooLarge() throws Exception { 247 server.enqueue(new MockResponse().setStatus("HTTP/1.1 2147483648 OK")); 248 server.play(); 249 250 connection = client.open(server.getUrl("/")); 251 try { 252 connection.getResponseCode(); 253 fail(); 254 } catch (IOException expected) { 255 } 256 } 257 258 @Test public void serverSendsInvalidCodeNotANumber() throws Exception { 259 server.enqueue(new MockResponse().setStatus("HTTP/1.1 00a OK")); 260 server.play(); 261 262 connection = client.open(server.getUrl("/")); 263 try { 264 connection.getResponseCode(); 265 fail(); 266 } catch (IOException expected) { 267 } 268 } 269 270 @Test public void serverSendsUnnecessaryWhitespace() throws Exception { 271 server.enqueue(new MockResponse().setStatus(" HTTP/1.1 2147483648 OK")); 272 server.play(); 273 274 connection = client.open(server.getUrl("/")); 275 try { 276 connection.getResponseCode(); 277 fail(); 278 } catch (IOException expected) { 279 } 280 } 281 282 @Test public void connectRetriesUntilConnectedOrFailed() throws Exception { 283 server.play(); 284 URL url = server.getUrl("/foo"); 285 server.shutdown(); 286 287 connection = client.open(url); 288 try { 289 connection.connect(); 290 fail(); 291 } catch (IOException expected) { 292 } 293 } 294 295 @Test public void requestBodySurvivesRetriesWithFixedLength() throws Exception { 296 testRequestBodySurvivesRetries(TransferKind.FIXED_LENGTH); 297 } 298 299 @Test public void requestBodySurvivesRetriesWithChunkedStreaming() throws Exception { 300 testRequestBodySurvivesRetries(TransferKind.CHUNKED); 301 } 302 303 @Test public void requestBodySurvivesRetriesWithBufferedBody() throws Exception { 304 testRequestBodySurvivesRetries(TransferKind.END_OF_STREAM); 305 } 306 307 private void testRequestBodySurvivesRetries(TransferKind transferKind) throws Exception { 308 server.enqueue(new MockResponse().setBody("abc")); 309 server.play(); 310 311 // Use a misconfigured proxy to guarantee that the request is retried. 312 server2.play(); 313 FakeProxySelector proxySelector = new FakeProxySelector(); 314 proxySelector.proxies.add(server2.toProxyAddress()); 315 client.setProxySelector(proxySelector); 316 server2.shutdown(); 317 318 connection = client.open(server.getUrl("/def")); 319 connection.setDoOutput(true); 320 transferKind.setForRequest(connection, 4); 321 connection.getOutputStream().write("body".getBytes("UTF-8")); 322 assertContent("abc", connection); 323 324 assertEquals("body", server.takeRequest().getUtf8Body()); 325 } 326 327 @Test public void getErrorStreamOnSuccessfulRequest() throws Exception { 328 server.enqueue(new MockResponse().setBody("A")); 329 server.play(); 330 connection = client.open(server.getUrl("/")); 331 assertNull(connection.getErrorStream()); 332 } 333 334 @Test public void getErrorStreamOnUnsuccessfulRequest() throws Exception { 335 server.enqueue(new MockResponse().setResponseCode(404).setBody("A")); 336 server.play(); 337 connection = client.open(server.getUrl("/")); 338 assertEquals("A", readAscii(connection.getErrorStream(), Integer.MAX_VALUE)); 339 } 340 341 // Check that if we don't read to the end of a response, the next request on the 342 // recycled connection doesn't get the unread tail of the first request's response. 343 // http://code.google.com/p/android/issues/detail?id=2939 344 @Test public void bug2939() throws Exception { 345 MockResponse response = new MockResponse().setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8); 346 347 server.enqueue(response); 348 server.enqueue(response); 349 server.play(); 350 351 assertContent("ABCDE", client.open(server.getUrl("/")), 5); 352 assertContent("ABCDE", client.open(server.getUrl("/")), 5); 353 } 354 355 // Check that we recognize a few basic mime types by extension. 356 // http://code.google.com/p/android/issues/detail?id=10100 357 @Test public void bug10100() throws Exception { 358 assertEquals("image/jpeg", URLConnection.guessContentTypeFromName("someFile.jpg")); 359 assertEquals("application/pdf", URLConnection.guessContentTypeFromName("stuff.pdf")); 360 } 361 362 @Test public void connectionsArePooled() throws Exception { 363 MockResponse response = new MockResponse().setBody("ABCDEFGHIJKLMNOPQR"); 364 365 server.enqueue(response); 366 server.enqueue(response); 367 server.enqueue(response); 368 server.play(); 369 370 assertContent("ABCDEFGHIJKLMNOPQR", client.open(server.getUrl("/foo"))); 371 assertEquals(0, server.takeRequest().getSequenceNumber()); 372 assertContent("ABCDEFGHIJKLMNOPQR", client.open(server.getUrl("/bar?baz=quux"))); 373 assertEquals(1, server.takeRequest().getSequenceNumber()); 374 assertContent("ABCDEFGHIJKLMNOPQR", client.open(server.getUrl("/z"))); 375 assertEquals(2, server.takeRequest().getSequenceNumber()); 376 } 377 378 @Test public void chunkedConnectionsArePooled() throws Exception { 379 MockResponse response = new MockResponse().setChunkedBody("ABCDEFGHIJKLMNOPQR", 5); 380 381 server.enqueue(response); 382 server.enqueue(response); 383 server.enqueue(response); 384 server.play(); 385 386 assertContent("ABCDEFGHIJKLMNOPQR", client.open(server.getUrl("/foo"))); 387 assertEquals(0, server.takeRequest().getSequenceNumber()); 388 assertContent("ABCDEFGHIJKLMNOPQR", client.open(server.getUrl("/bar?baz=quux"))); 389 assertEquals(1, server.takeRequest().getSequenceNumber()); 390 assertContent("ABCDEFGHIJKLMNOPQR", client.open(server.getUrl("/z"))); 391 assertEquals(2, server.takeRequest().getSequenceNumber()); 392 } 393 394 @Test public void serverClosesSocket() throws Exception { 395 testServerClosesOutput(DISCONNECT_AT_END); 396 } 397 398 @Test public void serverShutdownInput() throws Exception { 399 testServerClosesOutput(SHUTDOWN_INPUT_AT_END); 400 } 401 402 @Test public void serverShutdownOutput() throws Exception { 403 testServerClosesOutput(SHUTDOWN_OUTPUT_AT_END); 404 } 405 406 @Test public void invalidHost() throws Exception { 407 // Note that 1234.1.1.1 is an invalid host in a URI, but URL isn't as strict. 408 URL url = new URL("http://1234.1.1.1/index.html"); 409 HttpURLConnection connection = client.open(url); 410 try { 411 connection.connect(); 412 fail(); 413 } catch (UnknownHostException expected) { 414 } 415 } 416 417 private void testServerClosesOutput(SocketPolicy socketPolicy) throws Exception { 418 server.enqueue(new MockResponse().setBody("This connection won't pool properly") 419 .setSocketPolicy(socketPolicy)); 420 MockResponse responseAfter = new MockResponse().setBody("This comes after a busted connection"); 421 server.enqueue(responseAfter); 422 server.enqueue(responseAfter); // Enqueue 2x because the broken connection may be reused. 423 server.play(); 424 425 HttpURLConnection connection1 = client.open(server.getUrl("/a")); 426 connection1.setReadTimeout(100); 427 assertContent("This connection won't pool properly", connection1); 428 assertEquals(0, server.takeRequest().getSequenceNumber()); 429 HttpURLConnection connection2 = client.open(server.getUrl("/b")); 430 connection2.setReadTimeout(100); 431 assertContent("This comes after a busted connection", connection2); 432 433 // Check that a fresh connection was created, either immediately or after attempting reuse. 434 RecordedRequest requestAfter = server.takeRequest(); 435 if (server.getRequestCount() == 3) { 436 requestAfter = server.takeRequest(); // The failure consumed a response. 437 } 438 // sequence number 0 means the HTTP socket connection was not reused 439 assertEquals(0, requestAfter.getSequenceNumber()); 440 } 441 442 enum WriteKind {BYTE_BY_BYTE, SMALL_BUFFERS, LARGE_BUFFERS} 443 444 @Test public void chunkedUpload_byteByByte() throws Exception { 445 doUpload(TransferKind.CHUNKED, WriteKind.BYTE_BY_BYTE); 446 } 447 448 @Test public void chunkedUpload_smallBuffers() throws Exception { 449 doUpload(TransferKind.CHUNKED, WriteKind.SMALL_BUFFERS); 450 } 451 452 @Test public void chunkedUpload_largeBuffers() throws Exception { 453 doUpload(TransferKind.CHUNKED, WriteKind.LARGE_BUFFERS); 454 } 455 456 @Test public void fixedLengthUpload_byteByByte() throws Exception { 457 doUpload(TransferKind.FIXED_LENGTH, WriteKind.BYTE_BY_BYTE); 458 } 459 460 @Test public void fixedLengthUpload_smallBuffers() throws Exception { 461 doUpload(TransferKind.FIXED_LENGTH, WriteKind.SMALL_BUFFERS); 462 } 463 464 @Test public void fixedLengthUpload_largeBuffers() throws Exception { 465 doUpload(TransferKind.FIXED_LENGTH, WriteKind.LARGE_BUFFERS); 466 } 467 468 private void doUpload(TransferKind uploadKind, WriteKind writeKind) throws Exception { 469 int n = 512 * 1024; 470 server.setBodyLimit(0); 471 server.enqueue(new MockResponse()); 472 server.play(); 473 474 HttpURLConnection conn = client.open(server.getUrl("/")); 475 conn.setDoOutput(true); 476 conn.setRequestMethod("POST"); 477 if (uploadKind == TransferKind.CHUNKED) { 478 conn.setChunkedStreamingMode(-1); 479 } else { 480 conn.setFixedLengthStreamingMode(n); 481 } 482 OutputStream out = conn.getOutputStream(); 483 if (writeKind == WriteKind.BYTE_BY_BYTE) { 484 for (int i = 0; i < n; ++i) { 485 out.write('x'); 486 } 487 } else { 488 byte[] buf = new byte[writeKind == WriteKind.SMALL_BUFFERS ? 256 : 64 * 1024]; 489 Arrays.fill(buf, (byte) 'x'); 490 for (int i = 0; i < n; i += buf.length) { 491 out.write(buf, 0, Math.min(buf.length, n - i)); 492 } 493 } 494 out.close(); 495 assertEquals(200, conn.getResponseCode()); 496 RecordedRequest request = server.takeRequest(); 497 assertEquals(n, request.getBodySize()); 498 if (uploadKind == TransferKind.CHUNKED) { 499 assertTrue(request.getChunkSizes().size() > 0); 500 } else { 501 assertTrue(request.getChunkSizes().isEmpty()); 502 } 503 } 504 505 @Test public void getResponseCodeNoResponseBody() throws Exception { 506 server.enqueue(new MockResponse().addHeader("abc: def")); 507 server.play(); 508 509 URL url = server.getUrl("/"); 510 HttpURLConnection conn = client.open(url); 511 conn.setDoInput(false); 512 assertEquals("def", conn.getHeaderField("abc")); 513 assertEquals(200, conn.getResponseCode()); 514 try { 515 conn.getInputStream(); 516 fail(); 517 } catch (ProtocolException expected) { 518 } 519 } 520 521 @Test public void connectViaHttps() throws Exception { 522 server.useHttps(sslContext.getSocketFactory(), false); 523 server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); 524 server.play(); 525 526 client.setSslSocketFactory(sslContext.getSocketFactory()); 527 client.setHostnameVerifier(new RecordingHostnameVerifier()); 528 connection = client.open(server.getUrl("/foo")); 529 530 assertContent("this response comes via HTTPS", connection); 531 532 RecordedRequest request = server.takeRequest(); 533 assertEquals("GET /foo HTTP/1.1", request.getRequestLine()); 534 } 535 536 @Test public void inspectHandshakeThroughoutRequestLifecycle() throws Exception { 537 server.useHttps(sslContext.getSocketFactory(), false); 538 server.enqueue(new MockResponse()); 539 server.play(); 540 541 client.setSslSocketFactory(sslContext.getSocketFactory()); 542 client.setHostnameVerifier(new RecordingHostnameVerifier()); 543 544 HttpsURLConnection httpsConnection = (HttpsURLConnection) client.open(server.getUrl("/foo")); 545 546 // Prior to calling connect(), getting the cipher suite is forbidden. 547 try { 548 httpsConnection.getCipherSuite(); 549 fail(); 550 } catch (IllegalStateException expected) { 551 } 552 553 // Calling connect establishes a handshake... 554 httpsConnection.connect(); 555 assertNotNull(httpsConnection.getCipherSuite()); 556 557 // ...which remains after we read the response body... 558 assertContent("", httpsConnection); 559 assertNotNull(httpsConnection.getCipherSuite()); 560 561 // ...and after we disconnect. 562 httpsConnection.disconnect(); 563 assertNotNull(httpsConnection.getCipherSuite()); 564 } 565 566 @Test public void connectViaHttpsReusingConnections() throws IOException, InterruptedException { 567 server.useHttps(sslContext.getSocketFactory(), false); 568 server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); 569 server.enqueue(new MockResponse().setBody("another response via HTTPS")); 570 server.play(); 571 572 // The pool will only reuse sockets if the SSL socket factories are the same. 573 SSLSocketFactory clientSocketFactory = sslContext.getSocketFactory(); 574 RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); 575 576 client.setSslSocketFactory(clientSocketFactory); 577 client.setHostnameVerifier(hostnameVerifier); 578 connection = client.open(server.getUrl("/")); 579 assertContent("this response comes via HTTPS", connection); 580 581 connection = client.open(server.getUrl("/")); 582 assertContent("another response via HTTPS", connection); 583 584 assertEquals(0, server.takeRequest().getSequenceNumber()); 585 assertEquals(1, server.takeRequest().getSequenceNumber()); 586 } 587 588 @Test public void connectViaHttpsReusingConnectionsDifferentFactories() 589 throws IOException, InterruptedException { 590 server.useHttps(sslContext.getSocketFactory(), false); 591 server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); 592 server.enqueue(new MockResponse().setBody("another response via HTTPS")); 593 server.play(); 594 595 // install a custom SSL socket factory so the server can be authorized 596 client.setSslSocketFactory(sslContext.getSocketFactory()); 597 client.setHostnameVerifier(new RecordingHostnameVerifier()); 598 HttpURLConnection connection1 = client.open(server.getUrl("/")); 599 assertContent("this response comes via HTTPS", connection1); 600 601 client.setSslSocketFactory(null); 602 HttpURLConnection connection2 = client.open(server.getUrl("/")); 603 try { 604 readAscii(connection2.getInputStream(), Integer.MAX_VALUE); 605 fail("without an SSL socket factory, the connection should fail"); 606 } catch (SSLException expected) { 607 } 608 } 609 610 @Test public void connectViaHttpsWithSSLFallback() throws IOException, InterruptedException { 611 server.useHttps(sslContext.getSocketFactory(), false); 612 server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.FAIL_HANDSHAKE)); 613 server.enqueue(new MockResponse().setBody("this response comes via SSL")); 614 server.play(); 615 616 client.setSslSocketFactory(sslContext.getSocketFactory()); 617 client.setHostnameVerifier(new RecordingHostnameVerifier()); 618 connection = client.open(server.getUrl("/foo")); 619 620 assertContent("this response comes via SSL", connection); 621 622 RecordedRequest request = server.takeRequest(); 623 assertEquals("GET /foo HTTP/1.1", request.getRequestLine()); 624 } 625 626 /** 627 * When a pooled connection fails, don't blame the route. Otherwise pooled 628 * connection failures can cause unnecessary SSL fallbacks. 629 * 630 * https://github.com/square/okhttp/issues/515 631 */ 632 @Test public void sslFallbackNotUsedWhenRecycledConnectionFails() throws Exception { 633 server.useHttps(sslContext.getSocketFactory(), false); 634 server.enqueue(new MockResponse() 635 .setBody("abc") 636 .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END)); 637 server.enqueue(new MockResponse().setBody("def")); 638 server.play(); 639 640 client.setSslSocketFactory(sslContext.getSocketFactory()); 641 client.setHostnameVerifier(new RecordingHostnameVerifier()); 642 643 assertContent("abc", client.open(server.getUrl("/"))); 644 assertContent("def", client.open(server.getUrl("/"))); 645 646 RecordedRequest request1 = server.takeRequest(); 647 assertEquals("TLSv1", request1.getSslProtocol()); // OkHttp's current best TLS version. 648 649 RecordedRequest request2 = server.takeRequest(); 650 assertEquals("TLSv1", request2.getSslProtocol()); // OkHttp's current best TLS version. 651 } 652 653 /** 654 * Verify that we don't retry connections on certificate verification errors. 655 * 656 * http://code.google.com/p/android/issues/detail?id=13178 657 */ 658 @Test public void connectViaHttpsToUntrustedServer() throws IOException, InterruptedException { 659 server.useHttps(sslContext.getSocketFactory(), false); 660 server.enqueue(new MockResponse()); // unused 661 server.play(); 662 663 connection = client.open(server.getUrl("/foo")); 664 try { 665 connection.getInputStream(); 666 fail(); 667 } catch (SSLHandshakeException expected) { 668 assertTrue(expected.getCause() instanceof CertificateException); 669 } 670 assertEquals(0, server.getRequestCount()); 671 } 672 673 @Test public void connectViaProxyUsingProxyArg() throws Exception { 674 testConnectViaProxy(ProxyConfig.CREATE_ARG); 675 } 676 677 @Test public void connectViaProxyUsingProxySystemProperty() throws Exception { 678 testConnectViaProxy(ProxyConfig.PROXY_SYSTEM_PROPERTY); 679 } 680 681 @Test public void connectViaProxyUsingHttpProxySystemProperty() throws Exception { 682 testConnectViaProxy(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY); 683 } 684 685 private void testConnectViaProxy(ProxyConfig proxyConfig) throws Exception { 686 MockResponse mockResponse = new MockResponse().setBody("this response comes via a proxy"); 687 server.enqueue(mockResponse); 688 server.play(); 689 690 URL url = new URL("http://android.com/foo"); 691 connection = proxyConfig.connect(server, client, url); 692 assertContent("this response comes via a proxy", connection); 693 assertTrue(connection.usingProxy()); 694 695 RecordedRequest request = server.takeRequest(); 696 assertEquals("GET http://android.com/foo HTTP/1.1", request.getRequestLine()); 697 assertContains(request.getHeaders(), "Host: android.com"); 698 } 699 700 @Test public void contentDisagreesWithContentLengthHeader() throws IOException { 701 server.enqueue(new MockResponse().setBody("abc\r\nYOU SHOULD NOT SEE THIS") 702 .clearHeaders() 703 .addHeader("Content-Length: 3")); 704 server.play(); 705 706 assertContent("abc", client.open(server.getUrl("/"))); 707 } 708 709 @Test public void contentDisagreesWithChunkedHeader() throws IOException { 710 MockResponse mockResponse = new MockResponse(); 711 mockResponse.setChunkedBody("abc", 3); 712 ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); 713 bytesOut.write(mockResponse.getBody()); 714 bytesOut.write("\r\nYOU SHOULD NOT SEE THIS".getBytes("UTF-8")); 715 mockResponse.setBody(bytesOut.toByteArray()); 716 mockResponse.clearHeaders(); 717 mockResponse.addHeader("Transfer-encoding: chunked"); 718 719 server.enqueue(mockResponse); 720 server.play(); 721 722 assertContent("abc", client.open(server.getUrl("/"))); 723 } 724 725 @Test public void connectViaHttpProxyToHttpsUsingProxyArgWithNoProxy() throws Exception { 726 testConnectViaDirectProxyToHttps(ProxyConfig.NO_PROXY); 727 } 728 729 @Test public void connectViaHttpProxyToHttpsUsingHttpProxySystemProperty() throws Exception { 730 // https should not use http proxy 731 testConnectViaDirectProxyToHttps(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY); 732 } 733 734 private void testConnectViaDirectProxyToHttps(ProxyConfig proxyConfig) throws Exception { 735 server.useHttps(sslContext.getSocketFactory(), false); 736 server.enqueue(new MockResponse().setBody("this response comes via HTTPS")); 737 server.play(); 738 739 URL url = server.getUrl("/foo"); 740 client.setSslSocketFactory(sslContext.getSocketFactory()); 741 client.setHostnameVerifier(new RecordingHostnameVerifier()); 742 connection = proxyConfig.connect(server, client, url); 743 744 assertContent("this response comes via HTTPS", connection); 745 746 RecordedRequest request = server.takeRequest(); 747 assertEquals("GET /foo HTTP/1.1", request.getRequestLine()); 748 } 749 750 @Test public void connectViaHttpProxyToHttpsUsingProxyArg() throws Exception { 751 testConnectViaHttpProxyToHttps(ProxyConfig.CREATE_ARG); 752 } 753 754 /** 755 * We weren't honoring all of the appropriate proxy system properties when 756 * connecting via HTTPS. http://b/3097518 757 */ 758 @Test public void connectViaHttpProxyToHttpsUsingProxySystemProperty() throws Exception { 759 testConnectViaHttpProxyToHttps(ProxyConfig.PROXY_SYSTEM_PROPERTY); 760 } 761 762 @Test public void connectViaHttpProxyToHttpsUsingHttpsProxySystemProperty() throws Exception { 763 testConnectViaHttpProxyToHttps(ProxyConfig.HTTPS_PROXY_SYSTEM_PROPERTY); 764 } 765 766 /** 767 * We were verifying the wrong hostname when connecting to an HTTPS site 768 * through a proxy. http://b/3097277 769 */ 770 private void testConnectViaHttpProxyToHttps(ProxyConfig proxyConfig) throws Exception { 771 RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); 772 773 server.useHttps(sslContext.getSocketFactory(), true); 774 server.enqueue( 775 new MockResponse().setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END).clearHeaders()); 776 server.enqueue(new MockResponse().setBody("this response comes via a secure proxy")); 777 server.play(); 778 779 URL url = new URL("https://android.com/foo"); 780 client.setSslSocketFactory(sslContext.getSocketFactory()); 781 client.setHostnameVerifier(hostnameVerifier); 782 connection = proxyConfig.connect(server, client, url); 783 784 assertContent("this response comes via a secure proxy", connection); 785 786 RecordedRequest connect = server.takeRequest(); 787 assertEquals("Connect line failure on proxy", "CONNECT android.com:443 HTTP/1.1", 788 connect.getRequestLine()); 789 assertContains(connect.getHeaders(), "Host: android.com"); 790 791 RecordedRequest get = server.takeRequest(); 792 assertEquals("GET /foo HTTP/1.1", get.getRequestLine()); 793 assertContains(get.getHeaders(), "Host: android.com"); 794 assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls); 795 } 796 797 /** Tolerate bad https proxy response when using HttpResponseCache. http://b/6754912 */ 798 @Test public void connectViaHttpProxyToHttpsUsingBadProxyAndHttpResponseCache() throws Exception { 799 initResponseCache(); 800 801 server.useHttps(sslContext.getSocketFactory(), true); 802 MockResponse response = new MockResponse() // Key to reproducing b/6754912 803 .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END) 804 .setBody("bogus proxy connect response content"); 805 806 // Enqueue a pair of responses for every IP address held by localhost, because the 807 // route selector will try each in sequence. 808 // TODO: use the fake Dns implementation instead of a loop 809 for (InetAddress inetAddress : InetAddress.getAllByName(server.getHostName())) { 810 server.enqueue(response); // For the first TLS tolerant connection 811 server.enqueue(response); // For the backwards-compatible SSLv3 retry 812 } 813 server.play(); 814 client.setProxy(server.toProxyAddress()); 815 816 URL url = new URL("https://android.com/foo"); 817 client.setSslSocketFactory(sslContext.getSocketFactory()); 818 connection = client.open(url); 819 820 try { 821 connection.getResponseCode(); 822 fail(); 823 } catch (IOException expected) { 824 // Thrown when the connect causes SSLSocket.startHandshake() to throw 825 // when it sees the "bogus proxy connect response content" 826 // instead of a ServerHello handshake message. 827 } 828 829 RecordedRequest connect = server.takeRequest(); 830 assertEquals("Connect line failure on proxy", "CONNECT android.com:443 HTTP/1.1", 831 connect.getRequestLine()); 832 assertContains(connect.getHeaders(), "Host: android.com"); 833 } 834 835 private void initResponseCache() throws IOException { 836 String tmp = System.getProperty("java.io.tmpdir"); 837 File cacheDir = new File(tmp, "HttpCache-" + UUID.randomUUID()); 838 cache = new HttpResponseCache(cacheDir, Integer.MAX_VALUE); 839 client.setOkResponseCache(cache); 840 } 841 842 /** Test which headers are sent unencrypted to the HTTP proxy. */ 843 @Test public void proxyConnectIncludesProxyHeadersOnly() 844 throws IOException, InterruptedException { 845 RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); 846 847 server.useHttps(sslContext.getSocketFactory(), true); 848 server.enqueue( 849 new MockResponse().setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END).clearHeaders()); 850 server.enqueue(new MockResponse().setBody("encrypted response from the origin server")); 851 server.play(); 852 client.setProxy(server.toProxyAddress()); 853 854 URL url = new URL("https://android.com/foo"); 855 client.setSslSocketFactory(sslContext.getSocketFactory()); 856 client.setHostnameVerifier(hostnameVerifier); 857 connection = client.open(url); 858 connection.addRequestProperty("Private", "Secret"); 859 connection.addRequestProperty("Proxy-Authorization", "bar"); 860 connection.addRequestProperty("User-Agent", "baz"); 861 assertContent("encrypted response from the origin server", connection); 862 863 RecordedRequest connect = server.takeRequest(); 864 assertContainsNoneMatching(connect.getHeaders(), "Private.*"); 865 assertContains(connect.getHeaders(), "Proxy-Authorization: bar"); 866 assertContains(connect.getHeaders(), "User-Agent: baz"); 867 assertContains(connect.getHeaders(), "Host: android.com"); 868 assertContains(connect.getHeaders(), "Proxy-Connection: Keep-Alive"); 869 870 RecordedRequest get = server.takeRequest(); 871 assertContains(get.getHeaders(), "Private: Secret"); 872 assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls); 873 } 874 875 @Test public void proxyAuthenticateOnConnect() throws Exception { 876 Authenticator.setDefault(new RecordingAuthenticator()); 877 server.useHttps(sslContext.getSocketFactory(), true); 878 server.enqueue(new MockResponse().setResponseCode(407) 879 .addHeader("Proxy-Authenticate: Basic realm=\"localhost\"")); 880 server.enqueue( 881 new MockResponse().setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END).clearHeaders()); 882 server.enqueue(new MockResponse().setBody("A")); 883 server.play(); 884 client.setProxy(server.toProxyAddress()); 885 886 URL url = new URL("https://android.com/foo"); 887 client.setSslSocketFactory(sslContext.getSocketFactory()); 888 client.setHostnameVerifier(new RecordingHostnameVerifier()); 889 connection = client.open(url); 890 assertContent("A", connection); 891 892 RecordedRequest connect1 = server.takeRequest(); 893 assertEquals("CONNECT android.com:443 HTTP/1.1", connect1.getRequestLine()); 894 assertContainsNoneMatching(connect1.getHeaders(), "Proxy\\-Authorization.*"); 895 896 RecordedRequest connect2 = server.takeRequest(); 897 assertEquals("CONNECT android.com:443 HTTP/1.1", connect2.getRequestLine()); 898 assertContains(connect2.getHeaders(), 899 "Proxy-Authorization: Basic " + RecordingAuthenticator.BASE_64_CREDENTIALS); 900 901 RecordedRequest get = server.takeRequest(); 902 assertEquals("GET /foo HTTP/1.1", get.getRequestLine()); 903 assertContainsNoneMatching(get.getHeaders(), "Proxy\\-Authorization.*"); 904 } 905 906 // Don't disconnect after building a tunnel with CONNECT 907 // http://code.google.com/p/android/issues/detail?id=37221 908 @Test public void proxyWithConnectionClose() throws IOException { 909 server.useHttps(sslContext.getSocketFactory(), true); 910 server.enqueue( 911 new MockResponse().setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END).clearHeaders()); 912 server.enqueue(new MockResponse().setBody("this response comes via a proxy")); 913 server.play(); 914 client.setProxy(server.toProxyAddress()); 915 916 URL url = new URL("https://android.com/foo"); 917 client.setSslSocketFactory(sslContext.getSocketFactory()); 918 client.setHostnameVerifier(new RecordingHostnameVerifier()); 919 connection = client.open(url); 920 connection.setRequestProperty("Connection", "close"); 921 922 assertContent("this response comes via a proxy", connection); 923 } 924 925 @Test public void proxyWithConnectionReuse() throws IOException { 926 SSLSocketFactory socketFactory = sslContext.getSocketFactory(); 927 RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); 928 929 server.useHttps(socketFactory, true); 930 server.enqueue( 931 new MockResponse().setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END).clearHeaders()); 932 server.enqueue(new MockResponse().setBody("response 1")); 933 server.enqueue(new MockResponse().setBody("response 2")); 934 server.play(); 935 client.setProxy(server.toProxyAddress()); 936 937 URL url = new URL("https://android.com/foo"); 938 client.setSslSocketFactory(socketFactory); 939 client.setHostnameVerifier(hostnameVerifier); 940 assertContent("response 1", client.open(url)); 941 assertContent("response 2", client.open(url)); 942 } 943 944 @Test public void disconnectedConnection() throws IOException { 945 server.enqueue(new MockResponse().setBody("ABCDEFGHIJKLMNOPQR")); 946 server.play(); 947 948 connection = client.open(server.getUrl("/")); 949 InputStream in = connection.getInputStream(); 950 assertEquals('A', (char) in.read()); 951 connection.disconnect(); 952 try { 953 in.read(); 954 fail("Expected a connection closed exception"); 955 } catch (IOException expected) { 956 } 957 } 958 959 @Test public void disconnectBeforeConnect() throws IOException { 960 server.enqueue(new MockResponse().setBody("A")); 961 server.play(); 962 963 connection = client.open(server.getUrl("/")); 964 connection.disconnect(); 965 assertContent("A", connection); 966 assertEquals(200, connection.getResponseCode()); 967 } 968 969 @SuppressWarnings("deprecation") @Test public void defaultRequestProperty() throws Exception { 970 URLConnection.setDefaultRequestProperty("X-testSetDefaultRequestProperty", "A"); 971 assertNull(URLConnection.getDefaultRequestProperty("X-setDefaultRequestProperty")); 972 } 973 974 /** 975 * Reads {@code count} characters from the stream. If the stream is 976 * exhausted before {@code count} characters can be read, the remaining 977 * characters are returned and the stream is closed. 978 */ 979 private String readAscii(InputStream in, int count) throws IOException { 980 StringBuilder result = new StringBuilder(); 981 for (int i = 0; i < count; i++) { 982 int value = in.read(); 983 if (value == -1) { 984 in.close(); 985 break; 986 } 987 result.append((char) value); 988 } 989 return result.toString(); 990 } 991 992 @Test public void markAndResetWithContentLengthHeader() throws IOException { 993 testMarkAndReset(TransferKind.FIXED_LENGTH); 994 } 995 996 @Test public void markAndResetWithChunkedEncoding() throws IOException { 997 testMarkAndReset(TransferKind.CHUNKED); 998 } 999 1000 @Test public void markAndResetWithNoLengthHeaders() throws IOException { 1001 testMarkAndReset(TransferKind.END_OF_STREAM); 1002 } 1003 1004 private void testMarkAndReset(TransferKind transferKind) throws IOException { 1005 MockResponse response = new MockResponse(); 1006 transferKind.setBody(response, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1024); 1007 server.enqueue(response); 1008 server.enqueue(response); 1009 server.play(); 1010 1011 InputStream in = client.open(server.getUrl("/")).getInputStream(); 1012 assertFalse("This implementation claims to support mark().", in.markSupported()); 1013 in.mark(5); 1014 assertEquals("ABCDE", readAscii(in, 5)); 1015 try { 1016 in.reset(); 1017 fail(); 1018 } catch (IOException expected) { 1019 } 1020 assertEquals("FGHIJKLMNOPQRSTUVWXYZ", readAscii(in, Integer.MAX_VALUE)); 1021 assertContent("ABCDEFGHIJKLMNOPQRSTUVWXYZ", client.open(server.getUrl("/"))); 1022 } 1023 1024 /** 1025 * We've had a bug where we forget the HTTP response when we see response 1026 * code 401. This causes a new HTTP request to be issued for every call into 1027 * the URLConnection. 1028 */ 1029 @Test public void unauthorizedResponseHandling() throws IOException { 1030 MockResponse response = new MockResponse().addHeader("WWW-Authenticate: challenge") 1031 .setResponseCode(401) // UNAUTHORIZED 1032 .setBody("Unauthorized"); 1033 server.enqueue(response); 1034 server.enqueue(response); 1035 server.enqueue(response); 1036 server.play(); 1037 1038 URL url = server.getUrl("/"); 1039 HttpURLConnection conn = client.open(url); 1040 1041 assertEquals(401, conn.getResponseCode()); 1042 assertEquals(401, conn.getResponseCode()); 1043 assertEquals(401, conn.getResponseCode()); 1044 assertEquals(1, server.getRequestCount()); 1045 } 1046 1047 @Test public void nonHexChunkSize() throws IOException { 1048 server.enqueue(new MockResponse().setBody("5\r\nABCDE\r\nG\r\nFGHIJKLMNOPQRSTU\r\n0\r\n\r\n") 1049 .clearHeaders() 1050 .addHeader("Transfer-encoding: chunked")); 1051 server.play(); 1052 1053 URLConnection connection = client.open(server.getUrl("/")); 1054 try { 1055 readAscii(connection.getInputStream(), Integer.MAX_VALUE); 1056 fail(); 1057 } catch (IOException e) { 1058 } 1059 } 1060 1061 @Test public void missingChunkBody() throws IOException { 1062 server.enqueue(new MockResponse().setBody("5") 1063 .clearHeaders() 1064 .addHeader("Transfer-encoding: chunked") 1065 .setSocketPolicy(DISCONNECT_AT_END)); 1066 server.play(); 1067 1068 URLConnection connection = client.open(server.getUrl("/")); 1069 try { 1070 readAscii(connection.getInputStream(), Integer.MAX_VALUE); 1071 fail(); 1072 } catch (IOException e) { 1073 } 1074 } 1075 1076 /** 1077 * This test checks whether connections are gzipped by default. This 1078 * behavior in not required by the API, so a failure of this test does not 1079 * imply a bug in the implementation. 1080 */ 1081 @Test public void gzipEncodingEnabledByDefault() throws IOException, InterruptedException { 1082 server.enqueue(new MockResponse().setBody(gzip("ABCABCABC".getBytes("UTF-8"))) 1083 .addHeader("Content-Encoding: gzip")); 1084 server.play(); 1085 1086 URLConnection connection = client.open(server.getUrl("/")); 1087 assertEquals("ABCABCABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1088 assertNull(connection.getContentEncoding()); 1089 assertEquals(-1, connection.getContentLength()); 1090 1091 RecordedRequest request = server.takeRequest(); 1092 assertContains(request.getHeaders(), "Accept-Encoding: gzip"); 1093 } 1094 1095 @Test public void clientConfiguredGzipContentEncoding() throws Exception { 1096 byte[] bodyBytes = gzip("ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes("UTF-8")); 1097 server.enqueue(new MockResponse() 1098 .setBody(bodyBytes) 1099 .addHeader("Content-Encoding: gzip")); 1100 server.play(); 1101 1102 URLConnection connection = client.open(server.getUrl("/")); 1103 connection.addRequestProperty("Accept-Encoding", "gzip"); 1104 InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream()); 1105 assertEquals("ABCDEFGHIJKLMNOPQRSTUVWXYZ", readAscii(gunzippedIn, Integer.MAX_VALUE)); 1106 assertEquals(bodyBytes.length, connection.getContentLength()); 1107 1108 RecordedRequest request = server.takeRequest(); 1109 assertContains(request.getHeaders(), "Accept-Encoding: gzip"); 1110 } 1111 1112 @Test public void gzipAndConnectionReuseWithFixedLength() throws Exception { 1113 testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.FIXED_LENGTH, false); 1114 } 1115 1116 @Test public void gzipAndConnectionReuseWithChunkedEncoding() throws Exception { 1117 testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.CHUNKED, false); 1118 } 1119 1120 @Test public void gzipAndConnectionReuseWithFixedLengthAndTls() throws Exception { 1121 testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.FIXED_LENGTH, true); 1122 } 1123 1124 @Test public void gzipAndConnectionReuseWithChunkedEncodingAndTls() throws Exception { 1125 testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.CHUNKED, true); 1126 } 1127 1128 @Test public void clientConfiguredCustomContentEncoding() throws Exception { 1129 server.enqueue(new MockResponse().setBody("ABCDE").addHeader("Content-Encoding: custom")); 1130 server.play(); 1131 1132 URLConnection connection = client.open(server.getUrl("/")); 1133 connection.addRequestProperty("Accept-Encoding", "custom"); 1134 assertEquals("ABCDE", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1135 1136 RecordedRequest request = server.takeRequest(); 1137 assertContains(request.getHeaders(), "Accept-Encoding: custom"); 1138 } 1139 1140 /** 1141 * Test a bug where gzip input streams weren't exhausting the input stream, 1142 * which corrupted the request that followed or prevented connection reuse. 1143 * http://code.google.com/p/android/issues/detail?id=7059 1144 * http://code.google.com/p/android/issues/detail?id=38817 1145 */ 1146 private void testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind transferKind, 1147 boolean tls) throws Exception { 1148 if (tls) { 1149 SSLSocketFactory socketFactory = sslContext.getSocketFactory(); 1150 RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); 1151 server.useHttps(socketFactory, false); 1152 client.setSslSocketFactory(socketFactory); 1153 client.setHostnameVerifier(hostnameVerifier); 1154 } 1155 1156 MockResponse responseOne = new MockResponse(); 1157 responseOne.addHeader("Content-Encoding: gzip"); 1158 transferKind.setBody(responseOne, gzip("one (gzipped)".getBytes("UTF-8")), 5); 1159 server.enqueue(responseOne); 1160 MockResponse responseTwo = new MockResponse(); 1161 transferKind.setBody(responseTwo, "two (identity)", 5); 1162 server.enqueue(responseTwo); 1163 server.play(); 1164 1165 HttpURLConnection connection1 = client.open(server.getUrl("/")); 1166 connection1.addRequestProperty("Accept-Encoding", "gzip"); 1167 InputStream gunzippedIn = new GZIPInputStream(connection1.getInputStream()); 1168 assertEquals("one (gzipped)", readAscii(gunzippedIn, Integer.MAX_VALUE)); 1169 assertEquals(0, server.takeRequest().getSequenceNumber()); 1170 1171 HttpURLConnection connection2 = client.open(server.getUrl("/")); 1172 assertEquals("two (identity)", readAscii(connection2.getInputStream(), Integer.MAX_VALUE)); 1173 assertEquals(1, server.takeRequest().getSequenceNumber()); 1174 } 1175 1176 @Test public void transparentGzipWorksAfterExceptionRecovery() throws Exception { 1177 server.enqueue(new MockResponse() 1178 .setBody("a") 1179 .setSocketPolicy(SHUTDOWN_INPUT_AT_END)); 1180 server.enqueue(new MockResponse() 1181 .addHeader("Content-Encoding: gzip") 1182 .setBody(gzip("b".getBytes(UTF_8)))); 1183 server.play(); 1184 1185 // Seed the pool with a bad connection. 1186 assertContent("a", client.open(server.getUrl("/"))); 1187 1188 // This connection will need to be recovered. When it is, transparent gzip should still work! 1189 assertContent("b", client.open(server.getUrl("/"))); 1190 1191 assertEquals(0, server.takeRequest().getSequenceNumber()); 1192 assertEquals(0, server.takeRequest().getSequenceNumber()); // Connection is not pooled. 1193 } 1194 1195 @Test public void endOfStreamResponseIsNotPooled() throws Exception { 1196 server.enqueue(new MockResponse() 1197 .setBody("{}") 1198 .clearHeaders() 1199 .setSocketPolicy(DISCONNECT_AT_END)); 1200 server.play(); 1201 1202 ConnectionPool pool = ConnectionPool.getDefault(); 1203 pool.evictAll(); 1204 client.setConnectionPool(pool); 1205 1206 HttpURLConnection connection = client.open(server.getUrl("/")); 1207 assertContent("{}", connection); 1208 assertEquals(0, client.getConnectionPool().getConnectionCount()); 1209 } 1210 1211 @Test public void earlyDisconnectDoesntHarmPoolingWithChunkedEncoding() throws Exception { 1212 testEarlyDisconnectDoesntHarmPooling(TransferKind.CHUNKED); 1213 } 1214 1215 @Test public void earlyDisconnectDoesntHarmPoolingWithFixedLengthEncoding() throws Exception { 1216 testEarlyDisconnectDoesntHarmPooling(TransferKind.FIXED_LENGTH); 1217 } 1218 1219 private void testEarlyDisconnectDoesntHarmPooling(TransferKind transferKind) throws Exception { 1220 MockResponse response1 = new MockResponse(); 1221 transferKind.setBody(response1, "ABCDEFGHIJK", 1024); 1222 server.enqueue(response1); 1223 1224 MockResponse response2 = new MockResponse(); 1225 transferKind.setBody(response2, "LMNOPQRSTUV", 1024); 1226 server.enqueue(response2); 1227 1228 server.play(); 1229 1230 HttpURLConnection connection1 = client.open(server.getUrl("/")); 1231 InputStream in1 = connection1.getInputStream(); 1232 assertEquals("ABCDE", readAscii(in1, 5)); 1233 connection1.disconnect(); 1234 1235 HttpURLConnection connection2 = client.open(server.getUrl("/")); 1236 InputStream in2 = connection2.getInputStream(); 1237 assertEquals("LMNOP", readAscii(in2, 5)); 1238 connection2.disconnect(); 1239 1240 assertEquals(0, server.takeRequest().getSequenceNumber()); 1241 assertEquals(1, server.takeRequest().getSequenceNumber()); // Connection is pooled! 1242 } 1243 1244 @Test public void streamDiscardingIsTimely() throws Exception { 1245 // This response takes at least a full second to serve: 10,000 bytes served 100 bytes at a time. 1246 server.enqueue(new MockResponse() 1247 .setBody(new byte[10000]) 1248 .throttleBody(100, 10, MILLISECONDS)); 1249 server.enqueue(new MockResponse().setBody("A")); 1250 server.play(); 1251 1252 long startNanos = System.nanoTime(); 1253 URLConnection connection1 = client.open(server.getUrl("/")); 1254 InputStream in = connection1.getInputStream(); 1255 in.close(); 1256 long elapsedNanos = System.nanoTime() - startNanos; 1257 long elapsedMillis = NANOSECONDS.toMillis(elapsedNanos); 1258 1259 // If we're working correctly, this should be greater than 100ms, but less than double that. 1260 // Previously we had a bug where we would download the entire response body as long as no 1261 // individual read took longer than 100ms. 1262 assertTrue(String.format("Time to close: %sms", elapsedMillis), elapsedMillis < 500); 1263 1264 // Do another request to confirm that the discarded connection was not pooled. 1265 assertContent("A", client.open(server.getUrl("/"))); 1266 1267 assertEquals(0, server.takeRequest().getSequenceNumber()); 1268 assertEquals(0, server.takeRequest().getSequenceNumber()); // Connection is not pooled. 1269 } 1270 1271 @Test public void setChunkedStreamingMode() throws IOException, InterruptedException { 1272 server.enqueue(new MockResponse()); 1273 server.play(); 1274 1275 String body = "ABCDEFGHIJKLMNOPQ"; 1276 connection = client.open(server.getUrl("/")); 1277 connection.setChunkedStreamingMode(0); // OkHttp does not honor specific chunk sizes. 1278 connection.setDoOutput(true); 1279 OutputStream outputStream = connection.getOutputStream(); 1280 outputStream.write(body.getBytes("US-ASCII")); 1281 assertEquals(200, connection.getResponseCode()); 1282 1283 RecordedRequest request = server.takeRequest(); 1284 assertEquals(body, new String(request.getBody(), "US-ASCII")); 1285 assertEquals(Arrays.asList(body.length()), request.getChunkSizes()); 1286 } 1287 1288 @Test public void authenticateWithFixedLengthStreaming() throws Exception { 1289 testAuthenticateWithStreamingPost(StreamingMode.FIXED_LENGTH); 1290 } 1291 1292 @Test public void authenticateWithChunkedStreaming() throws Exception { 1293 testAuthenticateWithStreamingPost(StreamingMode.CHUNKED); 1294 } 1295 1296 private void testAuthenticateWithStreamingPost(StreamingMode streamingMode) throws Exception { 1297 MockResponse pleaseAuthenticate = new MockResponse().setResponseCode(401) 1298 .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") 1299 .setBody("Please authenticate."); 1300 server.enqueue(pleaseAuthenticate); 1301 server.play(); 1302 1303 Authenticator.setDefault(new RecordingAuthenticator()); 1304 connection = client.open(server.getUrl("/")); 1305 connection.setDoOutput(true); 1306 byte[] requestBody = { 'A', 'B', 'C', 'D' }; 1307 if (streamingMode == StreamingMode.FIXED_LENGTH) { 1308 connection.setFixedLengthStreamingMode(requestBody.length); 1309 } else if (streamingMode == StreamingMode.CHUNKED) { 1310 connection.setChunkedStreamingMode(0); 1311 } 1312 OutputStream outputStream = connection.getOutputStream(); 1313 outputStream.write(requestBody); 1314 outputStream.close(); 1315 try { 1316 connection.getInputStream(); 1317 fail(); 1318 } catch (HttpRetryException expected) { 1319 } 1320 1321 // no authorization header for the request... 1322 RecordedRequest request = server.takeRequest(); 1323 assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*"); 1324 assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody())); 1325 } 1326 1327 @Test public void nonStandardAuthenticationScheme() throws Exception { 1328 List<String> calls = authCallsForHeader("WWW-Authenticate: Foo"); 1329 assertEquals(Collections.<String>emptyList(), calls); 1330 } 1331 1332 @Test public void nonStandardAuthenticationSchemeWithRealm() throws Exception { 1333 List<String> calls = authCallsForHeader("WWW-Authenticate: Foo realm=\"Bar\""); 1334 assertEquals(0, calls.size()); 1335 } 1336 1337 // Digest auth is currently unsupported. Test that digest requests should fail reasonably. 1338 // http://code.google.com/p/android/issues/detail?id=11140 1339 @Test public void digestAuthentication() throws Exception { 1340 List<String> calls = authCallsForHeader("WWW-Authenticate: Digest " 1341 + "realm=\"testrealm@host.com\", qop=\"auth,auth-int\", " 1342 + "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", " 1343 + "opaque=\"5ccc069c403ebaf9f0171e9517f40e41\""); 1344 assertEquals(0, calls.size()); 1345 } 1346 1347 @Test public void allAttributesSetInServerAuthenticationCallbacks() throws Exception { 1348 List<String> calls = authCallsForHeader("WWW-Authenticate: Basic realm=\"Bar\""); 1349 assertEquals(1, calls.size()); 1350 URL url = server.getUrl("/"); 1351 String call = calls.get(0); 1352 assertTrue(call, call.contains("host=" + url.getHost())); 1353 assertTrue(call, call.contains("port=" + url.getPort())); 1354 assertTrue(call, call.contains("site=" + InetAddress.getAllByName(url.getHost())[0])); 1355 assertTrue(call, call.contains("url=" + url)); 1356 assertTrue(call, call.contains("type=" + Authenticator.RequestorType.SERVER)); 1357 assertTrue(call, call.contains("prompt=Bar")); 1358 assertTrue(call, call.contains("protocol=http")); 1359 assertTrue(call, call.toLowerCase().contains("scheme=basic")); // lowercase for the RI. 1360 } 1361 1362 @Test public void allAttributesSetInProxyAuthenticationCallbacks() throws Exception { 1363 List<String> calls = authCallsForHeader("Proxy-Authenticate: Basic realm=\"Bar\""); 1364 assertEquals(1, calls.size()); 1365 URL url = server.getUrl("/"); 1366 String call = calls.get(0); 1367 assertTrue(call, call.contains("host=" + url.getHost())); 1368 assertTrue(call, call.contains("port=" + url.getPort())); 1369 assertTrue(call, call.contains("site=" + InetAddress.getAllByName(url.getHost())[0])); 1370 assertTrue(call, call.contains("url=http://android.com")); 1371 assertTrue(call, call.contains("type=" + Authenticator.RequestorType.PROXY)); 1372 assertTrue(call, call.contains("prompt=Bar")); 1373 assertTrue(call, call.contains("protocol=http")); 1374 assertTrue(call, call.toLowerCase().contains("scheme=basic")); // lowercase for the RI. 1375 } 1376 1377 private List<String> authCallsForHeader(String authHeader) throws IOException { 1378 boolean proxy = authHeader.startsWith("Proxy-"); 1379 int responseCode = proxy ? 407 : 401; 1380 RecordingAuthenticator authenticator = new RecordingAuthenticator(null); 1381 Authenticator.setDefault(authenticator); 1382 MockResponse pleaseAuthenticate = new MockResponse().setResponseCode(responseCode) 1383 .addHeader(authHeader) 1384 .setBody("Please authenticate."); 1385 server.enqueue(pleaseAuthenticate); 1386 server.play(); 1387 1388 if (proxy) { 1389 client.setProxy(server.toProxyAddress()); 1390 connection = client.open(new URL("http://android.com")); 1391 } else { 1392 connection = client.open(server.getUrl("/")); 1393 } 1394 assertEquals(responseCode, connection.getResponseCode()); 1395 return authenticator.calls; 1396 } 1397 1398 @Test public void setValidRequestMethod() throws Exception { 1399 server.play(); 1400 assertValidRequestMethod("GET"); 1401 assertValidRequestMethod("DELETE"); 1402 assertValidRequestMethod("HEAD"); 1403 assertValidRequestMethod("OPTIONS"); 1404 assertValidRequestMethod("POST"); 1405 assertValidRequestMethod("PUT"); 1406 assertValidRequestMethod("TRACE"); 1407 assertValidRequestMethod("PATCH"); 1408 } 1409 1410 private void assertValidRequestMethod(String requestMethod) throws Exception { 1411 connection = client.open(server.getUrl("/")); 1412 connection.setRequestMethod(requestMethod); 1413 assertEquals(requestMethod, connection.getRequestMethod()); 1414 } 1415 1416 @Test public void setInvalidRequestMethodLowercase() throws Exception { 1417 server.play(); 1418 assertInvalidRequestMethod("get"); 1419 } 1420 1421 @Test public void setInvalidRequestMethodConnect() throws Exception { 1422 server.play(); 1423 assertInvalidRequestMethod("CONNECT"); 1424 } 1425 1426 private void assertInvalidRequestMethod(String requestMethod) throws Exception { 1427 connection = client.open(server.getUrl("/")); 1428 try { 1429 connection.setRequestMethod(requestMethod); 1430 fail(); 1431 } catch (ProtocolException expected) { 1432 } 1433 } 1434 1435 @Test public void shoutcast() throws Exception { 1436 server.enqueue(new MockResponse().setStatus("ICY 200 OK") 1437 // .addHeader("HTTP/1.0 200 OK") 1438 .addHeader("Accept-Ranges: none") 1439 .addHeader("Content-Type: audio/mpeg") 1440 .addHeader("icy-br:128") 1441 .addHeader("ice-audio-info: bitrate=128;samplerate=44100;channels=2") 1442 .addHeader("icy-br:128") 1443 .addHeader("icy-description:Rock") 1444 .addHeader("icy-genre:riders") 1445 .addHeader("icy-name:A2RRock") 1446 .addHeader("icy-pub:1") 1447 .addHeader("icy-url:http://www.A2Rradio.com") 1448 .addHeader("Server: Icecast 2.3.3-kh8") 1449 .addHeader("Cache-Control: no-cache") 1450 .addHeader("Pragma: no-cache") 1451 .addHeader("Expires: Mon, 26 Jul 1997 05:00:00 GMT") 1452 .addHeader("icy-metaint:16000") 1453 .setBody("mp3 data")); 1454 server.play(); 1455 connection = client.open(server.getUrl("/")); 1456 assertEquals(200, connection.getResponseCode()); 1457 assertEquals("OK", connection.getResponseMessage()); 1458 assertContent("mp3 data", connection); 1459 } 1460 1461 @Test public void cannotSetNegativeFixedLengthStreamingMode() throws Exception { 1462 server.play(); 1463 connection = client.open(server.getUrl("/")); 1464 try { 1465 connection.setFixedLengthStreamingMode(-2); 1466 fail(); 1467 } catch (IllegalArgumentException expected) { 1468 } 1469 } 1470 1471 @Test public void canSetNegativeChunkedStreamingMode() throws Exception { 1472 server.play(); 1473 connection = client.open(server.getUrl("/")); 1474 connection.setChunkedStreamingMode(-2); 1475 } 1476 1477 @Test public void cannotSetFixedLengthStreamingModeAfterConnect() throws Exception { 1478 server.enqueue(new MockResponse().setBody("A")); 1479 server.play(); 1480 connection = client.open(server.getUrl("/")); 1481 assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1482 try { 1483 connection.setFixedLengthStreamingMode(1); 1484 fail(); 1485 } catch (IllegalStateException expected) { 1486 } 1487 } 1488 1489 @Test public void cannotSetChunkedStreamingModeAfterConnect() throws Exception { 1490 server.enqueue(new MockResponse().setBody("A")); 1491 server.play(); 1492 connection = client.open(server.getUrl("/")); 1493 assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1494 try { 1495 connection.setChunkedStreamingMode(1); 1496 fail(); 1497 } catch (IllegalStateException expected) { 1498 } 1499 } 1500 1501 @Test public void cannotSetFixedLengthStreamingModeAfterChunkedStreamingMode() throws Exception { 1502 server.play(); 1503 connection = client.open(server.getUrl("/")); 1504 connection.setChunkedStreamingMode(1); 1505 try { 1506 connection.setFixedLengthStreamingMode(1); 1507 fail(); 1508 } catch (IllegalStateException expected) { 1509 } 1510 } 1511 1512 @Test public void cannotSetChunkedStreamingModeAfterFixedLengthStreamingMode() throws Exception { 1513 server.play(); 1514 connection = client.open(server.getUrl("/")); 1515 connection.setFixedLengthStreamingMode(1); 1516 try { 1517 connection.setChunkedStreamingMode(1); 1518 fail(); 1519 } catch (IllegalStateException expected) { 1520 } 1521 } 1522 1523 @Test public void secureFixedLengthStreaming() throws Exception { 1524 testSecureStreamingPost(StreamingMode.FIXED_LENGTH); 1525 } 1526 1527 @Test public void secureChunkedStreaming() throws Exception { 1528 testSecureStreamingPost(StreamingMode.CHUNKED); 1529 } 1530 1531 /** 1532 * Users have reported problems using HTTPS with streaming request bodies. 1533 * http://code.google.com/p/android/issues/detail?id=12860 1534 */ 1535 private void testSecureStreamingPost(StreamingMode streamingMode) throws Exception { 1536 server.useHttps(sslContext.getSocketFactory(), false); 1537 server.enqueue(new MockResponse().setBody("Success!")); 1538 server.play(); 1539 1540 client.setSslSocketFactory(sslContext.getSocketFactory()); 1541 client.setHostnameVerifier(new RecordingHostnameVerifier()); 1542 connection = client.open(server.getUrl("/")); 1543 connection.setDoOutput(true); 1544 byte[] requestBody = { 'A', 'B', 'C', 'D' }; 1545 if (streamingMode == StreamingMode.FIXED_LENGTH) { 1546 connection.setFixedLengthStreamingMode(requestBody.length); 1547 } else if (streamingMode == StreamingMode.CHUNKED) { 1548 connection.setChunkedStreamingMode(0); 1549 } 1550 OutputStream outputStream = connection.getOutputStream(); 1551 outputStream.write(requestBody); 1552 outputStream.close(); 1553 assertEquals("Success!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1554 1555 RecordedRequest request = server.takeRequest(); 1556 assertEquals("POST / HTTP/1.1", request.getRequestLine()); 1557 if (streamingMode == StreamingMode.FIXED_LENGTH) { 1558 assertEquals(Collections.<Integer>emptyList(), request.getChunkSizes()); 1559 } else if (streamingMode == StreamingMode.CHUNKED) { 1560 assertEquals(Arrays.asList(4), request.getChunkSizes()); 1561 } 1562 assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody())); 1563 } 1564 1565 enum StreamingMode { 1566 FIXED_LENGTH, CHUNKED 1567 } 1568 1569 @Test public void authenticateWithPost() throws Exception { 1570 MockResponse pleaseAuthenticate = new MockResponse().setResponseCode(401) 1571 .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") 1572 .setBody("Please authenticate."); 1573 // fail auth three times... 1574 server.enqueue(pleaseAuthenticate); 1575 server.enqueue(pleaseAuthenticate); 1576 server.enqueue(pleaseAuthenticate); 1577 // ...then succeed the fourth time 1578 server.enqueue(new MockResponse().setBody("Successful auth!")); 1579 server.play(); 1580 1581 Authenticator.setDefault(new RecordingAuthenticator()); 1582 connection = client.open(server.getUrl("/")); 1583 connection.setDoOutput(true); 1584 byte[] requestBody = { 'A', 'B', 'C', 'D' }; 1585 OutputStream outputStream = connection.getOutputStream(); 1586 outputStream.write(requestBody); 1587 outputStream.close(); 1588 assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1589 1590 // no authorization header for the first request... 1591 RecordedRequest request = server.takeRequest(); 1592 assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*"); 1593 1594 // ...but the three requests that follow include an authorization header 1595 for (int i = 0; i < 3; i++) { 1596 request = server.takeRequest(); 1597 assertEquals("POST / HTTP/1.1", request.getRequestLine()); 1598 assertContains(request.getHeaders(), 1599 "Authorization: Basic " + RecordingAuthenticator.BASE_64_CREDENTIALS); 1600 assertEquals(Arrays.toString(requestBody), Arrays.toString(request.getBody())); 1601 } 1602 } 1603 1604 @Test public void authenticateWithGet() throws Exception { 1605 MockResponse pleaseAuthenticate = new MockResponse().setResponseCode(401) 1606 .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") 1607 .setBody("Please authenticate."); 1608 // fail auth three times... 1609 server.enqueue(pleaseAuthenticate); 1610 server.enqueue(pleaseAuthenticate); 1611 server.enqueue(pleaseAuthenticate); 1612 // ...then succeed the fourth time 1613 server.enqueue(new MockResponse().setBody("Successful auth!")); 1614 server.play(); 1615 1616 Authenticator.setDefault(new RecordingAuthenticator()); 1617 connection = client.open(server.getUrl("/")); 1618 assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1619 1620 // no authorization header for the first request... 1621 RecordedRequest request = server.takeRequest(); 1622 assertContainsNoneMatching(request.getHeaders(), "Authorization: Basic .*"); 1623 1624 // ...but the three requests that follow requests include an authorization header 1625 for (int i = 0; i < 3; i++) { 1626 request = server.takeRequest(); 1627 assertEquals("GET / HTTP/1.1", request.getRequestLine()); 1628 assertContains(request.getHeaders(), 1629 "Authorization: Basic " + RecordingAuthenticator.BASE_64_CREDENTIALS); 1630 } 1631 } 1632 1633 /** https://github.com/square/okhttp/issues/342 */ 1634 @Test public void authenticateRealmUppercase() throws Exception { 1635 server.enqueue(new MockResponse().setResponseCode(401) 1636 .addHeader("wWw-aUtHeNtIcAtE: bAsIc rEaLm=\"pRoTeCtEd aReA\"") 1637 .setBody("Please authenticate.")); 1638 server.enqueue(new MockResponse().setBody("Successful auth!")); 1639 server.play(); 1640 1641 Authenticator.setDefault(new RecordingAuthenticator()); 1642 connection = client.open(server.getUrl("/")); 1643 assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1644 } 1645 1646 @Test public void redirectedWithChunkedEncoding() throws Exception { 1647 testRedirected(TransferKind.CHUNKED, true); 1648 } 1649 1650 @Test public void redirectedWithContentLengthHeader() throws Exception { 1651 testRedirected(TransferKind.FIXED_LENGTH, true); 1652 } 1653 1654 @Test public void redirectedWithNoLengthHeaders() throws Exception { 1655 testRedirected(TransferKind.END_OF_STREAM, false); 1656 } 1657 1658 private void testRedirected(TransferKind transferKind, boolean reuse) throws Exception { 1659 MockResponse response = new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1660 .addHeader("Location: /foo"); 1661 transferKind.setBody(response, "This page has moved!", 10); 1662 server.enqueue(response); 1663 server.enqueue(new MockResponse().setBody("This is the new location!")); 1664 server.play(); 1665 1666 URLConnection connection = client.open(server.getUrl("/")); 1667 assertEquals("This is the new location!", 1668 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1669 1670 RecordedRequest first = server.takeRequest(); 1671 assertEquals("GET / HTTP/1.1", first.getRequestLine()); 1672 RecordedRequest retry = server.takeRequest(); 1673 assertEquals("GET /foo HTTP/1.1", retry.getRequestLine()); 1674 if (reuse) { 1675 assertEquals("Expected connection reuse", 1, retry.getSequenceNumber()); 1676 } 1677 } 1678 1679 @Test public void redirectedOnHttps() throws IOException, InterruptedException { 1680 server.useHttps(sslContext.getSocketFactory(), false); 1681 server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1682 .addHeader("Location: /foo") 1683 .setBody("This page has moved!")); 1684 server.enqueue(new MockResponse().setBody("This is the new location!")); 1685 server.play(); 1686 1687 client.setSslSocketFactory(sslContext.getSocketFactory()); 1688 client.setHostnameVerifier(new RecordingHostnameVerifier()); 1689 connection = client.open(server.getUrl("/")); 1690 assertEquals("This is the new location!", 1691 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1692 1693 RecordedRequest first = server.takeRequest(); 1694 assertEquals("GET / HTTP/1.1", first.getRequestLine()); 1695 RecordedRequest retry = server.takeRequest(); 1696 assertEquals("GET /foo HTTP/1.1", retry.getRequestLine()); 1697 assertEquals("Expected connection reuse", 1, retry.getSequenceNumber()); 1698 } 1699 1700 @Test public void notRedirectedFromHttpsToHttp() throws IOException, InterruptedException { 1701 server.useHttps(sslContext.getSocketFactory(), false); 1702 server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1703 .addHeader("Location: http://anyhost/foo") 1704 .setBody("This page has moved!")); 1705 server.play(); 1706 1707 client.setFollowProtocolRedirects(false); 1708 client.setSslSocketFactory(sslContext.getSocketFactory()); 1709 client.setHostnameVerifier(new RecordingHostnameVerifier()); 1710 connection = client.open(server.getUrl("/")); 1711 assertEquals("This page has moved!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1712 } 1713 1714 @Test public void notRedirectedFromHttpToHttps() throws IOException, InterruptedException { 1715 server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1716 .addHeader("Location: https://anyhost/foo") 1717 .setBody("This page has moved!")); 1718 server.play(); 1719 1720 client.setFollowProtocolRedirects(false); 1721 connection = client.open(server.getUrl("/")); 1722 assertEquals("This page has moved!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1723 } 1724 1725 @Test public void redirectedFromHttpsToHttpFollowingProtocolRedirects() throws Exception { 1726 server2 = new MockWebServer(); 1727 server2.enqueue(new MockResponse().setBody("This is insecure HTTP!")); 1728 server2.play(); 1729 1730 server.useHttps(sslContext.getSocketFactory(), false); 1731 server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1732 .addHeader("Location: " + server2.getUrl("/")) 1733 .setBody("This page has moved!")); 1734 server.play(); 1735 1736 client.setSslSocketFactory(sslContext.getSocketFactory()); 1737 client.setHostnameVerifier(new RecordingHostnameVerifier()); 1738 client.setFollowProtocolRedirects(true); 1739 HttpsURLConnection connection = (HttpsURLConnection) client.open(server.getUrl("/")); 1740 assertContent("This is insecure HTTP!", connection); 1741 assertNull(connection.getCipherSuite()); 1742 assertNull(connection.getLocalCertificates()); 1743 assertNull(connection.getServerCertificates()); 1744 assertNull(connection.getPeerPrincipal()); 1745 assertNull(connection.getLocalPrincipal()); 1746 } 1747 1748 @Test public void redirectedFromHttpToHttpsFollowingProtocolRedirects() throws Exception { 1749 server2 = new MockWebServer(); 1750 server2.useHttps(sslContext.getSocketFactory(), false); 1751 server2.enqueue(new MockResponse().setBody("This is secure HTTPS!")); 1752 server2.play(); 1753 1754 server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1755 .addHeader("Location: " + server2.getUrl("/")) 1756 .setBody("This page has moved!")); 1757 server.play(); 1758 1759 client.setSslSocketFactory(sslContext.getSocketFactory()); 1760 client.setHostnameVerifier(new RecordingHostnameVerifier()); 1761 client.setFollowProtocolRedirects(true); 1762 connection = client.open(server.getUrl("/")); 1763 assertContent("This is secure HTTPS!", connection); 1764 assertFalse(connection instanceof HttpsURLConnection); 1765 } 1766 1767 @Test public void redirectToAnotherOriginServer() throws Exception { 1768 redirectToAnotherOriginServer(false); 1769 } 1770 1771 @Test public void redirectToAnotherOriginServerWithHttps() throws Exception { 1772 redirectToAnotherOriginServer(true); 1773 } 1774 1775 private void redirectToAnotherOriginServer(boolean https) throws Exception { 1776 server2 = new MockWebServer(); 1777 if (https) { 1778 server.useHttps(sslContext.getSocketFactory(), false); 1779 server2.useHttps(sslContext.getSocketFactory(), false); 1780 server2.setNpnEnabled(false); 1781 client.setSslSocketFactory(sslContext.getSocketFactory()); 1782 client.setHostnameVerifier(new RecordingHostnameVerifier()); 1783 } 1784 1785 server2.enqueue(new MockResponse().setBody("This is the 2nd server!")); 1786 server2.enqueue(new MockResponse().setBody("This is the 2nd server, again!")); 1787 server2.play(); 1788 1789 server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1790 .addHeader("Location: " + server2.getUrl("/").toString()) 1791 .setBody("This page has moved!")); 1792 server.enqueue(new MockResponse().setBody("This is the first server again!")); 1793 server.play(); 1794 1795 connection = client.open(server.getUrl("/")); 1796 assertContent("This is the 2nd server!", connection); 1797 assertEquals(server2.getUrl("/"), connection.getURL()); 1798 1799 // make sure the first server was careful to recycle the connection 1800 assertContent("This is the first server again!", client.open(server.getUrl("/"))); 1801 assertContent("This is the 2nd server, again!", client.open(server2.getUrl("/"))); 1802 1803 String server1Host = hostName + ":" + server.getPort(); 1804 String server2Host = hostName + ":" + server2.getPort(); 1805 assertContains(server.takeRequest().getHeaders(), "Host: " + server1Host); 1806 assertContains(server2.takeRequest().getHeaders(), "Host: " + server2Host); 1807 assertEquals("Expected connection reuse", 1, server.takeRequest().getSequenceNumber()); 1808 assertEquals("Expected connection reuse", 1, server2.takeRequest().getSequenceNumber()); 1809 } 1810 1811 @Test public void redirectWithProxySelector() throws Exception { 1812 final List<URI> proxySelectionRequests = new ArrayList<URI>(); 1813 client.setProxySelector(new ProxySelector() { 1814 @Override public List<Proxy> select(URI uri) { 1815 proxySelectionRequests.add(uri); 1816 MockWebServer proxyServer = (uri.getPort() == server.getPort()) ? server : server2; 1817 return Arrays.asList(proxyServer.toProxyAddress()); 1818 } 1819 @Override public void connectFailed(URI uri, SocketAddress address, IOException failure) { 1820 throw new AssertionError(); 1821 } 1822 }); 1823 1824 server2 = new MockWebServer(); 1825 server2.enqueue(new MockResponse().setBody("This is the 2nd server!")); 1826 server2.play(); 1827 1828 server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1829 .addHeader("Location: " + server2.getUrl("/b").toString()) 1830 .setBody("This page has moved!")); 1831 server.play(); 1832 1833 assertContent("This is the 2nd server!", client.open(server.getUrl("/a"))); 1834 1835 assertEquals(Arrays.asList(server.getUrl("/a").toURI(), server2.getUrl("/b").toURI()), 1836 proxySelectionRequests); 1837 1838 server2.shutdown(); 1839 } 1840 1841 @Test public void response300MultipleChoiceWithPost() throws Exception { 1842 // Chrome doesn't follow the redirect, but Firefox and the RI both do 1843 testResponseRedirectedWithPost(HttpURLConnection.HTTP_MULT_CHOICE); 1844 } 1845 1846 @Test public void response301MovedPermanentlyWithPost() throws Exception { 1847 testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_PERM); 1848 } 1849 1850 @Test public void response302MovedTemporarilyWithPost() throws Exception { 1851 testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_TEMP); 1852 } 1853 1854 @Test public void response303SeeOtherWithPost() throws Exception { 1855 testResponseRedirectedWithPost(HttpURLConnection.HTTP_SEE_OTHER); 1856 } 1857 1858 private void testResponseRedirectedWithPost(int redirectCode) throws Exception { 1859 server.enqueue(new MockResponse().setResponseCode(redirectCode) 1860 .addHeader("Location: /page2") 1861 .setBody("This page has moved!")); 1862 server.enqueue(new MockResponse().setBody("Page 2")); 1863 server.play(); 1864 1865 connection = client.open(server.getUrl("/page1")); 1866 connection.setDoOutput(true); 1867 byte[] requestBody = { 'A', 'B', 'C', 'D' }; 1868 OutputStream outputStream = connection.getOutputStream(); 1869 outputStream.write(requestBody); 1870 outputStream.close(); 1871 assertEquals("Page 2", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1872 assertTrue(connection.getDoOutput()); 1873 1874 RecordedRequest page1 = server.takeRequest(); 1875 assertEquals("POST /page1 HTTP/1.1", page1.getRequestLine()); 1876 assertEquals(Arrays.toString(requestBody), Arrays.toString(page1.getBody())); 1877 1878 RecordedRequest page2 = server.takeRequest(); 1879 assertEquals("GET /page2 HTTP/1.1", page2.getRequestLine()); 1880 } 1881 1882 @Test public void redirectedPostStripsRequestBodyHeaders() throws Exception { 1883 server.enqueue(new MockResponse() 1884 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1885 .addHeader("Location: /page2")); 1886 server.enqueue(new MockResponse().setBody("Page 2")); 1887 server.play(); 1888 1889 connection = client.open(server.getUrl("/page1")); 1890 connection.setDoOutput(true); 1891 connection.addRequestProperty("Content-Length", "4"); 1892 connection.addRequestProperty("Content-Type", "text/plain; charset=utf-8"); 1893 connection.addRequestProperty("Transfer-Encoding", "identity"); 1894 OutputStream outputStream = connection.getOutputStream(); 1895 outputStream.write("ABCD".getBytes("UTF-8")); 1896 outputStream.close(); 1897 assertEquals("Page 2", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1898 1899 assertEquals("POST /page1 HTTP/1.1", server.takeRequest().getRequestLine()); 1900 1901 RecordedRequest page2 = server.takeRequest(); 1902 assertEquals("GET /page2 HTTP/1.1", page2.getRequestLine()); 1903 assertContainsNoneMatching(page2.getHeaders(), "Content-Length"); 1904 assertContains(page2.getHeaders(), "Content-Type: text/plain; charset=utf-8"); 1905 assertContains(page2.getHeaders(), "Transfer-Encoding: identity"); 1906 } 1907 1908 @Test public void response305UseProxy() throws Exception { 1909 server.play(); 1910 server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_USE_PROXY) 1911 .addHeader("Location: " + server.getUrl("/")) 1912 .setBody("This page has moved!")); 1913 server.enqueue(new MockResponse().setBody("Proxy Response")); 1914 1915 connection = client.open(server.getUrl("/foo")); 1916 // Fails on the RI, which gets "Proxy Response" 1917 assertEquals("This page has moved!", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 1918 1919 RecordedRequest page1 = server.takeRequest(); 1920 assertEquals("GET /foo HTTP/1.1", page1.getRequestLine()); 1921 assertEquals(1, server.getRequestCount()); 1922 } 1923 1924 @Test public void response307WithGet() throws Exception { 1925 test307Redirect("GET"); 1926 } 1927 1928 @Test public void response307WithHead() throws Exception { 1929 test307Redirect("HEAD"); 1930 } 1931 1932 @Test public void response307WithOptions() throws Exception { 1933 test307Redirect("OPTIONS"); 1934 } 1935 1936 @Test public void response307WithPost() throws Exception { 1937 test307Redirect("POST"); 1938 } 1939 1940 private void test307Redirect(String method) throws Exception { 1941 MockResponse response1 = new MockResponse() 1942 .setResponseCode(HTTP_TEMP_REDIRECT) 1943 .addHeader("Location: /page2"); 1944 if (!method.equals("HEAD")) { 1945 response1.setBody("This page has moved!"); 1946 } 1947 server.enqueue(response1); 1948 server.enqueue(new MockResponse().setBody("Page 2")); 1949 server.play(); 1950 1951 connection = client.open(server.getUrl("/page1")); 1952 connection.setRequestMethod(method); 1953 byte[] requestBody = { 'A', 'B', 'C', 'D' }; 1954 if (method.equals("POST")) { 1955 connection.setDoOutput(true); 1956 OutputStream outputStream = connection.getOutputStream(); 1957 outputStream.write(requestBody); 1958 outputStream.close(); 1959 } 1960 1961 String response = readAscii(connection.getInputStream(), Integer.MAX_VALUE); 1962 1963 RecordedRequest page1 = server.takeRequest(); 1964 assertEquals(method + " /page1 HTTP/1.1", page1.getRequestLine()); 1965 1966 if (method.equals("GET")) { 1967 assertEquals("Page 2", response); 1968 } else if (method.equals("HEAD")) { 1969 assertEquals("", response); 1970 } else { 1971 // Methods other than GET/HEAD shouldn't follow the redirect 1972 if (method.equals("POST")) { 1973 assertTrue(connection.getDoOutput()); 1974 assertEquals(Arrays.toString(requestBody), Arrays.toString(page1.getBody())); 1975 } 1976 assertEquals(1, server.getRequestCount()); 1977 assertEquals("This page has moved!", response); 1978 return; 1979 } 1980 1981 // GET/HEAD requests should have followed the redirect with the same method 1982 assertFalse(connection.getDoOutput()); 1983 assertEquals(2, server.getRequestCount()); 1984 RecordedRequest page2 = server.takeRequest(); 1985 assertEquals(method + " /page2 HTTP/1.1", page2.getRequestLine()); 1986 } 1987 1988 @Test public void follow20Redirects() throws Exception { 1989 for (int i = 0; i < 20; i++) { 1990 server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 1991 .addHeader("Location: /" + (i + 1)) 1992 .setBody("Redirecting to /" + (i + 1))); 1993 } 1994 server.enqueue(new MockResponse().setBody("Success!")); 1995 server.play(); 1996 1997 connection = client.open(server.getUrl("/0")); 1998 assertContent("Success!", connection); 1999 assertEquals(server.getUrl("/20"), connection.getURL()); 2000 } 2001 2002 @Test public void doesNotFollow21Redirects() throws Exception { 2003 for (int i = 0; i < 21; i++) { 2004 server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 2005 .addHeader("Location: /" + (i + 1)) 2006 .setBody("Redirecting to /" + (i + 1))); 2007 } 2008 server.play(); 2009 2010 connection = client.open(server.getUrl("/0")); 2011 try { 2012 connection.getInputStream(); 2013 fail(); 2014 } catch (ProtocolException expected) { 2015 assertEquals(HttpURLConnection.HTTP_MOVED_TEMP, connection.getResponseCode()); 2016 assertEquals("Too many redirects: 21", expected.getMessage()); 2017 assertContent("Redirecting to /21", connection); 2018 assertEquals(server.getUrl("/20"), connection.getURL()); 2019 } 2020 } 2021 2022 @Test public void httpsWithCustomTrustManager() throws Exception { 2023 RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); 2024 RecordingTrustManager trustManager = new RecordingTrustManager(); 2025 SSLContext sc = SSLContext.getInstance("TLS"); 2026 sc.init(null, new TrustManager[] { trustManager }, new java.security.SecureRandom()); 2027 2028 client.setHostnameVerifier(hostnameVerifier); 2029 client.setSslSocketFactory(sc.getSocketFactory()); 2030 server.useHttps(sslContext.getSocketFactory(), false); 2031 server.enqueue(new MockResponse().setBody("ABC")); 2032 server.enqueue(new MockResponse().setBody("DEF")); 2033 server.enqueue(new MockResponse().setBody("GHI")); 2034 server.play(); 2035 2036 URL url = server.getUrl("/"); 2037 assertContent("ABC", client.open(url)); 2038 assertContent("DEF", client.open(url)); 2039 assertContent("GHI", client.open(url)); 2040 2041 assertEquals(Arrays.asList("verify " + hostName), hostnameVerifier.calls); 2042 assertEquals(Arrays.asList("checkServerTrusted [CN=" + hostName + " 1]"), trustManager.calls); 2043 } 2044 2045 @Test public void readTimeouts() throws IOException { 2046 // This relies on the fact that MockWebServer doesn't close the 2047 // connection after a response has been sent. This causes the client to 2048 // try to read more bytes than are sent, which results in a timeout. 2049 MockResponse timeout = 2050 new MockResponse().setBody("ABC").clearHeaders().addHeader("Content-Length: 4"); 2051 server.enqueue(timeout); 2052 server.enqueue(new MockResponse().setBody("unused")); // to keep the server alive 2053 server.play(); 2054 2055 URLConnection connection = client.open(server.getUrl("/")); 2056 connection.setReadTimeout(1000); 2057 InputStream in = connection.getInputStream(); 2058 assertEquals('A', in.read()); 2059 assertEquals('B', in.read()); 2060 assertEquals('C', in.read()); 2061 try { 2062 in.read(); // if Content-Length was accurate, this would return -1 immediately 2063 fail(); 2064 } catch (SocketTimeoutException expected) { 2065 } 2066 } 2067 2068 @Test public void setChunkedEncodingAsRequestProperty() throws IOException, InterruptedException { 2069 server.enqueue(new MockResponse()); 2070 server.play(); 2071 2072 connection = client.open(server.getUrl("/")); 2073 connection.setRequestProperty("Transfer-encoding", "chunked"); 2074 connection.setDoOutput(true); 2075 connection.getOutputStream().write("ABC".getBytes("UTF-8")); 2076 assertEquals(200, connection.getResponseCode()); 2077 2078 RecordedRequest request = server.takeRequest(); 2079 assertEquals("ABC", new String(request.getBody(), "UTF-8")); 2080 } 2081 2082 @Test public void connectionCloseInRequest() throws IOException, InterruptedException { 2083 server.enqueue(new MockResponse()); // server doesn't honor the connection: close header! 2084 server.enqueue(new MockResponse()); 2085 server.play(); 2086 2087 HttpURLConnection a = client.open(server.getUrl("/")); 2088 a.setRequestProperty("Connection", "close"); 2089 assertEquals(200, a.getResponseCode()); 2090 2091 HttpURLConnection b = client.open(server.getUrl("/")); 2092 assertEquals(200, b.getResponseCode()); 2093 2094 assertEquals(0, server.takeRequest().getSequenceNumber()); 2095 assertEquals("When connection: close is used, each request should get its own connection", 0, 2096 server.takeRequest().getSequenceNumber()); 2097 } 2098 2099 @Test public void connectionCloseInResponse() throws IOException, InterruptedException { 2100 server.enqueue(new MockResponse().addHeader("Connection: close")); 2101 server.enqueue(new MockResponse()); 2102 server.play(); 2103 2104 HttpURLConnection a = client.open(server.getUrl("/")); 2105 assertEquals(200, a.getResponseCode()); 2106 2107 HttpURLConnection b = client.open(server.getUrl("/")); 2108 assertEquals(200, b.getResponseCode()); 2109 2110 assertEquals(0, server.takeRequest().getSequenceNumber()); 2111 assertEquals("When connection: close is used, each request should get its own connection", 0, 2112 server.takeRequest().getSequenceNumber()); 2113 } 2114 2115 @Test public void connectionCloseWithRedirect() throws IOException, InterruptedException { 2116 MockResponse response = new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 2117 .addHeader("Location: /foo") 2118 .addHeader("Connection: close"); 2119 server.enqueue(response); 2120 server.enqueue(new MockResponse().setBody("This is the new location!")); 2121 server.play(); 2122 2123 URLConnection connection = client.open(server.getUrl("/")); 2124 assertEquals("This is the new location!", 2125 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 2126 2127 assertEquals(0, server.takeRequest().getSequenceNumber()); 2128 assertEquals("When connection: close is used, each request should get its own connection", 0, 2129 server.takeRequest().getSequenceNumber()); 2130 } 2131 2132 /** 2133 * Retry redirects if the socket is closed. 2134 * https://code.google.com/p/android/issues/detail?id=41576 2135 */ 2136 @Test public void sameConnectionRedirectAndReuse() throws Exception { 2137 server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 2138 .setSocketPolicy(SHUTDOWN_INPUT_AT_END) 2139 .addHeader("Location: /foo")); 2140 server.enqueue(new MockResponse().setBody("This is the new page!")); 2141 server.play(); 2142 2143 assertContent("This is the new page!", client.open(server.getUrl("/"))); 2144 2145 assertEquals(0, server.takeRequest().getSequenceNumber()); 2146 assertEquals(0, server.takeRequest().getSequenceNumber()); 2147 } 2148 2149 @Test public void responseCodeDisagreesWithHeaders() throws IOException, InterruptedException { 2150 server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NO_CONTENT) 2151 .setBody("This body is not allowed!")); 2152 server.play(); 2153 2154 URLConnection connection = client.open(server.getUrl("/")); 2155 assertEquals("This body is not allowed!", 2156 readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 2157 } 2158 2159 @Test public void singleByteReadIsSigned() throws IOException { 2160 server.enqueue(new MockResponse().setBody(new byte[] {-2, -1})); 2161 server.play(); 2162 2163 connection = client.open(server.getUrl("/")); 2164 InputStream in = connection.getInputStream(); 2165 assertEquals(254, in.read()); 2166 assertEquals(255, in.read()); 2167 assertEquals(-1, in.read()); 2168 } 2169 2170 @Test public void flushAfterStreamTransmittedWithChunkedEncoding() throws IOException { 2171 testFlushAfterStreamTransmitted(TransferKind.CHUNKED); 2172 } 2173 2174 @Test public void flushAfterStreamTransmittedWithFixedLength() throws IOException { 2175 testFlushAfterStreamTransmitted(TransferKind.FIXED_LENGTH); 2176 } 2177 2178 @Test public void flushAfterStreamTransmittedWithNoLengthHeaders() throws IOException { 2179 testFlushAfterStreamTransmitted(TransferKind.END_OF_STREAM); 2180 } 2181 2182 /** 2183 * We explicitly permit apps to close the upload stream even after it has 2184 * been transmitted. We also permit flush so that buffered streams can 2185 * do a no-op flush when they are closed. http://b/3038470 2186 */ 2187 private void testFlushAfterStreamTransmitted(TransferKind transferKind) throws IOException { 2188 server.enqueue(new MockResponse().setBody("abc")); 2189 server.play(); 2190 2191 connection = client.open(server.getUrl("/")); 2192 connection.setDoOutput(true); 2193 byte[] upload = "def".getBytes("UTF-8"); 2194 2195 if (transferKind == TransferKind.CHUNKED) { 2196 connection.setChunkedStreamingMode(0); 2197 } else if (transferKind == TransferKind.FIXED_LENGTH) { 2198 connection.setFixedLengthStreamingMode(upload.length); 2199 } 2200 2201 OutputStream out = connection.getOutputStream(); 2202 out.write(upload); 2203 assertEquals("abc", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 2204 2205 out.flush(); // Dubious but permitted. 2206 try { 2207 out.write("ghi".getBytes("UTF-8")); 2208 fail(); 2209 } catch (IOException expected) { 2210 } 2211 } 2212 2213 @Test public void getHeadersThrows() throws IOException { 2214 // Enqueue a response for every IP address held by localhost, because the route selector 2215 // will try each in sequence. 2216 // TODO: use the fake Dns implementation instead of a loop 2217 for (InetAddress inetAddress : InetAddress.getAllByName(server.getHostName())) { 2218 server.enqueue(new MockResponse().setSocketPolicy(DISCONNECT_AT_START)); 2219 } 2220 server.play(); 2221 2222 connection = client.open(server.getUrl("/")); 2223 try { 2224 connection.getInputStream(); 2225 fail(); 2226 } catch (IOException expected) { 2227 } 2228 2229 try { 2230 connection.getInputStream(); 2231 fail(); 2232 } catch (IOException expected) { 2233 } 2234 } 2235 2236 @Test public void dnsFailureThrowsIOException() throws IOException { 2237 connection = client.open(new URL("http://host.unlikelytld")); 2238 try { 2239 connection.connect(); 2240 fail(); 2241 } catch (IOException expected) { 2242 } 2243 } 2244 2245 @Test public void malformedUrlThrowsUnknownHostException() throws IOException { 2246 connection = client.open(new URL("http:///foo.html")); 2247 try { 2248 connection.connect(); 2249 fail(); 2250 } catch (UnknownHostException expected) { 2251 } 2252 } 2253 2254 @Test public void getKeepAlive() throws Exception { 2255 MockWebServer server = new MockWebServer(); 2256 server.enqueue(new MockResponse().setBody("ABC")); 2257 server.play(); 2258 2259 // The request should work once and then fail 2260 HttpURLConnection connection1 = client.open(server.getUrl("")); 2261 connection1.setReadTimeout(100); 2262 InputStream input = connection1.getInputStream(); 2263 assertEquals("ABC", readAscii(input, Integer.MAX_VALUE)); 2264 server.shutdown(); 2265 try { 2266 HttpURLConnection connection2 = client.open(server.getUrl("")); 2267 connection2.setReadTimeout(100); 2268 connection2.getInputStream(); 2269 fail(); 2270 } catch (ConnectException expected) { 2271 } 2272 } 2273 2274 /** Don't explode if the cache returns a null body. http://b/3373699 */ 2275 @Test public void responseCacheReturnsNullOutputStream() throws Exception { 2276 final AtomicBoolean aborted = new AtomicBoolean(); 2277 client.setResponseCache(new ResponseCache() { 2278 @Override public CacheResponse get(URI uri, String requestMethod, 2279 Map<String, List<String>> requestHeaders) throws IOException { 2280 return null; 2281 } 2282 2283 @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException { 2284 return new CacheRequest() { 2285 @Override public void abort() { 2286 aborted.set(true); 2287 } 2288 2289 @Override public OutputStream getBody() throws IOException { 2290 return null; 2291 } 2292 }; 2293 } 2294 }); 2295 2296 server.enqueue(new MockResponse().setBody("abcdef")); 2297 server.play(); 2298 2299 HttpURLConnection connection = client.open(server.getUrl("/")); 2300 InputStream in = connection.getInputStream(); 2301 assertEquals("abc", readAscii(in, 3)); 2302 in.close(); 2303 assertFalse(aborted.get()); // The best behavior is ambiguous, but RI 6 doesn't abort here 2304 } 2305 2306 /** http://code.google.com/p/android/issues/detail?id=14562 */ 2307 @Test public void readAfterLastByte() throws Exception { 2308 server.enqueue(new MockResponse().setBody("ABC") 2309 .clearHeaders() 2310 .addHeader("Connection: close") 2311 .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END)); 2312 server.play(); 2313 2314 connection = client.open(server.getUrl("/")); 2315 InputStream in = connection.getInputStream(); 2316 assertEquals("ABC", readAscii(in, 3)); 2317 assertEquals(-1, in.read()); 2318 assertEquals(-1, in.read()); // throws IOException in Gingerbread 2319 } 2320 2321 @Test public void getContent() throws Exception { 2322 server.enqueue(new MockResponse().addHeader("Content-Type: text/plain").setBody("A")); 2323 server.play(); 2324 connection = client.open(server.getUrl("/")); 2325 InputStream in = (InputStream) connection.getContent(); 2326 assertEquals("A", readAscii(in, Integer.MAX_VALUE)); 2327 } 2328 2329 @Test public void getContentOfType() throws Exception { 2330 server.enqueue(new MockResponse().addHeader("Content-Type: text/plain").setBody("A")); 2331 server.play(); 2332 connection = client.open(server.getUrl("/")); 2333 try { 2334 connection.getContent(null); 2335 fail(); 2336 } catch (NullPointerException expected) { 2337 } 2338 try { 2339 connection.getContent(new Class[] { null }); 2340 fail(); 2341 } catch (NullPointerException expected) { 2342 } 2343 assertNull(connection.getContent(new Class[] {getClass()})); 2344 } 2345 2346 @Test public void getOutputStreamOnGetFails() throws Exception { 2347 server.enqueue(new MockResponse()); 2348 server.play(); 2349 connection = client.open(server.getUrl("/")); 2350 try { 2351 connection.getOutputStream(); 2352 fail(); 2353 } catch (ProtocolException expected) { 2354 } 2355 } 2356 2357 @Test public void getOutputAfterGetInputStreamFails() throws Exception { 2358 server.enqueue(new MockResponse()); 2359 server.play(); 2360 connection = client.open(server.getUrl("/")); 2361 connection.setDoOutput(true); 2362 try { 2363 connection.getInputStream(); 2364 connection.getOutputStream(); 2365 fail(); 2366 } catch (ProtocolException expected) { 2367 } 2368 } 2369 2370 @Test public void setDoOutputOrDoInputAfterConnectFails() throws Exception { 2371 server.enqueue(new MockResponse()); 2372 server.play(); 2373 connection = client.open(server.getUrl("/")); 2374 connection.connect(); 2375 try { 2376 connection.setDoOutput(true); 2377 fail(); 2378 } catch (IllegalStateException expected) { 2379 } 2380 try { 2381 connection.setDoInput(true); 2382 fail(); 2383 } catch (IllegalStateException expected) { 2384 } 2385 } 2386 2387 @Test public void clientSendsContentLength() throws Exception { 2388 server.enqueue(new MockResponse().setBody("A")); 2389 server.play(); 2390 connection = client.open(server.getUrl("/")); 2391 connection.setDoOutput(true); 2392 OutputStream out = connection.getOutputStream(); 2393 out.write(new byte[] { 'A', 'B', 'C' }); 2394 out.close(); 2395 assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); 2396 RecordedRequest request = server.takeRequest(); 2397 assertContains(request.getHeaders(), "Content-Length: 3"); 2398 } 2399 2400 @Test public void getContentLengthConnects() throws Exception { 2401 server.enqueue(new MockResponse().setBody("ABC")); 2402 server.play(); 2403 connection = client.open(server.getUrl("/")); 2404 assertEquals(3, connection.getContentLength()); 2405 } 2406 2407 @Test public void getContentTypeConnects() throws Exception { 2408 server.enqueue(new MockResponse().addHeader("Content-Type: text/plain").setBody("ABC")); 2409 server.play(); 2410 connection = client.open(server.getUrl("/")); 2411 assertEquals("text/plain", connection.getContentType()); 2412 } 2413 2414 @Test public void getContentEncodingConnects() throws Exception { 2415 server.enqueue(new MockResponse().addHeader("Content-Encoding: identity").setBody("ABC")); 2416 server.play(); 2417 connection = client.open(server.getUrl("/")); 2418 assertEquals("identity", connection.getContentEncoding()); 2419 } 2420 2421 // http://b/4361656 2422 @Test public void urlContainsQueryButNoPath() throws Exception { 2423 server.enqueue(new MockResponse().setBody("A")); 2424 server.play(); 2425 URL url = new URL("http", server.getHostName(), server.getPort(), "?query"); 2426 assertEquals("A", readAscii(client.open(url).getInputStream(), Integer.MAX_VALUE)); 2427 RecordedRequest request = server.takeRequest(); 2428 assertEquals("GET /?query HTTP/1.1", request.getRequestLine()); 2429 } 2430 2431 // http://code.google.com/p/android/issues/detail?id=20442 2432 @Test public void inputStreamAvailableWithChunkedEncoding() throws Exception { 2433 testInputStreamAvailable(TransferKind.CHUNKED); 2434 } 2435 2436 @Test public void inputStreamAvailableWithContentLengthHeader() throws Exception { 2437 testInputStreamAvailable(TransferKind.FIXED_LENGTH); 2438 } 2439 2440 @Test public void inputStreamAvailableWithNoLengthHeaders() throws Exception { 2441 testInputStreamAvailable(TransferKind.END_OF_STREAM); 2442 } 2443 2444 private void testInputStreamAvailable(TransferKind transferKind) throws IOException { 2445 String body = "ABCDEFGH"; 2446 MockResponse response = new MockResponse(); 2447 transferKind.setBody(response, body, 4); 2448 server.enqueue(response); 2449 server.play(); 2450 connection = client.open(server.getUrl("/")); 2451 InputStream in = connection.getInputStream(); 2452 for (int i = 0; i < body.length(); i++) { 2453 assertTrue(in.available() >= 0); 2454 assertEquals(body.charAt(i), in.read()); 2455 } 2456 assertEquals(0, in.available()); 2457 assertEquals(-1, in.read()); 2458 } 2459 2460 @Test public void postFailsWithBufferedRequestForSmallRequest() throws Exception { 2461 reusedConnectionFailsWithPost(TransferKind.END_OF_STREAM, 1024); 2462 } 2463 2464 // This test is ignored because we don't (yet) reliably recover for large request bodies. 2465 @Test public void postFailsWithBufferedRequestForLargeRequest() throws Exception { 2466 reusedConnectionFailsWithPost(TransferKind.END_OF_STREAM, 16384); 2467 } 2468 2469 @Test public void postFailsWithChunkedRequestForSmallRequest() throws Exception { 2470 reusedConnectionFailsWithPost(TransferKind.CHUNKED, 1024); 2471 } 2472 2473 @Test public void postFailsWithChunkedRequestForLargeRequest() throws Exception { 2474 reusedConnectionFailsWithPost(TransferKind.CHUNKED, 16384); 2475 } 2476 2477 @Test public void postFailsWithFixedLengthRequestForSmallRequest() throws Exception { 2478 reusedConnectionFailsWithPost(TransferKind.FIXED_LENGTH, 1024); 2479 } 2480 2481 @Test public void postFailsWithFixedLengthRequestForLargeRequest() throws Exception { 2482 reusedConnectionFailsWithPost(TransferKind.FIXED_LENGTH, 16384); 2483 } 2484 2485 private void reusedConnectionFailsWithPost(TransferKind transferKind, int requestSize) 2486 throws Exception { 2487 server.enqueue(new MockResponse().setBody("A").setSocketPolicy(SHUTDOWN_INPUT_AT_END)); 2488 server.enqueue(new MockResponse().setBody("B")); 2489 server.enqueue(new MockResponse().setBody("C")); 2490 server.play(); 2491 2492 assertContent("A", client.open(server.getUrl("/a"))); 2493 2494 // If the request body is larger than OkHttp's replay buffer, the failure may still occur. 2495 byte[] requestBody = new byte[requestSize]; 2496 new Random(0).nextBytes(requestBody); 2497 2498 connection = client.open(server.getUrl("/b")); 2499 connection.setRequestMethod("POST"); 2500 transferKind.setForRequest(connection, requestBody.length); 2501 for (int i = 0; i < requestBody.length; i += 1024) { 2502 connection.getOutputStream().write(requestBody, i, 1024); 2503 } 2504 connection.getOutputStream().close(); 2505 assertContent("B", connection); 2506 2507 RecordedRequest requestA = server.takeRequest(); 2508 assertEquals("/a", requestA.getPath()); 2509 RecordedRequest requestB = server.takeRequest(); 2510 assertEquals("/b", requestB.getPath()); 2511 assertEquals(Arrays.toString(requestBody), Arrays.toString(requestB.getBody())); 2512 } 2513 2514 @Test public void fullyBufferedPostIsTooShort() throws Exception { 2515 server.enqueue(new MockResponse().setBody("A")); 2516 server.play(); 2517 2518 connection = client.open(server.getUrl("/b")); 2519 connection.setRequestProperty("Content-Length", "4"); 2520 connection.setRequestMethod("POST"); 2521 OutputStream out = connection.getOutputStream(); 2522 out.write('a'); 2523 out.write('b'); 2524 out.write('c'); 2525 try { 2526 out.close(); 2527 fail(); 2528 } catch (IOException expected) { 2529 } 2530 } 2531 2532 @Test public void fullyBufferedPostIsTooLong() throws Exception { 2533 server.enqueue(new MockResponse().setBody("A")); 2534 server.play(); 2535 2536 connection = client.open(server.getUrl("/b")); 2537 connection.setRequestProperty("Content-Length", "3"); 2538 connection.setRequestMethod("POST"); 2539 OutputStream out = connection.getOutputStream(); 2540 out.write('a'); 2541 out.write('b'); 2542 out.write('c'); 2543 try { 2544 out.write('d'); 2545 out.flush(); 2546 fail(); 2547 } catch (IOException expected) { 2548 } 2549 } 2550 2551 @Test @Ignore public void testPooledConnectionsDetectHttp10() { 2552 // TODO: write a test that shows pooled connections detect HTTP/1.0 (vs. HTTP/1.1) 2553 fail("TODO"); 2554 } 2555 2556 @Test @Ignore public void postBodiesRetransmittedOnAuthProblems() { 2557 fail("TODO"); 2558 } 2559 2560 @Test @Ignore public void cookiesAndTrailers() { 2561 // Do cookie headers get processed too many times? 2562 fail("TODO"); 2563 } 2564 2565 @Test @Ignore public void headerNamesContainingNullCharacter() { 2566 // This is relevant for SPDY 2567 fail("TODO"); 2568 } 2569 2570 @Test @Ignore public void headerValuesContainingNullCharacter() { 2571 // This is relevant for SPDY 2572 fail("TODO"); 2573 } 2574 2575 @Test public void emptyRequestHeaderValueIsAllowed() throws Exception { 2576 server.enqueue(new MockResponse().setBody("body")); 2577 server.play(); 2578 connection = client.open(server.getUrl("/")); 2579 connection.addRequestProperty("B", ""); 2580 assertContent("body", connection); 2581 assertEquals("", connection.getRequestProperty("B")); 2582 } 2583 2584 @Test public void emptyResponseHeaderValueIsAllowed() throws Exception { 2585 server.enqueue(new MockResponse().addHeader("A:").setBody("body")); 2586 server.play(); 2587 connection = client.open(server.getUrl("/")); 2588 assertContent("body", connection); 2589 assertEquals("", connection.getHeaderField("A")); 2590 } 2591 2592 @Test public void emptyRequestHeaderNameIsStrict() throws Exception { 2593 server.enqueue(new MockResponse().setBody("body")); 2594 server.play(); 2595 connection = client.open(server.getUrl("/")); 2596 try { 2597 connection.setRequestProperty("", "A"); 2598 fail(); 2599 } catch (IllegalArgumentException expected) { 2600 } 2601 } 2602 2603 @Test public void emptyResponseHeaderNameIsLenient() throws Exception { 2604 server.enqueue(new MockResponse().addHeader(":A").setBody("body")); 2605 server.play(); 2606 connection = client.open(server.getUrl("/")); 2607 connection.getResponseCode(); 2608 assertEquals("A", connection.getHeaderField("")); 2609 } 2610 2611 @Test @Ignore public void deflateCompression() { 2612 fail("TODO"); 2613 } 2614 2615 @Test @Ignore public void postBodiesRetransmittedOnIpAddressProblems() { 2616 fail("TODO"); 2617 } 2618 2619 @Test @Ignore public void pooledConnectionProblemsNotReportedToProxySelector() { 2620 fail("TODO"); 2621 } 2622 2623 @Test public void customAuthenticator() throws Exception { 2624 MockResponse pleaseAuthenticate = new MockResponse().setResponseCode(401) 2625 .addHeader("WWW-Authenticate: Basic realm=\"protected area\"") 2626 .setBody("Please authenticate."); 2627 server.enqueue(pleaseAuthenticate); 2628 server.enqueue(new MockResponse().setBody("A")); 2629 server.play(); 2630 2631 Credential credential = Credential.basic("jesse", "peanutbutter"); 2632 RecordingOkAuthenticator authenticator = new RecordingOkAuthenticator(credential); 2633 client.setAuthenticator(authenticator); 2634 assertContent("A", client.open(server.getUrl("/private"))); 2635 2636 assertContainsNoneMatching(server.takeRequest().getHeaders(), "Authorization: .*"); 2637 assertContains(server.takeRequest().getHeaders(), 2638 "Authorization: " + credential.getHeaderValue()); 2639 2640 assertEquals(1, authenticator.calls.size()); 2641 String call = authenticator.calls.get(0); 2642 assertTrue(call, call.contains("proxy=DIRECT")); 2643 assertTrue(call, call.contains("url=" + server.getUrl("/private"))); 2644 assertTrue(call, call.contains("challenges=[Basic realm=\"protected area\"]")); 2645 } 2646 2647 @Test public void npnSetsProtocolHeader_SPDY_3() throws Exception { 2648 npnSetsProtocolHeader(Protocol.SPDY_3); 2649 } 2650 2651 @Test public void npnSetsProtocolHeader_HTTP_2() throws Exception { 2652 npnSetsProtocolHeader(Protocol.HTTP_2); 2653 } 2654 2655 private void npnSetsProtocolHeader(Protocol protocol) throws IOException { 2656 enableNpn(protocol); 2657 server.enqueue(new MockResponse().setBody("A")); 2658 server.play(); 2659 client.setProtocols(Arrays.asList(Protocol.HTTP_11, protocol)); 2660 connection = client.open(server.getUrl("/")); 2661 List<String> protocolValues = connection.getHeaderFields().get(SELECTED_PROTOCOL); 2662 assertEquals(Arrays.asList(protocol.name.utf8()), protocolValues); 2663 assertContent("A", connection); 2664 } 2665 2666 /** For example, empty Protobuf RPC messages end up as a zero-length POST. */ 2667 @Test public void zeroLengthPost() throws IOException, InterruptedException { 2668 zeroLengthPayload("POST"); 2669 } 2670 2671 @Test public void zeroLengthPost_SPDY_3() throws Exception { 2672 enableNpn(Protocol.SPDY_3); 2673 zeroLengthPost(); 2674 } 2675 2676 @Test public void zeroLengthPost_HTTP_2() throws Exception { 2677 enableNpn(Protocol.HTTP_2); 2678 zeroLengthPost(); 2679 } 2680 2681 /** For example, creating an Amazon S3 bucket ends up as a zero-length POST. */ 2682 @Test public void zeroLengthPut() throws IOException, InterruptedException { 2683 zeroLengthPayload("PUT"); 2684 } 2685 2686 @Test public void zeroLengthPut_SPDY_3() throws Exception { 2687 enableNpn(Protocol.SPDY_3); 2688 zeroLengthPut(); 2689 } 2690 2691 @Test public void zeroLengthPut_HTTP_2() throws Exception { 2692 enableNpn(Protocol.HTTP_2); 2693 zeroLengthPut(); 2694 } 2695 2696 private void zeroLengthPayload(String method) 2697 throws IOException, InterruptedException { 2698 server.enqueue(new MockResponse()); 2699 server.play(); 2700 connection = client.open(server.getUrl("/")); 2701 connection.setRequestProperty("Content-Length", "0"); 2702 connection.setRequestMethod(method); 2703 connection.setFixedLengthStreamingMode(0); 2704 connection.setDoOutput(true); 2705 assertContent("", connection); 2706 RecordedRequest zeroLengthPayload = server.takeRequest(); 2707 assertEquals(method, zeroLengthPayload.getMethod()); 2708 assertEquals("0", zeroLengthPayload.getHeader("content-length")); 2709 assertEquals(0L, zeroLengthPayload.getBodySize()); 2710 } 2711 2712 @Test public void setProtocols() throws Exception { 2713 server.enqueue(new MockResponse().setBody("A")); 2714 server.play(); 2715 client.setProtocols(Arrays.asList(Protocol.HTTP_11)); 2716 assertContent("A", client.open(server.getUrl("/"))); 2717 } 2718 2719 @Test public void setProtocolsWithoutHttp11() throws Exception { 2720 try { 2721 client.setProtocols(Arrays.asList(Protocol.SPDY_3)); 2722 fail(); 2723 } catch (IllegalArgumentException expected) { 2724 } 2725 } 2726 2727 @Test public void setProtocolsWithNull() throws Exception { 2728 try { 2729 client.setProtocols(Arrays.asList(Protocol.HTTP_11, null)); 2730 fail(); 2731 } catch (IllegalArgumentException expected) { 2732 } 2733 } 2734 2735 @Test public void veryLargeFixedLengthRequest() throws Exception { 2736 server.setBodyLimit(0); 2737 server.enqueue(new MockResponse()); 2738 server.play(); 2739 2740 connection = client.open(server.getUrl("/")); 2741 connection.setDoOutput(true); 2742 long contentLength = Integer.MAX_VALUE + 1L; 2743 connection.setFixedLengthStreamingMode(contentLength); 2744 OutputStream out = connection.getOutputStream(); 2745 byte[] buffer = new byte[1024 * 1024]; 2746 for (long bytesWritten = 0; bytesWritten < contentLength; ) { 2747 int byteCount = (int) Math.min(buffer.length, contentLength - bytesWritten); 2748 out.write(buffer, 0, byteCount); 2749 bytesWritten += byteCount; 2750 } 2751 assertContent("", connection); 2752 2753 RecordedRequest request = server.takeRequest(); 2754 assertEquals(Long.toString(contentLength), request.getHeader("Content-Length")); 2755 } 2756 2757 /** 2758 * We had a bug where we attempted to gunzip responses that didn't have a 2759 * body. This only came up with 304s since that response code can include 2760 * headers (like "Content-Encoding") without any content to go along with it. 2761 * https://github.com/square/okhttp/issues/358 2762 */ 2763 @Test public void noTransparentGzipFor304NotModified() throws Exception { 2764 server.enqueue(new MockResponse() 2765 .clearHeaders() 2766 .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED) 2767 .addHeader("Content-Encoding: gzip")); 2768 server.enqueue(new MockResponse().setBody("b")); 2769 2770 server.play(); 2771 2772 HttpURLConnection connection1 = client.open(server.getUrl("/")); 2773 assertEquals(HttpURLConnection.HTTP_NOT_MODIFIED, connection1.getResponseCode()); 2774 assertContent("", connection1); 2775 2776 HttpURLConnection connection2 = client.open(server.getUrl("/")); 2777 assertEquals(HttpURLConnection.HTTP_OK, connection2.getResponseCode()); 2778 assertContent("b", connection2); 2779 2780 RecordedRequest requestA = server.takeRequest(); 2781 assertEquals(0, requestA.getSequenceNumber()); 2782 2783 RecordedRequest requestB = server.takeRequest(); 2784 assertEquals(1, requestB.getSequenceNumber()); 2785 } 2786 2787 /** 2788 * We had a bug where we weren't closing Gzip streams on redirects. 2789 * https://github.com/square/okhttp/issues/441 2790 */ 2791 @Test public void gzipWithRedirectAndConnectionReuse() throws Exception { 2792 server.enqueue(new MockResponse() 2793 .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) 2794 .addHeader("Location: /foo") 2795 .addHeader("Content-Encoding: gzip") 2796 .setBody(gzip("Moved! Moved! Moved!".getBytes(UTF_8)))); 2797 server.enqueue(new MockResponse().setBody("This is the new page!")); 2798 server.play(); 2799 2800 HttpURLConnection connection = client.open(server.getUrl("/")); 2801 assertContent("This is the new page!", connection); 2802 2803 RecordedRequest requestA = server.takeRequest(); 2804 assertEquals(0, requestA.getSequenceNumber()); 2805 2806 RecordedRequest requestB = server.takeRequest(); 2807 assertEquals(1, requestB.getSequenceNumber()); 2808 } 2809 2810 /** 2811 * The RFC is unclear in this regard as it only specifies that this should 2812 * invalidate the cache entry (if any). 2813 */ 2814 @Test public void bodyPermittedOnDelete() throws Exception { 2815 server.enqueue(new MockResponse()); 2816 server.play(); 2817 2818 HttpURLConnection connection = client.open(server.getUrl("/")); 2819 connection.setRequestMethod("DELETE"); 2820 connection.setDoOutput(true); 2821 connection.getOutputStream().write("BODY".getBytes(UTF_8)); 2822 assertEquals(200, connection.getResponseCode()); 2823 2824 RecordedRequest request = server.takeRequest(); 2825 assertEquals("DELETE", request.getMethod()); 2826 assertEquals("BODY", new String(request.getBody(), UTF_8)); 2827 } 2828 2829 /** Returns a gzipped copy of {@code bytes}. */ 2830 public byte[] gzip(byte[] bytes) throws IOException { 2831 ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); 2832 OutputStream gzippedOut = new GZIPOutputStream(bytesOut); 2833 gzippedOut.write(bytes); 2834 gzippedOut.close(); 2835 return bytesOut.toByteArray(); 2836 } 2837 2838 /** 2839 * Reads at most {@code limit} characters from {@code in} and asserts that 2840 * content equals {@code expected}. 2841 */ 2842 private void assertContent(String expected, HttpURLConnection connection, int limit) 2843 throws IOException { 2844 connection.connect(); 2845 assertEquals(expected, readAscii(connection.getInputStream(), limit)); 2846 } 2847 2848 private void assertContent(String expected, HttpURLConnection connection) throws IOException { 2849 assertContent(expected, connection, Integer.MAX_VALUE); 2850 } 2851 2852 private void assertContains(List<String> headers, String header) { 2853 assertTrue(headers.toString(), headers.contains(header)); 2854 } 2855 2856 private void assertContainsNoneMatching(List<String> headers, String pattern) { 2857 for (String header : headers) { 2858 if (header.matches(pattern)) { 2859 fail("Header " + header + " matches " + pattern); 2860 } 2861 } 2862 } 2863 2864 private Set<String> newSet(String... elements) { 2865 return new HashSet<String>(Arrays.asList(elements)); 2866 } 2867 2868 enum TransferKind { 2869 CHUNKED() { 2870 @Override void setBody(MockResponse response, byte[] content, int chunkSize) 2871 throws IOException { 2872 response.setChunkedBody(content, chunkSize); 2873 } 2874 @Override void setForRequest(HttpURLConnection connection, int contentLength) { 2875 connection.setChunkedStreamingMode(5); 2876 } 2877 }, 2878 FIXED_LENGTH() { 2879 @Override void setBody(MockResponse response, byte[] content, int chunkSize) { 2880 response.setBody(content); 2881 } 2882 @Override void setForRequest(HttpURLConnection connection, int contentLength) { 2883 connection.setFixedLengthStreamingMode(contentLength); 2884 } 2885 }, 2886 END_OF_STREAM() { 2887 @Override void setBody(MockResponse response, byte[] content, int chunkSize) { 2888 response.setBody(content); 2889 response.setSocketPolicy(DISCONNECT_AT_END); 2890 for (Iterator<String> h = response.getHeaders().iterator(); h.hasNext(); ) { 2891 if (h.next().startsWith("Content-Length:")) { 2892 h.remove(); 2893 break; 2894 } 2895 } 2896 } 2897 @Override void setForRequest(HttpURLConnection connection, int contentLength) { 2898 } 2899 }; 2900 2901 abstract void setBody(MockResponse response, byte[] content, int chunkSize) throws IOException; 2902 2903 abstract void setForRequest(HttpURLConnection connection, int contentLength); 2904 2905 void setBody(MockResponse response, String content, int chunkSize) throws IOException { 2906 setBody(response, content.getBytes("UTF-8"), chunkSize); 2907 } 2908 } 2909 2910 enum ProxyConfig { 2911 NO_PROXY() { 2912 @Override public HttpURLConnection connect(MockWebServer server, OkHttpClient client, URL url) 2913 throws IOException { 2914 client.setProxy(Proxy.NO_PROXY); 2915 return client.open(url); 2916 } 2917 }, 2918 2919 CREATE_ARG() { 2920 @Override public HttpURLConnection connect(MockWebServer server, OkHttpClient client, URL url) 2921 throws IOException { 2922 client.setProxy(server.toProxyAddress()); 2923 return client.open(url); 2924 } 2925 }, 2926 2927 PROXY_SYSTEM_PROPERTY() { 2928 @Override public HttpURLConnection connect(MockWebServer server, OkHttpClient client, URL url) 2929 throws IOException { 2930 System.setProperty("proxyHost", "localhost"); 2931 System.setProperty("proxyPort", Integer.toString(server.getPort())); 2932 return client.open(url); 2933 } 2934 }, 2935 2936 HTTP_PROXY_SYSTEM_PROPERTY() { 2937 @Override public HttpURLConnection connect(MockWebServer server, OkHttpClient client, URL url) 2938 throws IOException { 2939 System.setProperty("http.proxyHost", "localhost"); 2940 System.setProperty("http.proxyPort", Integer.toString(server.getPort())); 2941 return client.open(url); 2942 } 2943 }, 2944 2945 HTTPS_PROXY_SYSTEM_PROPERTY() { 2946 @Override public HttpURLConnection connect(MockWebServer server, OkHttpClient client, URL url) 2947 throws IOException { 2948 System.setProperty("https.proxyHost", "localhost"); 2949 System.setProperty("https.proxyPort", Integer.toString(server.getPort())); 2950 return client.open(url); 2951 } 2952 }; 2953 2954 public abstract HttpURLConnection connect(MockWebServer server, OkHttpClient client, URL url) 2955 throws IOException; 2956 } 2957 2958 private static class RecordingTrustManager implements X509TrustManager { 2959 private final List<String> calls = new ArrayList<String>(); 2960 2961 public X509Certificate[] getAcceptedIssuers() { 2962 return new X509Certificate[] { }; 2963 } 2964 2965 public void checkClientTrusted(X509Certificate[] chain, String authType) 2966 throws CertificateException { 2967 calls.add("checkClientTrusted " + certificatesToString(chain)); 2968 } 2969 2970 public void checkServerTrusted(X509Certificate[] chain, String authType) 2971 throws CertificateException { 2972 calls.add("checkServerTrusted " + certificatesToString(chain)); 2973 } 2974 2975 private String certificatesToString(X509Certificate[] certificates) { 2976 List<String> result = new ArrayList<String>(); 2977 for (X509Certificate certificate : certificates) { 2978 result.add(certificate.getSubjectDN() + " " + certificate.getSerialNumber()); 2979 } 2980 return result.toString(); 2981 } 2982 } 2983 2984 private static class FakeProxySelector extends ProxySelector { 2985 List<Proxy> proxies = new ArrayList<Proxy>(); 2986 2987 @Override public List<Proxy> select(URI uri) { 2988 // Don't handle 'socket' schemes, which the RI's Socket class may request (for SOCKS). 2989 return uri.getScheme().equals("http") || uri.getScheme().equals("https") ? proxies 2990 : Collections.singletonList(Proxy.NO_PROXY); 2991 } 2992 2993 @Override public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { 2994 } 2995 } 2996 2997 /** 2998 * Tests that use this will fail unless boot classpath is set. Ex. {@code 2999 * -Xbootclasspath/p:/tmp/npn-boot-8.1.2.v20120308.jar} 3000 */ 3001 private void enableNpn(Protocol protocol) { 3002 client.setSslSocketFactory(sslContext.getSocketFactory()); 3003 client.setHostnameVerifier(new RecordingHostnameVerifier()); 3004 client.setProtocols(Arrays.asList(protocol, Protocol.HTTP_11)); 3005 server.useHttps(sslContext.getSocketFactory(), false); 3006 server.setNpnEnabled(true); 3007 server.setNpnProtocols(client.getProtocols()); 3008 } 3009} 3010