co.paralleluniverse.fibers.okhttp.CallTest.java Source code

Java tutorial

Introduction

Here is the source code for co.paralleluniverse.fibers.okhttp.CallTest.java

Source

/*
 * COMSAT
 * Copyright (c) 2013-2015, Parallel Universe Software Co. All rights reserved.
 *
 * This program and the accompanying materials are dual-licensed under
 * either the terms of the Eclipse Public License v1.0 as published by
 * the Eclipse Foundation
 *
 *   or (per the licensee's choosing)
 *
 * under the terms of the GNU Lesser General Public License version 3.0
 * as published by the Free Software Foundation.
 */
/*
 * Based on the corresponding class in okhttp-tests.
 * Copyright 2015 Square, Inc.
 * Licensed under the Apache License, Version 2.0 (the "License").
 */
package co.paralleluniverse.fibers.okhttp;

import co.paralleluniverse.fibers.okhttp.internal.DoubleInetAddressNetwork;
import co.paralleluniverse.fibers.okhttp.internal.RecordingHostnameVerifier;
import co.paralleluniverse.fibers.okhttp.internal.RecordingOkAuthenticator;
import co.paralleluniverse.fibers.okhttp.internal.SingleInetAddressNetwork;
import com.squareup.okhttp.Cache;
import com.squareup.okhttp.Call;
import com.squareup.okhttp.Callback;
import com.squareup.okhttp.CertificatePinner;
import com.squareup.okhttp.ConnectionSpec;
import com.squareup.okhttp.Credentials;
import com.squareup.okhttp.HttpUrl;
import com.squareup.okhttp.Interceptor;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.Protocol;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;
import com.squareup.okhttp.internal.Internal;
import com.squareup.okhttp.internal.SslContextBuilder;
import com.squareup.okhttp.mockwebserver.Dispatcher;
import com.squareup.okhttp.mockwebserver.MockResponse;
import com.squareup.okhttp.mockwebserver.RecordedRequest;
import com.squareup.okhttp.mockwebserver.SocketPolicy;
import com.squareup.okhttp.mockwebserver.rule.MockWebServerRule;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.net.CookieManager;
import java.net.HttpCookie;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLProtocolException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import okio.Buffer;
import okio.BufferedSink;
import okio.BufferedSource;
import okio.GzipSink;
import okio.Okio;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.rules.TestRule;
import org.junit.rules.Timeout;

import static com.squareup.okhttp.internal.Internal.logger;
import com.squareup.okhttp.internal.Version;
import static java.net.CookiePolicy.ACCEPT_ORIGINAL_SERVER;
import java.net.ProtocolException;
import java.net.UnknownServiceException;
import java.util.concurrent.ExecutionException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

public final class CallTest {
    private static final SSLContext sslContext = SslContextBuilder.localhost();

    @Rule
    public final TemporaryFolder tempDir = new TemporaryFolder();
    @Rule
    public final TestRule timeout = new Timeout(30_000);

    @Rule
    public final MockWebServerRule server = new MockWebServerRule();
    @Rule
    public final MockWebServerRule server2 = new MockWebServerRule();
    private FiberOkHttpClient client = new FiberOkHttpClient();
    private RecordingCallback callback = new RecordingCallback();
    private TestLogHandler logHandler = new TestLogHandler();
    private Cache cache;

    @Before
    public void setUp() throws Exception {
        client = new FiberOkHttpClient();
        callback = new RecordingCallback();
        logHandler = new TestLogHandler();

        cache = new Cache(tempDir.getRoot(), Integer.MAX_VALUE);
        logger.addHandler(logHandler);
    }

    @After
    public void tearDown() throws Exception {
        cache.delete();
        logger.removeHandler(logHandler);
    }

    @Test
    public void get() throws Exception {
        server.enqueue(new MockResponse().setBody("abc").addHeader("Content-Type: text/plain"));

        Request request = new Request.Builder().url(server.getUrl("/")).header("User-Agent", "SyncApiTest").build();

        FiberOkHttpTestUtil.executeInFiberRecorded(client, request).assertCode(200).assertSuccessful()
                .assertHeader("Content-Type", "text/plain").assertBody("abc");

        RecordedRequest recordedRequest = server.takeRequest();
        assertEquals("GET", recordedRequest.getMethod());
        assertEquals("SyncApiTest", recordedRequest.getHeader("User-Agent"));
        assertEquals(0, recordedRequest.getBody().size());
        assertNull(recordedRequest.getHeader("Content-Length"));
    }

    @Test
    public void buildRequestUsingHttpUrl() throws Exception {
        server.enqueue(new MockResponse());

        HttpUrl httpUrl = HttpUrl.get(server.getUrl("/"));
        Request request = new Request.Builder().url(httpUrl).build();
        assertEquals(httpUrl, request.httpUrl());

        FiberOkHttpTestUtil.executeInFiberRecorded(client, request).assertSuccessful();
    }

    @Test
    public void invalidPort() throws Exception {
        Request.Builder requestBuilder = new Request.Builder();
        try {
            requestBuilder.url("http://localhost:65536/");
            fail();
        } catch (IllegalArgumentException expected) {
            assertEquals(expected.getMessage(), "unexpected url: http://localhost:65536/");
        }
    }

    @Test
    public void getReturns500() throws Exception {
        server.enqueue(new MockResponse().setResponseCode(500));

        Request request = new Request.Builder().url(server.getUrl("/")).build();

        FiberOkHttpTestUtil.executeInFiberRecorded(client, request).assertCode(500).assertNotSuccessful();
    }

    @Test
    public void get_SPDY_3() throws Exception {
        enableProtocol(Protocol.SPDY_3);
        get();
    }

    @Test
    public void get_HTTP_2() throws Exception {
        enableProtocol(Protocol.HTTP_2);
        get();
    }

    @Test
    public void getWithRequestBody() throws Exception {
        server.enqueue(new MockResponse());

        try {
            new Request.Builder().method("GET", RequestBody.create(MediaType.parse("text/plain"), "abc"));
            fail();
        } catch (IllegalArgumentException expected) {
        }
    }

    @Test
    public void head() throws Exception {
        server.enqueue(new MockResponse().addHeader("Content-Type: text/plain"));

        Request request = new Request.Builder().url(server.getUrl("/")).head().header("User-Agent", "SyncApiTest")
                .build();

        FiberOkHttpTestUtil.executeInFiberRecorded(client, request).assertCode(200).assertHeader("Content-Type",
                "text/plain");

        RecordedRequest recordedRequest = server.takeRequest();
        assertEquals("HEAD", recordedRequest.getMethod());
        assertEquals("SyncApiTest", recordedRequest.getHeader("User-Agent"));
        assertEquals(0, recordedRequest.getBody().size());
        assertNull(recordedRequest.getHeader("Content-Length"));
    }

    @Test
    public void head_SPDY_3() throws Exception {
        enableProtocol(Protocol.SPDY_3);
        head();
    }

    @Test
    public void head_HTTP_2() throws Exception {
        enableProtocol(Protocol.HTTP_2);
        head();
    }

    @Test
    public void post() throws Exception {
        server.enqueue(new MockResponse().setBody("abc"));

        Request request = new Request.Builder().url(server.getUrl("/"))
                .post(RequestBody.create(MediaType.parse("text/plain"), "def")).build();

        FiberOkHttpTestUtil.executeInFiberRecorded(client, request).assertCode(200).assertBody("abc");

        RecordedRequest recordedRequest = server.takeRequest();
        assertEquals("POST", recordedRequest.getMethod());
        assertEquals("def", recordedRequest.getBody().readUtf8());
        assertEquals("3", recordedRequest.getHeader("Content-Length"));
        assertEquals("text/plain; charset=utf-8", recordedRequest.getHeader("Content-Type"));
    }

    @Test
    public void post_SPDY_3() throws Exception {
        enableProtocol(Protocol.SPDY_3);
        post();
    }

    @Test
    public void post_HTTP_2() throws Exception {
        enableProtocol(Protocol.HTTP_2);
        post();
    }

    @Test
    public void postZeroLength() throws Exception {
        server.enqueue(new MockResponse().setBody("abc"));

        Request request = new Request.Builder().url(server.getUrl("/"))
                .method("POST", RequestBody.create(null, new byte[0])).build();

        FiberOkHttpTestUtil.executeInFiberRecorded(client, request).assertCode(200).assertBody("abc");

        RecordedRequest recordedRequest = server.takeRequest();
        assertEquals("POST", recordedRequest.getMethod());
        assertEquals(0, recordedRequest.getBody().size());
        assertEquals("0", recordedRequest.getHeader("Content-Length"));
        assertEquals(null, recordedRequest.getHeader("Content-Type"));
    }

