| /* |
| * 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.Ignore; |
| 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(); |
| |
| // Android-added: Use TLS 1.3 and 1.2 for testing |
| private static final ConnectionSpec TLS_SPEC_1_3 = |
| new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) |
| .tlsVersions(TlsVersion.TLS_1_3) |
| .build(); |
| |
| private static final ConnectionSpec TLS_SPEC_1_2 = |
| new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) |
| .tlsVersions(TlsVersion.TLS_1_2) |
| .build(); |
| |
| private static final List<ConnectionSpec> TLS_SPEC_NO_V1 |
| = Arrays.asList(TLS_SPEC_1_3, TLS_SPEC_1_2); |
| |
| 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); |
| // Android-added: Use TLS 1.3 and 1.2 for testing |
| client.setConnectionSpecs(TLS_SPEC_NO_V1); |
| 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<String> 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)); |
| // Android-added: Need an extra handshake fail when using TLS 1.3 and 1.2 for testing. |
| // Seems to be a testing quirk due to adding two ConnectionSpecs and has no impact |
| // on the logic being tested or the expected outcomes, so not gonna dig too deep. |
| 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()); |
| // Android-added: Use TLS 1.3 and 1.2 for testing |
| client.setConnectionSpecs(TLS_SPEC_NO_V1); |
| |
| Request request = new Request.Builder().url(server.url("/")).build(); |
| try { |
| client.newCall(request).execute(); |
| 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.useHttps(sslContext.getSocketFactory(), false); |
| server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.FAIL_HANDSHAKE)); |
| server.enqueue(new MockResponse().setBody("abc")); |
| |
| suppressTlsFallbackScsv(client); |
| client.setHostnameVerifier(new RecordingHostnameVerifier()); |
| // Android-added: Use TLS 1.3 and 1.2 for testing |
| client.setConnectionSpecs(TLS_SPEC_NO_V1); |
| |
| 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 |
| @Ignore("TODO(b/333847678 - diagnose and fix flake") |
| 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<Response> result = executor.submit(new Callable<Response>() { |
| @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 { |
| final CountDownLatch cancelSignal = new CountDownLatch(1); |
| |
| server.setDispatcher(new Dispatcher() { |
| @Override public MockResponse dispatch(RecordedRequest request) { |
| client.cancel("request"); |
| try { |
| cancelSignal.await(10L, TimeUnit.SECONDS); |
| } catch (InterruptedException e) { |
| // Do nothing |
| } |
| 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) { |
| cancelSignal.countDown(); |
| } |
| } |
| |
| @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<String> 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<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... |
| 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<SSLSocket> socketsCreated = new ArrayList<>(); |
| |
| public RecordingSSLSocketFactory(SSLSocketFactory delegate) { |
| super(delegate); |
| } |
| |
| @Override |
| protected SSLSocket configureSocket(SSLSocket sslSocket) throws IOException { |
| socketsCreated.add(sslSocket); |
| return 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 void suppressTlsFallbackScsv(OkHttpClient client) { |
| FallbackTestClientSocketFactory clientSocketFactory = |
| new FallbackTestClientSocketFactory(sslContext.getSocketFactory()); |
| client.setSslSocketFactory(clientSocketFactory); |
| } |
| } |