blob: 605ca68ab23da9ba4a71245d4e97d80f2e6cec60 [file] [log] [blame]
/*
* 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();
// 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 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);
}
}