    @Test
    public void postZeroLength_SPDY_3() throws Exception {
        enableProtocol(Protocol.SPDY_3);
        postZeroLength();
    }

    @Test
    public void postZerolength_HTTP_2() throws Exception {
        enableProtocol(Protocol.HTTP_2);
        postZeroLength();
    }

    @Test
    public void postBodyRetransmittedAfterAuthorizationFail() throws Exception {
        postBodyRetransmittedAfterAuthorizationFail("abc");
    }

    @Test
    public void postBodyRetransmittedAfterAuthorizationFail_SPDY_3() throws Exception {
        enableProtocol(Protocol.SPDY_3);
        postBodyRetransmittedAfterAuthorizationFail("abc");
    }

    @Test
    public void postBodyRetransmittedAfterAuthorizationFail_HTTP_2() throws Exception {
        enableProtocol(Protocol.HTTP_2);
        postBodyRetransmittedAfterAuthorizationFail("abc");
    }

    /** Don't explode when resending an empty post. https://github.com/square/okhttp/issues/1131 */
    @Test
    public void postEmptyBodyRetransmittedAfterAuthorizationFail() throws Exception {
        postBodyRetransmittedAfterAuthorizationFail("");
    }

    @Test
    public void postEmptyBodyRetransmittedAfterAuthorizationFail_SPDY_3() throws Exception {
        enableProtocol(Protocol.SPDY_3);
        postBodyRetransmittedAfterAuthorizationFail("");
    }

    @Test
    public void postEmptyBodyRetransmittedAfterAuthorizationFail_HTTP_2() throws Exception {
        enableProtocol(Protocol.HTTP_2);
        postBodyRetransmittedAfterAuthorizationFail("");
    }

    private void postBodyRetransmittedAfterAuthorizationFail(String body) throws Exception {
        server.enqueue(new MockResponse().setResponseCode(401));
        server.enqueue(new MockResponse());

        Request request = new Request.Builder().url(server.getUrl("/"))
                .method("POST", RequestBody.create(null, body)).build();

        String credential = Credentials.basic("jesse", "secret");
        client.setAuthenticator(new RecordingOkAuthenticator(credential));

        Response response = FiberOkHttpTestUtil.executeInFiberRecorded(client, request).response;
        assertEquals(200, response.code());

        RecordedRequest recordedRequest1 = server.takeRequest();
        assertEquals("POST", recordedRequest1.getMethod());
        assertEquals(body, recordedRequest1.getBody().readUtf8());
        assertNull(recordedRequest1.getHeader("Authorization"));

        RecordedRequest recordedRequest2 = server.takeRequest();
        assertEquals("POST", recordedRequest2.getMethod());
        assertEquals(body, recordedRequest2.getBody().readUtf8());
        assertEquals(credential, recordedRequest2.getHeader("Authorization"));
    }

    @Test
    public void attemptAuthorization20Times() throws Exception {
        for (int i = 0; i < 20; i++) {
            server.enqueue(new MockResponse().setResponseCode(401));
        }
        server.enqueue(new MockResponse().setBody("Success!"));

        String credential = Credentials.basic("jesse", "secret");
        client.setAuthenticator(new RecordingOkAuthenticator(credential));

        Request request = new Request.Builder().url(server.getUrl("/")).build();
        FiberOkHttpTestUtil.executeInFiberRecorded(client, request).assertCode(200).assertBody("Success!");
    }

    @Test
    public void doesNotAttemptAuthorization21Times() throws Exception {
        for (int i = 0; i < 21; i++) {
            server.enqueue(new MockResponse().setResponseCode(401));
        }

        String credential = Credentials.basic("jesse", "secret");
        client.setAuthenticator(new RecordingOkAuthenticator(credential));

        try {
            FiberOkHttpUtil.executeInFiber(client, new Request.Builder().url(server.getUrl("/0")).build());
            fail();
        } catch (IOException expected) {
            assertEquals("Too many follow-up requests: 21", expected.getMessage());
        }
    }

    @Test
    public void delete() throws Exception {
        server.enqueue(new MockResponse().setBody("abc"));

        Request request = new Request.Builder().url(server.getUrl("/")).delete().build();

        FiberOkHttpTestUtil.executeInFiberRecorded(client, request).assertCode(200).assertBody("abc");

        RecordedRequest recordedRequest = server.takeRequest();
        assertEquals("DELETE", recordedRequest.getMethod());
        assertEquals(0, recordedRequest.getBody().size());
        assertEquals("0", recordedRequest.getHeader("Content-Length"));
        assertEquals(null, recordedRequest.getHeader("Content-Type"));
    }

    @Test
    public void delete_SPDY_3() throws Exception {
        enableProtocol(Protocol.SPDY_3);
        delete();
    }

    @Test
    public void delete_HTTP_2() throws Exception {
        enableProtocol(Protocol.HTTP_2);
        delete();
    }

    @Test
    public void deleteWithRequestBody() throws Exception {
        server.enqueue(new MockResponse().setBody("abc"));

        Request request = new Request.Builder().url(server.getUrl("/"))
                .method("DELETE", RequestBody.create(MediaType.parse("text/plain"), "def")).build();

        FiberOkHttpTestUtil.executeInFiberRecorded(client, request).assertCode(200).assertBody("abc");

        RecordedRequest recordedRequest = server.takeRequest();
        assertEquals("DELETE", recordedRequest.getMethod());
        assertEquals("def", recordedRequest.getBody().readUtf8());
    }

    @Test
    public void put() throws Exception {
        server.enqueue(new MockResponse().setBody("abc"));

        Request request = new Request.Builder().url(server.getUrl("/"))
                .put(RequestBody.create(MediaType.parse("text/plain"), "def")).build();

        FiberOkHttpTestUtil.executeInFiberRecorded(client, request).assertCode(200).assertBody("abc");

        RecordedRequest recordedRequest = server.takeRequest();
        assertEquals("PUT", recordedRequest.getMethod());
        assertEquals("def", recordedRequest.getBody().readUtf8());
        assertEquals("3", recordedRequest.getHeader("Content-Length"));
        assertEquals("text/plain; charset=utf-8", recordedRequest.getHeader("Content-Type"));
    }

    @Test
    public void put_SPDY_3() throws Exception {
        enableProtocol(Protocol.SPDY_3);
        put();
    }

    @Test
    public void put_HTTP_2() throws Exception {
        enableProtocol(Protocol.HTTP_2);
        put();
    }

    @Test
    public void patch() throws Exception {
        server.enqueue(new MockResponse().setBody("abc"));

        Request request = new Request.Builder().url(server.getUrl("/"))
                .patch(RequestBody.create(MediaType.parse("text/plain"), "def")).build();

        FiberOkHttpTestUtil.executeInFiberRecorded(client, request).assertCode(200).assertBody("abc");

        RecordedRequest recordedRequest = server.takeRequest();
        assertEquals("PATCH", recordedRequest.getMethod());
        assertEquals("def", recordedRequest.getBody().readUtf8());
        assertEquals("3", recordedRequest.getHeader("Content-Length"));
        assertEquals("text/plain; charset=utf-8", recordedRequest.getHeader("Content-Type"));
    }

    @Test
    public void patch_SPDY_3() throws Exception {
        enableProtocol(Protocol.SPDY_3);
        patch();
    }

    @Test
    public void patch_HTTP_2() throws Exception {
        enableProtocol(Protocol.HTTP_2);
        patch();
    }

    @Test
    public void unspecifiedRequestBodyContentTypeDoesNotGetDefault() throws Exception {
        server.enqueue(new MockResponse());

        Request request = new Request.Builder().url(server.getUrl("/"))
                .method("POST", RequestBody.create(null, "abc")).build();

        FiberOkHttpTestUtil.executeInFiberRecorded(client, request).assertCode(200);

        RecordedRequest recordedRequest = server.takeRequest();
        assertEquals(null, recordedRequest.getHeader("Content-Type"));
        assertEquals("3", recordedRequest.getHeader("Content-Length"));
        assertEquals("abc", recordedRequest.getBody().readUtf8());
    }

