Java tutorial
/* * 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); } }