/* * Copyright (C) 2013 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.okhttp; import com.squareup.okhttp.internal.DoubleInetAddressDns; import com.squareup.okhttp.internal.RecordingOkAuthenticator; import com.squareup.okhttp.internal.SingleInetAddressDns; import com.squareup.okhttp.internal.SslContextBuilder; import com.squareup.okhttp.internal.Util; import com.squareup.okhttp.internal.Version; import com.squareup.okhttp.internal.http.FakeDns; import com.squareup.okhttp.internal.io.InMemoryFileSystem; import com.squareup.okhttp.mockwebserver.Dispatcher; import com.squareup.okhttp.mockwebserver.MockResponse; import com.squareup.okhttp.mockwebserver.MockWebServer; import com.squareup.okhttp.mockwebserver.RecordedRequest; import com.squareup.okhttp.mockwebserver.SocketPolicy; import com.squareup.okhttp.testing.RecordingHostnameVerifier; import java.io.File; 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.InetAddress; import java.net.InetSocketAddress; import java.net.ProtocolException; import java.net.ServerSocket; import java.net.UnknownServiceException; import java.security.cert.Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; 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.ServerSocketFactory; 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.TestRule; import org.junit.rules.Timeout; import static com.squareup.okhttp.internal.Internal.logger; import static java.net.CookiePolicy.ACCEPT_ORIGINAL_SERVER; 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 { @Rule public final TestRule timeout = new Timeout(30_000); @Rule public final MockWebServer server = new MockWebServer(); @Rule public final MockWebServer server2 = new MockWebServer(); @Rule public final InMemoryFileSystem fileSystem = new InMemoryFileSystem(); private SSLContext sslContext = SslContextBuilder.localhost(); private OkHttpClient client = new OkHttpClient(); private RecordingCallback callback = new RecordingCallback(); private TestLogHandler logHandler = new TestLogHandler(); private Cache cache = new Cache(new File("/cache/"), Integer.MAX_VALUE, fileSystem); private ServerSocket nullServer; @Before public void setUp() throws Exception { logger.addHandler(logHandler); } @After public void tearDown() throws Exception { cache.delete(); Util.closeQuietly(nullServer); 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.url("/")) .header("User-Agent", "SyncApiTest") .build(); executeSynchronously(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 = server.url("/"); Request request = new Request.Builder() .url(httpUrl) .build(); assertEquals(httpUrl, request.httpUrl()); executeSynchronously(request).assertSuccessful(); } @Test public void invalidScheme() throws Exception { Request.Builder requestBuilder = new Request.Builder(); try { requestBuilder.url("ftp://hostname/path"); fail(); } catch (IllegalArgumentException expected) { assertEquals(expected.getMessage(), "unexpected url: ftp://hostname/path"); } } @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.url("/")) .build(); executeSynchronously(request) .assertCode(500) .assertNotSuccessful(); } @Test public void get_HTTP_2() throws Exception { enableProtocol(Protocol.HTTP_2); get(); } @Test public void get_HTTPS() throws Exception { enableTls(); get(); } @Test public void get_SPDY_3() throws Exception { enableProtocol(Protocol.SPDY_3); get(); } @Test public void repeatedHeaderNames() throws Exception { server.enqueue(new MockResponse() .addHeader("B", "123") .addHeader("B", "234")); Request request = new Request.Builder() .url(server.url("/")) .addHeader("A", "345") .addHeader("A", "456") .build(); executeSynchronously(request) .assertCode(200) .assertHeader("B", "123", "234"); RecordedRequest recordedRequest = server.takeRequest(); assertEquals(Arrays.asList("345", "456"), recordedRequest.getHeaders().values("A")); } @Test public void repeatedHeaderNames_SPDY_3() throws Exception { enableProtocol(Protocol.SPDY_3); repeatedHeaderNames(); } @Test public void repeatedHeaderNames_HTTP_2() throws Exception { enableProtocol(Protocol.HTTP_2); repeatedHeaderNames(); } @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.url("/")) .head() .header("User-Agent", "SyncApiTest") .build(); executeSynchronously(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_HTTPS() throws Exception { enableTls(); head(); } @Test public void head_HTTP_2() throws Exception { enableProtocol(Protocol.HTTP_2); head(); } @Test public void head_SPDY_3() throws Exception { enableProtocol(Protocol.SPDY_3); head(); } @Test public void post() throws Exception { server.enqueue(new MockResponse().setBody("abc")); Request request = new Request.Builder() .url(server.url("/")) .post(RequestBody.create(MediaType.parse("text/plain"), "def")) .build(); executeSynchronously(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_HTTPS() throws Exception { enableTls(); post(); } @Test public void post_HTTP_2() throws Exception { enableProtocol(Protocol.HTTP_2); post(); } @Test public void post_SPDY_3() throws Exception { enableProtocol(Protocol.SPDY_3); post(); } @Test public void postZeroLength() throws Exception { server.enqueue(new MockResponse().setBody("abc")); Request request = new Request.Builder() .url(server.url("/")) .method("POST", RequestBody.create(null, new byte[0])) .build(); executeSynchronously(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_HTTPS() throws Exception { enableTls(); postZeroLength(); } @Test public void postZerolength_HTTP_2() throws Exception { enableProtocol(Protocol.HTTP_2); postZeroLength(); } @Test public void postZeroLength_SPDY_3() throws Exception { enableProtocol(Protocol.SPDY_3); postZeroLength(); } @Test public void postBodyRetransmittedAfterAuthorizationFail() throws Exception { postBodyRetransmittedAfterAuthorizationFail("abc"); } @Test public void postBodyRetransmittedAfterAuthorizationFail_HTTPS() throws Exception { enableTls(); postBodyRetransmittedAfterAuthorizationFail("abc"); } @Test public void postBodyRetransmittedAfterAuthorizationFail_HTTP_2() throws Exception { enableProtocol(Protocol.HTTP_2); postBodyRetransmittedAfterAuthorizationFail("abc"); } @Test public void postBodyRetransmittedAfterAuthorizationFail_SPDY_3() throws Exception { enableProtocol(Protocol.SPDY_3); 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_HTTPS() throws Exception { enableTls(); postBodyRetransmittedAfterAuthorizationFail(""); } @Test public void postEmptyBodyRetransmittedAfterAuthorizationFail_HTTP_2() throws Exception { enableProtocol(Protocol.HTTP_2); postBodyRetransmittedAfterAuthorizationFail(""); } @Test public void postEmptyBodyRetransmittedAfterAuthorizationFail_SPDY_3() throws Exception { enableProtocol(Protocol.SPDY_3); 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.url("/")) .method("POST", RequestBody.create(null, body)) .build(); String credential = Credentials.basic("jesse", "secret"); client.setAuthenticator(new RecordingOkAuthenticator(credential)); Response response = client.newCall(request).execute(); 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.url("/")).build(); executeSynchronously(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 { client.newCall(new Request.Builder().url(server.url("/0")).build()).execute(); 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.url("/")) .delete() .build(); executeSynchronously(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_HTTPS() throws Exception { enableTls(); delete(); } @Test public void delete_HTTP_2() throws Exception { enableProtocol(Protocol.HTTP_2); delete(); } @Test public void delete_SPDY_3() throws Exception { enableProtocol(Protocol.SPDY_3); delete(); } @Test public void deleteWithRequestBody() throws Exception { server.enqueue(new MockResponse().setBody("abc")); Request request = new Request.Builder() .url(server.url("/")) .method("DELETE", RequestBody.create(MediaType.parse("text/plain"), "def")) .build(); executeSynchronously(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.url("/")) .put(RequestBody.create(MediaType.parse("text/plain"), "def")) .build(); executeSynchronously(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_HTTPS() throws Exception { enableTls(); put(); } @Test public void put_HTTP_2() throws Exception { enableProtocol(Protocol.HTTP_2); put(); } @Test public void put_SPDY_3() throws Exception { enableProtocol(Protocol.SPDY_3); put(); } @Test public void patch() throws Exception { server.enqueue(new MockResponse().setBody("abc")); Request request = new Request.Builder() .url(server.url("/")) .patch(RequestBody.create(MediaType.parse("text/plain"), "def")) .build(); executeSynchronously(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_HTTP_2() throws Exception { enableProtocol(Protocol.HTTP_2); patch(); } @Test public void patch_HTTPS() throws Exception { enableTls(); patch(); } @Test public void patch_SPDY_3() throws Exception { enableProtocol(Protocol.SPDY_3); patch(); } @Test public void unspecifiedRequestBodyContentTypeDoesNotGetDefault() throws Exception { server.enqueue(new MockResponse()); Request request = new Request.Builder() .url(server.url("/")) .method("POST", RequestBody.create(null, "abc")) .build(); executeSynchronously(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.url("/")) .header("User-Agent", "SyncApiTest") .build(); Call call = client.newCall(request); Response response = call.execute(); response.body().close(); try { call.execute(); 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.url("/")) .header("User-Agent", "SyncApiTest") .build(); Call call = client.newCall(request); call.enqueue(callback); try { call.execute(); 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 get_Async() throws Exception { server.enqueue(new MockResponse() .setBody("abc") .addHeader("Content-Type: text/plain")); Request request = new Request.Builder() .url(server.url("/")) .header("User-Agent", "AsyncApiTest") .build(); client.newCall(request).enqueue(callback); callback.await(request.httpUrl()) .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.url("/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.url("/") + "...", 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")); executeSynchronously(new Request.Builder().url(server.url("/a")).build()) .assertBody("abc"); executeSynchronously(new Request.Builder().url(server.url("/b")).build()) .assertBody("def"); executeSynchronously(new Request.Builder().url(server.url("/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.url("/a")).build()).enqueue(callback); callback.await(server.url("/a")).assertBody("abc"); client.newCall(new Request.Builder().url(server.url("/b")).build()).enqueue(callback); callback.await(server.url("/b")).assertBody("def"); client.newCall(new Request.Builder().url(server.url("/c")).build()).enqueue(callback); callback.await(server.url("/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.url("/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.url("/b")).build()).enqueue(callback); } }); callback.await(server.url("/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); executeSynchronously(new Request.Builder().url(server.url("/a")).build()).assertBody("abc"); // Second request: time out after 250ms. client.setReadTimeout(250, TimeUnit.MILLISECONDS); Request request = new Request.Builder().url(server.url("/b")).build(); Response response = client.newCall(request).execute(); 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); } finally { bodySource.close(); } } /** 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!")); client.setDns(new DoubleInetAddressDns()); client.setReadTimeout(100, TimeUnit.MILLISECONDS); Request request = new Request.Builder().url(server.url("/")).build(); try { // If this succeeds, too many requests were made. client.newCall(request).execute(); fail(); } catch (InterruptedIOException expected) { } } /** https://github.com/square/okhttp/issues/1801 */ @Test public void asyncCallEngineInitialized() throws Exception { OkHttpClient c = new OkHttpClient(); c.interceptors().add(new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { throw new IOException(); } }); Request request = new Request.Builder().url(server.url("/")).build(); c.newCall(request).enqueue(callback); RecordedResponse response = callback.await(request.httpUrl()); assertEquals(request, response.request); } @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.url("/")) .method("POST", requestBody1) .build(); Response response1 = client.newCall(request1).execute(); 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.url("/")) .method("POST", requestBody2) .build(); Response response2 = client.newCall(request2).execute(); 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.url("/")).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.url("/")).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 { enableTls(); server.enqueue(new MockResponse() .setBody("abc") .addHeader("Content-Type: text/plain")); executeSynchronously(new Request.Builder().url(server.url("/")).build()) .assertHandshake(); } @Test public void tls_Async() throws Exception { enableTls(); server.enqueue(new MockResponse() .setBody("abc") .addHeader("Content-Type: text/plain")); Request request = new Request.Builder() .url(server.url("/")) .build(); client.newCall(request).enqueue(callback); callback.await(request.httpUrl()).assertHandshake(); } @Test public void recoverWhenRetryOnConnectionFailureIsTrue() throws Exception { server.enqueue(new MockResponse().setBody("seed connection pool")); server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AFTER_REQUEST)); server.enqueue(new MockResponse().setBody("retry success")); client.setDns(new DoubleInetAddressDns()); assertTrue(client.getRetryOnConnectionFailure()); Request request = new Request.Builder().url(server.url("/")).build(); executeSynchronously(request).assertBody("seed connection pool"); executeSynchronously(request).assertBody("retry success"); } @Test public void noRecoverWhenRetryOnConnectionFailureIsFalse() throws Exception { server.enqueue(new MockResponse().setBody("seed connection pool")); server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AFTER_REQUEST)); server.enqueue(new MockResponse().setBody("unreachable!")); client.setDns(new DoubleInetAddressDns()); client.setRetryOnConnectionFailure(false); Request request = new Request.Builder().url(server.url("/")).build(); executeSynchronously(request).assertBody("seed connection pool"); try { // If this succeeds, too many requests were made. client.newCall(request).execute(); fail(); } catch (IOException expected) { } } @Test public void recoverFromTlsHandshakeFailure() throws Exception { server.useHttps(sslContext.getSocketFactory(), false); server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.FAIL_HANDSHAKE)); server.enqueue(new MockResponse().setBody("abc")); suppressTlsFallbackScsv(client); client.setHostnameVerifier(new RecordingHostnameVerifier()); client.setDns(new SingleInetAddressDns()); executeSynchronously(new Request.Builder().url(server.url("/")).build()) .assertBody("abc"); } @Test public void recoverFromTlsHandshakeFailure_tlsFallbackScsvEnabled() throws Exception { final String tlsFallbackScsv = "TLS_FALLBACK_SCSV"; List supportedCiphers = Arrays.asList(sslContext.getSocketFactory().getSupportedCipherSuites()); if (!supportedCiphers.contains(tlsFallbackScsv)) { // This only works if the client socket supports TLS_FALLBACK_SCSV. return; } server.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()); client.setDns(new SingleInetAddressDns()); Request request = new Request.Builder().url(server.url("/")).build(); try { client.newCall(request).execute(); fail(); } catch (SSLHandshakeException expected) { } List 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.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.url("/")) .build(); client.newCall(request).enqueue(callback); callback.await(request.httpUrl()).assertBody("abc"); } @Test public void noRecoveryFromTlsHandshakeFailureWhenTlsFallbackIsDisabled() throws Exception { client.setConnectionSpecs(Arrays.asList(ConnectionSpec.MODERN_TLS, ConnectionSpec.CLEARTEXT)); server.useHttps(sslContext.getSocketFactory(), false); server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.FAIL_HANDSHAKE)); suppressTlsFallbackScsv(client); client.setHostnameVerifier(new RecordingHostnameVerifier()); client.setDns(new SingleInetAddressDns()); Request request = new Request.Builder().url(server.url("/")).build(); try { client.newCall(request).execute(); 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.url("/")).build(); try { client.newCall(request).execute(); fail(); } catch (UnknownServiceException expected) { assertTrue(expected.getMessage().contains("CLEARTEXT communication not supported")); } } @Test public void setFollowSslRedirectsFalse() throws Exception { enableTls(); server.enqueue(new MockResponse() .setResponseCode(301) .addHeader("Location: http://square.com")); client.setFollowSslRedirects(false); Request request = new Request.Builder().url(server.url("/")).build(); Response response = client.newCall(request).execute(); assertEquals(301, response.code()); response.body().close(); } @Test public void matchingPinnedCertificate() throws Exception { enableTls(); server.enqueue(new MockResponse()); server.enqueue(new MockResponse()); // Make a first request without certificate pinning. Use it to collect certificates to pin. Request request1 = new Request.Builder().url(server.url("/")).build(); Response response1 = client.newCall(request1).execute(); CertificatePinner.Builder certificatePinnerBuilder = new CertificatePinner.Builder(); for (Certificate certificate : response1.handshake().peerCertificates()) { certificatePinnerBuilder.add(server.getHostName(), CertificatePinner.pin(certificate)); } response1.body().close(); // Make another request with certificate pinning. It should complete normally. client.setCertificatePinner(certificatePinnerBuilder.build()); Request request2 = new Request.Builder().url(server.url("/")).build(); Response response2 = client.newCall(request2).execute(); assertNotSame(response2.handshake(), response1.handshake()); response2.body().close(); } @Test public void unmatchingPinnedCertificate() throws Exception { enableTls(); server.enqueue(new MockResponse()); // Pin publicobject.com's cert. client.setCertificatePinner(new CertificatePinner.Builder() .add(server.getHostName(), "sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=") .build()); // When we pin the wrong certificate, connectivity fails. Request request = new Request.Builder().url(server.url("/")).build(); try { client.newCall(request).execute(); 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.url("/")) .post(RequestBody.create(MediaType.parse("text/plain"), "def")) .build(); client.newCall(request).enqueue(callback); callback.await(request.httpUrl()) .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.url("/")).build(); Response response1 = client.newCall(request1).execute(); assertEquals("abc", response1.body().string()); Request request2 = new Request.Builder() .url(server.url("/")) .post(RequestBody.create(MediaType.parse("text/plain"), "body!")) .build(); Response response2 = client.newCall(request2).execute(); 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. HttpUrl url = server.url("/"); Request cacheStoreRequest = new Request.Builder() .url(url) .addHeader("Accept-Language", "fr-CA") .addHeader("Accept-Charset", "UTF-8") .build(); executeSynchronously(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 = executeSynchronously(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. HttpUrl url = server.url("/"); Request cacheStoreRequest = new Request.Builder() .url(url) .addHeader("Accept-Language", "fr-CA") .addHeader("Accept-Charset", "UTF-8") .build(); executeSynchronously(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 = executeSynchronously(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.url("/")) .build(); client.newCall(request1).enqueue(callback); callback.await(request1.httpUrl()).assertCode(200).assertBody("A"); assertNull(server.takeRequest().getHeader("If-None-Match")); Request request2 = new Request.Builder() .url(server.url("/")) .build(); client.newCall(request2).enqueue(callback); callback.await(request2.httpUrl()).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.url("/")) .addHeader("Accept-Language", "fr-CA") .addHeader("Accept-Charset", "UTF-8") .build(); executeSynchronously(cacheStoreRequest) .assertCode(200) .assertBody("A"); assertNull(server.takeRequest().getHeader("If-None-Match")); Request cacheMissRequest = new Request.Builder() .url(server.url("/")) .addHeader("Accept-Language", "en-US") // Different, but Vary says it doesn't matter. .addHeader("Accept-Charset", "UTF-8") .build(); RecordedResponse cacheHit = executeSynchronously(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.url("/")) .build(); client.newCall(request1).enqueue(callback); callback.await(request1.httpUrl()).assertCode(200).assertBody("A"); assertNull(server.takeRequest().getHeader("If-None-Match")); Request request2 = new Request.Builder() .url(server.url("/")) .build(); client.newCall(request2).enqueue(callback); callback.await(request2.httpUrl()).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.url("/")) .header("Cache-Control", "only-if-cached") .build(); executeSynchronously(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")); executeSynchronously(new Request.Builder().url(server.url("/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 = client.newCall(new Request.Builder() .url(server.url("/page1")) .post(RequestBody.create(MediaType.parse("text/plain"), "Request Body")) .build()).execute(); 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 propfindRedirectsToPropfind() 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 = client.newCall(new Request.Builder() .url(server.url("/page1")) .method("PROPFIND", RequestBody.create(MediaType.parse("text/plain"), "Request Body")) .build()).execute(); assertEquals("Page 2", response.body().string()); RecordedRequest page1 = server.takeRequest(); assertEquals("PROPFIND /page1 HTTP/1.1", page1.getRequestLine()); assertEquals("Request Body", page1.getBody().readUtf8()); RecordedRequest page2 = server.takeRequest(); assertEquals("PROPFIND /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.url("/"))); CookieManager cookieManager = new CookieManager(null, ACCEPT_ORIGINAL_SERVER); HttpCookie cookie = new HttpCookie("c", "cookie"); cookie.setDomain(server.getCookieDomain()); cookie.setPath("/"); String portList = Integer.toString(server.getPort()); cookie.setPortlist(portList); cookieManager.getCookieStore().add(server.url("/").uri(), cookie); client.setCookieHandler(cookieManager); Response response = client.newCall(new Request.Builder() .url(server.url("/page1")) .build()).execute(); assertEquals("Page 2", response.body().string()); RecordedRequest request1 = server.takeRequest(); assertEquals("$Version=\"1\"; c=\"cookie\";$Path=\"/\";$Domain=\"" + server.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.url("/b"))); client.setAuthenticator(new RecordingOkAuthenticator(Credentials.basic("jesse", "secret"))); Request request = new Request.Builder().url(server.url("/a")).build(); Response response = client.newCall(request).execute(); 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.url("/a")).build(); client.newCall(request).enqueue(callback); callback.await(server.url("/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!")); executeSynchronously(new Request.Builder().url(server.url("/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.url("/0")).build(); client.newCall(request).enqueue(callback); callback.await(server.url("/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 { client.newCall(new Request.Builder().url(server.url("/0")).build()).execute(); 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.url("/0")).build(); client.newCall(request).enqueue(callback); callback.await(server.url("/20")).assertFailure("Too many follow-up requests: 21"); } @Test public void http204WithBodyDisallowed() throws IOException { server.enqueue(new MockResponse() .setResponseCode(204) .setBody("I'm not even supposed to be here today.")); try { executeSynchronously(new Request.Builder().url(server.url("/")).build()); fail(); } catch (ProtocolException e) { assertEquals("HTTP 204 had non-zero Content-Length: 39", e.getMessage()); } } @Test public void http205WithBodyDisallowed() throws IOException { server.enqueue(new MockResponse() .setResponseCode(205) .setBody("I'm not even supposed to be here today.")); try { executeSynchronously(new Request.Builder().url(server.url("/")).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.url("/a")).build()); call.cancel(); try { call.execute(); fail(); } catch (IOException expected) { } assertEquals(0, server.getRequestCount()); } @Test public void cancelDuringHttpConnect() throws Exception { cancelDuringConnect("http"); } @Test public void cancelDuringHttpsConnect() throws Exception { cancelDuringConnect("https"); } /** Cancel a call that's waiting for connect to complete. */ private void cancelDuringConnect(String scheme) throws Exception { InetSocketAddress socketAddress = startNullServer(); HttpUrl url = new HttpUrl.Builder() .scheme(scheme) .host(socketAddress.getHostName()) .port(socketAddress.getPort()) .build(); long cancelDelayMillis = 300L; Call call = client.newCall(new Request.Builder().url(url).build()); cancelLater(call, cancelDelayMillis); long startNanos = System.nanoTime(); try { call.execute(); fail(); } catch (IOException expected) { } long elapsedNanos = System.nanoTime() - startNanos; assertEquals(cancelDelayMillis, TimeUnit.NANOSECONDS.toMillis(elapsedNanos), 100f); } private InetSocketAddress startNullServer() throws IOException { InetSocketAddress address = new InetSocketAddress(InetAddress.getByName("localhost"), 0); nullServer = ServerSocketFactory.getDefault().createServerSocket(); nullServer.bind(address); return new InetSocketAddress(address.getAddress(), nullServer.getLocalPort()); } @Test public void cancelTagImmediatelyAfterEnqueue() throws Exception { server.enqueue(new MockResponse()); Call call = client.newCall(new Request.Builder() .url(server.url("/a")) .tag("request") .build()); call.enqueue(callback); client.cancel("request"); callback.await(server.url("/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.url("/a")).build()); ExecutorService executor = Executors.newSingleThreadExecutor(); Future result = executor.submit(new Callable() { @Override public Response call() throws Exception { return call.execute(); } }); 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.setDispatcher(new Dispatcher() { @Override public MockResponse dispatch(RecordedRequest request) { client.cancel("request"); return new MockResponse().setBody("A"); } }); Request request = new Request.Builder().url(server.url("/a")).tag("request").build(); try { client.newCall(request).execute(); fail(); } catch (IOException expected) { } } @Test public void cancelInFlightBeforeResponseReadThrowsIOE_HTTPS() throws Exception { enableTls(); cancelInFlightBeforeResponseReadThrowsIOE(); } @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.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.url("/a")).tag("request A").build(); client.newCall(requestA).enqueue(callback); assertEquals("/a", server.takeRequest().getPath()); Request requestB = new Request.Builder().url(server.url("/b")).tag("request B").build(); client.newCall(requestB).enqueue(callback); callback.await(requestA.httpUrl()).assertBody("A"); // At this point we know the callback is ready, and that it will receive a cancel failure. callback.await(requestB.httpUrl()).assertFailure("Canceled"); } @Test public void canceledBeforeIOSignalsOnFailure_HTTPS() throws Exception { enableTls(); canceledBeforeIOSignalsOnFailure(); } @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.url("/a")).tag("request A").build(); final Call call = client.newCall(requestA); server.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.httpUrl()).assertFailure("Canceled", "stream was reset: CANCEL", "Socket closed"); } @Test public void canceledBeforeResponseReadSignalsOnFailure_HTTPS() throws Exception { enableTls(); canceledBeforeResponseReadSignalsOnFailure(); } @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 bodyRef = new AtomicReference<>(); final AtomicBoolean failureRef = new AtomicBoolean(); Request request = new Request.Builder().url(server.url("/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_HTTPS() throws Exception { enableTls(); canceledAfterResponseIsDeliveredBreaksStreamButSignalsOnce(); } @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.url("/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.url("/")) .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 = executeSynchronously(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"); } /** https://github.com/square/okhttp/issues/1927 */ @Test public void gzipResponseAfterAuthenticationChallenge() throws Exception { server.enqueue(new MockResponse() .setResponseCode(401)); server.enqueue(new MockResponse() .setBody(gzip("abcabcabc")) .addHeader("Content-Encoding: gzip")); client.setAuthenticator(new RecordingOkAuthenticator("password")); Request request = new Request.Builder() .url(server.url("/")) .build(); executeSynchronously(request) .assertBody("abcabcabc"); } @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.url("/")) .header("User-Agent", "SyncApiTest") .build(); final BlockingQueue 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... executeSynchronously(new Request.Builder().url(server.url("/")).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()); executeSynchronously(new Request.Builder().url(server.url("/")).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 = executeSynchronously( new Request.Builder().url(server.url("/a")).build()); recordedResponse .assertBody("A") .assertCode(302); } @Test public void expect100ContinueNonEmptyRequestBody() throws Exception { server.enqueue(new MockResponse()); Request request = new Request.Builder() .url(server.url("/")) .header("Expect", "100-continue") .post(RequestBody.create(MediaType.parse("text/plain"), "abc")) .build(); executeSynchronously(request) .assertCode(200) .assertSuccessful(); assertEquals("abc", server.takeRequest().getBody().readUtf8()); } @Test public void expect100ContinueEmptyRequestBody() throws Exception { server.enqueue(new MockResponse()); Request request = new Request.Builder() .url(server.url("/")) .header("Expect", "100-continue") .post(RequestBody.create(MediaType.parse("text/plain"), "")) .build(); executeSynchronously(request) .assertCode(200) .assertSuccessful(); } /** We forbid non-ASCII characters in outgoing request headers, but accept UTF-8. */ @Test public void responseHeaderParsingIsLenient() throws Exception { Headers headers = new Headers.Builder() .add("Content-Length", "0") .addLenient("a\tb: c\u007fd") .addLenient(": ef") .addLenient("\ud83c\udf69: \u2615\ufe0f") .build(); server.enqueue(new MockResponse().setHeaders(headers)); Request request = new Request.Builder() .url(server.url("/")) .build(); executeSynchronously(request) .assertHeader("a\tb", "c\u007fd") .assertHeader("\ud83c\udf69", "\u2615\ufe0f") .assertHeader("", "ef"); } @Test public void customDns() throws Exception { // Configure a DNS that returns our MockWebServer for every hostname. FakeDns dns = new FakeDns(); dns.addresses(Dns.SYSTEM.lookup(server.url("/").host())); client.setDns(dns); server.enqueue(new MockResponse()); Request request = new Request.Builder() .url(server.url("/").newBuilder().host("android.com").build()) .build(); executeSynchronously(request).assertCode(200); dns.assertRequests("android.com"); } /** We had a bug where failed HTTP/2 calls could break the entire connection. */ @Test public void failingCallsDoNotInterfereWithConnection() throws Exception { enableProtocol(Protocol.HTTP_2); server.enqueue(new MockResponse().setBody("Response 1")); server.enqueue(new MockResponse().setBody("Response 2")); RequestBody requestBody = new RequestBody() { @Override public MediaType contentType() { return null; } @Override public void writeTo(BufferedSink sink) throws IOException { sink.writeUtf8("abc"); sink.flush(); makeFailingCall(); sink.writeUtf8("def"); sink.flush(); } }; Call call = client.newCall(new Request.Builder() .url(server.url("/")) .post(requestBody) .build()); assertEquals("Response 1", call.execute().body().string()); } /** Test which headers are sent unencrypted to the HTTP proxy. */ @Test public void proxyConnectOmitsApplicationHeaders() throws Exception { server.useHttps(sslContext.getSocketFactory(), true); server.enqueue(new MockResponse() .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END) .clearHeaders()); server.enqueue(new MockResponse() .setBody("encrypted response from the origin server")); client.setSslSocketFactory(sslContext.getSocketFactory()); client.setProxy(server.toProxyAddress()); RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier(); client.setHostnameVerifier(hostnameVerifier); Request request = new Request.Builder() .url("https://android.com/foo") .header("Private", "Secret") .header("User-Agent", "App 1.0") .build(); Response response = client.newCall(request).execute(); assertEquals("encrypted response from the origin server", response.body().string()); RecordedRequest connect = server.takeRequest(); assertNull(connect.getHeader("Private")); assertEquals(Version.userAgent(), connect.getHeader("User-Agent")); assertEquals("Keep-Alive", connect.getHeader("Proxy-Connection")); assertEquals("android.com:443", connect.getHeader("Host")); RecordedRequest get = server.takeRequest(); assertEquals("Secret", get.getHeader("Private")); assertEquals("App 1.0", get.getHeader("User-Agent")); assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls); } /** Respond to a proxy authorization challenge. */ @Test public void proxyAuthenticateOnConnect() throws Exception { server.useHttps(sslContext.getSocketFactory(), true); server.enqueue(new MockResponse() .setResponseCode(407) .addHeader("Proxy-Authenticate: Basic realm=\"localhost\"")); server.enqueue(new MockResponse() .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END) .clearHeaders()); server.enqueue(new MockResponse() .setBody("response body")); client.setSslSocketFactory(sslContext.getSocketFactory()); client.setProxy(server.toProxyAddress()); client.setAuthenticator(new RecordingOkAuthenticator("password")); client.setHostnameVerifier(new RecordingHostnameVerifier()); Request request = new Request.Builder() .url("https://android.com/foo") .build(); Response response = client.newCall(request).execute(); assertEquals("response body", response.body().string()); RecordedRequest connect1 = server.takeRequest(); assertEquals("CONNECT android.com:443 HTTP/1.1", connect1.getRequestLine()); assertNull(connect1.getHeader("Proxy-Authorization")); RecordedRequest connect2 = server.takeRequest(); assertEquals("CONNECT android.com:443 HTTP/1.1", connect2.getRequestLine()); assertEquals("password", connect2.getHeader("Proxy-Authorization")); RecordedRequest get = server.takeRequest(); assertEquals("GET /foo HTTP/1.1", get.getRequestLine()); assertNull(get.getHeader("Proxy-Authorization")); } /** * Confirm that we don't send the Proxy-Authorization header from the request to the proxy server. * We used to have that behavior but it is problematic because unrelated requests end up sharing * credentials. Worse, that approach leaks proxy credentials to the origin server. */ @Test public void noProactiveProxyAuthorization() throws Exception { server.useHttps(sslContext.getSocketFactory(), true); server.enqueue(new MockResponse() .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END) .clearHeaders()); server.enqueue(new MockResponse() .setBody("response body")); client.setSslSocketFactory(sslContext.getSocketFactory()); client.setProxy(server.toProxyAddress()); client.setHostnameVerifier(new RecordingHostnameVerifier()); Request request = new Request.Builder() .url("https://android.com/foo") .header("Proxy-Authorization", "password") .build(); Response response = client.newCall(request).execute(); assertEquals("response body", response.body().string()); RecordedRequest connect = server.takeRequest(); assertNull(connect.getHeader("Proxy-Authorization")); RecordedRequest get = server.takeRequest(); assertEquals("password", get.getHeader("Proxy-Authorization")); } /** https://github.com/square/okhttp/issues/2344 */ @Test public void ipv6HostHasSquareBraces() throws Exception { // Use a proxy to fake IPv6 connectivity, even if localhost doesn't have IPv6. server.useHttps(sslContext.getSocketFactory(), true); server.setProtocols(Collections.singletonList(Protocol.HTTP_1_1)); server.enqueue(new MockResponse() .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END) .clearHeaders()); server.enqueue(new MockResponse() .setBody("response body")); client .setSslSocketFactory(sslContext.getSocketFactory()) .setHostnameVerifier(new RecordingHostnameVerifier()) .setProxy(server.toProxyAddress()); Request request = new Request.Builder() .url("https://[::1]/") .build(); Response response = client.newCall(request).execute(); assertEquals("response body", response.body().string()); RecordedRequest connect = server.takeRequest(); assertEquals("CONNECT [::1]:443 HTTP/1.1", connect.getRequestLine()); assertEquals("[::1]:443", connect.getHeader("Host")); RecordedRequest get = server.takeRequest(); assertEquals("GET / HTTP/1.1", get.getRequestLine()); assertEquals("[::1]", get.getHeader("Host")); } private void makeFailingCall() { RequestBody requestBody = new RequestBody() { @Override public MediaType contentType() { return null; } @Override public long contentLength() throws IOException { return 1; } @Override public void writeTo(BufferedSink sink) throws IOException { throw new IOException("write body fail!"); } }; Call call = client.newCall(new Request.Builder() .url(server.url("/")) .post(requestBody) .build()); try { call.execute(); fail(); } catch (IOException expected) { assertEquals("write body fail!", expected.getMessage()); } } 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) { enableTls(); client.setProtocols(Arrays.asList(protocol, Protocol.HTTP_1_1)); server.setProtocols(client.getProtocols()); } private void enableTls() { client.setSslSocketFactory(sslContext.getSocketFactory()); client.setHostnameVerifier(new RecordingHostnameVerifier()); server.useHttps(sslContext.getSocketFactory(), false); } 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 void cancelLater(final Call call, final long delay) { new Thread("canceler") { @Override public void run() { try { Thread.sleep(delay); } catch (InterruptedException e) { throw new AssertionError(); } call.cancel(); } }.start(); } private static class RecordingSSLSocketFactory extends DelegatingSSLSocketFactory { private List socketsCreated = new ArrayList<>(); public RecordingSSLSocketFactory(SSLSocketFactory delegate) { super(delegate); } @Override protected SSLSocket configureSocket(SSLSocket sslSocket) throws IOException { socketsCreated.add(sslSocket); return sslSocket; } public List 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 void suppressTlsFallbackScsv(OkHttpClient client) { FallbackTestClientSocketFactory clientSocketFactory = new FallbackTestClientSocketFactory(sslContext.getSocketFactory()); client.setSslSocketFactory(clientSocketFactory); } }