    @Test
    public void illegalToExecuteTwice() throws Exception {
        server.enqueue(new MockResponse().setBody("abc").addHeader("Content-Type: text/plain"));

        Request request = new Request.Builder().url(server.getUrl("/")).header("User-Agent", "SyncApiTest").build();

        Call call = client.newCall(request);
        FiberOkHttpUtil.executeInFiber(call);

        try {
            FiberOkHttpUtil.executeInFiber(call);
            fail();
        } catch (IllegalStateException e) {
            assertEquals("Already Executed", e.getMessage());
        }

        try {
            call.enqueue(callback);
            fail();
        } catch (IllegalStateException e) {
            assertEquals("Already Executed", e.getMessage());
        }

        assertEquals("SyncApiTest", server.takeRequest().getHeader("User-Agent"));
    }

    @Test
    public void illegalToExecuteTwice_Async() throws Exception {
        server.enqueue(new MockResponse().setBody("abc").addHeader("Content-Type: text/plain"));

        Request request = new Request.Builder().url(server.getUrl("/")).header("User-Agent", "SyncApiTest").build();

        Call call = client.newCall(request);
        call.enqueue(callback);

        try {
            FiberOkHttpUtil.executeInFiber(call);
            fail();
        } catch (IllegalStateException e) {
            assertEquals("Already Executed", e.getMessage());
        }

        try {
            FiberOkHttpUtil.executeInFiber(call);
            fail();
        } catch (IllegalStateException e) {
            assertEquals("Already Executed", e.getMessage());
        }

        assertEquals("SyncApiTest", server.takeRequest().getHeader("User-Agent"));
    }

    @Test
    public void get_Async() throws Exception {
        server.enqueue(new MockResponse().setBody("abc").addHeader("Content-Type: text/plain"));

        Request request = new Request.Builder().url(server.getUrl("/")).header("User-Agent", "AsyncApiTest")
                .build();
        client.newCall(request).enqueue(callback);

        callback.await(request.url()).assertCode(200).assertHeader("Content-Type", "text/plain").assertBody("abc");

        assertEquals("AsyncApiTest", server.takeRequest().getHeader("User-Agent"));
    }

    @Test
    public void exceptionThrownByOnResponseIsRedactedAndLogged() throws Exception {
        server.enqueue(new MockResponse());

        Request request = new Request.Builder().url(server.getUrl("/secret")).build();

        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Request request, IOException e) {
                fail();
            }

