1/*
2 * Copyright (C) 2013 Square, Inc.
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 */
16package com.squareup.okhttp;
17
18import com.squareup.okhttp.internal.DoubleInetAddressNetwork;
19import com.squareup.okhttp.internal.Internal;
20import com.squareup.okhttp.internal.RecordingHostnameVerifier;
21import com.squareup.okhttp.internal.RecordingOkAuthenticator;
22import com.squareup.okhttp.internal.SingleInetAddressNetwork;
23import com.squareup.okhttp.internal.SslContextBuilder;
24import com.squareup.okhttp.internal.Version;
25import com.squareup.okhttp.mockwebserver.Dispatcher;
26import com.squareup.okhttp.mockwebserver.MockResponse;
27import com.squareup.okhttp.mockwebserver.RecordedRequest;
28import com.squareup.okhttp.mockwebserver.SocketPolicy;
29import com.squareup.okhttp.mockwebserver.rule.MockWebServerRule;
30import java.io.IOException;
31import java.io.InputStream;
32import java.io.InterruptedIOException;
33import java.net.CookieManager;
34import java.net.HttpCookie;
35import java.net.HttpURLConnection;
36import java.net.URL;
37import java.net.UnknownServiceException;
38import java.security.cert.Certificate;
39import java.util.ArrayList;
40import java.util.Arrays;
41import java.util.List;
42import java.util.concurrent.BlockingQueue;
43import java.util.concurrent.Callable;
44import java.util.concurrent.CountDownLatch;
45import java.util.concurrent.ExecutorService;
46import java.util.concurrent.Executors;
47import java.util.concurrent.Future;
48import java.util.concurrent.SynchronousQueue;
49import java.util.concurrent.TimeUnit;
50import java.util.concurrent.atomic.AtomicBoolean;
51import java.util.concurrent.atomic.AtomicReference;
52import javax.net.ssl.SSLContext;
53import javax.net.ssl.SSLHandshakeException;
54import javax.net.ssl.SSLPeerUnverifiedException;
55import javax.net.ssl.SSLProtocolException;
56import javax.net.ssl.SSLSocket;
57import javax.net.ssl.SSLSocketFactory;
58import okio.Buffer;
59import okio.BufferedSink;
60import okio.BufferedSource;
61import okio.GzipSink;
62import okio.Okio;
63import org.junit.After;
64import org.junit.Before;
65import org.junit.Ignore;
66import org.junit.Rule;
67import org.junit.Test;
68import org.junit.rules.TemporaryFolder;
69import org.junit.rules.TestRule;
70import org.junit.rules.Timeout;
71
72import static com.squareup.okhttp.internal.Internal.logger;
73import static java.net.CookiePolicy.ACCEPT_ORIGINAL_SERVER;
74import static org.junit.Assert.assertEquals;
75import static org.junit.Assert.assertFalse;
76import static org.junit.Assert.assertNotNull;
77import static org.junit.Assert.assertNotSame;
78import static org.junit.Assert.assertNull;
79import static org.junit.Assert.assertTrue;
80import static org.junit.Assert.fail;
81
82public final class CallTest {
83  private static final SSLContext sslContext = SslContextBuilder.localhost();
84
85  @Rule public final TemporaryFolder tempDir = new TemporaryFolder();
86  @Rule public final TestRule timeout = new Timeout(30_000);
87
88  @Rule public final MockWebServerRule server = new MockWebServerRule();
89  @Rule public final MockWebServerRule server2 = new MockWebServerRule();
90  private OkHttpClient client = new OkHttpClient();
91  private RecordingCallback callback = new RecordingCallback();
92  private TestLogHandler logHandler = new TestLogHandler();
93  private Cache cache;
94
95  @Before public void setUp() throws Exception {
96    client = new OkHttpClient();
97    callback = new RecordingCallback();
98    logHandler = new TestLogHandler();
99
100    cache = new Cache(tempDir.getRoot(), Integer.MAX_VALUE);
101    logger.addHandler(logHandler);
102  }
103
104  @After public void tearDown() throws Exception {
105    cache.delete();
106    logger.removeHandler(logHandler);
107  }
108
109  @Test public void get() throws Exception {
110    server.enqueue(new MockResponse().setBody("abc").addHeader("Content-Type: text/plain"));
111
112    Request request = new Request.Builder()
113        .url(server.getUrl("/"))
114        .header("User-Agent", "SyncApiTest")
115        .build();
116
117    executeSynchronously(request)
118        .assertCode(200)
119        .assertSuccessful()
120        .assertHeader("Content-Type", "text/plain")
121        .assertBody("abc");
122
123    RecordedRequest recordedRequest = server.takeRequest();
124    assertEquals("GET", recordedRequest.getMethod());
125    assertEquals("SyncApiTest", recordedRequest.getHeader("User-Agent"));
126    assertEquals(0, recordedRequest.getBody().size());
127    assertNull(recordedRequest.getHeader("Content-Length"));
128  }
129
130  @Test public void lazilyEvaluateRequestUrl() throws Exception {
131    server.enqueue(new MockResponse().setBody("abc"));
132
133    Request request1 = new Request.Builder()
134        .url("foo://bar?baz")
135        .build();
136    Request request2 = request1.newBuilder()
137        .url(server.getUrl("/"))
138        .build();
139    executeSynchronously(request2)
140        .assertCode(200)
141        .assertSuccessful()
142        .assertBody("abc");
143  }
144
145  @Ignore // TODO(jwilson): fix.
146  @Test public void invalidScheme() throws Exception {
147    try {
148      Request request = new Request.Builder()
149          .url("ftp://hostname/path")
150          .build();
151      executeSynchronously(request);
152      fail();
153    } catch (IllegalArgumentException expected) {
154    }
155  }
156
157  @Test public void invalidPort() throws Exception {
158    Request request = new Request.Builder()
159        .url("http://localhost:65536/")
160        .build();
161    client.newCall(request).enqueue(callback);
162    callback.await(request.url())
163        .assertFailure("No route to localhost:65536; port is out of range");
164  }
165
166  @Test public void getReturns500() throws Exception {
167    server.enqueue(new MockResponse().setResponseCode(500));
168
169    Request request = new Request.Builder()
170        .url(server.getUrl("/"))
171        .build();
172
173    executeSynchronously(request)
174        .assertCode(500)
175        .assertNotSuccessful();
176  }
177
178  @Test public void get_SPDY_3() throws Exception {
179    enableProtocol(Protocol.SPDY_3);
180    get();
181  }
182
183  @Test public void get_HTTP_2() throws Exception {
184    enableProtocol(Protocol.HTTP_2);
185    get();
186  }
187
188  @Test public void getWithRequestBody() throws Exception {
189    server.enqueue(new MockResponse());
190
191    try {
192      new Request.Builder().method("GET", RequestBody.create(MediaType.parse("text/plain"), "abc"));
193      fail();
194    } catch (IllegalArgumentException expected) {
195    }
196  }
197
198  @Test public void head() throws Exception {
199    server.enqueue(new MockResponse().addHeader("Content-Type: text/plain"));
200
201    Request request = new Request.Builder()
202        .url(server.getUrl("/"))
203        .head()
204        .header("User-Agent", "SyncApiTest")
205        .build();
206
207    executeSynchronously(request)
208        .assertCode(200)
209        .assertHeader("Content-Type", "text/plain");
210
211    RecordedRequest recordedRequest = server.takeRequest();
212    assertEquals("HEAD", recordedRequest.getMethod());
213    assertEquals("SyncApiTest", recordedRequest.getHeader("User-Agent"));
214    assertEquals(0, recordedRequest.getBody().size());
215    assertNull(recordedRequest.getHeader("Content-Length"));
216  }
217
218  @Test public void head_SPDY_3() throws Exception {
219    enableProtocol(Protocol.SPDY_3);
220    head();
221  }
222
223  @Test public void head_HTTP_2() throws Exception {
224    enableProtocol(Protocol.HTTP_2);
225    head();
226  }
227
228  @Test public void post() throws Exception {
229    server.enqueue(new MockResponse().setBody("abc"));
230
231    Request request = new Request.Builder()
232        .url(server.getUrl("/"))
233        .post(RequestBody.create(MediaType.parse("text/plain"), "def"))
234        .build();
235
236    executeSynchronously(request)
237        .assertCode(200)
238        .assertBody("abc");
239
240    RecordedRequest recordedRequest = server.takeRequest();
241    assertEquals("POST", recordedRequest.getMethod());
242    assertEquals("def", recordedRequest.getBody().readUtf8());
243    assertEquals("3", recordedRequest.getHeader("Content-Length"));
244    assertEquals("text/plain; charset=utf-8", recordedRequest.getHeader("Content-Type"));
245  }
246
247  @Test public void post_SPDY_3() throws Exception {
248    enableProtocol(Protocol.SPDY_3);
249    post();
250  }
251
252  @Test public void post_HTTP_2() throws Exception {
253    enableProtocol(Protocol.HTTP_2);
254    post();
255  }
256
257  @Test public void postZeroLength() throws Exception {
258    server.enqueue(new MockResponse().setBody("abc"));
259
260    Request request = new Request.Builder()
261        .url(server.getUrl("/"))
262        .method("POST", RequestBody.create(null, new byte[0]))
263        .build();
264
265    executeSynchronously(request)
266        .assertCode(200)
267        .assertBody("abc");
268
269    RecordedRequest recordedRequest = server.takeRequest();
270    assertEquals("POST", recordedRequest.getMethod());
271    assertEquals(0, recordedRequest.getBody().size());
272    assertEquals("0", recordedRequest.getHeader("Content-Length"));
273    assertEquals(null, recordedRequest.getHeader("Content-Type"));
274  }
275
276  @Test public void postZeroLength_SPDY_3() throws Exception {
277    enableProtocol(Protocol.SPDY_3);
278    postZeroLength();
279  }
280
281  @Test public void postZerolength_HTTP_2() throws Exception {
282    enableProtocol(Protocol.HTTP_2);
283    postZeroLength();
284  }
285
286  @Test public void postBodyRetransmittedAfterAuthorizationFail() throws Exception {
287    postBodyRetransmittedAfterAuthorizationFail("abc");
288  }
289
290  @Test public void postBodyRetransmittedAfterAuthorizationFail_SPDY_3() throws Exception {
291    enableProtocol(Protocol.SPDY_3);
292    postBodyRetransmittedAfterAuthorizationFail("abc");
293  }
294
295  @Test public void postBodyRetransmittedAfterAuthorizationFail_HTTP_2() throws Exception {
296    enableProtocol(Protocol.HTTP_2);
297    postBodyRetransmittedAfterAuthorizationFail("abc");
298  }
299
300  /** Don't explode when resending an empty post. https://github.com/square/okhttp/issues/1131 */
301  @Test public void postEmptyBodyRetransmittedAfterAuthorizationFail() throws Exception {
302    postBodyRetransmittedAfterAuthorizationFail("");
303  }
304
305  @Test public void postEmptyBodyRetransmittedAfterAuthorizationFail_SPDY_3() throws Exception {
306    enableProtocol(Protocol.SPDY_3);
307    postBodyRetransmittedAfterAuthorizationFail("");
308  }
309
310  @Test public void postEmptyBodyRetransmittedAfterAuthorizationFail_HTTP_2() throws Exception {
311    enableProtocol(Protocol.HTTP_2);
312    postBodyRetransmittedAfterAuthorizationFail("");
313  }
314
315  private void postBodyRetransmittedAfterAuthorizationFail(String body) throws Exception {
316    server.enqueue(new MockResponse().setResponseCode(401));
317    server.enqueue(new MockResponse());
318
319    Request request = new Request.Builder()
320        .url(server.getUrl("/"))
321        .method("POST", RequestBody.create(null, body))
322        .build();
323
324    String credential = Credentials.basic("jesse", "secret");
325    client.setAuthenticator(new RecordingOkAuthenticator(credential));
326
327    Response response = client.newCall(request).execute();
328    assertEquals(200, response.code());
329
330    RecordedRequest recordedRequest1 = server.takeRequest();
331    assertEquals("POST", recordedRequest1.getMethod());
332    assertEquals(body, recordedRequest1.getBody().readUtf8());
333    assertNull(recordedRequest1.getHeader("Authorization"));
334
335    RecordedRequest recordedRequest2 = server.takeRequest();
336    assertEquals("POST", recordedRequest2.getMethod());
337    assertEquals(body, recordedRequest2.getBody().readUtf8());
338    assertEquals(credential, recordedRequest2.getHeader("Authorization"));
339  }
340
341  @Test public void attemptAuthorization20Times() throws Exception {
342    for (int i = 0; i < 20; i++) {
343      server.enqueue(new MockResponse().setResponseCode(401));
344    }
345    server.enqueue(new MockResponse().setBody("Success!"));
346
347    String credential = Credentials.basic("jesse", "secret");
348    client.setAuthenticator(new RecordingOkAuthenticator(credential));
349
350    Request request = new Request.Builder().url(server.getUrl("/")).build();
351    executeSynchronously(request)
352        .assertCode(200)
353        .assertBody("Success!");
354  }
355
356  @Test public void doesNotAttemptAuthorization21Times() throws Exception {
357    for (int i = 0; i < 21; i++) {
358      server.enqueue(new MockResponse().setResponseCode(401));
359    }
360
361    String credential = Credentials.basic("jesse", "secret");
362    client.setAuthenticator(new RecordingOkAuthenticator(credential));
363
364    try {
365      client.newCall(new Request.Builder().url(server.getUrl("/0")).build()).execute();
366      fail();
367    } catch (IOException expected) {
368      assertEquals("Too many follow-up requests: 21", expected.getMessage());
369    }
370  }
371
372  @Test public void delete() throws Exception {
373    server.enqueue(new MockResponse().setBody("abc"));
374
375    Request request = new Request.Builder()
376        .url(server.getUrl("/"))
377        .delete()
378        .build();
379
380    executeSynchronously(request)
381        .assertCode(200)
382        .assertBody("abc");
383
384    RecordedRequest recordedRequest = server.takeRequest();
385    assertEquals("DELETE", recordedRequest.getMethod());
386    assertEquals(0, recordedRequest.getBody().size());
387    assertEquals("0", recordedRequest.getHeader("Content-Length"));
388    assertEquals(null, recordedRequest.getHeader("Content-Type"));
389  }
390
391  @Test public void delete_SPDY_3() throws Exception {
392    enableProtocol(Protocol.SPDY_3);
393    delete();
394  }
395
396  @Test public void delete_HTTP_2() throws Exception {
397    enableProtocol(Protocol.HTTP_2);
398    delete();
399  }
400
401  @Test public void deleteWithRequestBody() throws Exception {
402    server.enqueue(new MockResponse().setBody("abc"));
403
404    Request request = new Request.Builder()
405        .url(server.getUrl("/"))
406        .method("DELETE", RequestBody.create(MediaType.parse("text/plain"), "def"))
407        .build();
408
409    executeSynchronously(request)
410        .assertCode(200)
411        .assertBody("abc");
412
413    RecordedRequest recordedRequest = server.takeRequest();
414    assertEquals("DELETE", recordedRequest.getMethod());
415    assertEquals("def", recordedRequest.getBody().readUtf8());
416  }
417
418  @Test public void put() throws Exception {
419    server.enqueue(new MockResponse().setBody("abc"));
420
421    Request request = new Request.Builder()
422        .url(server.getUrl("/"))
423        .put(RequestBody.create(MediaType.parse("text/plain"), "def"))
424        .build();
425
426    executeSynchronously(request)
427        .assertCode(200)
428        .assertBody("abc");
429
430    RecordedRequest recordedRequest = server.takeRequest();
431    assertEquals("PUT", recordedRequest.getMethod());
432    assertEquals("def", recordedRequest.getBody().readUtf8());
433    assertEquals("3", recordedRequest.getHeader("Content-Length"));
434    assertEquals("text/plain; charset=utf-8", recordedRequest.getHeader("Content-Type"));
435  }
436
437  @Test public void put_SPDY_3() throws Exception {
438    enableProtocol(Protocol.SPDY_3);
439    put();
440  }
441
442  @Test public void put_HTTP_2() throws Exception {
443    enableProtocol(Protocol.HTTP_2);
444    put();
445  }
446
447  @Test public void patch() throws Exception {
448    server.enqueue(new MockResponse().setBody("abc"));
449
450    Request request = new Request.Builder()
451        .url(server.getUrl("/"))
452        .patch(RequestBody.create(MediaType.parse("text/plain"), "def"))
453        .build();
454
455    executeSynchronously(request)
456        .assertCode(200)
457        .assertBody("abc");
458
459    RecordedRequest recordedRequest = server.takeRequest();
460    assertEquals("PATCH", recordedRequest.getMethod());
461    assertEquals("def", recordedRequest.getBody().readUtf8());
462    assertEquals("3", recordedRequest.getHeader("Content-Length"));
463    assertEquals("text/plain; charset=utf-8", recordedRequest.getHeader("Content-Type"));
464  }
465
466  @Test public void patch_SPDY_3() throws Exception {
467    enableProtocol(Protocol.SPDY_3);
468    patch();
469  }
470
471  @Test public void patch_HTTP_2() throws Exception {
472    enableProtocol(Protocol.HTTP_2);
473    patch();
474  }
475
476  @Test public void unspecifiedRequestBodyContentTypeDoesNotGetDefault() throws Exception {
477    server.enqueue(new MockResponse());
478
479    Request request = new Request.Builder()
480        .url(server.getUrl("/"))
481        .method("POST", RequestBody.create(null, "abc"))
482        .build();
483
484    executeSynchronously(request).assertCode(200);
485
486    RecordedRequest recordedRequest = server.takeRequest();
487    assertEquals(null, recordedRequest.getHeader("Content-Type"));
488    assertEquals("3", recordedRequest.getHeader("Content-Length"));
489    assertEquals("abc", recordedRequest.getBody().readUtf8());
490  }
491
492  @Test public void illegalToExecuteTwice() throws Exception {
493    server.enqueue(new MockResponse()
494        .setBody("abc")
495        .addHeader("Content-Type: text/plain"));
496
497    Request request = new Request.Builder()
498        .url(server.getUrl("/"))
499        .header("User-Agent", "SyncApiTest")
500        .build();
501
502    Call call = client.newCall(request);
503    call.execute();
504
505    try {
506      call.execute();
507      fail();
508    } catch (IllegalStateException e){
509      assertEquals("Already Executed", e.getMessage());
510    }
511
512    try {
513      call.enqueue(callback);
514      fail();
515    } catch (IllegalStateException e){
516      assertEquals("Already Executed", e.getMessage());
517    }
518
519    assertEquals("SyncApiTest", server.takeRequest().getHeader("User-Agent"));
520  }
521
522  @Test public void illegalToExecuteTwice_Async() throws Exception {
523    server.enqueue(new MockResponse()
524        .setBody("abc")
525        .addHeader("Content-Type: text/plain"));
526
527    Request request = new Request.Builder()
528        .url(server.getUrl("/"))
529        .header("User-Agent", "SyncApiTest")
530        .build();
531
532    Call call = client.newCall(request);
533    call.enqueue(callback);
534
535    try {
536      call.execute();
537      fail();
538    } catch (IllegalStateException e){
539      assertEquals("Already Executed", e.getMessage());
540    }
541
542    try {
543      call.enqueue(callback);
544      fail();
545    } catch (IllegalStateException e){
546      assertEquals("Already Executed", e.getMessage());
547    }
548
549    assertEquals("SyncApiTest", server.takeRequest().getHeader("User-Agent"));
550  }
551
552  @Test public void get_Async() throws Exception {
553    server.enqueue(new MockResponse()
554        .setBody("abc")
555        .addHeader("Content-Type: text/plain"));
556
557    Request request = new Request.Builder()
558        .url(server.getUrl("/"))
559        .header("User-Agent", "AsyncApiTest")
560        .build();
561    client.newCall(request).enqueue(callback);
562
563    callback.await(request.url())
564        .assertCode(200)
565        .assertHeader("Content-Type", "text/plain")
566        .assertBody("abc");
567
568    assertEquals("AsyncApiTest", server.takeRequest().getHeader("User-Agent"));
569  }
570
571  @Test public void exceptionThrownByOnResponseIsRedactedAndLogged() throws Exception {
572    server.enqueue(new MockResponse());
573
574    Request request = new Request.Builder()
575        .url(server.getUrl("/secret"))
576        .build();
577
578    client.newCall(request).enqueue(new Callback() {
579      @Override public void onFailure(Request request, IOException e) {
580        fail();
581      }
582
583      @Override public void onResponse(Response response) throws IOException {
584        throw new IOException("a");
585      }
586    });
587
588    assertEquals("INFO: Callback failure for call to " + server.getUrl("/") + "...",
589        logHandler.take());
590  }
591
592  @Test public void connectionPooling() throws Exception {
593    server.enqueue(new MockResponse().setBody("abc"));
594    server.enqueue(new MockResponse().setBody("def"));
595    server.enqueue(new MockResponse().setBody("ghi"));
596
597    executeSynchronously(new Request.Builder().url(server.getUrl("/a")).build())
598        .assertBody("abc");
599
600    executeSynchronously(new Request.Builder().url(server.getUrl("/b")).build())
601        .assertBody("def");
602
603    executeSynchronously(new Request.Builder().url(server.getUrl("/c")).build())
604        .assertBody("ghi");
605
606    assertEquals(0, server.takeRequest().getSequenceNumber());
607    assertEquals(1, server.takeRequest().getSequenceNumber());
608    assertEquals(2, server.takeRequest().getSequenceNumber());
609  }
610
611  @Test public void connectionPooling_Async() throws Exception {
612    server.enqueue(new MockResponse().setBody("abc"));
613    server.enqueue(new MockResponse().setBody("def"));
614    server.enqueue(new MockResponse().setBody("ghi"));
615
616    client.newCall(new Request.Builder().url(server.getUrl("/a")).build()).enqueue(callback);
617    callback.await(server.getUrl("/a")).assertBody("abc");
618
619    client.newCall(new Request.Builder().url(server.getUrl("/b")).build()).enqueue(callback);
620    callback.await(server.getUrl("/b")).assertBody("def");
621
622    client.newCall(new Request.Builder().url(server.getUrl("/c")).build()).enqueue(callback);
623    callback.await(server.getUrl("/c")).assertBody("ghi");
624
625    assertEquals(0, server.takeRequest().getSequenceNumber());
626    assertEquals(1, server.takeRequest().getSequenceNumber());
627    assertEquals(2, server.takeRequest().getSequenceNumber());
628  }
629
630  @Test public void connectionReuseWhenResponseBodyConsumed_Async() throws Exception {
631    server.enqueue(new MockResponse().setBody("abc"));
632    server.enqueue(new MockResponse().setBody("def"));
633
634    Request request = new Request.Builder().url(server.getUrl("/a")).build();
635    client.newCall(request).enqueue(new Callback() {
636      @Override public void onFailure(Request request, IOException e) {
637        throw new AssertionError();
638      }
639
640      @Override public void onResponse(Response response) throws IOException {
641        InputStream bytes = response.body().byteStream();
642        assertEquals('a', bytes.read());
643        assertEquals('b', bytes.read());
644        assertEquals('c', bytes.read());
645
646        // This request will share a connection with 'A' cause it's all done.
647        client.newCall(new Request.Builder().url(server.getUrl("/b")).build()).enqueue(callback);
648      }
649    });
650
651    callback.await(server.getUrl("/b")).assertCode(200).assertBody("def");
652    assertEquals(0, server.takeRequest().getSequenceNumber()); // New connection.
653    assertEquals(1, server.takeRequest().getSequenceNumber()); // Connection reuse!
654  }
655
656  @Test public void timeoutsUpdatedOnReusedConnections() throws Exception {
657    server.enqueue(new MockResponse().setBody("abc"));
658    server.enqueue(new MockResponse().setBody("def").throttleBody(1, 750, TimeUnit.MILLISECONDS));
659
660    // First request: time out after 1000ms.
661    client.setReadTimeout(1000, TimeUnit.MILLISECONDS);
662    executeSynchronously(new Request.Builder().url(server.getUrl("/a")).build()).assertBody("abc");
663
664    // Second request: time out after 250ms.
665    client.setReadTimeout(250, TimeUnit.MILLISECONDS);
666    Request request = new Request.Builder().url(server.getUrl("/b")).build();
667    Response response = client.newCall(request).execute();
668    BufferedSource bodySource = response.body().source();
669    assertEquals('d', bodySource.readByte());
670
671    // The second byte of this request will be delayed by 750ms so we should time out after 250ms.
672    long startNanos = System.nanoTime();
673    try {
674      bodySource.readByte();
675      fail();
676    } catch (IOException expected) {
677      // Timed out as expected.
678      long elapsedNanos = System.nanoTime() - startNanos;
679      long elapsedMillis = TimeUnit.NANOSECONDS.toMillis(elapsedNanos);
680      assertTrue(String.format("Timed out: %sms", elapsedMillis), elapsedMillis < 500);
681    }
682  }
683
684  // https://github.com/square/okhttp/issues/442
685  @Test public void timeoutsNotRetried() throws Exception {
686    server.enqueue(new MockResponse()
687        .setSocketPolicy(SocketPolicy.NO_RESPONSE));
688    server.enqueue(new MockResponse()
689        .setBody("unreachable!"));
690
691    Internal.instance.setNetwork(client, new DoubleInetAddressNetwork());
692    client.setReadTimeout(100, TimeUnit.MILLISECONDS);
693
694    Request request = new Request.Builder().url(server.getUrl("/")).build();
695    try {
696      // If this succeeds, too many requests were made.
697      client.newCall(request).execute();
698      fail();
699    } catch (InterruptedIOException expected) {
700    }
701  }
702
703  @Test public void reusedSinksGetIndependentTimeoutInstances() throws Exception {
704    server.enqueue(new MockResponse());
705    server.enqueue(new MockResponse());
706
707    // Call 1: set a deadline on the request body.
708    RequestBody requestBody1 = new RequestBody() {
709      @Override public MediaType contentType() {
710        return MediaType.parse("text/plain");
711      }
712      @Override public void writeTo(BufferedSink sink) throws IOException {
713        sink.writeUtf8("abc");
714        sink.timeout().deadline(5, TimeUnit.SECONDS);
715      }
716    };
717    Request request1 = new Request.Builder()
718        .url(server.getUrl("/"))
719        .method("POST", requestBody1)
720        .build();
721    Response response1 = client.newCall(request1).execute();
722    assertEquals(200, response1.code());
723
724    // Call 2: check for the absence of a deadline on the request body.
725    RequestBody requestBody2 = new RequestBody() {
726      @Override public MediaType contentType() {
727        return MediaType.parse("text/plain");
728      }
729      @Override public void writeTo(BufferedSink sink) throws IOException {
730        assertFalse(sink.timeout().hasDeadline());
731        sink.writeUtf8("def");
732      }
733    };
734    Request request2 = new Request.Builder()
735        .url(server.getUrl("/"))
736        .method("POST", requestBody2)
737        .build();
738    Response response2 = client.newCall(request2).execute();
739    assertEquals(200, response2.code());
740
741    // Use sequence numbers to confirm the connection was pooled.
742    assertEquals(0, server.takeRequest().getSequenceNumber());
743    assertEquals(1, server.takeRequest().getSequenceNumber());
744  }
745
746  @Test public void reusedSourcesGetIndependentTimeoutInstances() throws Exception {
747    server.enqueue(new MockResponse().setBody("abc"));
748    server.enqueue(new MockResponse().setBody("def"));
749
750    // Call 1: set a deadline on the response body.
751    Request request1 = new Request.Builder().url(server.getUrl("/")).build();
752    Response response1 = client.newCall(request1).execute();
753    BufferedSource body1 = response1.body().source();
754    assertEquals("abc", body1.readUtf8());
755    body1.timeout().deadline(5, TimeUnit.SECONDS);
756
757    // Call 2: check for the absence of a deadline on the request body.
758    Request request2 = new Request.Builder().url(server.getUrl("/")).build();
759    Response response2 = client.newCall(request2).execute();
760    BufferedSource body2 = response2.body().source();
761    assertEquals("def", body2.readUtf8());
762    assertFalse(body2.timeout().hasDeadline());
763
764    // Use sequence numbers to confirm the connection was pooled.
765    assertEquals(0, server.takeRequest().getSequenceNumber());
766    assertEquals(1, server.takeRequest().getSequenceNumber());
767  }
768
769  @Test public void tls() throws Exception {
770    server.get().useHttps(sslContext.getSocketFactory(), false);
771    server.enqueue(new MockResponse()
772        .setBody("abc")
773        .addHeader("Content-Type: text/plain"));
774
775    client.setSslSocketFactory(sslContext.getSocketFactory());
776    client.setHostnameVerifier(new RecordingHostnameVerifier());
777
778    executeSynchronously(new Request.Builder().url(server.getUrl("/")).build())
779        .assertHandshake();
780  }
781
782  @Test public void tls_Async() throws Exception {
783    server.get().useHttps(sslContext.getSocketFactory(), false);
784    server.enqueue(new MockResponse()
785        .setBody("abc")
786        .addHeader("Content-Type: text/plain"));
787
788    client.setSslSocketFactory(sslContext.getSocketFactory());
789    client.setHostnameVerifier(new RecordingHostnameVerifier());
790
791    Request request = new Request.Builder()
792        .url(server.getUrl("/"))
793        .build();
794    client.newCall(request).enqueue(callback);
795
796    callback.await(request.url()).assertHandshake();
797  }
798
799  @Test public void recoverWhenRetryOnConnectionFailureIsTrue() throws Exception {
800    server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AT_START));
801    server.enqueue(new MockResponse().setBody("retry success"));
802
803    Internal.instance.setNetwork(client, new DoubleInetAddressNetwork());
804    assertTrue(client.getRetryOnConnectionFailure());
805
806    Request request = new Request.Builder().url(server.getUrl("/")).build();
807    Response response = client.newCall(request).execute();
808    assertEquals("retry success", response.body().string());
809  }
810
811  @Test public void noRecoverWhenRetryOnConnectionFailureIsFalse() throws Exception {
812    server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AT_START));
813    server.enqueue(new MockResponse().setBody("unreachable!"));
814
815    Internal.instance.setNetwork(client, new DoubleInetAddressNetwork());
816    client.setRetryOnConnectionFailure(false);
817
818    Request request = new Request.Builder().url(server.getUrl("/")).build();
819    try {
820      // If this succeeds, too many requests were made.
821      client.newCall(request).execute();
822      fail();
823    } catch (IOException expected) {
824    }
825  }
826
827  @Test public void recoverFromTlsHandshakeFailure() throws Exception {
828    server.get().useHttps(sslContext.getSocketFactory(), false);
829    server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.FAIL_HANDSHAKE));
830    server.enqueue(new MockResponse().setBody("abc"));
831
832    suppressTlsFallbackScsv(client);
833    client.setHostnameVerifier(new RecordingHostnameVerifier());
834    Internal.instance.setNetwork(client, new SingleInetAddressNetwork());
835
836    executeSynchronously(new Request.Builder().url(server.getUrl("/")).build())
837        .assertBody("abc");
838  }
839
840  @Test public void recoverFromTlsHandshakeFailure_tlsFallbackScsvEnabled() throws Exception {
841    final String tlsFallbackScsv = "TLS_FALLBACK_SCSV";
842    List<String> supportedCiphers =
843        Arrays.asList(sslContext.getSocketFactory().getSupportedCipherSuites());
844    if (!supportedCiphers.contains(tlsFallbackScsv)) {
845      // This only works if the client socket supports TLS_FALLBACK_SCSV.
846      return;
847    }
848
849    server.get().useHttps(sslContext.getSocketFactory(), false);
850    server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.FAIL_HANDSHAKE));
851
852    RecordingSSLSocketFactory clientSocketFactory =
853        new RecordingSSLSocketFactory(sslContext.getSocketFactory());
854    client.setSslSocketFactory(clientSocketFactory);
855    client.setHostnameVerifier(new RecordingHostnameVerifier());
856    Internal.instance.setNetwork(client, new SingleInetAddressNetwork());
857
858    Request request = new Request.Builder().url(server.getUrl("/")).build();
859    try {
860      client.newCall(request).execute();
861      fail();
862    } catch (SSLHandshakeException expected) {
863    }
864
865    List<SSLSocket> clientSockets = clientSocketFactory.getSocketsCreated();
866    SSLSocket firstSocket = clientSockets.get(0);
867    assertFalse(Arrays.asList(firstSocket.getEnabledCipherSuites()).contains(tlsFallbackScsv));
868    SSLSocket secondSocket = clientSockets.get(1);
869    assertTrue(Arrays.asList(secondSocket.getEnabledCipherSuites()).contains(tlsFallbackScsv));
870  }
871
872  @Test public void recoverFromTlsHandshakeFailure_Async() throws Exception {
873    server.get().useHttps(sslContext.getSocketFactory(), false);
874    server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.FAIL_HANDSHAKE));
875    server.enqueue(new MockResponse().setBody("abc"));
876
877    suppressTlsFallbackScsv(client);
878    client.setHostnameVerifier(new RecordingHostnameVerifier());
879
880    Request request = new Request.Builder()
881        .url(server.getUrl("/"))
882        .build();
883    client.newCall(request).enqueue(callback);
884
885    callback.await(request.url()).assertBody("abc");
886  }
887
888  @Test public void noRecoveryFromTlsHandshakeFailureWhenTlsFallbackIsDisabled() throws Exception {
889    client.setConnectionSpecs(Arrays.asList(ConnectionSpec.MODERN_TLS, ConnectionSpec.CLEARTEXT));
890
891    server.get().useHttps(sslContext.getSocketFactory(), false);
892    server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.FAIL_HANDSHAKE));
893
894    suppressTlsFallbackScsv(client);
895    client.setHostnameVerifier(new RecordingHostnameVerifier());
896    Internal.instance.setNetwork(client, new SingleInetAddressNetwork());
897
898    Request request = new Request.Builder().url(server.getUrl("/")).build();
899    try {
900      client.newCall(request).execute();
901      fail();
902    } catch (SSLProtocolException expected) {
903      // RI response to the FAIL_HANDSHAKE
904    } catch (SSLHandshakeException expected) {
905      // Android's response to the FAIL_HANDSHAKE
906    }
907  }
908
909  @Test public void cleartextCallsFailWhenCleartextIsDisabled() throws Exception {
910    // Configure the client with only TLS configurations. No cleartext!
911    client.setConnectionSpecs(
912        Arrays.asList(ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS));
913
914    server.enqueue(new MockResponse());
915
916    Request request = new Request.Builder().url(server.getUrl("/")).build();
917    try {
918      client.newCall(request).execute();
919      fail();
920    } catch (UnknownServiceException expected) {
921      assertTrue(expected.getMessage().contains("CLEARTEXT communication not supported"));
922    }
923  }
924
925  @Test public void setFollowSslRedirectsFalse() throws Exception {
926    server.get().useHttps(sslContext.getSocketFactory(), false);
927    server.enqueue(new MockResponse().setResponseCode(301).addHeader("Location: http://square.com"));
928
929    client.setFollowSslRedirects(false);
930    client.setSslSocketFactory(sslContext.getSocketFactory());
931    client.setHostnameVerifier(new RecordingHostnameVerifier());
932
933    Request request = new Request.Builder().url(server.getUrl("/")).build();
934    Response response = client.newCall(request).execute();
935    assertEquals(301, response.code());
936  }
937
938  @Test public void matchingPinnedCertificate() throws Exception {
939    server.get().useHttps(sslContext.getSocketFactory(), false);
940    server.enqueue(new MockResponse());
941    server.enqueue(new MockResponse());
942
943    client.setSslSocketFactory(sslContext.getSocketFactory());
944    client.setHostnameVerifier(new RecordingHostnameVerifier());
945
946    // Make a first request without certificate pinning. Use it to collect certificates to pin.
947    Request request1 = new Request.Builder().url(server.getUrl("/")).build();
948    Response response1 = client.newCall(request1).execute();
949    CertificatePinner.Builder certificatePinnerBuilder = new CertificatePinner.Builder();
950    for (Certificate certificate : response1.handshake().peerCertificates()) {
951      certificatePinnerBuilder.add(server.get().getHostName(), CertificatePinner.pin(certificate));
952    }
953
954    // Make another request with certificate pinning. It should complete normally.
955    client.setCertificatePinner(certificatePinnerBuilder.build());
956    Request request2 = new Request.Builder().url(server.getUrl("/")).build();
957    Response response2 = client.newCall(request2).execute();
958    assertNotSame(response2.handshake(), response1.handshake());
959  }
960
961  @Test public void unmatchingPinnedCertificate() throws Exception {
962    server.get().useHttps(sslContext.getSocketFactory(), false);
963    server.enqueue(new MockResponse());
964
965    client.setSslSocketFactory(sslContext.getSocketFactory());
966    client.setHostnameVerifier(new RecordingHostnameVerifier());
967
968    // Pin publicobject.com's cert.
969    client.setCertificatePinner(new CertificatePinner.Builder()
970        .add(server.get().getHostName(), "sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=")
971        .build());
972
973    // When we pin the wrong certificate, connectivity fails.
974    Request request = new Request.Builder().url(server.getUrl("/")).build();
975    try {
976      client.newCall(request).execute();
977      fail();
978    } catch (SSLPeerUnverifiedException expected) {
979      assertTrue(expected.getMessage().startsWith("Certificate pinning failure!"));
980    }
981  }
982
983  @Test public void post_Async() throws Exception {
984    server.enqueue(new MockResponse().setBody("abc"));
985
986    Request request = new Request.Builder()
987        .url(server.getUrl("/"))
988        .post(RequestBody.create(MediaType.parse("text/plain"), "def"))
989        .build();
990    client.newCall(request).enqueue(callback);
991
992    callback.await(request.url())
993        .assertCode(200)
994        .assertBody("abc");
995
996    RecordedRequest recordedRequest = server.takeRequest();
997    assertEquals("def", recordedRequest.getBody().readUtf8());
998    assertEquals("3", recordedRequest.getHeader("Content-Length"));
999    assertEquals("text/plain; charset=utf-8", recordedRequest.getHeader("Content-Type"));
1000  }
1001
1002  @Test public void postBodyRetransmittedOnFailureRecovery() throws Exception {
1003    server.enqueue(new MockResponse().setBody("abc"));
1004    server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AFTER_REQUEST));
1005    server.enqueue(new MockResponse().setBody("def"));
1006
1007    // Seed the connection pool so we have something that can fail.
1008    Request request1 = new Request.Builder().url(server.getUrl("/")).build();
1009    Response response1 = client.newCall(request1).execute();
1010    assertEquals("abc", response1.body().string());
1011
1012    Request request2 = new Request.Builder()
1013        .url(server.getUrl("/"))
1014        .post(RequestBody.create(MediaType.parse("text/plain"), "body!"))
1015        .build();
1016    Response response2 = client.newCall(request2).execute();
1017    assertEquals("def", response2.body().string());
1018
1019    RecordedRequest get = server.takeRequest();
1020    assertEquals(0, get.getSequenceNumber());
1021
1022    RecordedRequest post1 = server.takeRequest();
1023    assertEquals("body!", post1.getBody().readUtf8());
1024    assertEquals(1, post1.getSequenceNumber());
1025
1026    RecordedRequest post2 = server.takeRequest();
1027    assertEquals("body!", post2.getBody().readUtf8());
1028    assertEquals(0, post2.getSequenceNumber());
1029  }
1030
1031  @Test public void cacheHit() throws Exception {
1032    server.enqueue(new MockResponse()
1033        .addHeader("ETag: v1")
1034        .addHeader("Cache-Control: max-age=60")
1035        .addHeader("Vary: Accept-Charset")
1036        .setBody("A"));
1037
1038    client.setCache(cache);
1039
1040    // Store a response in the cache.
1041    URL url = server.getUrl("/");
1042    Request cacheStoreRequest = new Request.Builder()
1043        .url(url)
1044        .addHeader("Accept-Language", "fr-CA")
1045        .addHeader("Accept-Charset", "UTF-8")
1046        .build();
1047    executeSynchronously(cacheStoreRequest)
1048        .assertCode(200)
1049        .assertBody("A");
1050    assertNull(server.takeRequest().getHeader("If-None-Match"));
1051
1052    // Hit that stored response.
1053    Request cacheHitRequest = new Request.Builder()
1054        .url(url)
1055        .addHeader("Accept-Language", "en-US") // Different, but Vary says it doesn't matter.
1056        .addHeader("Accept-Charset", "UTF-8")
1057        .build();
1058    RecordedResponse cacheHit = executeSynchronously(cacheHitRequest);
1059
1060    // Check the merged response. The request is the application's original request.
1061    cacheHit.assertCode(200)
1062        .assertBody("A")
1063        .assertHeader("ETag", "v1")
1064        .assertRequestUrl(cacheStoreRequest.url())
1065        .assertRequestHeader("Accept-Language", "en-US")
1066        .assertRequestHeader("Accept-Charset", "UTF-8");
1067
1068    // Check the cached response. Its request contains only the saved Vary headers.
1069    cacheHit.cacheResponse()
1070        .assertCode(200)
1071        .assertHeader("ETag", "v1")
1072        .assertRequestMethod("GET")
1073        .assertRequestUrl(cacheStoreRequest.url())
1074        .assertRequestHeader("Accept-Language")
1075        .assertRequestHeader("Accept-Charset", "UTF-8");
1076
1077    cacheHit.assertNoNetworkResponse();
1078  }
1079
1080  @Test public void conditionalCacheHit() throws Exception {
1081    server.enqueue(new MockResponse()
1082        .addHeader("ETag: v1")
1083        .addHeader("Vary: Accept-Charset")
1084        .addHeader("Donut: a")
1085        .setBody("A"));
1086    server.enqueue(new MockResponse().clearHeaders()
1087        .addHeader("Donut: b")
1088        .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
1089
1090    client.setCache(cache);
1091
1092    // Store a response in the cache.
1093    URL url = server.getUrl("/");
1094    Request cacheStoreRequest = new Request.Builder()
1095        .url(url)
1096        .addHeader("Accept-Language", "fr-CA")
1097        .addHeader("Accept-Charset", "UTF-8")
1098        .build();
1099    executeSynchronously(cacheStoreRequest)
1100        .assertCode(200)
1101        .assertHeader("Donut", "a")
1102        .assertBody("A");
1103    assertNull(server.takeRequest().getHeader("If-None-Match"));
1104
1105    // Hit that stored response.
1106    Request cacheHitRequest = new Request.Builder()
1107        .url(url)
1108        .addHeader("Accept-Language", "en-US") // Different, but Vary says it doesn't matter.
1109        .addHeader("Accept-Charset", "UTF-8")
1110        .build();
1111    RecordedResponse cacheHit = executeSynchronously(cacheHitRequest);
1112    assertEquals("v1", server.takeRequest().getHeader("If-None-Match"));
1113
1114    // Check the merged response. The request is the application's original request.
1115    cacheHit.assertCode(200)
1116        .assertBody("A")
1117        .assertHeader("Donut", "b")
1118        .assertRequestUrl(cacheStoreRequest.url())
1119        .assertRequestHeader("Accept-Language", "en-US")
1120        .assertRequestHeader("Accept-Charset", "UTF-8")
1121        .assertRequestHeader("If-None-Match"); // No If-None-Match on the user's request.
1122
1123    // Check the cached response. Its request contains only the saved Vary headers.
1124    cacheHit.cacheResponse()
1125        .assertCode(200)
1126        .assertHeader("Donut", "a")
1127        .assertHeader("ETag", "v1")
1128        .assertRequestUrl(cacheStoreRequest.url())
1129        .assertRequestHeader("Accept-Language") // No Vary on Accept-Language.
1130        .assertRequestHeader("Accept-Charset", "UTF-8") // Because of Vary on Accept-Charset.
1131        .assertRequestHeader("If-None-Match"); // This wasn't present in the original request.
1132
1133    // Check the network response. It has the caller's request, plus some caching headers.
1134    cacheHit.networkResponse()
1135        .assertCode(304)
1136        .assertHeader("Donut", "b")
1137        .assertRequestHeader("Accept-Language", "en-US")
1138        .assertRequestHeader("Accept-Charset", "UTF-8")
1139        .assertRequestHeader("If-None-Match", "v1"); // If-None-Match in the validation request.
1140  }
1141
1142  @Test public void conditionalCacheHit_Async() throws Exception {
1143    server.enqueue(new MockResponse().setBody("A").addHeader("ETag: v1"));
1144    server.enqueue(new MockResponse()
1145        .clearHeaders()
1146        .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
1147
1148    client.setCache(cache);
1149
1150    Request request1 = new Request.Builder()
1151        .url(server.getUrl("/"))
1152        .build();
1153    client.newCall(request1).enqueue(callback);
1154    callback.await(request1.url()).assertCode(200).assertBody("A");
1155    assertNull(server.takeRequest().getHeader("If-None-Match"));
1156
1157    Request request2 = new Request.Builder()
1158        .url(server.getUrl("/"))
1159        .build();
1160    client.newCall(request2).enqueue(callback);
1161    callback.await(request2.url()).assertCode(200).assertBody("A");
1162    assertEquals("v1", server.takeRequest().getHeader("If-None-Match"));
1163  }
1164
1165  @Test public void conditionalCacheMiss() throws Exception {
1166    server.enqueue(new MockResponse()
1167        .addHeader("ETag: v1")
1168        .addHeader("Vary: Accept-Charset")
1169        .addHeader("Donut: a")
1170        .setBody("A"));
1171    server.enqueue(new MockResponse()
1172        .addHeader("Donut: b")
1173        .setBody("B"));
1174
1175    client.setCache(cache);
1176
1177    Request cacheStoreRequest = new Request.Builder()
1178        .url(server.getUrl("/"))
1179        .addHeader("Accept-Language", "fr-CA")
1180        .addHeader("Accept-Charset", "UTF-8")
1181        .build();
1182    executeSynchronously(cacheStoreRequest)
1183        .assertCode(200)
1184        .assertBody("A");
1185    assertNull(server.takeRequest().getHeader("If-None-Match"));
1186
1187    Request cacheMissRequest = new Request.Builder()
1188        .url(server.getUrl("/"))
1189        .addHeader("Accept-Language", "en-US") // Different, but Vary says it doesn't matter.
1190        .addHeader("Accept-Charset", "UTF-8")
1191        .build();
1192    RecordedResponse cacheHit = executeSynchronously(cacheMissRequest);
1193    assertEquals("v1", server.takeRequest().getHeader("If-None-Match"));
1194
1195    // Check the user response. It has the application's original request.
1196    cacheHit.assertCode(200)
1197        .assertBody("B")
1198        .assertHeader("Donut", "b")
1199        .assertRequestUrl(cacheStoreRequest.url());
1200
1201    // Check the cache response. Even though it's a miss, we used the cache.
1202    cacheHit.cacheResponse()
1203        .assertCode(200)
1204        .assertHeader("Donut", "a")
1205        .assertHeader("ETag", "v1")
1206        .assertRequestUrl(cacheStoreRequest.url());
1207
1208    // Check the network response. It has the network request, plus caching headers.
1209    cacheHit.networkResponse()
1210        .assertCode(200)
1211        .assertHeader("Donut", "b")
1212        .assertRequestHeader("If-None-Match", "v1")  // If-None-Match in the validation request.
1213        .assertRequestUrl(cacheStoreRequest.url());
1214  }
1215
1216  @Test public void conditionalCacheMiss_Async() throws Exception {
1217    server.enqueue(new MockResponse().setBody("A").addHeader("ETag: v1"));
1218    server.enqueue(new MockResponse().setBody("B"));
1219
1220    client.setCache(cache);
1221
1222    Request request1 = new Request.Builder()
1223        .url(server.getUrl("/"))
1224        .build();
1225    client.newCall(request1).enqueue(callback);
1226    callback.await(request1.url()).assertCode(200).assertBody("A");
1227    assertNull(server.takeRequest().getHeader("If-None-Match"));
1228
1229    Request request2 = new Request.Builder()
1230        .url(server.getUrl("/"))
1231        .build();
1232    client.newCall(request2).enqueue(callback);
1233    callback.await(request2.url()).assertCode(200).assertBody("B");
1234    assertEquals("v1", server.takeRequest().getHeader("If-None-Match"));
1235  }
1236
1237  @Test public void onlyIfCachedReturns504WhenNotCached() throws Exception {
1238    Request request = new Request.Builder()
1239        .url(server.getUrl("/"))
1240        .header("Cache-Control", "only-if-cached")
1241        .build();
1242
1243    executeSynchronously(request)
1244        .assertCode(504)
1245        .assertBody("")
1246        .assertNoNetworkResponse()
1247        .assertNoCacheResponse();
1248  }
1249
1250  @Test public void redirect() throws Exception {
1251    server.enqueue(new MockResponse()
1252        .setResponseCode(301)
1253        .addHeader("Location: /b")
1254        .addHeader("Test", "Redirect from /a to /b")
1255        .setBody("/a has moved!"));
1256    server.enqueue(new MockResponse()
1257        .setResponseCode(302)
1258        .addHeader("Location: /c")
1259        .addHeader("Test", "Redirect from /b to /c")
1260        .setBody("/b has moved!"));
1261    server.enqueue(new MockResponse().setBody("C"));
1262
1263    executeSynchronously(new Request.Builder().url(server.getUrl("/a")).build())
1264        .assertCode(200)
1265        .assertBody("C")
1266        .priorResponse()
1267        .assertCode(302)
1268        .assertHeader("Test", "Redirect from /b to /c")
1269        .priorResponse()
1270        .assertCode(301)
1271        .assertHeader("Test", "Redirect from /a to /b");
1272
1273    assertEquals(0, server.takeRequest().getSequenceNumber()); // New connection.
1274    assertEquals(1, server.takeRequest().getSequenceNumber()); // Connection reused.
1275    assertEquals(2, server.takeRequest().getSequenceNumber()); // Connection reused again!
1276  }
1277
1278  @Test public void postRedirectsToGet() throws Exception {
1279    server.enqueue(new MockResponse()
1280        .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1281        .addHeader("Location: /page2")
1282        .setBody("This page has moved!"));
1283    server.enqueue(new MockResponse().setBody("Page 2"));
1284
1285    Response response = client.newCall(new Request.Builder()
1286        .url(server.getUrl("/page1"))
1287        .post(RequestBody.create(MediaType.parse("text/plain"), "Request Body"))
1288        .build()).execute();
1289    assertEquals("Page 2", response.body().string());
1290
1291    RecordedRequest page1 = server.takeRequest();
1292    assertEquals("POST /page1 HTTP/1.1", page1.getRequestLine());
1293    assertEquals("Request Body", page1.getBody().readUtf8());
1294
1295    RecordedRequest page2 = server.takeRequest();
1296    assertEquals("GET /page2 HTTP/1.1", page2.getRequestLine());
1297  }
1298
1299  @Test public void redirectsDoNotIncludeTooManyCookies() throws Exception {
1300    server2.enqueue(new MockResponse().setBody("Page 2"));
1301    server.enqueue(new MockResponse()
1302        .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
1303        .addHeader("Location: " + server2.getUrl("/")));
1304
1305    CookieManager cookieManager = new CookieManager(null, ACCEPT_ORIGINAL_SERVER);
1306    HttpCookie cookie = new HttpCookie("c", "cookie");
1307    cookie.setDomain(server.get().getCookieDomain());
1308    cookie.setPath("/");
1309    String portList = Integer.toString(server.getPort());
1310    cookie.setPortlist(portList);
1311    cookieManager.getCookieStore().add(server.getUrl("/").toURI(), cookie);
1312    client.setCookieHandler(cookieManager);
1313
1314    Response response = client.newCall(new Request.Builder()
1315        .url(server.getUrl("/page1"))
1316        .build()).execute();
1317    assertEquals("Page 2", response.body().string());
1318
1319    RecordedRequest request1 = server.takeRequest();
1320    assertEquals("$Version=\"1\"; c=\"cookie\";$Path=\"/\";$Domain=\""
1321        + server.get().getCookieDomain()
1322        + "\";$Port=\""
1323        + portList
1324        + "\"", request1.getHeader("Cookie"));
1325
1326    RecordedRequest request2 = server2.takeRequest();
1327    assertNull(request2.getHeader("Cookie"));
1328  }
1329
1330  @Test public void redirectsDoNotIncludeTooManyAuthHeaders() throws Exception {
1331    server2.enqueue(new MockResponse().setBody("Page 2"));
1332    server.enqueue(new MockResponse()
1333        .setResponseCode(401));
1334    server.enqueue(new MockResponse()
1335        .setResponseCode(302)
1336        .addHeader("Location: " + server2.getUrl("/b")));
1337
1338    client.setAuthenticator(new RecordingOkAuthenticator(Credentials.basic("jesse", "secret")));
1339
1340    Request request = new Request.Builder().url(server.getUrl("/a")).build();
1341    Response response = client.newCall(request).execute();
1342    assertEquals("Page 2", response.body().string());
1343
1344    RecordedRequest redirectRequest = server2.takeRequest();
1345    assertNull(redirectRequest.getHeader("Authorization"));
1346    assertEquals("/b", redirectRequest.getPath());
1347  }
1348
1349  @Test public void redirect_Async() throws Exception {
1350    server.enqueue(new MockResponse()
1351        .setResponseCode(301)
1352        .addHeader("Location: /b")
1353        .addHeader("Test", "Redirect from /a to /b")
1354        .setBody("/a has moved!"));
1355    server.enqueue(new MockResponse()
1356        .setResponseCode(302)
1357        .addHeader("Location: /c")
1358        .addHeader("Test", "Redirect from /b to /c")
1359        .setBody("/b has moved!"));
1360    server.enqueue(new MockResponse().setBody("C"));
1361
1362    Request request = new Request.Builder().url(server.getUrl("/a")).build();
1363    client.newCall(request).enqueue(callback);
1364
1365    callback.await(server.getUrl("/c"))
1366        .assertCode(200)
1367        .assertBody("C")
1368        .priorResponse()
1369        .assertCode(302)
1370        .assertHeader("Test", "Redirect from /b to /c")
1371        .priorResponse()
1372        .assertCode(301)
1373        .assertHeader("Test", "Redirect from /a to /b");
1374
1375    assertEquals(0, server.takeRequest().getSequenceNumber()); // New connection.
1376    assertEquals(1, server.takeRequest().getSequenceNumber()); // Connection reused.
1377    assertEquals(2, server.takeRequest().getSequenceNumber()); // Connection reused again!
1378  }
1379
1380  @Test public void follow20Redirects() throws Exception {
1381    for (int i = 0; i < 20; i++) {
1382      server.enqueue(new MockResponse()
1383          .setResponseCode(301)
1384          .addHeader("Location: /" + (i + 1))
1385          .setBody("Redirecting to /" + (i + 1)));
1386    }
1387    server.enqueue(new MockResponse().setBody("Success!"));
1388
1389    executeSynchronously(new Request.Builder().url(server.getUrl("/0")).build())
1390        .assertCode(200)
1391        .assertBody("Success!");
1392  }
1393
1394  @Test public void follow20Redirects_Async() throws Exception {
1395    for (int i = 0; i < 20; i++) {
1396      server.enqueue(new MockResponse()
1397          .setResponseCode(301)
1398          .addHeader("Location: /" + (i + 1))
1399          .setBody("Redirecting to /" + (i + 1)));
1400    }
1401    server.enqueue(new MockResponse().setBody("Success!"));
1402
1403    Request request = new Request.Builder().url(server.getUrl("/0")).build();
1404    client.newCall(request).enqueue(callback);
1405    callback.await(server.getUrl("/20"))
1406        .assertCode(200)
1407        .assertBody("Success!");
1408  }
1409
1410  @Test public void doesNotFollow21Redirects() throws Exception {
1411    for (int i = 0; i < 21; i++) {
1412      server.enqueue(new MockResponse()
1413          .setResponseCode(301)
1414          .addHeader("Location: /" + (i + 1))
1415          .setBody("Redirecting to /" + (i + 1)));
1416    }
1417
1418    try {
1419      client.newCall(new Request.Builder().url(server.getUrl("/0")).build()).execute();
1420      fail();
1421    } catch (IOException expected) {
1422      assertEquals("Too many follow-up requests: 21", expected.getMessage());
1423    }
1424  }
1425
1426  @Test public void doesNotFollow21Redirects_Async() throws Exception {
1427    for (int i = 0; i < 21; i++) {
1428      server.enqueue(new MockResponse()
1429          .setResponseCode(301)
1430          .addHeader("Location: /" + (i + 1))
1431          .setBody("Redirecting to /" + (i + 1)));
1432    }
1433
1434    Request request = new Request.Builder().url(server.getUrl("/0")).build();
1435    client.newCall(request).enqueue(callback);
1436    callback.await(server.getUrl("/20")).assertFailure("Too many follow-up requests: 21");
1437  }
1438
1439  @Test public void canceledBeforeExecute() throws Exception {
1440    Call call = client.newCall(new Request.Builder().url(server.getUrl("/a")).build());
1441    call.cancel();
1442
1443    try {
1444      call.execute();
1445      fail();
1446    } catch (IOException expected) {
1447    }
1448    assertEquals(0, server.getRequestCount());
1449  }
1450
1451  @Test public void cancelTagImmediatelyAfterEnqueue() throws Exception {
1452    Call call = client.newCall(new Request.Builder()
1453        .url(server.getUrl("/a"))
1454        .tag("request")
1455        .build());
1456    call.enqueue(callback);
1457    client.cancel("request");
1458    assertEquals(0, server.getRequestCount());
1459    callback.await(server.getUrl("/a")).assertFailure("Canceled");
1460  }
1461
1462  @Test public void cancelBeforeBodyIsRead() throws Exception {
1463    server.enqueue(new MockResponse().setBody("def").throttleBody(1, 750, TimeUnit.MILLISECONDS));
1464
1465    final Call call = client.newCall(new Request.Builder().url(server.getUrl("/a")).build());
1466    ExecutorService executor = Executors.newSingleThreadExecutor();
1467    Future<Response> result = executor.submit(new Callable<Response>() {
1468      @Override public Response call() throws Exception {
1469        return call.execute();
1470      }
1471    });
1472
1473    Thread.sleep(100); // wait for it to go in flight.
1474
1475    call.cancel();
1476    try {
1477      result.get().body().bytes();
1478      fail();
1479    } catch (IOException expected) {
1480    }
1481    assertEquals(1, server.getRequestCount());
1482  }
1483
1484  @Test public void cancelInFlightBeforeResponseReadThrowsIOE() throws Exception {
1485    server.get().setDispatcher(new Dispatcher() {
1486      @Override public MockResponse dispatch(RecordedRequest request) {
1487        client.cancel("request");
1488        return new MockResponse().setBody("A");
1489      }
1490    });
1491
1492    Request request = new Request.Builder().url(server.getUrl("/a")).tag("request").build();
1493    try {
1494      client.newCall(request).execute();
1495      fail();
1496    } catch (IOException expected) {
1497    }
1498  }
1499
1500  @Test public void cancelInFlightBeforeResponseReadThrowsIOE_HTTP_2() throws Exception {
1501    enableProtocol(Protocol.HTTP_2);
1502    cancelInFlightBeforeResponseReadThrowsIOE();
1503  }
1504
1505  @Test public void cancelInFlightBeforeResponseReadThrowsIOE_SPDY_3() throws Exception {
1506    enableProtocol(Protocol.SPDY_3);
1507    cancelInFlightBeforeResponseReadThrowsIOE();
1508  }
1509
1510  /**
1511   * This test puts a request in front of one that is to be canceled, so that it is canceled before
1512   * I/O takes place.
1513   */
1514  @Test public void canceledBeforeIOSignalsOnFailure() throws Exception {
1515    client.getDispatcher().setMaxRequests(1); // Force requests to be executed serially.
1516    server.get().setDispatcher(new Dispatcher() {
1517      char nextResponse = 'A';
1518
1519      @Override public MockResponse dispatch(RecordedRequest request) {
1520        client.cancel("request B");
1521        return new MockResponse().setBody(Character.toString(nextResponse++));
1522      }
1523    });
1524
1525    Request requestA = new Request.Builder().url(server.getUrl("/a")).tag("request A").build();
1526    client.newCall(requestA).enqueue(callback);
1527    assertEquals("/a", server.takeRequest().getPath());
1528
1529    Request requestB = new Request.Builder().url(server.getUrl("/b")).tag("request B").build();
1530    client.newCall(requestB).enqueue(callback);
1531
1532    callback.await(requestA.url()).assertBody("A");
1533    // At this point we know the callback is ready, and that it will receive a cancel failure.
1534    callback.await(requestB.url()).assertFailure("Canceled");
1535  }
1536
1537  @Test public void canceledBeforeIOSignalsOnFailure_HTTP_2() throws Exception {
1538    enableProtocol(Protocol.HTTP_2);
1539    canceledBeforeIOSignalsOnFailure();
1540  }
1541
1542  @Test public void canceledBeforeIOSignalsOnFailure_SPDY_3() throws Exception {
1543    enableProtocol(Protocol.SPDY_3);
1544    canceledBeforeIOSignalsOnFailure();
1545  }
1546
1547  @Test public void canceledBeforeResponseReadSignalsOnFailure() throws Exception {
1548    Request requestA = new Request.Builder().url(server.getUrl("/a")).tag("request A").build();
1549    final Call call = client.newCall(requestA);
1550    server.get().setDispatcher(new Dispatcher() {
1551      @Override public MockResponse dispatch(RecordedRequest request) {
1552        call.cancel();
1553        return new MockResponse().setBody("A");
1554      }
1555    });
1556
1557    call.enqueue(callback);
1558    assertEquals("/a", server.takeRequest().getPath());
1559
1560    callback.await(requestA.url()).assertFailure("Canceled", "stream was reset: CANCEL",
1561        "Socket closed");
1562  }
1563
1564  @Test public void canceledBeforeResponseReadSignalsOnFailure_HTTP_2() throws Exception {
1565    enableProtocol(Protocol.HTTP_2);
1566    canceledBeforeResponseReadSignalsOnFailure();
1567  }
1568
1569  @Test public void canceledBeforeResponseReadSignalsOnFailure_SPDY_3() throws Exception {
1570    enableProtocol(Protocol.SPDY_3);
1571    canceledBeforeResponseReadSignalsOnFailure();
1572  }
1573
1574  /**
1575   * There's a race condition where the cancel may apply after the stream has already been
1576   * processed.
1577   */
1578  @Test public void canceledAfterResponseIsDeliveredBreaksStreamButSignalsOnce() throws Exception {
1579    server.enqueue(new MockResponse().setBody("A"));
1580
1581    final CountDownLatch latch = new CountDownLatch(1);
1582    final AtomicReference<String> bodyRef = new AtomicReference<>();
1583    final AtomicBoolean failureRef = new AtomicBoolean();
1584
1585    Request request = new Request.Builder().url(server.getUrl("/a")).tag("request A").build();
1586    final Call call = client.newCall(request);
1587    call.enqueue(new Callback() {
1588      @Override public void onFailure(Request request, IOException e) {
1589        failureRef.set(true);
1590        latch.countDown();
1591      }
1592
1593      @Override public void onResponse(Response response) throws IOException {
1594        call.cancel();
1595        try {
1596          bodyRef.set(response.body().string());
1597        } catch (IOException e) { // It is ok if this broke the stream.
1598          bodyRef.set("A");
1599          throw e; // We expect to not loop into onFailure in this case.
1600        } finally {
1601          latch.countDown();
1602        }
1603      }
1604    });
1605
1606    latch.await();
1607    assertEquals("A", bodyRef.get());
1608    assertFalse(failureRef.get());
1609  }
1610
1611  @Test public void canceledAfterResponseIsDeliveredBreaksStreamButSignalsOnce_HTTP_2()
1612      throws Exception {
1613    enableProtocol(Protocol.HTTP_2);
1614    canceledAfterResponseIsDeliveredBreaksStreamButSignalsOnce();
1615  }
1616
1617  @Test public void canceledAfterResponseIsDeliveredBreaksStreamButSignalsOnce_SPDY_3()
1618      throws Exception {
1619    enableProtocol(Protocol.SPDY_3);
1620    canceledAfterResponseIsDeliveredBreaksStreamButSignalsOnce();
1621  }
1622
1623  @Test public void cancelWithInterceptor() throws Exception {
1624    client.interceptors().add(new Interceptor() {
1625      @Override public Response intercept(Chain chain) throws IOException {
1626        chain.proceed(chain.request());
1627        throw new AssertionError(); // We expect an exception.
1628      }
1629    });
1630
1631    Call call = client.newCall(new Request.Builder().url(server.getUrl("/a")).build());
1632    call.cancel();
1633
1634    try {
1635      call.execute();
1636      fail();
1637    } catch (IOException expected) {
1638    }
1639    assertEquals(0, server.getRequestCount());
1640  }
1641
1642  @Test public void gzip() throws Exception {
1643    Buffer gzippedBody = gzip("abcabcabc");
1644    String bodySize = Long.toString(gzippedBody.size());
1645
1646    server.enqueue(new MockResponse()
1647        .setBody(gzippedBody)
1648        .addHeader("Content-Encoding: gzip"));
1649
1650    Request request = new Request.Builder()
1651        .url(server.getUrl("/"))
1652        .build();
1653
1654    // Confirm that the user request doesn't have Accept-Encoding, and the user
1655    // response doesn't have a Content-Encoding or Content-Length.
1656    RecordedResponse userResponse = executeSynchronously(request);
1657    userResponse.assertCode(200)
1658        .assertRequestHeader("Accept-Encoding")
1659        .assertHeader("Content-Encoding")
1660        .assertHeader("Content-Length")
1661        .assertBody("abcabcabc");
1662
1663    // But the network request doesn't lie. OkHttp used gzip for this call.
1664    userResponse.networkResponse()
1665        .assertHeader("Content-Encoding", "gzip")
1666        .assertHeader("Content-Length", bodySize)
1667        .assertRequestHeader("Accept-Encoding", "gzip");
1668  }
1669
1670  @Test public void asyncResponseCanBeConsumedLater() throws Exception {
1671    server.enqueue(new MockResponse().setBody("abc"));
1672    server.enqueue(new MockResponse().setBody("def"));
1673
1674    Request request = new Request.Builder()
1675        .url(server.getUrl("/"))
1676        .header("User-Agent", "SyncApiTest")
1677        .build();
1678
1679    final BlockingQueue<Response> responseRef = new SynchronousQueue<>();
1680    client.newCall(request).enqueue(new Callback() {
1681      @Override public void onFailure(Request request, IOException e) {
1682        throw new AssertionError();
1683      }
1684
1685      @Override public void onResponse(Response response) throws IOException {
1686        try {
1687          responseRef.put(response);
1688        } catch (InterruptedException e) {
1689          throw new AssertionError();
1690        }
1691      }
1692    });
1693
1694    Response response = responseRef.take();
1695    assertEquals(200, response.code());
1696    assertEquals("abc", response.body().string());
1697
1698    // Make another request just to confirm that that connection can be reused...
1699    executeSynchronously(new Request.Builder().url(server.getUrl("/")).build()).assertBody("def");
1700    assertEquals(0, server.takeRequest().getSequenceNumber()); // New connection.
1701    assertEquals(1, server.takeRequest().getSequenceNumber()); // Connection reused.
1702
1703    // ... even before we close the response body!
1704    response.body().close();
1705  }
1706
1707  @Test public void userAgentIsIncludedByDefault() throws Exception {
1708    server.enqueue(new MockResponse());
1709
1710    executeSynchronously(new Request.Builder().url(server.getUrl("/")).build());
1711
1712    RecordedRequest recordedRequest = server.takeRequest();
1713    assertTrue(recordedRequest.getHeader("User-Agent")
1714        .matches(Version.userAgent()));
1715  }
1716
1717  @Test public void setFollowRedirectsFalse() throws Exception {
1718    server.enqueue(new MockResponse()
1719        .setResponseCode(302)
1720        .addHeader("Location: /b")
1721        .setBody("A"));
1722    server.enqueue(new MockResponse().setBody("B"));
1723
1724    client.setFollowRedirects(false);
1725    RecordedResponse recordedResponse = executeSynchronously(
1726        new Request.Builder().url(server.getUrl("/a")).build());
1727
1728    recordedResponse
1729        .assertBody("A")
1730        .assertCode(302);
1731  }
1732
1733  @Test public void expect100ContinueNonEmptyRequestBody() throws Exception {
1734    server.enqueue(new MockResponse());
1735
1736    Request request = new Request.Builder()
1737        .url(server.getUrl("/"))
1738        .header("Expect", "100-continue")
1739        .post(RequestBody.create(MediaType.parse("text/plain"), "abc"))
1740        .build();
1741
1742    executeSynchronously(request)
1743        .assertCode(200)
1744        .assertSuccessful();
1745
1746    assertEquals("abc", server.takeRequest().getUtf8Body());
1747  }
1748
1749  @Test public void expect100ContinueEmptyRequestBody() throws Exception {
1750    server.enqueue(new MockResponse());
1751
1752    Request request = new Request.Builder()
1753        .url(server.getUrl("/"))
1754        .header("Expect", "100-continue")
1755        .post(RequestBody.create(MediaType.parse("text/plain"), ""))
1756        .build();
1757
1758    executeSynchronously(request)
1759        .assertCode(200)
1760        .assertSuccessful();
1761  }
1762
1763  private RecordedResponse executeSynchronously(Request request) throws IOException {
1764    Response response = client.newCall(request).execute();
1765    return new RecordedResponse(request, response, null, response.body().string(), null);
1766  }
1767
1768  /**
1769   * Tests that use this will fail unless boot classpath is set. Ex. {@code
1770   * -Xbootclasspath/p:/tmp/alpn-boot-8.0.0.v20140317}
1771   */
1772  private void enableProtocol(Protocol protocol) {
1773    client.setSslSocketFactory(sslContext.getSocketFactory());
1774    client.setHostnameVerifier(new RecordingHostnameVerifier());
1775    client.setProtocols(Arrays.asList(protocol, Protocol.HTTP_1_1));
1776    server.get().useHttps(sslContext.getSocketFactory(), false);
1777    server.get().setProtocols(client.getProtocols());
1778  }
1779
1780  private Buffer gzip(String data) throws IOException {
1781    Buffer result = new Buffer();
1782    BufferedSink sink = Okio.buffer(new GzipSink(result));
1783    sink.writeUtf8(data);
1784    sink.close();
1785    return result;
1786  }
1787
1788  private static class RecordingSSLSocketFactory extends DelegatingSSLSocketFactory {
1789
1790    private List<SSLSocket> socketsCreated = new ArrayList<SSLSocket>();
1791
1792    public RecordingSSLSocketFactory(SSLSocketFactory delegate) {
1793      super(delegate);
1794    }
1795
1796    @Override
1797    protected void configureSocket(SSLSocket sslSocket) throws IOException {
1798      socketsCreated.add(sslSocket);
1799    }
1800
1801    public List<SSLSocket> getSocketsCreated() {
1802      return socketsCreated;
1803    }
1804  }
1805
1806  /**
1807   * Used during tests that involve TLS connection fallback attempts. OkHttp includes the
1808   * TLS_FALLBACK_SCSV cipher on fallback connections. See
1809   * {@link com.squareup.okhttp.FallbackTestClientSocketFactory} for details.
1810   */
1811  private static void suppressTlsFallbackScsv(OkHttpClient client) {
1812    FallbackTestClientSocketFactory clientSocketFactory =
1813        new FallbackTestClientSocketFactory(sslContext.getSocketFactory());
1814    client.setSslSocketFactory(clientSocketFactory);
1815  }
1816}
1817