            @Override
            public void onResponse(Response response) throws IOException {
                throw new IOException("a");
            }
        });

        assertEquals("INFO: Callback failure for call to " + server.getUrl("/") + "...", logHandler.take());
    }

    @Test
    public void connectionPooling() throws Exception {
        server.enqueue(new MockResponse().setBody("abc"));
        server.enqueue(new MockResponse().setBody("def"));
        server.enqueue(new MockResponse().setBody("ghi"));

        FiberOkHttpTestUtil.executeInFiberRecorded(client, new Request.Builder().url(server.getUrl("/a")).build())
                .assertBody("abc");

        FiberOkHttpTestUtil.executeInFiberRecorded(client, new Request.Builder().url(server.getUrl("/b")).build())
                .assertBody("def");

        FiberOkHttpTestUtil.executeInFiberRecorded(client, new Request.Builder().url(server.getUrl("/c")).build())
                .assertBody("ghi");

        assertEquals(0, server.takeRequest().getSequenceNumber());
        assertEquals(1, server.takeRequest().getSequenceNumber());
        assertEquals(2, server.takeRequest().getSequenceNumber());
    }

    @Test
    public void connectionPooling_Async() throws Exception {
        server.enqueue(new MockResponse().setBody("abc"));
        server.enqueue(new MockResponse().setBody("def"));
        server.enqueue(new MockResponse().setBody("ghi"));

        client.newCall(new Request.Builder().url(server.getUrl("/a")).build()).enqueue(callback);
        callback.await(server.getUrl("/a")).assertBody("abc");

        client.newCall(new Request.Builder().url(server.getUrl("/b")).build()).enqueue(callback);
        callback.await(server.getUrl("/b")).assertBody("def");

        client.newCall(new Request.Builder().url(server.getUrl("/c")).build()).enqueue(callback);
        callback.await(server.getUrl("/c")).assertBody("ghi");

        assertEquals(0, server.takeRequest().getSequenceNumber());
        assertEquals(1, server.takeRequest().getSequenceNumber());
        assertEquals(2, server.takeRequest().getSequenceNumber());
    }

    @Test
    public void connectionReuseWhenResponseBodyConsumed_Async() throws Exception {
        server.enqueue(new MockResponse().setBody("abc"));
        server.enqueue(new MockResponse().setBody("def"));

        Request request = new Request.Builder().url(server.getUrl("/a")).build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Request request, IOException e) {
                throw new AssertionError();
            }

            @Override
            public void onResponse(Response response) throws IOException {
                InputStream bytes = response.body().byteStream();
                assertEquals('a', bytes.read());
                assertEquals('b', bytes.read());
                assertEquals('c', bytes.read());

                // This request will share a connection with 'A' cause it's all done.
                client.newCall(new Request.Builder().url(server.getUrl("/b")).build()).enqueue(callback);
            }
        });

        callback.await(server.getUrl("/b")).assertCode(200).assertBody("def");
        assertEquals(0, server.takeRequest().getSequenceNumber()); // New connection.
        assertEquals(1, server.takeRequest().getSequenceNumber()); // Connection reuse!
    }

    @Test
    public void timeoutsUpdatedOnReusedConnections() throws Exception {
        server.enqueue(new MockResponse().setBody("abc"));
        server.enqueue(new MockResponse().setBody("def").throttleBody(1, 750, TimeUnit.MILLISECONDS));

        // First request: time out after 1000ms.
        client.setReadTimeout(1000, TimeUnit.MILLISECONDS);
        FiberOkHttpTestUtil.executeInFiberRecorded(client, new Request.Builder().url(server.getUrl("/a")).build())
                .assertBody("abc");

        // Second request: time out after 250ms.
        client.setReadTimeout(250, TimeUnit.MILLISECONDS);
        Request request = new Request.Builder().url(server.getUrl("/b")).build();
        Response response = FiberOkHttpUtil.executeInFiber(client, request);
        BufferedSource bodySource = response.body().source();
        assertEquals('d', bodySource.readByte());

        // The second byte of this request will be delayed by 750ms so we should time out after 250ms.
        long startNanos = System.nanoTime();
        try {
            bodySource.readByte();
            fail();
        } catch (IOException expected) {
            // Timed out as expected.
            long elapsedNanos = System.nanoTime() - startNanos;
            long elapsedMillis = TimeUnit.NANOSECONDS.toMillis(elapsedNanos);
            assertTrue(String.format("Timed out: %sms", elapsedMillis), elapsedMillis < 500);
        }
    }

    // https://github.com/square/okhttp/issues/442
    @Test
    public void timeoutsNotRetried() throws Exception {
        server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.NO_RESPONSE));
        server.enqueue(new MockResponse().setBody("unreachable!"));

        Internal.instance.setNetwork(client, new DoubleInetAddressNetwork());
        client.setReadTimeout(100, TimeUnit.MILLISECONDS);

        Request request = new Request.Builder().url(server.getUrl("/")).build();
        try {
            // If this succeeds, too many requests were made.
            FiberOkHttpUtil.executeInFiber(client, request);
            fail();
        } catch (InterruptedIOException expected) {
        }
    }

    @Test
    public void reusedSinksGetIndependentTimeoutInstances() throws Exception {
        server.enqueue(new MockResponse());
        server.enqueue(new MockResponse());

        // Call 1: set a deadline on the request body.
        RequestBody requestBody1 = new RequestBody() {
            @Override
            public MediaType contentType() {
                return MediaType.parse("text/plain");
            }

            @Override
            public void writeTo(BufferedSink sink) throws IOException {
                sink.writeUtf8("abc");
                sink.timeout().deadline(5, TimeUnit.SECONDS);
            }
        };
        Request request1 = new Request.Builder().url(server.getUrl("/")).method("POST", requestBody1).build();
        Response response1 = FiberOkHttpUtil.executeInFiber(client, request1);
        assertEquals(200, response1.code());

        // Call 2: check for the absence of a deadline on the request body.
        RequestBody requestBody2 = new RequestBody() {
            @Override
            public MediaType contentType() {
                return MediaType.parse("text/plain");
            }

            @Override
            public void writeTo(BufferedSink sink) throws IOException {
                assertFalse(sink.timeout().hasDeadline());
                sink.writeUtf8("def");
            }
        };
        Request request2 = new Request.Builder().url(server.getUrl("/")).method("POST", requestBody2).build();
        Response response2 = FiberOkHttpUtil.executeInFiber(client, request2);
        assertEquals(200, response2.code());

        // Use sequence numbers to confirm the connection was pooled.
        assertEquals(0, server.takeRequest().getSequenceNumber());
        assertEquals(1, server.takeRequest().getSequenceNumber());
    }

    @Test
    public void reusedSourcesGetIndependentTimeoutInstances() throws Exception {
        server.enqueue(new MockResponse().setBody("abc"));
        server.enqueue(new MockResponse().setBody("def"));

        // Call 1: set a deadline on the response body.
        Request request1 = new Request.Builder().url(server.getUrl("/")).build();
        Response response1 = client.newCall(request1).execute();
        BufferedSource body1 = response1.body().source();
        assertEquals("abc", body1.readUtf8());
        body1.timeout().deadline(5, TimeUnit.SECONDS);

        // Call 2: check for the absence of a deadline on the request body.
        Request request2 = new Request.Builder().url(server.getUrl("/")).build();
        Response response2 = client.newCall(request2).execute();
        BufferedSource body2 = response2.body().source();
        assertEquals("def", body2.readUtf8());
        assertFalse(body2.timeout().hasDeadline());

        // Use sequence numbers to confirm the connection was pooled.
        assertEquals(0, server.takeRequest().getSequenceNumber());
        assertEquals(1, server.takeRequest().getSequenceNumber());
    }

    @Test
    public void tls() throws Exception {
        server.get().useHttps(sslContext.getSocketFactory(), false);
        server.enqueue(new MockResponse().setBody("abc").addHeader("Content-Type: text/plain"));

        client.setSslSocketFactory(sslContext.getSocketFactory());
        client.setHostnameVerifier(new RecordingHostnameVerifier());

        FiberOkHttpTestUtil.executeInFiberRecorded(client, new Request.Builder().url(server.getUrl("/")).build())
                .assertHandshake();
    }

    @Test
    public void tls_Async() throws Exception {
        server.get().useHttps(sslContext.getSocketFactory(), false);
        server.enqueue(new MockResponse().setBody("abc").addHeader("Content-Type: text/plain"));

        client.setSslSocketFactory(sslContext.getSocketFactory());
        client.setHostnameVerifier(new RecordingHostnameVerifier());

        Request request = new Request.Builder().url(server.getUrl("/")).build();
        client.newCall(request).enqueue(callback);

        callback.await(request.url()).assertHandshake();
    }

    @Test
    public void recoverWhenRetryOnConnectionFailureIsTrue() throws Exception {
        server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AT_START));
        server.enqueue(new MockResponse().setBody("retry success"));

        Internal.instance.setNetwork(client, new DoubleInetAddressNetwork());
        assertTrue(client.getRetryOnConnectionFailure());

        Request request = new Request.Builder().url(server.getUrl("/")).build();
        Response response = FiberOkHttpUtil.executeInFiber(client, request);
        assertEquals("retry success", response.body().string());
    }

    @Test
    public void noRecoverWhenRetryOnConnectionFailureIsFalse() throws Exception {
        server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AT_START));
        server.enqueue(new MockResponse().setBody("unreachable!"));

        Internal.instance.setNetwork(client, new DoubleInetAddressNetwork());
        client.setRetryOnConnectionFailure(false);

        Request request = new Request.Builder().url(server.getUrl("/")).build();
        try {
            // If this succeeds, too many requests were made.
            FiberOkHttpUtil.executeInFiber(client, request);
            fail();
        } catch (IOException expected) {
        }
    }

    @Test
    public void recoverFromTlsHandshakeFailure() throws Exception {
        server.get().useHttps(sslContext.getSocketFactory(), false);
        server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.FAIL_HANDSHAKE));
        server.enqueue(new MockResponse().setBody("abc"));

        suppressTlsFallbackScsv(client);
        client.setHostnameVerifier(new RecordingHostnameVerifier());
        Internal.instance.setNetwork(client, new SingleInetAddressNetwork());

        FiberOkHttpTestUtil.executeInFiberRecorded(client, new Request.Builder().url(server.getUrl("/")).build())
                .assertBody("abc");
    }

    @Test
    public void recoverFromTlsHandshakeFailure_tlsFallbackScsvEnabled() throws Exception {
        final String tlsFallbackScsv = "TLS_FALLBACK_SCSV";
        List<String> supportedCiphers = Arrays.asList(sslContext.getSocketFactory().getSupportedCipherSuites());
        if (!supportedCiphers.contains(tlsFallbackScsv)) {
            // This only works if the client socket supports TLS_FALLBACK_SCSV.
            return;
        }

        server.get().useHttps(sslContext.getSocketFactory(), false);
        server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.FAIL_HANDSHAKE));

        RecordingSSLSocketFactory clientSocketFactory = new RecordingSSLSocketFactory(
                sslContext.getSocketFactory());
        client.setSslSocketFactory(clientSocketFactory);
        client.setHostnameVerifier(new RecordingHostnameVerifier());
        Internal.instance.setNetwork(client, new SingleInetAddressNetwork());

        Request request = new Request.Builder().url(server.getUrl("/")).build();
        try {
            FiberOkHttpUtil.executeInFiber(client, request);
            fail();
        } catch (SSLHandshakeException expected) {
        }

        List<SSLSocket> clientSockets = clientSocketFactory.getSocketsCreated();
        SSLSocket firstSocket = clientSockets.get(0);
        assertFalse(Arrays.asList(firstSocket.getEnabledCipherSuites()).contains(tlsFallbackScsv));
        SSLSocket secondSocket = clientSockets.get(1);
        assertTrue(Arrays.asList(secondSocket.getEnabledCipherSuites()).contains(tlsFallbackScsv));
    }

    @Test
    public void recoverFromTlsHandshakeFailure_Async() throws Exception {
        server.get().useHttps(sslContext.getSocketFactory(), false);
        server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.FAIL_HANDSHAKE));
        server.enqueue(new MockResponse().setBody("abc"));

        suppressTlsFallbackScsv(client);
        client.setHostnameVerifier(new RecordingHostnameVerifier());

        Request request = new Request.Builder().url(server.getUrl("/")).build();
        client.newCall(request).enqueue(callback);

        callback.await(request.url()).assertBody("abc");
    }

    @Test
    public void noRecoveryFromTlsHandshakeFailureWhenTlsFallbackIsDisabled() throws Exception {
        client.setConnectionSpecs(Arrays.asList(ConnectionSpec.MODERN_TLS, ConnectionSpec.CLEARTEXT));

        server.get().useHttps(sslContext.getSocketFactory(), false);
        server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.FAIL_HANDSHAKE));

        suppressTlsFallbackScsv(client);
        client.setHostnameVerifier(new RecordingHostnameVerifier());
        Internal.instance.setNetwork(client, new SingleInetAddressNetwork());

        Request request = new Request.Builder().url(server.getUrl("/")).build();
        try {
            FiberOkHttpUtil.executeInFiber(client, request);
            fail();
        } catch (SSLProtocolException expected) {
            // RI response to the FAIL_HANDSHAKE
        } catch (SSLHandshakeException expected) {
            // Android's response to the FAIL_HANDSHAKE
        }
    }

    @Test
    public void cleartextCallsFailWhenCleartextIsDisabled() throws Exception {
        // Configure the client with only TLS configurations. No cleartext!
        client.setConnectionSpecs(Arrays.asList(ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS));

        server.enqueue(new MockResponse());

        Request request = new Request.Builder().url(server.getUrl("/")).build();
        try {
            FiberOkHttpUtil.executeInFiber(client, request);
            fail();
        } catch (UnknownServiceException expected) {
            assertTrue(expected.getMessage().contains("CLEARTEXT communication not supported"));
        }
    }

    @Test
    public void setFollowSslRedirectsFalse() throws Exception {
        server.get().useHttps(sslContext.getSocketFactory(), false);
        server.enqueue(new MockResponse().setResponseCode(301).addHeader("Location: http://square.com"));

        client.setFollowSslRedirects(false);
        client.setSslSocketFactory(sslContext.getSocketFactory());
        client.setHostnameVerifier(new RecordingHostnameVerifier());

        Request request = new Request.Builder().url(server.getUrl("/")).build();
        Response response = FiberOkHttpUtil.executeInFiber(client, request);
        assertEquals(301, response.code());
    }

    @Test
    public void matchingPinnedCertificate() throws Exception {
        server.get().useHttps(sslContext.getSocketFactory(), false);
        server.enqueue(new MockResponse());
        server.enqueue(new MockResponse());

        client.setSslSocketFactory(sslContext.getSocketFactory());
        client.setHostnameVerifier(new RecordingHostnameVerifier());

        // Make a first request without certificate pinning. Use it to collect certificates to pin.
        Request request1 = new Request.Builder().url(server.getUrl("/")).build();
        Response response1 = FiberOkHttpUtil.executeInFiber(client, request1);
        CertificatePinner.Builder certificatePinnerBuilder = new CertificatePinner.Builder();
        for (Certificate certificate : response1.handshake().peerCertificates()) {
            certificatePinnerBuilder.add(server.get().getHostName(), CertificatePinner.pin(certificate));
        }

        // Make another request with certificate pinning. It should complete normally.
        client.setCertificatePinner(certificatePinnerBuilder.build());
        Request request2 = new Request.Builder().url(server.getUrl("/")).build();
        Response response2 = FiberOkHttpUtil.executeInFiber(client, request2);
        assertNotSame(response2.handshake(), response1.handshake());
    }

    @Test
    public void unmatchingPinnedCertificate() throws Exception {
        server.get().useHttps(sslContext.getSocketFactory(), false);
        server.enqueue(new MockResponse());

        client.setSslSocketFactory(sslContext.getSocketFactory());
        client.setHostnameVerifier(new RecordingHostnameVerifier());

        // Pin publicobject.com's cert.
        client.setCertificatePinner(new CertificatePinner.Builder()
                .add(server.get().getHostName(), "sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=").build());

        // When we pin the wrong certificate, connectivity fails.
        Request request = new Request.Builder().url(server.getUrl("/")).build();
        try {
            FiberOkHttpUtil.executeInFiber(client, request);
            fail();
        } catch (SSLPeerUnverifiedException expected) {
            assertTrue(expected.getMessage().startsWith("Certificate pinning failure!"));
        }
    }

    @Test
    public void post_Async() throws Exception {
        server.enqueue(new MockResponse().setBody("abc"));

        Request request = new Request.Builder().url(server.getUrl("/"))
                .post(RequestBody.create(MediaType.parse("text/plain"), "def")).build();
        client.newCall(request).enqueue(callback);

        callback.await(request.url()).assertCode(200).assertBody("abc");

        RecordedRequest recordedRequest = server.takeRequest();
        assertEquals("def", recordedRequest.getBody().readUtf8());
        assertEquals("3", recordedRequest.getHeader("Content-Length"));
        assertEquals("text/plain; charset=utf-8", recordedRequest.getHeader("Content-Type"));
    }

    @Test
    public void postBodyRetransmittedOnFailureRecovery() throws Exception {
        server.enqueue(new MockResponse().setBody("abc"));
        server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AFTER_REQUEST));
        server.enqueue(new MockResponse().setBody("def"));

        // Seed the connection pool so we have something that can fail.
        Request request1 = new Request.Builder().url(server.getUrl("/")).build();
        Response response1 = FiberOkHttpUtil.executeInFiber(client, request1);
        assertEquals("abc", response1.body().string());

        Request request2 = new Request.Builder().url(server.getUrl("/"))
                .post(RequestBody.create(MediaType.parse("text/plain"), "body!")).build();
        Response response2 = FiberOkHttpUtil.executeInFiber(client, request2);
        assertEquals("def", response2.body().string());

        RecordedRequest get = server.takeRequest();
        assertEquals(0, get.getSequenceNumber());

        RecordedRequest post1 = server.takeRequest();
        assertEquals("body!", post1.getBody().readUtf8());
        assertEquals(1, post1.getSequenceNumber());

        RecordedRequest post2 = server.takeRequest();
        assertEquals("body!", post2.getBody().readUtf8());
        assertEquals(0, post2.getSequenceNumber());
    }

    @Test
    public void cacheHit() throws Exception {
        server.enqueue(new MockResponse().addHeader("ETag: v1").addHeader("Cache-Control: max-age=60")
                .addHeader("Vary: Accept-Charset").setBody("A"));

        client.setCache(cache);

        // Store a response in the cache.
        URL url = server.getUrl("/");
        Request cacheStoreRequest = new Request.Builder().url(url).addHeader("Accept-Language", "fr-CA")
                .addHeader("Accept-Charset", "UTF-8").build();
        FiberOkHttpTestUtil.executeInFiberRecorded(client, cacheStoreRequest).assertCode(200).assertBody("A");
        assertNull(server.takeRequest().getHeader("If-None-Match"));

        // Hit that stored response.
        Request cacheHitRequest = new Request.Builder().url(url).addHeader("Accept-Language", "en-US") // Different, but Vary says it doesn't matter.
                .addHeader("Accept-Charset", "UTF-8").build();
        RecordedResponse cacheHit = FiberOkHttpTestUtil.executeInFiberRecorded(client, cacheHitRequest);

        // Check the merged response. The request is the application's original request.
        cacheHit.assertCode(200).assertBody("A").assertHeader("ETag", "v1")
                .assertRequestUrl(cacheStoreRequest.url()).assertRequestHeader("Accept-Language", "en-US")
                .assertRequestHeader("Accept-Charset", "UTF-8");

        // Check the cached response. Its request contains only the saved Vary headers.
        cacheHit.cacheResponse().assertCode(200).assertHeader("ETag", "v1").assertRequestMethod("GET")
                .assertRequestUrl(cacheStoreRequest.url()).assertRequestHeader("Accept-Language")
                .assertRequestHeader("Accept-Charset", "UTF-8");

        cacheHit.assertNoNetworkResponse();
    }

    @Test
    public void conditionalCacheHit() throws Exception {
        server.enqueue(new MockResponse().addHeader("ETag: v1").addHeader("Vary: Accept-Charset")
                .addHeader("Donut: a").setBody("A"));
        server.enqueue(new MockResponse().clearHeaders().addHeader("Donut: b")
                .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));

        client.setCache(cache);

        // Store a response in the cache.
        URL url = server.getUrl("/");
        Request cacheStoreRequest = new Request.Builder().url(url).addHeader("Accept-Language", "fr-CA")
                .addHeader("Accept-Charset", "UTF-8").build();
        FiberOkHttpTestUtil.executeInFiberRecorded(client, cacheStoreRequest).assertCode(200)
                .assertHeader("Donut", "a").assertBody("A");
        assertNull(server.takeRequest().getHeader("If-None-Match"));

        // Hit that stored response.
        Request cacheHitRequest = new Request.Builder().url(url).addHeader("Accept-Language", "en-US") // Different, but Vary says it doesn't matter.
                .addHeader("Accept-Charset", "UTF-8").build();
        RecordedResponse cacheHit = FiberOkHttpTestUtil.executeInFiberRecorded(client, cacheHitRequest);
        assertEquals("v1", server.takeRequest().getHeader("If-None-Match"));

        // Check the merged response. The request is the application's original request.
        cacheHit.assertCode(200).assertBody("A").assertHeader("Donut", "b")
                .assertRequestUrl(cacheStoreRequest.url()).assertRequestHeader("Accept-Language", "en-US")
                .assertRequestHeader("Accept-Charset", "UTF-8").assertRequestHeader("If-None-Match"); // No If-None-Match on the user's request.

        // Check the cached response. Its request contains only the saved Vary headers.
        cacheHit.cacheResponse().assertCode(200).assertHeader("Donut", "a").assertHeader("ETag", "v1")
                .assertRequestUrl(cacheStoreRequest.url()).assertRequestHeader("Accept-Language") // No Vary on Accept-Language.
                .assertRequestHeader("Accept-Charset", "UTF-8") // Because of Vary on Accept-Charset.
                .assertRequestHeader("If-None-Match"); // This wasn't present in the original request.

        // Check the network response. It has the caller's request, plus some caching headers.
        cacheHit.networkResponse().assertCode(304).assertHeader("Donut", "b")
                .assertRequestHeader("Accept-Language", "en-US").assertRequestHeader("Accept-Charset", "UTF-8")
                .assertRequestHeader("If-None-Match", "v1"); // If-None-Match in the validation request.
    }

    @Test
    public void conditionalCacheHit_Async() throws Exception {
        server.enqueue(new MockResponse().setBody("A").addHeader("ETag: v1"));
        server.enqueue(new MockResponse().clearHeaders().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));

        client.setCache(cache);

        Request request1 = new Request.Builder().url(server.getUrl("/")).build();
        client.newCall(request1).enqueue(callback);
        callback.await(request1.url()).assertCode(200).assertBody("A");
        assertNull(server.takeRequest().getHeader("If-None-Match"));

        Request request2 = new Request.Builder().url(server.getUrl("/")).build();
        client.newCall(request2).enqueue(callback);
        callback.await(request2.url()).assertCode(200).assertBody("A");
        assertEquals("v1", server.takeRequest().getHeader("If-None-Match"));
    }

    @Test
    public void conditionalCacheMiss() throws Exception {
        server.enqueue(new MockResponse().addHeader("ETag: v1").addHeader("Vary: Accept-Charset")
                .addHeader("Donut: a").setBody("A"));
        server.enqueue(new MockResponse().addHeader("Donut: b").setBody("B"));

        client.setCache(cache);

        Request cacheStoreRequest = new Request.Builder().url(server.getUrl("/"))
                .addHeader("Accept-Language", "fr-CA").addHeader("Accept-Charset", "UTF-8").build();
        FiberOkHttpTestUtil.executeInFiberRecorded(client, cacheStoreRequest).assertCode(200).assertBody("A");
        assertNull(server.takeRequest().getHeader("If-None-Match"));

        Request cacheMissRequest = new Request.Builder().url(server.getUrl("/"))
                .addHeader("Accept-Language", "en-US") // Different, but Vary says it doesn't matter.
                .addHeader("Accept-Charset", "UTF-8").build();
        RecordedResponse cacheHit = FiberOkHttpTestUtil.executeInFiberRecorded(client, cacheMissRequest);
        assertEquals("v1", server.takeRequest().getHeader("If-None-Match"));

        // Check the user response. It has the application's original request.
        cacheHit.assertCode(200).assertBody("B").assertHeader("Donut", "b")
                .assertRequestUrl(cacheStoreRequest.url());

        // Check the cache response. Even though it's a miss, we used the cache.
        cacheHit.cacheResponse().assertCode(200).assertHeader("Donut", "a").assertHeader("ETag", "v1")
                .assertRequestUrl(cacheStoreRequest.url());

        // Check the network response. It has the network request, plus caching headers.
        cacheHit.networkResponse().assertCode(200).assertHeader("Donut", "b")
                .assertRequestHeader("If-None-Match", "v1") // If-None-Match in the validation request.
                .assertRequestUrl(cacheStoreRequest.url());
    }

    @Test
    public void conditionalCacheMiss_Async() throws Exception {
        server.enqueue(new MockResponse().setBody("A").addHeader("ETag: v1"));
        server.enqueue(new MockResponse().setBody("B"));

        client.setCache(cache);

        Request request1 = new Request.Builder().url(server.getUrl("/")).build();
        client.newCall(request1).enqueue(callback);
        callback.await(request1.url()).assertCode(200).assertBody("A");
        assertNull(server.takeRequest().getHeader("If-None-Match"));

        Request request2 = new Request.Builder().url(server.getUrl("/")).build();
        client.newCall(request2).enqueue(callback);
        callback.await(request2.url()).assertCode(200).assertBody("B");
        assertEquals("v1", server.takeRequest().getHeader("If-None-Match"));
    }

    @Test
    public void onlyIfCachedReturns504WhenNotCached() throws Exception {
        Request request = new Request.Builder().url(server.getUrl("/")).header("Cache-Control", "only-if-cached")
                .build();

        FiberOkHttpTestUtil.executeInFiberRecorded(client, request).assertCode(504).assertBody("")
                .assertNoNetworkResponse().assertNoCacheResponse();
    }

    @Test
    public void redirect() throws Exception {
        server.enqueue(new MockResponse().setResponseCode(301).addHeader("Location: /b")
                .addHeader("Test", "Redirect from /a to /b").setBody("/a has moved!"));
        server.enqueue(new MockResponse().setResponseCode(302).addHeader("Location: /c")
                .addHeader("Test", "Redirect from /b to /c").setBody("/b has moved!"));
        server.enqueue(new MockResponse().setBody("C"));

        FiberOkHttpTestUtil.executeInFiberRecorded(client, new Request.Builder().url(server.getUrl("/a")).build())
                .assertCode(200).assertBody("C").priorResponse().assertCode(302)
                .assertHeader("Test", "Redirect from /b to /c").priorResponse().assertCode(301)
                .assertHeader("Test", "Redirect from /a to /b");

        assertEquals(0, server.takeRequest().getSequenceNumber()); // New connection.
        assertEquals(1, server.takeRequest().getSequenceNumber()); // Connection reused.
        assertEquals(2, server.takeRequest().getSequenceNumber()); // Connection reused again!
    }

    @Test
    public void postRedirectsToGet() throws Exception {
        server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
                .addHeader("Location: /page2").setBody("This page has moved!"));
        server.enqueue(new MockResponse().setBody("Page 2"));

        Response response = FiberOkHttpUtil.executeInFiber(client,
                new Request.Builder().url(server.getUrl("/page1"))
                        .post(RequestBody.create(MediaType.parse("text/plain"), "Request Body")).build());
        assertEquals("Page 2", response.body().string());

        RecordedRequest page1 = server.takeRequest();
        assertEquals("POST /page1 HTTP/1.1", page1.getRequestLine());
        assertEquals("Request Body", page1.getBody().readUtf8());

        RecordedRequest page2 = server.takeRequest();
        assertEquals("GET /page2 HTTP/1.1", page2.getRequestLine());
    }

    @Test
    public void redirectsDoNotIncludeTooManyCookies() throws Exception {
        server2.enqueue(new MockResponse().setBody("Page 2"));
        server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
                .addHeader("Location: " + server2.getUrl("/")));

        CookieManager cookieManager = new CookieManager(null, ACCEPT_ORIGINAL_SERVER);
        HttpCookie cookie = new HttpCookie("c", "cookie");
        cookie.setDomain(server.get().getCookieDomain());
        cookie.setPath("/");
        String portList = Integer.toString(server.getPort());
        cookie.setPortlist(portList);
        cookieManager.getCookieStore().add(server.getUrl("/").toURI(), cookie);
        client.setCookieHandler(cookieManager);

        Response response = FiberOkHttpUtil.executeInFiber(client,
                new Request.Builder().url(server.getUrl("/page1")).build());
        assertEquals("Page 2", response.body().string());

        RecordedRequest request1 = server.takeRequest();
        assertEquals("$Version=\"1\"; c=\"cookie\";$Path=\"/\";$Domain=\"" + server.get().getCookieDomain()
                + "\";$Port=\"" + portList + "\"", request1.getHeader("Cookie"));

        RecordedRequest request2 = server2.takeRequest();
        assertNull(request2.getHeader("Cookie"));
    }

    @Test
    public void redirectsDoNotIncludeTooManyAuthHeaders() throws Exception {
        server2.enqueue(new MockResponse().setBody("Page 2"));
        server.enqueue(new MockResponse().setResponseCode(401));
        server.enqueue(new MockResponse().setResponseCode(302).addHeader("Location: " + server2.getUrl("/b")));

        client.setAuthenticator(new RecordingOkAuthenticator(Credentials.basic("jesse", "secret")));

        Request request = new Request.Builder().url(server.getUrl("/a")).build();
        Response response = FiberOkHttpUtil.executeInFiber(client, request);
        assertEquals("Page 2", response.body().string());

        RecordedRequest redirectRequest = server2.takeRequest();
        assertNull(redirectRequest.getHeader("Authorization"));
        assertEquals("/b", redirectRequest.getPath());
    }

    @Test
    public void redirect_Async() throws Exception {
        server.enqueue(new MockResponse().setResponseCode(301).addHeader("Location: /b")
                .addHeader("Test", "Redirect from /a to /b").setBody("/a has moved!"));
        server.enqueue(new MockResponse().setResponseCode(302).addHeader("Location: /c")
                .addHeader("Test", "Redirect from /b to /c").setBody("/b has moved!"));
        server.enqueue(new MockResponse().setBody("C"));

        Request request = new Request.Builder().url(server.getUrl("/a")).build();
        client.newCall(request).enqueue(callback);

        callback.await(server.getUrl("/c")).assertCode(200).assertBody("C").priorResponse().assertCode(302)
                .assertHeader("Test", "Redirect from /b to /c").priorResponse().assertCode(301)
                .assertHeader("Test", "Redirect from /a to /b");

        assertEquals(0, server.takeRequest().getSequenceNumber()); // New connection.
        assertEquals(1, server.takeRequest().getSequenceNumber()); // Connection reused.
        assertEquals(2, server.takeRequest().getSequenceNumber()); // Connection reused again!
    }

    @Test
    public void follow20Redirects() throws Exception {
        for (int i = 0; i < 20; i++) {
            server.enqueue(new MockResponse().setResponseCode(301).addHeader("Location: /" + (i + 1))
                    .setBody("Redirecting to /" + (i + 1)));
        }
        server.enqueue(new MockResponse().setBody("Success!"));

        FiberOkHttpTestUtil.executeInFiberRecorded(client, new Request.Builder().url(server.getUrl("/0")).build())
                .assertCode(200).assertBody("Success!");
    }

    @Test
    public void follow20Redirects_Async() throws Exception {
        for (int i = 0; i < 20; i++) {
            server.enqueue(new MockResponse().setResponseCode(301).addHeader("Location: /" + (i + 1))
                    .setBody("Redirecting to /" + (i + 1)));
        }
        server.enqueue(new MockResponse().setBody("Success!"));

        Request request = new Request.Builder().url(server.getUrl("/0")).build();
        client.newCall(request).enqueue(callback);
        callback.await(server.getUrl("/20")).assertCode(200).assertBody("Success!");
    }

    @Test
    public void doesNotFollow21Redirects() throws Exception {
        for (int i = 0; i < 21; i++) {
            server.enqueue(new MockResponse().setResponseCode(301).addHeader("Location: /" + (i + 1))
                    .setBody("Redirecting to /" + (i + 1)));
        }

        try {
            FiberOkHttpUtil.executeInFiber(client, new Request.Builder().url(server.getUrl("/0")).build());
            fail();
        } catch (IOException expected) {
            assertEquals("Too many follow-up requests: 21", expected.getMessage());
        }
    }

    @Test
    public void doesNotFollow21Redirects_Async() throws Exception {
        for (int i = 0; i < 21; i++) {
            server.enqueue(new MockResponse().setResponseCode(301).addHeader("Location: /" + (i + 1))
                    .setBody("Redirecting to /" + (i + 1)));
        }

        Request request = new Request.Builder().url(server.getUrl("/0")).build();
        client.newCall(request).enqueue(callback);
        callback.await(server.getUrl("/20")).assertFailure("Too many follow-up requests: 21");
    }

    @Test
    public void http204WithBodyDisallowed() throws IOException, InterruptedException, ExecutionException {
        server.enqueue(new MockResponse().setResponseCode(204).setBody("I'm not even supposed to be here today."));

        try {
            FiberOkHttpUtil.executeInFiber(client, new Request.Builder().url(server.getUrl("/")).build());
            fail();
        } catch (ProtocolException e) {
            assertEquals("HTTP 204 had non-zero Content-Length: 39", e.getMessage());
        }
    }

    @Test
    public void http205WithBodyDisallowed() throws IOException, InterruptedException, ExecutionException {
        server.enqueue(new MockResponse().setResponseCode(205).setBody("I'm not even supposed to be here today."));

        try {
            FiberOkHttpUtil.executeInFiber(client, new Request.Builder().url(server.getUrl("/")).build());
            fail();
        } catch (ProtocolException e) {
            assertEquals("HTTP 205 had non-zero Content-Length: 39", e.getMessage());
        }
    }

    @Test
    public void canceledBeforeExecute() throws Exception {
        Call call = client.newCall(new Request.Builder().url(server.getUrl("/a")).build());
        call.cancel();

        try {
            FiberOkHttpUtil.executeInFiber(call);
            fail();
        } catch (IOException expected) {
        }
        assertEquals(0, server.getRequestCount());
    }

    @Test
    public void cancelTagImmediatelyAfterEnqueue() throws Exception {
        Call call = client.newCall(new Request.Builder().url(server.getUrl("/a")).tag("request").build());
        call.enqueue(callback);
        client.cancel("request");
        assertEquals(0, server.getRequestCount());
        callback.await(server.getUrl("/a")).assertFailure("Canceled");
    }

    @Test
    public void cancelBeforeBodyIsRead() throws Exception {
        server.enqueue(new MockResponse().setBody("def").throttleBody(1, 750, TimeUnit.MILLISECONDS));

        final Call call = client.newCall(new Request.Builder().url(server.getUrl("/a")).build());
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<Response> result = executor.submit(new Callable<Response>() {
            @Override
            public Response call() throws Exception {
                return FiberOkHttpUtil.executeInFiber(call);
            }
        });

        Thread.sleep(100); // wait for it to go in flight.

        call.cancel();
        try {
            result.get().body().bytes();
            fail();
        } catch (IOException expected) {
        }
        assertEquals(1, server.getRequestCount());
    }

    @Test
    public void cancelInFlightBeforeResponseReadThrowsIOE() throws Exception {
        server.get().setDispatcher(new Dispatcher() {
            @Override
            public MockResponse dispatch(RecordedRequest request) {
                client.cancel("request");
                return new MockResponse().setBody("A");
            }
        });

        Request request = new Request.Builder().url(server.getUrl("/a")).tag("request").build();
        try {
            FiberOkHttpUtil.executeInFiber(client, request);
            fail();
        } catch (IOException expected) {
        }
    }

    @Test
    public void cancelInFlightBeforeResponseReadThrowsIOE_HTTP_2() throws Exception {
        enableProtocol(Protocol.HTTP_2);
        cancelInFlightBeforeResponseReadThrowsIOE();
    }

    @Test
    public void cancelInFlightBeforeResponseReadThrowsIOE_SPDY_3() throws Exception {
        enableProtocol(Protocol.SPDY_3);
        cancelInFlightBeforeResponseReadThrowsIOE();
    }

    /**
     * This test puts a request in front of one that is to be canceled, so that it is canceled before
     * I/O takes place.
     */
    @Test
    public void canceledBeforeIOSignalsOnFailure() throws Exception {
        client.getDispatcher().setMaxRequests(1); // Force requests to be executed serially.
        server.get().setDispatcher(new Dispatcher() {
            char nextResponse = 'A';

            @Override
            public MockResponse dispatch(RecordedRequest request) {
                client.cancel("request B");
                return new MockResponse().setBody(Character.toString(nextResponse++));
            }
        });

        Request requestA = new Request.Builder().url(server.getUrl("/a")).tag("request A").build();
        client.newCall(requestA).enqueue(callback);
        assertEquals("/a", server.takeRequest().getPath());

        Request requestB = new Request.Builder().url(server.getUrl("/b")).tag("request B").build();
        client.newCall(requestB).enqueue(callback);

        callback.await(requestA.url()).assertBody("A");
        // At this point we know the callback is ready, and that it will receive a cancel failure.
        callback.await(requestB.url()).assertFailure("Canceled");
    }

    @Test
    public void canceledBeforeIOSignalsOnFailure_HTTP_2() throws Exception {
        enableProtocol(Protocol.HTTP_2);
        canceledBeforeIOSignalsOnFailure();
    }

    @Test
    public void canceledBeforeIOSignalsOnFailure_SPDY_3() throws Exception {
        enableProtocol(Protocol.SPDY_3);
        canceledBeforeIOSignalsOnFailure();
    }

    @Test
    public void canceledBeforeResponseReadSignalsOnFailure() throws Exception {
        Request requestA = new Request.Builder().url(server.getUrl("/a")).tag("request A").build();
        final Call call = client.newCall(requestA);
        server.get().setDispatcher(new Dispatcher() {
            @Override
            public MockResponse dispatch(RecordedRequest request) {
                call.cancel();
                return new MockResponse().setBody("A");
            }
        });

        call.enqueue(callback);
        assertEquals("/a", server.takeRequest().getPath());

        callback.await(requestA.url()).assertFailure("Canceled", "stream was reset: CANCEL", "Socket closed");
    }

    @Test
    public void canceledBeforeResponseReadSignalsOnFailure_HTTP_2() throws Exception {
        enableProtocol(Protocol.HTTP_2);
        canceledBeforeResponseReadSignalsOnFailure();
    }

    @Test
    public void canceledBeforeResponseReadSignalsOnFailure_SPDY_3() throws Exception {
        enableProtocol(Protocol.SPDY_3);
        canceledBeforeResponseReadSignalsOnFailure();
    }

    /**
     * There's a race condition where the cancel may apply after the stream has already been
     * processed.
     */
    @Test
    public void canceledAfterResponseIsDeliveredBreaksStreamButSignalsOnce() throws Exception {
        server.enqueue(new MockResponse().setBody("A"));

        final CountDownLatch latch = new CountDownLatch(1);
        final AtomicReference<String> bodyRef = new AtomicReference<>();
        final AtomicBoolean failureRef = new AtomicBoolean();

        Request request = new Request.Builder().url(server.getUrl("/a")).tag("request A").build();
        final Call call = client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Request request, IOException e) {
                failureRef.set(true);
                latch.countDown();
            }

            @Override
            public void onResponse(Response response) throws IOException {
                call.cancel();
                try {
                    bodyRef.set(response.body().string());
                } catch (IOException e) { // It is ok if this broke the stream.
                    bodyRef.set("A");
                    throw e; // We expect to not loop into onFailure in this case.
                } finally {
                    latch.countDown();
                }
            }
        });

        latch.await();
        assertEquals("A", bodyRef.get());
        assertFalse(failureRef.get());
    }

    @Test
    public void canceledAfterResponseIsDeliveredBreaksStreamButSignalsOnce_HTTP_2() throws Exception {
        enableProtocol(Protocol.HTTP_2);
        canceledAfterResponseIsDeliveredBreaksStreamButSignalsOnce();
    }

    @Test
    public void canceledAfterResponseIsDeliveredBreaksStreamButSignalsOnce_SPDY_3() throws Exception {
        enableProtocol(Protocol.SPDY_3);
        canceledAfterResponseIsDeliveredBreaksStreamButSignalsOnce();
    }

    @Test
    public void cancelWithInterceptor() throws Exception {
        client.interceptors().add(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                chain.proceed(chain.request());
                throw new AssertionError(); // We expect an exception.
            }
        });

        Call call = client.newCall(new Request.Builder().url(server.getUrl("/a")).build());
        call.cancel();

        try {
            call.execute();
            fail();
        } catch (IOException expected) {
        }
        assertEquals(0, server.getRequestCount());
    }

    @Test
    public void gzip() throws Exception {
        Buffer gzippedBody = gzip("abcabcabc");
        String bodySize = Long.toString(gzippedBody.size());

        server.enqueue(new MockResponse().setBody(gzippedBody).addHeader("Content-Encoding: gzip"));

        Request request = new Request.Builder().url(server.getUrl("/")).build();

        // Confirm that the user request doesn't have Accept-Encoding, and the user
        // response doesn't have a Content-Encoding or Content-Length.
        RecordedResponse userResponse = FiberOkHttpTestUtil.executeInFiberRecorded(client, request);
        userResponse.assertCode(200).assertRequestHeader("Accept-Encoding").assertHeader("Content-Encoding")
                .assertHeader("Content-Length").assertBody("abcabcabc");

        // But the network request doesn't lie. OkHttp used gzip for this call.
        userResponse.networkResponse().assertHeader("Content-Encoding", "gzip")
                .assertHeader("Content-Length", bodySize).assertRequestHeader("Accept-Encoding", "gzip");
    }

    @Test
    public void asyncResponseCanBeConsumedLater() throws Exception {
        server.enqueue(new MockResponse().setBody("abc"));
        server.enqueue(new MockResponse().setBody("def"));

        Request request = new Request.Builder().url(server.getUrl("/")).header("User-Agent", "SyncApiTest").build();

        final BlockingQueue<Response> responseRef = new SynchronousQueue<>();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Request request, IOException e) {
                throw new AssertionError();
            }

            @Override
            public void onResponse(Response response) throws IOException {
                try {
                    responseRef.put(response);
                } catch (InterruptedException e) {
                    throw new AssertionError();
                }
            }
        });

        Response response = responseRef.take();
        assertEquals(200, response.code());
        assertEquals("abc", response.body().string());

        // Make another request just to confirm that that connection can be reused...
        FiberOkHttpTestUtil.executeInFiberRecorded(client, new Request.Builder().url(server.getUrl("/")).build())
                .assertBody("def");
        assertEquals(0, server.takeRequest().getSequenceNumber()); // New connection.
        assertEquals(1, server.takeRequest().getSequenceNumber()); // Connection reused.

        // ... even before we close the response body!
        response.body().close();
    }

    @Test
    public void userAgentIsIncludedByDefault() throws Exception {
        server.enqueue(new MockResponse());

        FiberOkHttpUtil.executeInFiber(client, new Request.Builder().url(server.getUrl("/")).build());

        RecordedRequest recordedRequest = server.takeRequest();
        assertTrue(recordedRequest.getHeader("User-Agent").matches(Version.userAgent()));
    }

    @Test
    public void setFollowRedirectsFalse() throws Exception {
        server.enqueue(new MockResponse().setResponseCode(302).addHeader("Location: /b").setBody("A"));
        server.enqueue(new MockResponse().setBody("B"));

        client.setFollowRedirects(false);
        RecordedResponse recordedResponse = FiberOkHttpTestUtil.executeInFiberRecorded(client,
                new Request.Builder().url(server.getUrl("/a")).build());

        recordedResponse.assertBody("A").assertCode(302);
    }

    @Test
    public void expect100ContinueNonEmptyRequestBody() throws Exception {
        server.enqueue(new MockResponse());

        Request request = new Request.Builder().url(server.getUrl("/")).header("Expect", "100-continue")
                .post(RequestBody.create(MediaType.parse("text/plain"), "abc")).build();

        FiberOkHttpTestUtil.executeInFiberRecorded(client, request).assertCode(200).assertSuccessful();

        assertEquals("abc", server.takeRequest().getUtf8Body());
    }

    @Test
    public void expect100ContinueEmptyRequestBody() throws Exception {
        server.enqueue(new MockResponse());

        Request request = new Request.Builder().url(server.getUrl("/")).header("Expect", "100-continue")
                .post(RequestBody.create(MediaType.parse("text/plain"), "")).build();

        FiberOkHttpTestUtil.executeInFiberRecorded(client, request).assertCode(200).assertSuccessful();
    }

    private RecordedResponse executeSynchronously(Request request) throws IOException {
        Response response = client.newCall(request).execute();
        return new RecordedResponse(request, response, null, response.body().string(), null);
    }

    /**
     * Tests that use this will fail unless boot classpath is set. Ex. {@code
     * -Xbootclasspath/p:/tmp/alpn-boot-8.0.0.v20140317}
     */
    private void enableProtocol(Protocol protocol) {
        client.setSslSocketFactory(sslContext.getSocketFactory());
        client.setHostnameVerifier(new RecordingHostnameVerifier());
        client.setProtocols(Arrays.asList(protocol, Protocol.HTTP_1_1));
        server.get().useHttps(sslContext.getSocketFactory(), false);
        server.get().setProtocols(client.getProtocols());
    }

    private Buffer gzip(String data) throws IOException {
        Buffer result = new Buffer();
        BufferedSink sink = Okio.buffer(new GzipSink(result));
        sink.writeUtf8(data);
        sink.close();
        return result;
    }

    private static class RecordingSSLSocketFactory extends DelegatingSSLSocketFactory {

        private List<SSLSocket> socketsCreated = new ArrayList<SSLSocket>();

        public RecordingSSLSocketFactory(SSLSocketFactory delegate) {
            super(delegate);
        }

        @Override
        protected void configureSocket(SSLSocket sslSocket) throws IOException {
            socketsCreated.add(sslSocket);
        }

        public List<SSLSocket> getSocketsCreated() {
            return socketsCreated;
        }
    }

    /**
     * Used during tests that involve TLS connection fallback attempts. OkHttp includes the
     * TLS_FALLBACK_SCSV cipher on fallback connections. See
     * {@link com.squareup.okhttp.FallbackTestClientSocketFactory} for details.
     */
    private static void suppressTlsFallbackScsv(FiberOkHttpClient client) {
        FallbackTestClientSocketFactory clientSocketFactory = new FallbackTestClientSocketFactory(
                sslContext.getSocketFactory());
        client.setSslSocketFactory(clientSocketFactory);
    }
}