mirror of
https://github.com/square/okhttp.git
synced 2026-01-15 20:56:41 +03:00
Previously we would sometimes delegate to the asynchronous implementation, and that would prevent exceptions from reaching their destination.
3741 lines
143 KiB
Java
3741 lines
143 KiB
Java
/*
|
|
* Copyright (C) 2009 The Android Open Source Project
|
|
*
|
|
* 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 okhttp3;
|
|
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.net.Authenticator;
|
|
import java.net.ConnectException;
|
|
import java.net.HttpRetryException;
|
|
import java.net.HttpURLConnection;
|
|
import java.net.InetAddress;
|
|
import java.net.ProtocolException;
|
|
import java.net.Proxy;
|
|
import java.net.ProxySelector;
|
|
import java.net.ServerSocket;
|
|
import java.net.Socket;
|
|
import java.net.SocketAddress;
|
|
import java.net.SocketTimeoutException;
|
|
import java.net.URI;
|
|
import java.net.URL;
|
|
import java.net.URLConnection;
|
|
import java.net.UnknownHostException;
|
|
import java.security.KeyStore;
|
|
import java.security.cert.CertificateException;
|
|
import java.security.cert.X509Certificate;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.EnumSet;
|
|
import java.util.LinkedHashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Random;
|
|
import java.util.Set;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.zip.GZIPInputStream;
|
|
import javax.net.ServerSocketFactory;
|
|
import javax.net.SocketFactory;
|
|
import javax.net.ssl.HttpsURLConnection;
|
|
import javax.net.ssl.SSLContext;
|
|
import javax.net.ssl.SSLException;
|
|
import javax.net.ssl.SSLHandshakeException;
|
|
import javax.net.ssl.SSLSocketFactory;
|
|
import javax.net.ssl.TrustManager;
|
|
import javax.net.ssl.TrustManagerFactory;
|
|
import javax.net.ssl.X509TrustManager;
|
|
import okhttp3.internal.DoubleInetAddressDns;
|
|
import okhttp3.internal.Internal;
|
|
import okhttp3.internal.RecordingAuthenticator;
|
|
import okhttp3.internal.RecordingOkAuthenticator;
|
|
import okhttp3.internal.SingleInetAddressDns;
|
|
import okhttp3.internal.Util;
|
|
import okhttp3.internal.Version;
|
|
import okhttp3.internal.tls.SslClient;
|
|
import okhttp3.mockwebserver.MockResponse;
|
|
import okhttp3.mockwebserver.MockWebServer;
|
|
import okhttp3.mockwebserver.RecordedRequest;
|
|
import okhttp3.mockwebserver.SocketPolicy;
|
|
import okio.Buffer;
|
|
import okio.BufferedSink;
|
|
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.TemporaryFolder;
|
|
|
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
|
import static java.util.concurrent.TimeUnit.NANOSECONDS;
|
|
import static okhttp3.TestUtil.defaultClient;
|
|
import static okhttp3.internal.Util.UTF_8;
|
|
import static okhttp3.internal.http.StatusLine.HTTP_PERM_REDIRECT;
|
|
import static okhttp3.internal.http.StatusLine.HTTP_TEMP_REDIRECT;
|
|
import static okhttp3.internal.huc.OkHttpURLConnection.SELECTED_PROTOCOL;
|
|
import static okhttp3.mockwebserver.SocketPolicy.DISCONNECT_AFTER_REQUEST;
|
|
import static okhttp3.mockwebserver.SocketPolicy.DISCONNECT_AT_END;
|
|
import static okhttp3.mockwebserver.SocketPolicy.DISCONNECT_AT_START;
|
|
import static okhttp3.mockwebserver.SocketPolicy.FAIL_HANDSHAKE;
|
|
import static okhttp3.mockwebserver.SocketPolicy.SHUTDOWN_INPUT_AT_END;
|
|
import static okhttp3.mockwebserver.SocketPolicy.SHUTDOWN_OUTPUT_AT_END;
|
|
import static okhttp3.mockwebserver.SocketPolicy.UPGRADE_TO_SSL_AT_END;
|
|
import static org.junit.Assert.assertEquals;
|
|
import static org.junit.Assert.assertFalse;
|
|
import static org.junit.Assert.assertNotNull;
|
|
import static org.junit.Assert.assertNull;
|
|
import static org.junit.Assert.assertTrue;
|
|
import static org.junit.Assert.fail;
|
|
import static org.junit.Assume.assumeTrue;
|
|
|
|
/** Android's URLConnectionTest. */
|
|
public final class URLConnectionTest {
|
|
@Rule public final MockWebServer server = new MockWebServer();
|
|
@Rule public final MockWebServer server2 = new MockWebServer();
|
|
@Rule public final TemporaryFolder tempDir = new TemporaryFolder();
|
|
|
|
private SslClient sslClient = SslClient.localhost();
|
|
private OkUrlFactory urlFactory;
|
|
private HttpURLConnection connection;
|
|
private Cache cache;
|
|
|
|
@Before public void setUp() throws Exception {
|
|
server.setProtocolNegotiationEnabled(false);
|
|
urlFactory = new OkUrlFactory(defaultClient());
|
|
}
|
|
|
|
@After public void tearDown() throws Exception {
|
|
Authenticator.setDefault(null);
|
|
System.clearProperty("proxyHost");
|
|
System.clearProperty("proxyPort");
|
|
System.clearProperty("http.agent");
|
|
System.clearProperty("http.proxyHost");
|
|
System.clearProperty("http.proxyPort");
|
|
System.clearProperty("https.proxyHost");
|
|
System.clearProperty("https.proxyPort");
|
|
if (cache != null) {
|
|
cache.delete();
|
|
}
|
|
}
|
|
|
|
@Test public void requestHeaders() throws IOException, InterruptedException {
|
|
server.enqueue(new MockResponse());
|
|
|
|
connection = urlFactory.open(server.url("/").url());
|
|
connection.addRequestProperty("D", "e");
|
|
connection.addRequestProperty("D", "f");
|
|
assertEquals("f", connection.getRequestProperty("D"));
|
|
assertEquals("f", connection.getRequestProperty("d"));
|
|
Map<String, List<String>> requestHeaders = connection.getRequestProperties();
|
|
assertEquals(newSet("e", "f"), new LinkedHashSet<>(requestHeaders.get("D")));
|
|
assertEquals(newSet("e", "f"), new LinkedHashSet<>(requestHeaders.get("d")));
|
|
try {
|
|
requestHeaders.put("G", Arrays.asList("h"));
|
|
fail("Modified an unmodifiable view.");
|
|
} catch (UnsupportedOperationException expected) {
|
|
}
|
|
try {
|
|
requestHeaders.get("D").add("i");
|
|
fail("Modified an unmodifiable view.");
|
|
} catch (UnsupportedOperationException expected) {
|
|
}
|
|
try {
|
|
connection.setRequestProperty(null, "j");
|
|
fail();
|
|
} catch (NullPointerException expected) {
|
|
}
|
|
try {
|
|
connection.addRequestProperty(null, "k");
|
|
fail();
|
|
} catch (NullPointerException expected) {
|
|
}
|
|
connection.setRequestProperty("NullValue", null);
|
|
assertNull(connection.getRequestProperty("NullValue"));
|
|
connection.addRequestProperty("AnotherNullValue", null);
|
|
assertNull(connection.getRequestProperty("AnotherNullValue"));
|
|
|
|
connection.getResponseCode();
|
|
RecordedRequest request = server.takeRequest();
|
|
assertEquals(Arrays.asList("e", "f"), request.getHeaders().values("D"));
|
|
assertNull(request.getHeader("NullValue"));
|
|
assertNull(request.getHeader("AnotherNullValue"));
|
|
assertNull(request.getHeader("G"));
|
|
assertNull(request.getHeader("null"));
|
|
|
|
try {
|
|
connection.addRequestProperty("N", "o");
|
|
fail("Set header after connect");
|
|
} catch (IllegalStateException expected) {
|
|
}
|
|
try {
|
|
connection.setRequestProperty("P", "q");
|
|
fail("Set header after connect");
|
|
} catch (IllegalStateException expected) {
|
|
}
|
|
try {
|
|
connection.getRequestProperties();
|
|
fail();
|
|
} catch (IllegalStateException expected) {
|
|
}
|
|
}
|
|
|
|
@Test public void getRequestPropertyReturnsLastValue() throws Exception {
|
|
connection = urlFactory.open(server.url("/").url());
|
|
connection.addRequestProperty("A", "value1");
|
|
connection.addRequestProperty("A", "value2");
|
|
assertEquals("value2", connection.getRequestProperty("A"));
|
|
}
|
|
|
|
@Test public void responseHeaders() throws IOException, InterruptedException {
|
|
server.enqueue(new MockResponse().setStatus("HTTP/1.0 200 Fantastic")
|
|
.addHeader("A: c")
|
|
.addHeader("B: d")
|
|
.addHeader("A: e")
|
|
.setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8));
|
|
|
|
connection = urlFactory.open(server.url("/").url());
|
|
assertEquals(200, connection.getResponseCode());
|
|
assertEquals("Fantastic", connection.getResponseMessage());
|
|
assertEquals("HTTP/1.0 200 Fantastic", connection.getHeaderField(null));
|
|
Map<String, List<String>> responseHeaders = connection.getHeaderFields();
|
|
assertEquals(Arrays.asList("HTTP/1.0 200 Fantastic"), responseHeaders.get(null));
|
|
assertEquals(newSet("c", "e"), new LinkedHashSet<>(responseHeaders.get("A")));
|
|
assertEquals(newSet("c", "e"), new LinkedHashSet<>(responseHeaders.get("a")));
|
|
try {
|
|
responseHeaders.put("N", Arrays.asList("o"));
|
|
fail("Modified an unmodifiable view.");
|
|
} catch (UnsupportedOperationException expected) {
|
|
}
|
|
try {
|
|
responseHeaders.get("A").add("f");
|
|
fail("Modified an unmodifiable view.");
|
|
} catch (UnsupportedOperationException expected) {
|
|
}
|
|
assertEquals("A", connection.getHeaderFieldKey(0));
|
|
assertEquals("c", connection.getHeaderField(0));
|
|
assertEquals("B", connection.getHeaderFieldKey(1));
|
|
assertEquals("d", connection.getHeaderField(1));
|
|
assertEquals("A", connection.getHeaderFieldKey(2));
|
|
assertEquals("e", connection.getHeaderField(2));
|
|
connection.getInputStream().close();
|
|
}
|
|
|
|
@Test public void serverSendsInvalidResponseHeaders() throws Exception {
|
|
server.enqueue(new MockResponse().setStatus("HTP/1.1 200 OK"));
|
|
|
|
connection = urlFactory.open(server.url("/").url());
|
|
try {
|
|
connection.getResponseCode();
|
|
fail();
|
|
} catch (IOException expected) {
|
|
}
|
|
}
|
|
|
|
@Test public void serverSendsInvalidCodeTooLarge() throws Exception {
|
|
server.enqueue(new MockResponse().setStatus("HTTP/1.1 2147483648 OK"));
|
|
|
|
connection = urlFactory.open(server.url("/").url());
|
|
try {
|
|
connection.getResponseCode();
|
|
fail();
|
|
} catch (IOException expected) {
|
|
}
|
|
}
|
|
|
|
@Test public void serverSendsInvalidCodeNotANumber() throws Exception {
|
|
server.enqueue(new MockResponse().setStatus("HTTP/1.1 00a OK"));
|
|
|
|
connection = urlFactory.open(server.url("/").url());
|
|
try {
|
|
connection.getResponseCode();
|
|
fail();
|
|
} catch (IOException expected) {
|
|
}
|
|
}
|
|
|
|
@Test public void serverSendsUnnecessaryWhitespace() throws Exception {
|
|
server.enqueue(new MockResponse().setStatus(" HTTP/1.1 2147483648 OK"));
|
|
|
|
connection = urlFactory.open(server.url("/").url());
|
|
try {
|
|
connection.getResponseCode();
|
|
fail();
|
|
} catch (IOException expected) {
|
|
}
|
|
}
|
|
|
|
@Test public void connectRetriesUntilConnectedOrFailed() throws Exception {
|
|
URL url = server.url("/foo").url();
|
|
server.shutdown();
|
|
|
|
connection = urlFactory.open(url);
|
|
try {
|
|
connection.connect();
|
|
fail();
|
|
} catch (IOException expected) {
|
|
}
|
|
}
|
|
|
|
@Test public void requestBodySurvivesRetriesWithFixedLength() throws Exception {
|
|
testRequestBodySurvivesRetries(TransferKind.FIXED_LENGTH);
|
|
}
|
|
|
|
@Test public void requestBodySurvivesRetriesWithChunkedStreaming() throws Exception {
|
|
testRequestBodySurvivesRetries(TransferKind.CHUNKED);
|
|
}
|
|
|
|
@Test public void requestBodySurvivesRetriesWithBufferedBody() throws Exception {
|
|
testRequestBodySurvivesRetries(TransferKind.END_OF_STREAM);
|
|
}
|
|
|
|
private void testRequestBodySurvivesRetries(TransferKind transferKind) throws Exception {
|
|
server.enqueue(new MockResponse().setBody("abc"));
|
|
|
|
// Use a misconfigured proxy to guarantee that the request is retried.
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.proxySelector(new FakeProxySelector()
|
|
.addProxy(server2.toProxyAddress()))
|
|
.build());
|
|
server2.shutdown();
|
|
|
|
connection = urlFactory.open(server.url("/def").url());
|
|
connection.setDoOutput(true);
|
|
transferKind.setForRequest(connection, 4);
|
|
connection.getOutputStream().write("body".getBytes("UTF-8"));
|
|
assertContent("abc", connection);
|
|
|
|
assertEquals("body", server.takeRequest().getBody().readUtf8());
|
|
}
|
|
|
|
@Test public void streamedBodyIsNotRetried() throws Exception {
|
|
server.enqueue(new MockResponse()
|
|
.setSocketPolicy(SocketPolicy.DISCONNECT_AFTER_REQUEST));
|
|
|
|
urlFactory = new OkUrlFactory(new OkHttpClient.Builder()
|
|
.dns(new DoubleInetAddressDns())
|
|
.build());
|
|
HttpURLConnection connection = urlFactory.open(server.url("/").url());
|
|
connection.setDoOutput(true);
|
|
connection.setChunkedStreamingMode(100);
|
|
OutputStream os = connection.getOutputStream();
|
|
os.write("OutputStream is no fun.".getBytes("UTF-8"));
|
|
os.close();
|
|
|
|
try {
|
|
connection.getResponseCode();
|
|
fail();
|
|
} catch (IOException expected) {
|
|
}
|
|
|
|
assertEquals(1, server.getRequestCount());
|
|
}
|
|
|
|
@Test public void getErrorStreamOnSuccessfulRequest() throws Exception {
|
|
server.enqueue(new MockResponse().setBody("A"));
|
|
connection = urlFactory.open(server.url("/").url());
|
|
assertNull(connection.getErrorStream());
|
|
connection.getInputStream().close();
|
|
}
|
|
|
|
@Test public void getErrorStreamOnUnsuccessfulRequest() throws Exception {
|
|
server.enqueue(new MockResponse().setResponseCode(404).setBody("A"));
|
|
connection = urlFactory.open(server.url("/").url());
|
|
assertEquals("A", readAscii(connection.getErrorStream(), Integer.MAX_VALUE));
|
|
}
|
|
|
|
// Check that if we don't read to the end of a response, the next request on the
|
|
// recycled connection doesn't get the unread tail of the first request's response.
|
|
// http://code.google.com/p/android/issues/detail?id=2939
|
|
@Test public void bug2939() throws Exception {
|
|
MockResponse response = new MockResponse().setChunkedBody("ABCDE\nFGHIJ\nKLMNO\nPQR", 8);
|
|
|
|
server.enqueue(response);
|
|
server.enqueue(response);
|
|
|
|
HttpURLConnection c1 = urlFactory.open(server.url("/").url());
|
|
assertContent("ABCDE", c1, 5);
|
|
HttpURLConnection c2 = urlFactory.open(server.url("/").url());
|
|
assertContent("ABCDE", c2, 5);
|
|
|
|
c1.getInputStream().close();
|
|
c2.getInputStream().close();
|
|
}
|
|
|
|
// Check that we recognize a few basic mime types by extension.
|
|
// http://code.google.com/p/android/issues/detail?id=10100
|
|
@Test public void bug10100() throws Exception {
|
|
assertEquals("image/jpeg", URLConnection.guessContentTypeFromName("someFile.jpg"));
|
|
assertEquals("application/pdf", URLConnection.guessContentTypeFromName("stuff.pdf"));
|
|
}
|
|
|
|
@Test public void connectionsArePooled() throws Exception {
|
|
MockResponse response = new MockResponse().setBody("ABCDEFGHIJKLMNOPQR");
|
|
|
|
server.enqueue(response);
|
|
server.enqueue(response);
|
|
server.enqueue(response);
|
|
|
|
assertContent("ABCDEFGHIJKLMNOPQR", urlFactory.open(server.url("/foo").url()));
|
|
assertEquals(0, server.takeRequest().getSequenceNumber());
|
|
assertContent("ABCDEFGHIJKLMNOPQR", urlFactory.open(server.url("/bar?baz=quux").url()));
|
|
assertEquals(1, server.takeRequest().getSequenceNumber());
|
|
assertContent("ABCDEFGHIJKLMNOPQR", urlFactory.open(server.url("/z").url()));
|
|
assertEquals(2, server.takeRequest().getSequenceNumber());
|
|
}
|
|
|
|
@Test public void chunkedConnectionsArePooled() throws Exception {
|
|
MockResponse response = new MockResponse().setChunkedBody("ABCDEFGHIJKLMNOPQR", 5);
|
|
|
|
server.enqueue(response);
|
|
server.enqueue(response);
|
|
server.enqueue(response);
|
|
|
|
assertContent("ABCDEFGHIJKLMNOPQR", urlFactory.open(server.url("/foo").url()));
|
|
assertEquals(0, server.takeRequest().getSequenceNumber());
|
|
assertContent("ABCDEFGHIJKLMNOPQR", urlFactory.open(server.url("/bar?baz=quux").url()));
|
|
assertEquals(1, server.takeRequest().getSequenceNumber());
|
|
assertContent("ABCDEFGHIJKLMNOPQR", urlFactory.open(server.url("/z").url()));
|
|
assertEquals(2, server.takeRequest().getSequenceNumber());
|
|
}
|
|
|
|
@Test public void serverClosesSocket() throws Exception {
|
|
testServerClosesOutput(DISCONNECT_AT_END);
|
|
}
|
|
|
|
@Test public void serverShutdownInput() throws Exception {
|
|
testServerClosesOutput(SHUTDOWN_INPUT_AT_END);
|
|
}
|
|
|
|
@Test public void serverShutdownOutput() throws Exception {
|
|
testServerClosesOutput(SHUTDOWN_OUTPUT_AT_END);
|
|
}
|
|
|
|
@Test public void invalidHost() throws Exception {
|
|
// Note that 1234.1.1.1 is an invalid host in a URI, but URL isn't as strict.
|
|
URL url = new URL("http://1234.1.1.1/index.html");
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.dns(new FakeDns())
|
|
.build());
|
|
HttpURLConnection connection = urlFactory.open(url);
|
|
try {
|
|
connection.connect();
|
|
fail();
|
|
} catch (UnknownHostException expected) {
|
|
}
|
|
}
|
|
|
|
private void testServerClosesOutput(SocketPolicy socketPolicy) throws Exception {
|
|
server.enqueue(new MockResponse().setBody("This connection won't pool properly")
|
|
.setSocketPolicy(socketPolicy));
|
|
MockResponse responseAfter = new MockResponse().setBody("This comes after a busted connection");
|
|
server.enqueue(responseAfter);
|
|
server.enqueue(responseAfter); // Enqueue 2x because the broken connection may be reused.
|
|
|
|
HttpURLConnection connection1 = urlFactory.open(server.url("/a").url());
|
|
connection1.setReadTimeout(100);
|
|
assertContent("This connection won't pool properly", connection1);
|
|
assertEquals(0, server.takeRequest().getSequenceNumber());
|
|
|
|
// Give the server time to enact the socket policy if it's one that could happen after the
|
|
// client has received the response.
|
|
Thread.sleep(500);
|
|
|
|
HttpURLConnection connection2 = urlFactory.open(server.url("/b").url());
|
|
connection2.setReadTimeout(100);
|
|
assertContent("This comes after a busted connection", connection2);
|
|
|
|
// Check that a fresh connection was created, either immediately or after attempting reuse.
|
|
RecordedRequest requestAfter = server.takeRequest();
|
|
if (server.getRequestCount() == 3) {
|
|
requestAfter = server.takeRequest(); // The failure consumed a response.
|
|
}
|
|
// sequence number 0 means the HTTP socket connection was not reused
|
|
assertEquals(0, requestAfter.getSequenceNumber());
|
|
}
|
|
|
|
enum WriteKind {BYTE_BY_BYTE, SMALL_BUFFERS, LARGE_BUFFERS}
|
|
|
|
@Test public void chunkedUpload_byteByByte() throws Exception {
|
|
doUpload(TransferKind.CHUNKED, WriteKind.BYTE_BY_BYTE);
|
|
}
|
|
|
|
@Test public void chunkedUpload_smallBuffers() throws Exception {
|
|
doUpload(TransferKind.CHUNKED, WriteKind.SMALL_BUFFERS);
|
|
}
|
|
|
|
@Test public void chunkedUpload_largeBuffers() throws Exception {
|
|
doUpload(TransferKind.CHUNKED, WriteKind.LARGE_BUFFERS);
|
|
}
|
|
|
|
@Test public void fixedLengthUpload_byteByByte() throws Exception {
|
|
doUpload(TransferKind.FIXED_LENGTH, WriteKind.BYTE_BY_BYTE);
|
|
}
|
|
|
|
@Test public void fixedLengthUpload_smallBuffers() throws Exception {
|
|
doUpload(TransferKind.FIXED_LENGTH, WriteKind.SMALL_BUFFERS);
|
|
}
|
|
|
|
@Test public void fixedLengthUpload_largeBuffers() throws Exception {
|
|
doUpload(TransferKind.FIXED_LENGTH, WriteKind.LARGE_BUFFERS);
|
|
}
|
|
|
|
private void doUpload(TransferKind uploadKind, WriteKind writeKind) throws Exception {
|
|
int n = 512 * 1024;
|
|
server.setBodyLimit(0);
|
|
server.enqueue(new MockResponse());
|
|
|
|
HttpURLConnection conn = urlFactory.open(server.url("/").url());
|
|
conn.setDoOutput(true);
|
|
conn.setRequestMethod("POST");
|
|
if (uploadKind == TransferKind.CHUNKED) {
|
|
conn.setChunkedStreamingMode(-1);
|
|
} else {
|
|
conn.setFixedLengthStreamingMode(n);
|
|
}
|
|
OutputStream out = conn.getOutputStream();
|
|
if (writeKind == WriteKind.BYTE_BY_BYTE) {
|
|
for (int i = 0; i < n; ++i) {
|
|
out.write('x');
|
|
}
|
|
} else {
|
|
byte[] buf = new byte[writeKind == WriteKind.SMALL_BUFFERS ? 256 : 64 * 1024];
|
|
Arrays.fill(buf, (byte) 'x');
|
|
for (int i = 0; i < n; i += buf.length) {
|
|
out.write(buf, 0, Math.min(buf.length, n - i));
|
|
}
|
|
}
|
|
out.close();
|
|
assertEquals(200, conn.getResponseCode());
|
|
RecordedRequest request = server.takeRequest();
|
|
assertEquals(n, request.getBodySize());
|
|
if (uploadKind == TransferKind.CHUNKED) {
|
|
assertTrue(request.getChunkSizes().size() > 0);
|
|
} else {
|
|
assertTrue(request.getChunkSizes().isEmpty());
|
|
}
|
|
}
|
|
|
|
@Test public void getResponseCodeNoResponseBody() throws Exception {
|
|
server.enqueue(new MockResponse().addHeader("abc: def"));
|
|
|
|
URL url = server.url("/").url();
|
|
HttpURLConnection conn = urlFactory.open(url);
|
|
conn.setDoInput(false);
|
|
assertEquals("def", conn.getHeaderField("abc"));
|
|
assertEquals(200, conn.getResponseCode());
|
|
try {
|
|
conn.getInputStream();
|
|
fail();
|
|
} catch (ProtocolException expected) {
|
|
}
|
|
}
|
|
|
|
@Test public void connectViaHttps() throws Exception {
|
|
server.useHttps(sslClient.socketFactory, false);
|
|
server.enqueue(new MockResponse().setBody("this response comes via HTTPS"));
|
|
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.sslSocketFactory(sslClient.socketFactory, sslClient.trustManager)
|
|
.hostnameVerifier(new RecordingHostnameVerifier())
|
|
.build());
|
|
connection = urlFactory.open(server.url("/foo").url());
|
|
|
|
assertContent("this response comes via HTTPS", connection);
|
|
|
|
RecordedRequest request = server.takeRequest();
|
|
assertEquals("GET /foo HTTP/1.1", request.getRequestLine());
|
|
}
|
|
|
|
@Test public void inspectHandshakeThroughoutRequestLifecycle() throws Exception {
|
|
server.useHttps(sslClient.socketFactory, false);
|
|
server.enqueue(new MockResponse());
|
|
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.sslSocketFactory(sslClient.socketFactory, sslClient.trustManager)
|
|
.hostnameVerifier(new RecordingHostnameVerifier())
|
|
.build());
|
|
|
|
HttpsURLConnection httpsConnection
|
|
= (HttpsURLConnection) urlFactory.open(server.url("/foo").url());
|
|
|
|
// Prior to calling connect(), getting the cipher suite is forbidden.
|
|
try {
|
|
httpsConnection.getCipherSuite();
|
|
fail();
|
|
} catch (IllegalStateException expected) {
|
|
}
|
|
|
|
// Calling connect establishes a handshake...
|
|
httpsConnection.connect();
|
|
assertNotNull(httpsConnection.getCipherSuite());
|
|
|
|
// ...which remains after we read the response body...
|
|
assertContent("", httpsConnection);
|
|
assertNotNull(httpsConnection.getCipherSuite());
|
|
|
|
// ...and after we disconnect.
|
|
httpsConnection.disconnect();
|
|
assertNotNull(httpsConnection.getCipherSuite());
|
|
}
|
|
|
|
@Test public void connectViaHttpsReusingConnections() throws IOException, InterruptedException {
|
|
server.useHttps(sslClient.socketFactory, false);
|
|
server.enqueue(new MockResponse().setBody("this response comes via HTTPS"));
|
|
server.enqueue(new MockResponse().setBody("another response via HTTPS"));
|
|
|
|
// The pool will only reuse sockets if the SSL socket factories are the same.
|
|
SSLSocketFactory clientSocketFactory = sslClient.socketFactory;
|
|
RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
|
|
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.sslSocketFactory(clientSocketFactory, sslClient.trustManager)
|
|
.hostnameVerifier(hostnameVerifier)
|
|
.build());
|
|
connection = urlFactory.open(server.url("/").url());
|
|
assertContent("this response comes via HTTPS", connection);
|
|
|
|
connection = urlFactory.open(server.url("/").url());
|
|
assertContent("another response via HTTPS", connection);
|
|
|
|
assertEquals(0, server.takeRequest().getSequenceNumber());
|
|
assertEquals(1, server.takeRequest().getSequenceNumber());
|
|
}
|
|
|
|
@Test public void connectViaHttpsReusingConnectionsDifferentFactories() throws Exception {
|
|
server.useHttps(sslClient.socketFactory, false);
|
|
server.enqueue(new MockResponse().setBody("this response comes via HTTPS"));
|
|
server.enqueue(new MockResponse().setBody("another response via HTTPS"));
|
|
|
|
// install a custom SSL socket factory so the server can be authorized
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.sslSocketFactory(sslClient.socketFactory, sslClient.trustManager)
|
|
.hostnameVerifier(new RecordingHostnameVerifier())
|
|
.build());
|
|
HttpURLConnection connection1 = urlFactory.open(server.url("/").url());
|
|
assertContent("this response comes via HTTPS", connection1);
|
|
|
|
SSLContext sslContext2 = SSLContext.getInstance("TLS");
|
|
sslContext2.init(null, null, null);
|
|
SSLSocketFactory sslSocketFactory2 = sslContext2.getSocketFactory();
|
|
|
|
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
|
|
TrustManagerFactory.getDefaultAlgorithm());
|
|
trustManagerFactory.init((KeyStore) null);
|
|
X509TrustManager trustManager = (X509TrustManager) trustManagerFactory.getTrustManagers()[0];
|
|
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.sslSocketFactory(sslSocketFactory2, trustManager)
|
|
.build());
|
|
HttpURLConnection connection2 = urlFactory.open(server.url("/").url());
|
|
try {
|
|
readAscii(connection2.getInputStream(), Integer.MAX_VALUE);
|
|
fail("without an SSL socket factory, the connection should fail");
|
|
} catch (SSLException expected) {
|
|
}
|
|
}
|
|
|
|
// TODO(jwilson): tests below this marker need to be migrated to OkHttp's request/response API.
|
|
|
|
@Test public void connectViaHttpsWithSSLFallback() throws Exception {
|
|
server.useHttps(sslClient.socketFactory, false);
|
|
server.enqueue(new MockResponse().setSocketPolicy(FAIL_HANDSHAKE));
|
|
server.enqueue(new MockResponse().setBody("this response comes via SSL"));
|
|
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.hostnameVerifier(new RecordingHostnameVerifier())
|
|
.sslSocketFactory(suppressTlsFallbackClientSocketFactory(), sslClient.trustManager)
|
|
.build());
|
|
connection = urlFactory.open(server.url("/foo").url());
|
|
|
|
assertContent("this response comes via SSL", connection);
|
|
|
|
RecordedRequest failHandshakeRequest = server.takeRequest();
|
|
assertNull(failHandshakeRequest.getRequestLine());
|
|
|
|
RecordedRequest fallbackRequest = server.takeRequest();
|
|
assertEquals("GET /foo HTTP/1.1", fallbackRequest.getRequestLine());
|
|
assertEquals(TlsVersion.TLS_1_0, fallbackRequest.getTlsVersion());
|
|
}
|
|
|
|
@Test public void connectViaHttpsWithSSLFallbackFailuresRecorded() throws Exception {
|
|
server.useHttps(sslClient.socketFactory, false);
|
|
server.enqueue(new MockResponse().setSocketPolicy(FAIL_HANDSHAKE));
|
|
server.enqueue(new MockResponse().setSocketPolicy(FAIL_HANDSHAKE));
|
|
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.dns(new SingleInetAddressDns())
|
|
.hostnameVerifier(new RecordingHostnameVerifier())
|
|
.sslSocketFactory(suppressTlsFallbackClientSocketFactory(), sslClient.trustManager)
|
|
.build());
|
|
connection = urlFactory.open(server.url("/foo").url());
|
|
|
|
try {
|
|
connection.getResponseCode();
|
|
fail();
|
|
} catch (IOException expected) {
|
|
assertEquals(1, expected.getSuppressed().length);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* When a pooled connection fails, don't blame the route. Otherwise pooled connection failures can
|
|
* cause unnecessary SSL fallbacks.
|
|
*
|
|
* https://github.com/square/okhttp/issues/515
|
|
*/
|
|
@Test public void sslFallbackNotUsedWhenRecycledConnectionFails() throws Exception {
|
|
server.useHttps(sslClient.socketFactory, false);
|
|
server.enqueue(new MockResponse()
|
|
.setBody("abc")
|
|
.setSocketPolicy(DISCONNECT_AT_END));
|
|
server.enqueue(new MockResponse().setBody("def"));
|
|
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.hostnameVerifier(new RecordingHostnameVerifier())
|
|
.sslSocketFactory(suppressTlsFallbackClientSocketFactory(), sslClient.trustManager)
|
|
.build());
|
|
|
|
assertContent("abc", urlFactory.open(server.url("/").url()));
|
|
|
|
// Give the server time to disconnect.
|
|
Thread.sleep(500);
|
|
|
|
assertContent("def", urlFactory.open(server.url("/").url()));
|
|
|
|
Set<TlsVersion> tlsVersions =
|
|
EnumSet.of(TlsVersion.TLS_1_0, TlsVersion.TLS_1_2); // v1.2 on OpenJDK 8.
|
|
|
|
RecordedRequest request1 = server.takeRequest();
|
|
assertTrue(tlsVersions.contains(request1.getTlsVersion()));
|
|
|
|
RecordedRequest request2 = server.takeRequest();
|
|
assertTrue(tlsVersions.contains(request2.getTlsVersion()));
|
|
}
|
|
|
|
/**
|
|
* Verify that we don't retry connections on certificate verification errors.
|
|
*
|
|
* http://code.google.com/p/android/issues/detail?id=13178
|
|
*/
|
|
@Test public void connectViaHttpsToUntrustedServer() throws IOException, InterruptedException {
|
|
server.useHttps(sslClient.socketFactory, false);
|
|
server.enqueue(new MockResponse()); // unused
|
|
|
|
connection = urlFactory.open(server.url("/foo").url());
|
|
try {
|
|
connection.getInputStream();
|
|
fail();
|
|
} catch (SSLHandshakeException expected) {
|
|
assertTrue(expected.getCause() instanceof CertificateException);
|
|
}
|
|
assertEquals(0, server.getRequestCount());
|
|
}
|
|
|
|
@Test public void connectViaProxyUsingProxyArg() throws Exception {
|
|
testConnectViaProxy(ProxyConfig.CREATE_ARG);
|
|
}
|
|
|
|
@Test public void connectViaProxyUsingProxySystemProperty() throws Exception {
|
|
testConnectViaProxy(ProxyConfig.PROXY_SYSTEM_PROPERTY);
|
|
}
|
|
|
|
@Test public void connectViaProxyUsingHttpProxySystemProperty() throws Exception {
|
|
testConnectViaProxy(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY);
|
|
}
|
|
|
|
private void testConnectViaProxy(ProxyConfig proxyConfig) throws Exception {
|
|
MockResponse mockResponse = new MockResponse().setBody("this response comes via a proxy");
|
|
server.enqueue(mockResponse);
|
|
|
|
URL url = new URL("http://android.com/foo");
|
|
connection = proxyConfig.connect(server, urlFactory, url);
|
|
assertContent("this response comes via a proxy", connection);
|
|
assertTrue(connection.usingProxy());
|
|
|
|
RecordedRequest request = server.takeRequest();
|
|
assertEquals("GET http://android.com/foo HTTP/1.1", request.getRequestLine());
|
|
assertEquals("android.com", request.getHeader("Host"));
|
|
}
|
|
|
|
@Test public void contentDisagreesWithContentLengthHeaderBodyTooLong() throws IOException {
|
|
server.enqueue(new MockResponse().setBody("abc\r\nYOU SHOULD NOT SEE THIS")
|
|
.clearHeaders()
|
|
.addHeader("Content-Length: 3"));
|
|
assertContent("abc", urlFactory.open(server.url("/").url()));
|
|
}
|
|
|
|
@Test public void contentDisagreesWithContentLengthHeaderBodyTooShort() throws IOException {
|
|
server.enqueue(new MockResponse().setBody("abc")
|
|
.setHeader("Content-Length", "5")
|
|
.setSocketPolicy(DISCONNECT_AT_END));
|
|
try {
|
|
readAscii(urlFactory.open(server.url("/").url()).getInputStream(), 5);
|
|
fail();
|
|
} catch (ProtocolException expected) {
|
|
}
|
|
}
|
|
|
|
public void testConnectViaSocketFactory(boolean useHttps) throws IOException {
|
|
SocketFactory uselessSocketFactory = new SocketFactory() {
|
|
public Socket createSocket() {
|
|
throw new IllegalArgumentException("useless");
|
|
}
|
|
|
|
public Socket createSocket(InetAddress host, int port) {
|
|
return null;
|
|
}
|
|
|
|
public Socket createSocket(InetAddress address, int port, InetAddress localAddress,
|
|
int localPort) {
|
|
return null;
|
|
}
|
|
|
|
public Socket createSocket(String host, int port) {
|
|
return null;
|
|
}
|
|
|
|
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
if (useHttps) {
|
|
server.useHttps(sslClient.socketFactory, false);
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.sslSocketFactory(sslClient.socketFactory, sslClient.trustManager)
|
|
.hostnameVerifier(new RecordingHostnameVerifier())
|
|
.build());
|
|
}
|
|
|
|
server.enqueue(new MockResponse().setStatus("HTTP/1.1 200 OK"));
|
|
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.socketFactory(uselessSocketFactory)
|
|
.build());
|
|
connection = urlFactory.open(server.url("/").url());
|
|
try {
|
|
connection.getResponseCode();
|
|
fail();
|
|
} catch (IllegalArgumentException expected) {
|
|
}
|
|
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.socketFactory(SocketFactory.getDefault())
|
|
.build());
|
|
connection = urlFactory.open(server.url("/").url());
|
|
assertEquals(200, connection.getResponseCode());
|
|
}
|
|
|
|
@Test public void connectHttpViaSocketFactory() throws Exception {
|
|
testConnectViaSocketFactory(false);
|
|
}
|
|
|
|
@Test public void connectHttpsViaSocketFactory() throws Exception {
|
|
testConnectViaSocketFactory(true);
|
|
}
|
|
|
|
@Test public void contentDisagreesWithChunkedHeaderBodyTooLong() throws IOException {
|
|
MockResponse mockResponse = new MockResponse();
|
|
mockResponse.setChunkedBody("abc", 3);
|
|
Buffer buffer = mockResponse.getBody();
|
|
buffer.writeUtf8("\r\nYOU SHOULD NOT SEE THIS");
|
|
mockResponse.setBody(buffer);
|
|
mockResponse.clearHeaders();
|
|
mockResponse.addHeader("Transfer-encoding: chunked");
|
|
|
|
server.enqueue(mockResponse);
|
|
|
|
assertContent("abc", urlFactory.open(server.url("/").url()));
|
|
}
|
|
|
|
@Test public void contentDisagreesWithChunkedHeaderBodyTooShort() throws IOException {
|
|
MockResponse mockResponse = new MockResponse();
|
|
mockResponse.setChunkedBody("abcde", 5);
|
|
|
|
Buffer truncatedBody = new Buffer();
|
|
Buffer fullBody = mockResponse.getBody();
|
|
truncatedBody.write(fullBody, fullBody.indexOf((byte) 'e'));
|
|
mockResponse.setBody(truncatedBody);
|
|
|
|
mockResponse.clearHeaders();
|
|
mockResponse.addHeader("Transfer-encoding: chunked");
|
|
mockResponse.setSocketPolicy(DISCONNECT_AT_END);
|
|
|
|
server.enqueue(mockResponse);
|
|
|
|
try {
|
|
readAscii(urlFactory.open(server.url("/").url()).getInputStream(), 5);
|
|
fail();
|
|
} catch (ProtocolException expected) {
|
|
}
|
|
}
|
|
|
|
@Test public void connectViaHttpProxyToHttpsUsingProxyArgWithNoProxy() throws Exception {
|
|
testConnectViaDirectProxyToHttps(ProxyConfig.NO_PROXY);
|
|
}
|
|
|
|
@Test public void connectViaHttpProxyToHttpsUsingHttpProxySystemProperty() throws Exception {
|
|
// https should not use http proxy
|
|
testConnectViaDirectProxyToHttps(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY);
|
|
}
|
|
|
|
private void testConnectViaDirectProxyToHttps(ProxyConfig proxyConfig) throws Exception {
|
|
server.useHttps(sslClient.socketFactory, false);
|
|
server.enqueue(new MockResponse().setBody("this response comes via HTTPS"));
|
|
|
|
URL url = server.url("/foo").url();
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.sslSocketFactory(sslClient.socketFactory, sslClient.trustManager)
|
|
.hostnameVerifier(new RecordingHostnameVerifier())
|
|
.build());
|
|
connection = proxyConfig.connect(server, urlFactory, url);
|
|
|
|
assertContent("this response comes via HTTPS", connection);
|
|
|
|
RecordedRequest request = server.takeRequest();
|
|
assertEquals("GET /foo HTTP/1.1", request.getRequestLine());
|
|
}
|
|
|
|
@Test public void connectViaHttpProxyToHttpsUsingProxyArg() throws Exception {
|
|
testConnectViaHttpProxyToHttps(ProxyConfig.CREATE_ARG);
|
|
}
|
|
|
|
/**
|
|
* We weren't honoring all of the appropriate proxy system properties when connecting via HTTPS.
|
|
* http://b/3097518
|
|
*/
|
|
@Test public void connectViaHttpProxyToHttpsUsingProxySystemProperty() throws Exception {
|
|
testConnectViaHttpProxyToHttps(ProxyConfig.PROXY_SYSTEM_PROPERTY);
|
|
}
|
|
|
|
@Test public void connectViaHttpProxyToHttpsUsingHttpsProxySystemProperty() throws Exception {
|
|
testConnectViaHttpProxyToHttps(ProxyConfig.HTTPS_PROXY_SYSTEM_PROPERTY);
|
|
}
|
|
|
|
/**
|
|
* We were verifying the wrong hostname when connecting to an HTTPS site through a proxy.
|
|
* http://b/3097277
|
|
*/
|
|
private void testConnectViaHttpProxyToHttps(ProxyConfig proxyConfig) throws Exception {
|
|
RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
|
|
|
|
server.useHttps(sslClient.socketFactory, true);
|
|
server.enqueue(
|
|
new MockResponse().setSocketPolicy(UPGRADE_TO_SSL_AT_END).clearHeaders());
|
|
server.enqueue(new MockResponse().setBody("this response comes via a secure proxy"));
|
|
|
|
URL url = new URL("https://android.com/foo");
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.sslSocketFactory(sslClient.socketFactory, sslClient.trustManager)
|
|
.hostnameVerifier(hostnameVerifier)
|
|
.build());
|
|
connection = proxyConfig.connect(server, urlFactory, url);
|
|
|
|
assertContent("this response comes via a secure proxy", connection);
|
|
|
|
RecordedRequest connect = server.takeRequest();
|
|
assertEquals("Connect line failure on proxy", "CONNECT android.com:443 HTTP/1.1",
|
|
connect.getRequestLine());
|
|
assertEquals("android.com:443", connect.getHeader("Host"));
|
|
|
|
RecordedRequest get = server.takeRequest();
|
|
assertEquals("GET /foo HTTP/1.1", get.getRequestLine());
|
|
assertEquals("android.com", get.getHeader("Host"));
|
|
assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls);
|
|
}
|
|
|
|
/** Tolerate bad https proxy response when using HttpResponseCache. Android bug 6754912. */
|
|
@Test public void connectViaHttpProxyToHttpsUsingBadProxyAndHttpResponseCache() throws Exception {
|
|
initResponseCache();
|
|
|
|
server.useHttps(sslClient.socketFactory, true);
|
|
// The inclusion of a body in the response to a CONNECT is key to reproducing b/6754912.
|
|
MockResponse badProxyResponse = new MockResponse()
|
|
.setSocketPolicy(UPGRADE_TO_SSL_AT_END)
|
|
.setBody("bogus proxy connect response content");
|
|
server.enqueue(badProxyResponse);
|
|
server.enqueue(new MockResponse().setBody("response"));
|
|
|
|
// Configure a single IP address for the host and a single configuration, so we only need one
|
|
// failure to fail permanently.
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.dns(new SingleInetAddressDns())
|
|
.sslSocketFactory(sslClient.socketFactory, sslClient.trustManager)
|
|
.connectionSpecs(Util.immutableList(ConnectionSpec.MODERN_TLS))
|
|
.hostnameVerifier(new RecordingHostnameVerifier())
|
|
.proxy(server.toProxyAddress())
|
|
.build());
|
|
|
|
URL url = new URL("https://android.com/foo");
|
|
connection = urlFactory.open(url);
|
|
assertContent("response", connection);
|
|
|
|
RecordedRequest connect = server.takeRequest();
|
|
assertEquals("CONNECT android.com:443 HTTP/1.1", connect.getRequestLine());
|
|
assertEquals("android.com:443", connect.getHeader("Host"));
|
|
}
|
|
|
|
private void initResponseCache() throws IOException {
|
|
cache = new Cache(tempDir.getRoot(), Integer.MAX_VALUE);
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.cache(cache)
|
|
.build());
|
|
}
|
|
|
|
/** Test which headers are sent unencrypted to the HTTP proxy. */
|
|
@Test public void proxyConnectIncludesProxyHeadersOnly()
|
|
throws IOException, InterruptedException {
|
|
RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
|
|
|
|
server.useHttps(sslClient.socketFactory, true);
|
|
server.enqueue(
|
|
new MockResponse().setSocketPolicy(UPGRADE_TO_SSL_AT_END).clearHeaders());
|
|
server.enqueue(new MockResponse().setBody("encrypted response from the origin server"));
|
|
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.proxy(server.toProxyAddress())
|
|
.sslSocketFactory(sslClient.socketFactory, sslClient.trustManager)
|
|
.hostnameVerifier(hostnameVerifier)
|
|
.build());
|
|
|
|
URL url = new URL("https://android.com/foo");
|
|
connection = urlFactory.open(url);
|
|
connection.addRequestProperty("Private", "Secret");
|
|
connection.addRequestProperty("Proxy-Authorization", "bar");
|
|
connection.addRequestProperty("User-Agent", "baz");
|
|
assertContent("encrypted response from the origin server", connection);
|
|
|
|
RecordedRequest connect = server.takeRequest();
|
|
assertNull(connect.getHeader("Private"));
|
|
assertNull(connect.getHeader("Proxy-Authorization"));
|
|
assertEquals(Version.userAgent(), connect.getHeader("User-Agent"));
|
|
assertEquals("android.com:443", connect.getHeader("Host"));
|
|
assertEquals("Keep-Alive", connect.getHeader("Proxy-Connection"));
|
|
|
|
RecordedRequest get = server.takeRequest();
|
|
assertEquals("Secret", get.getHeader("Private"));
|
|
assertEquals(Arrays.asList("verify android.com"), hostnameVerifier.calls);
|
|
}
|
|
|
|
@Test public void proxyAuthenticateOnConnect() throws Exception {
|
|
Authenticator.setDefault(new RecordingAuthenticator());
|
|
server.useHttps(sslClient.socketFactory, true);
|
|
server.enqueue(new MockResponse().setResponseCode(407)
|
|
.addHeader("Proxy-Authenticate: Basic realm=\"localhost\""));
|
|
server.enqueue(
|
|
new MockResponse().setSocketPolicy(UPGRADE_TO_SSL_AT_END).clearHeaders());
|
|
server.enqueue(new MockResponse().setBody("A"));
|
|
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.proxyAuthenticator(new JavaNetAuthenticator())
|
|
.proxy(server.toProxyAddress())
|
|
.sslSocketFactory(sslClient.socketFactory, sslClient.trustManager)
|
|
.hostnameVerifier(new RecordingHostnameVerifier())
|
|
.build());
|
|
|
|
URL url = new URL("https://android.com/foo");
|
|
connection = urlFactory.open(url);
|
|
assertContent("A", connection);
|
|
|
|
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("Basic " + RecordingAuthenticator.BASE_64_CREDENTIALS,
|
|
connect2.getHeader("Proxy-Authorization"));
|
|
|
|
RecordedRequest get = server.takeRequest();
|
|
assertEquals("GET /foo HTTP/1.1", get.getRequestLine());
|
|
assertNull(get.getHeader("Proxy-Authorization"));
|
|
}
|
|
|
|
// Don't disconnect after building a tunnel with CONNECT
|
|
// http://code.google.com/p/android/issues/detail?id=37221
|
|
@Test public void proxyWithConnectionClose() throws IOException {
|
|
server.useHttps(sslClient.socketFactory, true);
|
|
server.enqueue(
|
|
new MockResponse().setSocketPolicy(UPGRADE_TO_SSL_AT_END).clearHeaders());
|
|
server.enqueue(new MockResponse().setBody("this response comes via a proxy"));
|
|
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.proxy(server.toProxyAddress())
|
|
.sslSocketFactory(sslClient.socketFactory, sslClient.trustManager)
|
|
.hostnameVerifier(new RecordingHostnameVerifier())
|
|
.build());
|
|
|
|
URL url = new URL("https://android.com/foo");
|
|
connection = urlFactory.open(url);
|
|
connection.setRequestProperty("Connection", "close");
|
|
|
|
assertContent("this response comes via a proxy", connection);
|
|
}
|
|
|
|
@Test public void proxyWithConnectionReuse() throws IOException {
|
|
SSLSocketFactory socketFactory = sslClient.socketFactory;
|
|
RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
|
|
|
|
server.useHttps(socketFactory, true);
|
|
server.enqueue(
|
|
new MockResponse().setSocketPolicy(UPGRADE_TO_SSL_AT_END).clearHeaders());
|
|
server.enqueue(new MockResponse().setBody("response 1"));
|
|
server.enqueue(new MockResponse().setBody("response 2"));
|
|
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.proxy(server.toProxyAddress())
|
|
.sslSocketFactory(socketFactory, sslClient.trustManager)
|
|
.hostnameVerifier(hostnameVerifier)
|
|
.build());
|
|
URL url = new URL("https://android.com/foo");
|
|
assertContent("response 1", urlFactory.open(url));
|
|
assertContent("response 2", urlFactory.open(url));
|
|
}
|
|
|
|
@Test public void disconnectedConnection() throws IOException {
|
|
server.enqueue(new MockResponse()
|
|
.throttleBody(2, 100, TimeUnit.MILLISECONDS)
|
|
.setBody("ABCD"));
|
|
|
|
connection = urlFactory.open(server.url("/").url());
|
|
InputStream in = connection.getInputStream();
|
|
assertEquals('A', (char) in.read());
|
|
connection.disconnect();
|
|
try {
|
|
// Reading 'B' may succeed if it's buffered.
|
|
in.read();
|
|
|
|
// But 'C' shouldn't be buffered (the response is throttled) and this should fail.
|
|
in.read();
|
|
fail("Expected a connection closed exception");
|
|
} catch (IOException expected) {
|
|
}
|
|
in.close();
|
|
}
|
|
|
|
@Test public void disconnectBeforeConnect() throws IOException {
|
|
server.enqueue(new MockResponse().setBody("A"));
|
|
|
|
connection = urlFactory.open(server.url("/").url());
|
|
connection.disconnect();
|
|
assertContent("A", connection);
|
|
assertEquals(200, connection.getResponseCode());
|
|
}
|
|
|
|
@SuppressWarnings("deprecation") @Test public void defaultRequestProperty() throws Exception {
|
|
URLConnection.setDefaultRequestProperty("X-testSetDefaultRequestProperty", "A");
|
|
assertNull(URLConnection.getDefaultRequestProperty("X-setDefaultRequestProperty"));
|
|
}
|
|
|
|
/**
|
|
* Reads {@code count} characters from the stream. If the stream is exhausted before {@code count}
|
|
* characters can be read, the remaining characters are returned and the stream is closed.
|
|
*/
|
|
private String readAscii(InputStream in, int count) throws IOException {
|
|
StringBuilder result = new StringBuilder();
|
|
for (int i = 0; i < count; i++) {
|
|
int value = in.read();
|
|
if (value == -1) {
|
|
in.close();
|
|
break;
|
|
}
|
|
result.append((char) value);
|
|
}
|
|
return result.toString();
|
|
}
|
|
|
|
@Test public void markAndResetWithContentLengthHeader() throws IOException {
|
|
testMarkAndReset(TransferKind.FIXED_LENGTH);
|
|
}
|
|
|
|
@Test public void markAndResetWithChunkedEncoding() throws IOException {
|
|
testMarkAndReset(TransferKind.CHUNKED);
|
|
}
|
|
|
|
@Test public void markAndResetWithNoLengthHeaders() throws IOException {
|
|
testMarkAndReset(TransferKind.END_OF_STREAM);
|
|
}
|
|
|
|
private void testMarkAndReset(TransferKind transferKind) throws IOException {
|
|
MockResponse response = new MockResponse();
|
|
transferKind.setBody(response, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1024);
|
|
server.enqueue(response);
|
|
server.enqueue(response);
|
|
|
|
InputStream in = urlFactory.open(server.url("/").url()).getInputStream();
|
|
assertFalse("This implementation claims to support mark().", in.markSupported());
|
|
in.mark(5);
|
|
assertEquals("ABCDE", readAscii(in, 5));
|
|
try {
|
|
in.reset();
|
|
fail();
|
|
} catch (IOException expected) {
|
|
}
|
|
assertEquals("FGHIJKLMNOPQRSTUVWXYZ", readAscii(in, Integer.MAX_VALUE));
|
|
in.close();
|
|
assertContent("ABCDEFGHIJKLMNOPQRSTUVWXYZ", urlFactory.open(server.url("/").url()));
|
|
}
|
|
|
|
/**
|
|
* We've had a bug where we forget the HTTP response when we see response code 401. This causes a
|
|
* new HTTP request to be issued for every call into the URLConnection.
|
|
*/
|
|
@Test public void unauthorizedResponseHandling() throws IOException {
|
|
MockResponse response = new MockResponse().addHeader("WWW-Authenticate: challenge")
|
|
.setResponseCode(401) // UNAUTHORIZED
|
|
.setBody("Unauthorized");
|
|
server.enqueue(response);
|
|
server.enqueue(response);
|
|
server.enqueue(response);
|
|
|
|
URL url = server.url("/").url();
|
|
HttpURLConnection conn = urlFactory.open(url);
|
|
|
|
assertEquals(401, conn.getResponseCode());
|
|
assertEquals(401, conn.getResponseCode());
|
|
assertEquals(401, conn.getResponseCode());
|
|
assertEquals(1, server.getRequestCount());
|
|
conn.getErrorStream().close();
|
|
}
|
|
|
|
@Test public void nonHexChunkSize() throws IOException {
|
|
server.enqueue(new MockResponse().setBody("5\r\nABCDE\r\nG\r\nFGHIJKLMNOPQRSTU\r\n0\r\n\r\n")
|
|
.clearHeaders()
|
|
.addHeader("Transfer-encoding: chunked"));
|
|
|
|
URLConnection connection = urlFactory.open(server.url("/").url());
|
|
try {
|
|
readAscii(connection.getInputStream(), Integer.MAX_VALUE);
|
|
fail();
|
|
} catch (IOException e) {
|
|
}
|
|
connection.getInputStream().close();
|
|
}
|
|
|
|
@Test public void malformedChunkSize() throws IOException {
|
|
server.enqueue(new MockResponse().setBody("5:x\r\nABCDE\r\n0\r\n\r\n")
|
|
.clearHeaders()
|
|
.addHeader("Transfer-encoding: chunked"));
|
|
|
|
URLConnection connection = urlFactory.open(server.url("/").url());
|
|
try {
|
|
readAscii(connection.getInputStream(), Integer.MAX_VALUE);
|
|
fail();
|
|
} catch (IOException e) {
|
|
} finally {
|
|
connection.getInputStream().close();
|
|
}
|
|
}
|
|
|
|
@Test public void extensionAfterChunkSize() throws IOException {
|
|
server.enqueue(new MockResponse().setBody("5;x\r\nABCDE\r\n0\r\n\r\n")
|
|
.clearHeaders()
|
|
.addHeader("Transfer-encoding: chunked"));
|
|
|
|
HttpURLConnection connection = urlFactory.open(server.url("/").url());
|
|
assertContent("ABCDE", connection);
|
|
}
|
|
|
|
@Test public void missingChunkBody() throws IOException {
|
|
server.enqueue(new MockResponse().setBody("5")
|
|
.clearHeaders()
|
|
.addHeader("Transfer-encoding: chunked")
|
|
.setSocketPolicy(DISCONNECT_AT_END));
|
|
|
|
URLConnection connection = urlFactory.open(server.url("/").url());
|
|
try {
|
|
readAscii(connection.getInputStream(), Integer.MAX_VALUE);
|
|
fail();
|
|
} catch (IOException e) {
|
|
} finally {
|
|
connection.getInputStream().close();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This test checks whether connections are gzipped by default. This behavior in not required by
|
|
* the API, so a failure of this test does not imply a bug in the implementation.
|
|
*/
|
|
@Test public void gzipEncodingEnabledByDefault() throws IOException, InterruptedException {
|
|
server.enqueue(new MockResponse()
|
|
.setBody(gzip("ABCABCABC"))
|
|
.addHeader("Content-Encoding: gzip"));
|
|
|
|
URLConnection connection = urlFactory.open(server.url("/").url());
|
|
assertEquals("ABCABCABC", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
|
|
assertNull(connection.getContentEncoding());
|
|
assertEquals(-1, connection.getContentLength());
|
|
|
|
RecordedRequest request = server.takeRequest();
|
|
assertEquals("gzip", request.getHeader("Accept-Encoding"));
|
|
}
|
|
|
|
@Test public void clientConfiguredGzipContentEncoding() throws Exception {
|
|
Buffer bodyBytes = gzip("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
|
|
server.enqueue(new MockResponse()
|
|
.setBody(bodyBytes)
|
|
.addHeader("Content-Encoding: gzip"));
|
|
|
|
URLConnection connection = urlFactory.open(server.url("/").url());
|
|
connection.addRequestProperty("Accept-Encoding", "gzip");
|
|
InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream());
|
|
assertEquals("ABCDEFGHIJKLMNOPQRSTUVWXYZ", readAscii(gunzippedIn, Integer.MAX_VALUE));
|
|
assertEquals(bodyBytes.size(), connection.getContentLength());
|
|
|
|
RecordedRequest request = server.takeRequest();
|
|
assertEquals("gzip", request.getHeader("Accept-Encoding"));
|
|
}
|
|
|
|
@Test public void gzipAndConnectionReuseWithFixedLength() throws Exception {
|
|
testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.FIXED_LENGTH, false);
|
|
}
|
|
|
|
@Test public void gzipAndConnectionReuseWithChunkedEncoding() throws Exception {
|
|
testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.CHUNKED, false);
|
|
}
|
|
|
|
@Test public void gzipAndConnectionReuseWithFixedLengthAndTls() throws Exception {
|
|
testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.FIXED_LENGTH, true);
|
|
}
|
|
|
|
@Test public void gzipAndConnectionReuseWithChunkedEncodingAndTls() throws Exception {
|
|
testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.CHUNKED, true);
|
|
}
|
|
|
|
@Test public void clientConfiguredCustomContentEncoding() throws Exception {
|
|
server.enqueue(new MockResponse().setBody("ABCDE").addHeader("Content-Encoding: custom"));
|
|
|
|
URLConnection connection = urlFactory.open(server.url("/").url());
|
|
connection.addRequestProperty("Accept-Encoding", "custom");
|
|
assertEquals("ABCDE", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
|
|
|
|
RecordedRequest request = server.takeRequest();
|
|
assertEquals("custom", request.getHeader("Accept-Encoding"));
|
|
}
|
|
|
|
/**
|
|
* Test a bug where gzip input streams weren't exhausting the input stream, which corrupted the
|
|
* request that followed or prevented connection reuse.
|
|
* http://code.google.com/p/android/issues/detail?id=7059
|
|
* http://code.google.com/p/android/issues/detail?id=38817
|
|
*/
|
|
private void testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind transferKind,
|
|
boolean tls) throws Exception {
|
|
if (tls) {
|
|
SSLSocketFactory socketFactory = sslClient.socketFactory;
|
|
RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
|
|
server.useHttps(socketFactory, false);
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.sslSocketFactory(socketFactory, sslClient.trustManager)
|
|
.hostnameVerifier(hostnameVerifier)
|
|
.build());
|
|
}
|
|
|
|
MockResponse responseOne = new MockResponse();
|
|
responseOne.addHeader("Content-Encoding: gzip");
|
|
transferKind.setBody(responseOne, gzip("one (gzipped)"), 5);
|
|
server.enqueue(responseOne);
|
|
MockResponse responseTwo = new MockResponse();
|
|
transferKind.setBody(responseTwo, "two (identity)", 5);
|
|
server.enqueue(responseTwo);
|
|
|
|
HttpURLConnection connection1 = urlFactory.open(server.url("/").url());
|
|
connection1.addRequestProperty("Accept-Encoding", "gzip");
|
|
InputStream gunzippedIn = new GZIPInputStream(connection1.getInputStream());
|
|
assertEquals("one (gzipped)", readAscii(gunzippedIn, Integer.MAX_VALUE));
|
|
assertEquals(0, server.takeRequest().getSequenceNumber());
|
|
|
|
HttpURLConnection connection2 = urlFactory.open(server.url("/").url());
|
|
assertEquals("two (identity)", readAscii(connection2.getInputStream(), Integer.MAX_VALUE));
|
|
assertEquals(1, server.takeRequest().getSequenceNumber());
|
|
}
|
|
|
|
@Test public void transparentGzipWorksAfterExceptionRecovery() throws Exception {
|
|
server.enqueue(new MockResponse()
|
|
.setBody("a")
|
|
.setSocketPolicy(SHUTDOWN_INPUT_AT_END));
|
|
server.enqueue(new MockResponse()
|
|
.addHeader("Content-Encoding: gzip")
|
|
.setBody(gzip("b")));
|
|
|
|
// Seed the pool with a bad connection.
|
|
assertContent("a", urlFactory.open(server.url("/").url()));
|
|
|
|
// Give the server time to disconnect.
|
|
Thread.sleep(500);
|
|
|
|
// This connection will need to be recovered. When it is, transparent gzip should still work!
|
|
assertContent("b", urlFactory.open(server.url("/").url()));
|
|
|
|
assertEquals(0, server.takeRequest().getSequenceNumber());
|
|
assertEquals(0, server.takeRequest().getSequenceNumber()); // Connection is not pooled.
|
|
}
|
|
|
|
@Test public void endOfStreamResponseIsNotPooled() throws Exception {
|
|
urlFactory.client().connectionPool().evictAll();
|
|
server.enqueue(new MockResponse()
|
|
.setBody("{}")
|
|
.clearHeaders()
|
|
.setSocketPolicy(DISCONNECT_AT_END));
|
|
|
|
HttpURLConnection connection = urlFactory.open(server.url("/").url());
|
|
assertContent("{}", connection);
|
|
assertEquals(0, urlFactory.client().connectionPool().idleConnectionCount());
|
|
}
|
|
|
|
@Test public void earlyDisconnectDoesntHarmPoolingWithChunkedEncoding() throws Exception {
|
|
testEarlyDisconnectDoesntHarmPooling(TransferKind.CHUNKED);
|
|
}
|
|
|
|
@Test public void earlyDisconnectDoesntHarmPoolingWithFixedLengthEncoding() throws Exception {
|
|
testEarlyDisconnectDoesntHarmPooling(TransferKind.FIXED_LENGTH);
|
|
}
|
|
|
|
private void testEarlyDisconnectDoesntHarmPooling(TransferKind transferKind) throws Exception {
|
|
MockResponse response1 = new MockResponse();
|
|
transferKind.setBody(response1, "ABCDEFGHIJK", 1024);
|
|
server.enqueue(response1);
|
|
|
|
MockResponse response2 = new MockResponse();
|
|
transferKind.setBody(response2, "LMNOPQRSTUV", 1024);
|
|
server.enqueue(response2);
|
|
|
|
HttpURLConnection connection1 = urlFactory.open(server.url("/").url());
|
|
InputStream in1 = connection1.getInputStream();
|
|
assertEquals("ABCDE", readAscii(in1, 5));
|
|
in1.close();
|
|
connection1.disconnect();
|
|
|
|
HttpURLConnection connection2 = urlFactory.open(server.url("/").url());
|
|
InputStream in2 = connection2.getInputStream();
|
|
assertEquals("LMNOP", readAscii(in2, 5));
|
|
in2.close();
|
|
connection2.disconnect();
|
|
|
|
assertEquals(0, server.takeRequest().getSequenceNumber());
|
|
assertEquals(1, server.takeRequest().getSequenceNumber()); // Connection is pooled!
|
|
}
|
|
|
|
@Test public void streamDiscardingIsTimely() throws Exception {
|
|
// This response takes at least a full second to serve: 10,000 bytes served 100 bytes at a time.
|
|
server.enqueue(new MockResponse()
|
|
.setBody(new Buffer().write(new byte[10000]))
|
|
.throttleBody(100, 10, MILLISECONDS));
|
|
server.enqueue(new MockResponse().setBody("A"));
|
|
|
|
long startNanos = System.nanoTime();
|
|
URLConnection connection1 = urlFactory.open(server.url("/").url());
|
|
InputStream in = connection1.getInputStream();
|
|
in.close();
|
|
long elapsedNanos = System.nanoTime() - startNanos;
|
|
long elapsedMillis = NANOSECONDS.toMillis(elapsedNanos);
|
|
|
|
// If we're working correctly, this should be greater than 100ms, but less than double that.
|
|
// Previously we had a bug where we would download the entire response body as long as no
|
|
// individual read took longer than 100ms.
|
|
assertTrue(Util.format("Time to close: %sms", elapsedMillis), elapsedMillis < 500);
|
|
|
|
// Do another request to confirm that the discarded connection was not pooled.
|
|
assertContent("A", urlFactory.open(server.url("/").url()));
|
|
|
|
assertEquals(0, server.takeRequest().getSequenceNumber());
|
|
assertEquals(0, server.takeRequest().getSequenceNumber()); // Connection is not pooled.
|
|
}
|
|
|
|
@Test public void setChunkedStreamingMode() throws IOException, InterruptedException {
|
|
server.enqueue(new MockResponse());
|
|
|
|
String body = "ABCDEFGHIJKLMNOPQ";
|
|
connection = urlFactory.open(server.url("/").url());
|
|
connection.setChunkedStreamingMode(0); // OkHttp does not honor specific chunk sizes.
|
|
connection.setDoOutput(true);
|
|
OutputStream outputStream = connection.getOutputStream();
|
|
outputStream.write(body.getBytes("US-ASCII"));
|
|
assertEquals(200, connection.getResponseCode());
|
|
connection.getInputStream().close();
|
|
|
|
RecordedRequest request = server.takeRequest();
|
|
assertEquals(body, request.getBody().readUtf8());
|
|
assertEquals(Arrays.asList(body.length()), request.getChunkSizes());
|
|
}
|
|
|
|
@Test public void authenticateWithFixedLengthStreaming() throws Exception {
|
|
testAuthenticateWithStreamingPost(StreamingMode.FIXED_LENGTH);
|
|
}
|
|
|
|
@Test public void authenticateWithChunkedStreaming() throws Exception {
|
|
testAuthenticateWithStreamingPost(StreamingMode.CHUNKED);
|
|
}
|
|
|
|
private void testAuthenticateWithStreamingPost(StreamingMode streamingMode) throws Exception {
|
|
MockResponse pleaseAuthenticate = new MockResponse().setResponseCode(401)
|
|
.addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
|
|
.setBody("Please authenticate.");
|
|
server.enqueue(pleaseAuthenticate);
|
|
|
|
Authenticator.setDefault(new RecordingAuthenticator());
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.authenticator(new JavaNetAuthenticator())
|
|
.build());
|
|
connection = urlFactory.open(server.url("/").url());
|
|
connection.setDoOutput(true);
|
|
byte[] requestBody = {'A', 'B', 'C', 'D'};
|
|
if (streamingMode == StreamingMode.FIXED_LENGTH) {
|
|
connection.setFixedLengthStreamingMode(requestBody.length);
|
|
} else if (streamingMode == StreamingMode.CHUNKED) {
|
|
connection.setChunkedStreamingMode(0);
|
|
}
|
|
OutputStream outputStream = connection.getOutputStream();
|
|
outputStream.write(requestBody);
|
|
outputStream.close();
|
|
try {
|
|
connection.getInputStream();
|
|
fail();
|
|
} catch (HttpRetryException expected) {
|
|
}
|
|
|
|
// no authorization header for the request...
|
|
RecordedRequest request = server.takeRequest();
|
|
assertNull(request.getHeader("Authorization"));
|
|
assertEquals("ABCD", request.getBody().readUtf8());
|
|
}
|
|
|
|
@Test public void postBodyRetransmittedAfterAuthorizationFail() throws Exception {
|
|
postBodyRetransmittedAfterAuthorizationFail("abc");
|
|
}
|
|
|
|
@Test public void postBodyRetransmittedAfterAuthorizationFail_SPDY_3() throws Exception {
|
|
enableProtocol(Protocol.SPDY_3);
|
|
postBodyRetransmittedAfterAuthorizationFail("abc");
|
|
}
|
|
|
|
@Test public void postBodyRetransmittedAfterAuthorizationFail_HTTP_2() throws Exception {
|
|
enableProtocol(Protocol.HTTP_2);
|
|
postBodyRetransmittedAfterAuthorizationFail("abc");
|
|
}
|
|
|
|
/** Don't explode when resending an empty post. https://github.com/square/okhttp/issues/1131 */
|
|
@Test public void postEmptyBodyRetransmittedAfterAuthorizationFail() throws Exception {
|
|
postBodyRetransmittedAfterAuthorizationFail("");
|
|
}
|
|
|
|
@Test public void postEmptyBodyRetransmittedAfterAuthorizationFail_SPDY_3() throws Exception {
|
|
enableProtocol(Protocol.SPDY_3);
|
|
postBodyRetransmittedAfterAuthorizationFail("");
|
|
}
|
|
|
|
@Test public void postEmptyBodyRetransmittedAfterAuthorizationFail_HTTP_2() throws Exception {
|
|
enableProtocol(Protocol.HTTP_2);
|
|
postBodyRetransmittedAfterAuthorizationFail("");
|
|
}
|
|
|
|
private void postBodyRetransmittedAfterAuthorizationFail(String body) throws Exception {
|
|
server.enqueue(new MockResponse().setResponseCode(401));
|
|
server.enqueue(new MockResponse());
|
|
|
|
String credential = Credentials.basic("jesse", "secret");
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.authenticator(new RecordingOkAuthenticator(credential))
|
|
.build());
|
|
|
|
connection = urlFactory.open(server.url("/").url());
|
|
connection.setDoOutput(true);
|
|
OutputStream outputStream = connection.getOutputStream();
|
|
outputStream.write(body.getBytes("UTF-8"));
|
|
outputStream.close();
|
|
assertEquals(200, connection.getResponseCode());
|
|
|
|
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 nonStandardAuthenticationScheme() throws Exception {
|
|
List<String> calls = authCallsForHeader("WWW-Authenticate: Foo");
|
|
assertEquals(Collections.<String>emptyList(), calls);
|
|
}
|
|
|
|
@Test public void nonStandardAuthenticationSchemeWithRealm() throws Exception {
|
|
List<String> calls = authCallsForHeader("WWW-Authenticate: Foo realm=\"Bar\"");
|
|
assertEquals(0, calls.size());
|
|
}
|
|
|
|
// Digest auth is currently unsupported. Test that digest requests should fail reasonably.
|
|
// http://code.google.com/p/android/issues/detail?id=11140
|
|
@Test public void digestAuthentication() throws Exception {
|
|
List<String> calls = authCallsForHeader("WWW-Authenticate: Digest "
|
|
+ "realm=\"testrealm@host.com\", qop=\"auth,auth-int\", "
|
|
+ "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", "
|
|
+ "opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"");
|
|
assertEquals(0, calls.size());
|
|
}
|
|
|
|
@Test public void allAttributesSetInServerAuthenticationCallbacks() throws Exception {
|
|
List<String> calls = authCallsForHeader("WWW-Authenticate: Basic realm=\"Bar\"");
|
|
assertEquals(1, calls.size());
|
|
URL url = server.url("/").url();
|
|
String call = calls.get(0);
|
|
assertTrue(call, call.contains("host=" + url.getHost()));
|
|
assertTrue(call, call.contains("port=" + url.getPort()));
|
|
assertTrue(call, call.contains("site=" + url.getHost()));
|
|
assertTrue(call, call.contains("url=" + url));
|
|
assertTrue(call, call.contains("type=" + Authenticator.RequestorType.SERVER));
|
|
assertTrue(call, call.contains("prompt=Bar"));
|
|
assertTrue(call, call.contains("protocol=http"));
|
|
assertTrue(call, call.toLowerCase().contains("scheme=basic")); // lowercase for the RI.
|
|
}
|
|
|
|
@Test public void allAttributesSetInProxyAuthenticationCallbacks() throws Exception {
|
|
List<String> calls = authCallsForHeader("Proxy-Authenticate: Basic realm=\"Bar\"");
|
|
assertEquals(1, calls.size());
|
|
URL url = server.url("/").url();
|
|
String call = calls.get(0);
|
|
assertTrue(call, call.contains("host=" + url.getHost()));
|
|
assertTrue(call, call.contains("port=" + url.getPort()));
|
|
assertTrue(call, call.contains("site=" + url.getHost()));
|
|
assertTrue(call, call.contains("url=http://android.com"));
|
|
assertTrue(call, call.contains("type=" + Authenticator.RequestorType.PROXY));
|
|
assertTrue(call, call.contains("prompt=Bar"));
|
|
assertTrue(call, call.contains("protocol=http"));
|
|
assertTrue(call, call.toLowerCase().contains("scheme=basic")); // lowercase for the RI.
|
|
}
|
|
|
|
private List<String> authCallsForHeader(String authHeader) throws IOException {
|
|
boolean proxy = authHeader.startsWith("Proxy-");
|
|
int responseCode = proxy ? 407 : 401;
|
|
RecordingAuthenticator authenticator = new RecordingAuthenticator(null);
|
|
Authenticator.setDefault(authenticator);
|
|
MockResponse pleaseAuthenticate = new MockResponse()
|
|
.setResponseCode(responseCode)
|
|
.addHeader(authHeader)
|
|
.setBody("Please authenticate.");
|
|
server.enqueue(pleaseAuthenticate);
|
|
|
|
if (proxy) {
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.proxy(server.toProxyAddress())
|
|
.proxyAuthenticator(new JavaNetAuthenticator())
|
|
.build());
|
|
connection = urlFactory.open(new URL("http://android.com/"));
|
|
} else {
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.authenticator(new JavaNetAuthenticator())
|
|
.build());
|
|
connection = urlFactory.open(server.url("/").url());
|
|
}
|
|
assertEquals(responseCode, connection.getResponseCode());
|
|
connection.getErrorStream().close();
|
|
return authenticator.calls;
|
|
}
|
|
|
|
@Test public void setValidRequestMethod() throws Exception {
|
|
assertValidRequestMethod("GET");
|
|
assertValidRequestMethod("DELETE");
|
|
assertValidRequestMethod("HEAD");
|
|
assertValidRequestMethod("OPTIONS");
|
|
assertValidRequestMethod("POST");
|
|
assertValidRequestMethod("PUT");
|
|
assertValidRequestMethod("TRACE");
|
|
assertValidRequestMethod("PATCH");
|
|
}
|
|
|
|
private void assertValidRequestMethod(String requestMethod) throws Exception {
|
|
connection = urlFactory.open(server.url("/").url());
|
|
connection.setRequestMethod(requestMethod);
|
|
assertEquals(requestMethod, connection.getRequestMethod());
|
|
}
|
|
|
|
@Test public void setInvalidRequestMethodLowercase() throws Exception {
|
|
assertInvalidRequestMethod("get");
|
|
}
|
|
|
|
@Test public void setInvalidRequestMethodConnect() throws Exception {
|
|
assertInvalidRequestMethod("CONNECT");
|
|
}
|
|
|
|
private void assertInvalidRequestMethod(String requestMethod) throws Exception {
|
|
connection = urlFactory.open(server.url("/").url());
|
|
try {
|
|
connection.setRequestMethod(requestMethod);
|
|
fail();
|
|
} catch (ProtocolException expected) {
|
|
}
|
|
}
|
|
|
|
@Test public void shoutcast() throws Exception {
|
|
server.enqueue(new MockResponse().setStatus("ICY 200 OK")
|
|
// .addHeader("HTTP/1.0 200 OK")
|
|
.addHeader("Accept-Ranges: none")
|
|
.addHeader("Content-Type: audio/mpeg")
|
|
.addHeader("icy-br:128")
|
|
.addHeader("ice-audio-info: bitrate=128;samplerate=44100;channels=2")
|
|
.addHeader("icy-br:128")
|
|
.addHeader("icy-description:Rock")
|
|
.addHeader("icy-genre:riders")
|
|
.addHeader("icy-name:A2RRock")
|
|
.addHeader("icy-pub:1")
|
|
.addHeader("icy-url:http://www.A2Rradio.com")
|
|
.addHeader("Server: Icecast 2.3.3-kh8")
|
|
.addHeader("Cache-Control: no-cache")
|
|
.addHeader("Pragma: no-cache")
|
|
.addHeader("Expires: Mon, 26 Jul 1997 05:00:00 GMT")
|
|
.addHeader("icy-metaint:16000")
|
|
.setBody("mp3 data"));
|
|
connection = urlFactory.open(server.url("/").url());
|
|
assertEquals(200, connection.getResponseCode());
|
|
assertEquals("OK", connection.getResponseMessage());
|
|
assertContent("mp3 data", connection);
|
|
}
|
|
|
|
@Test public void cannotSetNegativeFixedLengthStreamingMode() throws Exception {
|
|
connection = urlFactory.open(server.url("/").url());
|
|
try {
|
|
connection.setFixedLengthStreamingMode(-2);
|
|
fail();
|
|
} catch (IllegalArgumentException expected) {
|
|
}
|
|
}
|
|
|
|
@Test public void canSetNegativeChunkedStreamingMode() throws Exception {
|
|
connection = urlFactory.open(server.url("/").url());
|
|
connection.setChunkedStreamingMode(-2);
|
|
}
|
|
|
|
@Test public void cannotSetFixedLengthStreamingModeAfterConnect() throws Exception {
|
|
server.enqueue(new MockResponse().setBody("A"));
|
|
connection = urlFactory.open(server.url("/").url());
|
|
assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
|
|
try {
|
|
connection.setFixedLengthStreamingMode(1);
|
|
fail();
|
|
} catch (IllegalStateException expected) {
|
|
}
|
|
}
|
|
|
|
@Test public void cannotSetChunkedStreamingModeAfterConnect() throws Exception {
|
|
server.enqueue(new MockResponse().setBody("A"));
|
|
connection = urlFactory.open(server.url("/").url());
|
|
assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
|
|
try {
|
|
connection.setChunkedStreamingMode(1);
|
|
fail();
|
|
} catch (IllegalStateException expected) {
|
|
}
|
|
}
|
|
|
|
@Test public void cannotSetFixedLengthStreamingModeAfterChunkedStreamingMode() throws Exception {
|
|
connection = urlFactory.open(server.url("/").url());
|
|
connection.setChunkedStreamingMode(1);
|
|
try {
|
|
connection.setFixedLengthStreamingMode(1);
|
|
fail();
|
|
} catch (IllegalStateException expected) {
|
|
}
|
|
}
|
|
|
|
@Test public void cannotSetChunkedStreamingModeAfterFixedLengthStreamingMode() throws Exception {
|
|
connection = urlFactory.open(server.url("/").url());
|
|
connection.setFixedLengthStreamingMode(1);
|
|
try {
|
|
connection.setChunkedStreamingMode(1);
|
|
fail();
|
|
} catch (IllegalStateException expected) {
|
|
}
|
|
}
|
|
|
|
@Test public void secureFixedLengthStreaming() throws Exception {
|
|
testSecureStreamingPost(StreamingMode.FIXED_LENGTH);
|
|
}
|
|
|
|
@Test public void secureChunkedStreaming() throws Exception {
|
|
testSecureStreamingPost(StreamingMode.CHUNKED);
|
|
}
|
|
|
|
/**
|
|
* Users have reported problems using HTTPS with streaming request bodies.
|
|
* http://code.google.com/p/android/issues/detail?id=12860
|
|
*/
|
|
private void testSecureStreamingPost(StreamingMode streamingMode) throws Exception {
|
|
server.useHttps(sslClient.socketFactory, false);
|
|
server.enqueue(new MockResponse().setBody("Success!"));
|
|
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.sslSocketFactory(sslClient.socketFactory, sslClient.trustManager)
|
|
.hostnameVerifier(new RecordingHostnameVerifier())
|
|
.build());
|
|
connection = urlFactory.open(server.url("/").url());
|
|
connection.setDoOutput(true);
|
|
byte[] requestBody = {'A', 'B', 'C', 'D'};
|
|
if (streamingMode == StreamingMode.FIXED_LENGTH) {
|
|
connection.setFixedLengthStreamingMode(requestBody.length);
|
|
} else if (streamingMode == StreamingMode.CHUNKED) {
|
|
connection.setChunkedStreamingMode(0);
|
|
}
|
|
OutputStream outputStream = connection.getOutputStream();
|
|
outputStream.write(requestBody);
|
|
outputStream.close();
|
|
assertEquals("Success!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
|
|
|
|
RecordedRequest request = server.takeRequest();
|
|
assertEquals("POST / HTTP/1.1", request.getRequestLine());
|
|
if (streamingMode == StreamingMode.FIXED_LENGTH) {
|
|
assertEquals(Collections.<Integer>emptyList(), request.getChunkSizes());
|
|
} else if (streamingMode == StreamingMode.CHUNKED) {
|
|
assertEquals(Arrays.asList(4), request.getChunkSizes());
|
|
}
|
|
assertEquals("ABCD", request.getBody().readUtf8());
|
|
}
|
|
|
|
enum StreamingMode {
|
|
FIXED_LENGTH, CHUNKED
|
|
}
|
|
|
|
@Test public void authenticateWithPost() throws Exception {
|
|
MockResponse pleaseAuthenticate = new MockResponse().setResponseCode(401)
|
|
.addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
|
|
.setBody("Please authenticate.");
|
|
// fail auth three times...
|
|
server.enqueue(pleaseAuthenticate);
|
|
server.enqueue(pleaseAuthenticate);
|
|
server.enqueue(pleaseAuthenticate);
|
|
// ...then succeed the fourth time
|
|
server.enqueue(new MockResponse().setBody("Successful auth!"));
|
|
|
|
Authenticator.setDefault(new RecordingAuthenticator());
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.authenticator(new JavaNetAuthenticator())
|
|
.build());
|
|
connection = urlFactory.open(server.url("/").url());
|
|
connection.setDoOutput(true);
|
|
byte[] requestBody = {'A', 'B', 'C', 'D'};
|
|
OutputStream outputStream = connection.getOutputStream();
|
|
outputStream.write(requestBody);
|
|
outputStream.close();
|
|
assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
|
|
|
|
// no authorization header for the first request...
|
|
RecordedRequest request = server.takeRequest();
|
|
assertNull(request.getHeader("Authorization"));
|
|
|
|
// ...but the three requests that follow include an authorization header
|
|
for (int i = 0; i < 3; i++) {
|
|
request = server.takeRequest();
|
|
assertEquals("POST / HTTP/1.1", request.getRequestLine());
|
|
assertEquals("Basic " + RecordingAuthenticator.BASE_64_CREDENTIALS,
|
|
request.getHeader("Authorization"));
|
|
assertEquals("ABCD", request.getBody().readUtf8());
|
|
}
|
|
}
|
|
|
|
@Test public void authenticateWithGet() throws Exception {
|
|
MockResponse pleaseAuthenticate = new MockResponse().setResponseCode(401)
|
|
.addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
|
|
.setBody("Please authenticate.");
|
|
// fail auth three times...
|
|
server.enqueue(pleaseAuthenticate);
|
|
server.enqueue(pleaseAuthenticate);
|
|
server.enqueue(pleaseAuthenticate);
|
|
// ...then succeed the fourth time
|
|
server.enqueue(new MockResponse().setBody("Successful auth!"));
|
|
|
|
Authenticator.setDefault(new RecordingAuthenticator());
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.authenticator(new JavaNetAuthenticator())
|
|
.build());
|
|
connection = urlFactory.open(server.url("/").url());
|
|
assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
|
|
|
|
// no authorization header for the first request...
|
|
RecordedRequest request = server.takeRequest();
|
|
assertNull(request.getHeader("Authorization"));
|
|
|
|
// ...but the three requests that follow requests include an authorization header
|
|
for (int i = 0; i < 3; i++) {
|
|
request = server.takeRequest();
|
|
assertEquals("GET / HTTP/1.1", request.getRequestLine());
|
|
assertEquals("Basic " + RecordingAuthenticator.BASE_64_CREDENTIALS,
|
|
request.getHeader("Authorization"));
|
|
}
|
|
}
|
|
|
|
/** https://code.google.com/p/android/issues/detail?id=74026 */
|
|
@Test public void authenticateWithGetAndTransparentGzip() throws Exception {
|
|
MockResponse pleaseAuthenticate = new MockResponse().setResponseCode(401)
|
|
.addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
|
|
.setBody("Please authenticate.");
|
|
// fail auth three times...
|
|
server.enqueue(pleaseAuthenticate);
|
|
server.enqueue(pleaseAuthenticate);
|
|
server.enqueue(pleaseAuthenticate);
|
|
// ...then succeed the fourth time
|
|
MockResponse successfulResponse = new MockResponse()
|
|
.addHeader("Content-Encoding", "gzip")
|
|
.setBody(gzip("Successful auth!"));
|
|
server.enqueue(successfulResponse);
|
|
|
|
Authenticator.setDefault(new RecordingAuthenticator());
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.authenticator(new JavaNetAuthenticator())
|
|
.build());
|
|
connection = urlFactory.open(server.url("/").url());
|
|
assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
|
|
|
|
// no authorization header for the first request...
|
|
RecordedRequest request = server.takeRequest();
|
|
assertNull(request.getHeader("Authorization"));
|
|
|
|
// ...but the three requests that follow requests include an authorization header
|
|
for (int i = 0; i < 3; i++) {
|
|
request = server.takeRequest();
|
|
assertEquals("GET / HTTP/1.1", request.getRequestLine());
|
|
assertEquals("Basic " + RecordingAuthenticator.BASE_64_CREDENTIALS,
|
|
request.getHeader("Authorization"));
|
|
}
|
|
}
|
|
|
|
/** https://github.com/square/okhttp/issues/342 */
|
|
@Test public void authenticateRealmUppercase() throws Exception {
|
|
server.enqueue(new MockResponse().setResponseCode(401)
|
|
.addHeader("wWw-aUtHeNtIcAtE: bAsIc rEaLm=\"pRoTeCtEd aReA\"")
|
|
.setBody("Please authenticate."));
|
|
server.enqueue(new MockResponse().setBody("Successful auth!"));
|
|
|
|
Authenticator.setDefault(new RecordingAuthenticator());
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.authenticator(new JavaNetAuthenticator())
|
|
.build());
|
|
connection = urlFactory.open(server.url("/").url());
|
|
assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
|
|
}
|
|
|
|
@Test public void redirectedWithChunkedEncoding() throws Exception {
|
|
testRedirected(TransferKind.CHUNKED, true);
|
|
}
|
|
|
|
@Test public void redirectedWithContentLengthHeader() throws Exception {
|
|
testRedirected(TransferKind.FIXED_LENGTH, true);
|
|
}
|
|
|
|
@Test public void redirectedWithNoLengthHeaders() throws Exception {
|
|
testRedirected(TransferKind.END_OF_STREAM, false);
|
|
}
|
|
|
|
private void testRedirected(TransferKind transferKind, boolean reuse) throws Exception {
|
|
MockResponse response = new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
|
|
.addHeader("Location: /foo");
|
|
transferKind.setBody(response, "This page has moved!", 10);
|
|
server.enqueue(response);
|
|
server.enqueue(new MockResponse().setBody("This is the new location!"));
|
|
|
|
URLConnection connection = urlFactory.open(server.url("/").url());
|
|
assertEquals("This is the new location!",
|
|
readAscii(connection.getInputStream(), Integer.MAX_VALUE));
|
|
|
|
RecordedRequest first = server.takeRequest();
|
|
assertEquals("GET / HTTP/1.1", first.getRequestLine());
|
|
RecordedRequest retry = server.takeRequest();
|
|
assertEquals("GET /foo HTTP/1.1", retry.getRequestLine());
|
|
if (reuse) {
|
|
assertEquals("Expected connection reuse", 1, retry.getSequenceNumber());
|
|
}
|
|
}
|
|
|
|
@Test public void redirectedOnHttps() throws IOException, InterruptedException {
|
|
server.useHttps(sslClient.socketFactory, false);
|
|
server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
|
|
.addHeader("Location: /foo")
|
|
.setBody("This page has moved!"));
|
|
server.enqueue(new MockResponse().setBody("This is the new location!"));
|
|
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.sslSocketFactory(sslClient.socketFactory, sslClient.trustManager)
|
|
.hostnameVerifier(new RecordingHostnameVerifier())
|
|
.build());
|
|
connection = urlFactory.open(server.url("/").url());
|
|
assertEquals("This is the new location!",
|
|
readAscii(connection.getInputStream(), Integer.MAX_VALUE));
|
|
|
|
RecordedRequest first = server.takeRequest();
|
|
assertEquals("GET / HTTP/1.1", first.getRequestLine());
|
|
RecordedRequest retry = server.takeRequest();
|
|
assertEquals("GET /foo HTTP/1.1", retry.getRequestLine());
|
|
assertEquals("Expected connection reuse", 1, retry.getSequenceNumber());
|
|
}
|
|
|
|
@Test public void notRedirectedFromHttpsToHttp() throws IOException, InterruptedException {
|
|
server.useHttps(sslClient.socketFactory, false);
|
|
server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
|
|
.addHeader("Location: http://anyhost/foo")
|
|
.setBody("This page has moved!"));
|
|
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.followSslRedirects(false)
|
|
.sslSocketFactory(sslClient.socketFactory, sslClient.trustManager)
|
|
.hostnameVerifier(new RecordingHostnameVerifier())
|
|
.build());
|
|
connection = urlFactory.open(server.url("/").url());
|
|
assertEquals("This page has moved!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
|
|
}
|
|
|
|
@Test public void notRedirectedFromHttpToHttps() throws IOException, InterruptedException {
|
|
server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
|
|
.addHeader("Location: https://anyhost/foo")
|
|
.setBody("This page has moved!"));
|
|
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.followSslRedirects(false)
|
|
.build());
|
|
connection = urlFactory.open(server.url("/").url());
|
|
assertEquals("This page has moved!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
|
|
}
|
|
|
|
@Test public void redirectedFromHttpsToHttpFollowingProtocolRedirects() throws Exception {
|
|
server2.enqueue(new MockResponse().setBody("This is insecure HTTP!"));
|
|
|
|
server.useHttps(sslClient.socketFactory, false);
|
|
server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
|
|
.addHeader("Location: " + server2.url("/").url())
|
|
.setBody("This page has moved!"));
|
|
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.sslSocketFactory(sslClient.socketFactory, sslClient.trustManager)
|
|
.hostnameVerifier(new RecordingHostnameVerifier())
|
|
.followSslRedirects(true)
|
|
.build());
|
|
HttpsURLConnection connection = (HttpsURLConnection) urlFactory.open(server.url("/").url());
|
|
assertContent("This is insecure HTTP!", connection);
|
|
assertNull(connection.getCipherSuite());
|
|
assertNull(connection.getLocalCertificates());
|
|
assertNull(connection.getServerCertificates());
|
|
assertNull(connection.getPeerPrincipal());
|
|
assertNull(connection.getLocalPrincipal());
|
|
}
|
|
|
|
@Test public void redirectedFromHttpToHttpsFollowingProtocolRedirects() throws Exception {
|
|
server2.useHttps(sslClient.socketFactory, false);
|
|
server2.enqueue(new MockResponse().setBody("This is secure HTTPS!"));
|
|
|
|
server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
|
|
.addHeader("Location: " + server2.url("/").url())
|
|
.setBody("This page has moved!"));
|
|
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.sslSocketFactory(sslClient.socketFactory, sslClient.trustManager)
|
|
.hostnameVerifier(new RecordingHostnameVerifier())
|
|
.followSslRedirects(true)
|
|
.build());
|
|
connection = urlFactory.open(server.url("/").url());
|
|
assertContent("This is secure HTTPS!", connection);
|
|
assertFalse(connection instanceof HttpsURLConnection);
|
|
}
|
|
|
|
@Test public void redirectToAnotherOriginServer() throws Exception {
|
|
redirectToAnotherOriginServer(false);
|
|
}
|
|
|
|
@Test public void redirectToAnotherOriginServerWithHttps() throws Exception {
|
|
redirectToAnotherOriginServer(true);
|
|
}
|
|
|
|
private void redirectToAnotherOriginServer(boolean https) throws Exception {
|
|
if (https) {
|
|
server.useHttps(sslClient.socketFactory, false);
|
|
server2.useHttps(sslClient.socketFactory, false);
|
|
server2.setProtocolNegotiationEnabled(false);
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.sslSocketFactory(sslClient.socketFactory, sslClient.trustManager)
|
|
.hostnameVerifier(new RecordingHostnameVerifier())
|
|
.build());
|
|
}
|
|
|
|
server2.enqueue(new MockResponse().setBody("This is the 2nd server!"));
|
|
server2.enqueue(new MockResponse().setBody("This is the 2nd server, again!"));
|
|
|
|
server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
|
|
.addHeader("Location: " + server2.url("/").url().toString())
|
|
.setBody("This page has moved!"));
|
|
server.enqueue(new MockResponse().setBody("This is the first server again!"));
|
|
|
|
connection = urlFactory.open(server.url("/").url());
|
|
assertContent("This is the 2nd server!", connection);
|
|
assertEquals(server2.url("/").url(), connection.getURL());
|
|
|
|
// make sure the first server was careful to recycle the connection
|
|
assertContent("This is the first server again!", urlFactory.open(server.url("/").url()));
|
|
assertContent("This is the 2nd server, again!", urlFactory.open(server2.url("/").url()));
|
|
|
|
String server1Host = server.getHostName() + ":" + server.getPort();
|
|
String server2Host = server2.getHostName() + ":" + server2.getPort();
|
|
assertEquals(server1Host, server.takeRequest().getHeader("Host"));
|
|
assertEquals(server2Host, server2.takeRequest().getHeader("Host"));
|
|
assertEquals("Expected connection reuse", 1, server.takeRequest().getSequenceNumber());
|
|
assertEquals("Expected connection reuse", 1, server2.takeRequest().getSequenceNumber());
|
|
}
|
|
|
|
@Test public void redirectWithProxySelector() throws Exception {
|
|
final List<URI> proxySelectionRequests = new ArrayList<URI>();
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.proxySelector(new ProxySelector() {
|
|
@Override public List<Proxy> select(URI uri) {
|
|
proxySelectionRequests.add(uri);
|
|
MockWebServer proxyServer = (uri.getPort() == server.getPort())
|
|
? server
|
|
: server2;
|
|
return Arrays.asList(proxyServer.toProxyAddress());
|
|
}
|
|
|
|
@Override public void connectFailed(URI uri, SocketAddress address, IOException failure) {
|
|
throw new AssertionError();
|
|
}
|
|
})
|
|
.build());
|
|
|
|
server2.enqueue(new MockResponse().setBody("This is the 2nd server!"));
|
|
|
|
server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
|
|
.addHeader("Location: " + server2.url("/b").url().toString())
|
|
.setBody("This page has moved!"));
|
|
|
|
assertContent("This is the 2nd server!", urlFactory.open(server.url("/a").url()));
|
|
|
|
assertEquals(Arrays.asList(server.url("/").url().toURI(), server2.url("/").url().toURI()),
|
|
proxySelectionRequests);
|
|
}
|
|
|
|
@Test public void redirectWithAuthentication() 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").url()));
|
|
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.authenticator(new RecordingOkAuthenticator(Credentials.basic("jesse", "secret")))
|
|
.build());
|
|
assertContent("Page 2", urlFactory.open(server.url("/a").url()));
|
|
|
|
RecordedRequest redirectRequest = server2.takeRequest();
|
|
assertNull(redirectRequest.getHeader("Authorization"));
|
|
assertEquals("/b", redirectRequest.getPath());
|
|
}
|
|
|
|
@Test public void response300MultipleChoiceWithPost() throws Exception {
|
|
// Chrome doesn't follow the redirect, but Firefox and the RI both do
|
|
testResponseRedirectedWithPost(HttpURLConnection.HTTP_MULT_CHOICE, TransferKind.END_OF_STREAM);
|
|
}
|
|
|
|
@Test public void response301MovedPermanentlyWithPost() throws Exception {
|
|
testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_PERM, TransferKind.END_OF_STREAM);
|
|
}
|
|
|
|
@Test public void response302MovedTemporarilyWithPost() throws Exception {
|
|
testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_TEMP, TransferKind.END_OF_STREAM);
|
|
}
|
|
|
|
@Test public void response303SeeOtherWithPost() throws Exception {
|
|
testResponseRedirectedWithPost(HttpURLConnection.HTTP_SEE_OTHER, TransferKind.END_OF_STREAM);
|
|
}
|
|
|
|
@Test public void postRedirectToGetWithChunkedRequest() throws Exception {
|
|
testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_TEMP, TransferKind.CHUNKED);
|
|
}
|
|
|
|
@Test public void postRedirectToGetWithStreamedRequest() throws Exception {
|
|
testResponseRedirectedWithPost(HttpURLConnection.HTTP_MOVED_TEMP, TransferKind.FIXED_LENGTH);
|
|
}
|
|
|
|
private void testResponseRedirectedWithPost(int redirectCode, TransferKind transferKind)
|
|
throws Exception {
|
|
server.enqueue(new MockResponse().setResponseCode(redirectCode)
|
|
.addHeader("Location: /page2")
|
|
.setBody("This page has moved!"));
|
|
server.enqueue(new MockResponse().setBody("Page 2"));
|
|
|
|
connection = urlFactory.open(server.url("/page1").url());
|
|
connection.setDoOutput(true);
|
|
transferKind.setForRequest(connection, 4);
|
|
byte[] requestBody = {'A', 'B', 'C', 'D'};
|
|
OutputStream outputStream = connection.getOutputStream();
|
|
outputStream.write(requestBody);
|
|
outputStream.close();
|
|
assertEquals("Page 2", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
|
|
assertTrue(connection.getDoOutput());
|
|
|
|
RecordedRequest page1 = server.takeRequest();
|
|
assertEquals("POST /page1 HTTP/1.1", page1.getRequestLine());
|
|
assertEquals("ABCD", page1.getBody().readUtf8());
|
|
|
|
RecordedRequest page2 = server.takeRequest();
|
|
assertEquals("GET /page2 HTTP/1.1", page2.getRequestLine());
|
|
}
|
|
|
|
@Test public void redirectedPostStripsRequestBodyHeaders() throws Exception {
|
|
server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
|
|
.addHeader("Location: /page2"));
|
|
server.enqueue(new MockResponse().setBody("Page 2"));
|
|
|
|
connection = urlFactory.open(server.url("/page1").url());
|
|
connection.setDoOutput(true);
|
|
connection.addRequestProperty("Content-Length", "4");
|
|
connection.addRequestProperty("Content-Type", "text/plain; charset=utf-8");
|
|
connection.addRequestProperty("Transfer-Encoding", "identity");
|
|
OutputStream outputStream = connection.getOutputStream();
|
|
outputStream.write("ABCD".getBytes("UTF-8"));
|
|
outputStream.close();
|
|
assertEquals("Page 2", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
|
|
|
|
assertEquals("POST /page1 HTTP/1.1", server.takeRequest().getRequestLine());
|
|
|
|
RecordedRequest page2 = server.takeRequest();
|
|
assertEquals("GET /page2 HTTP/1.1", page2.getRequestLine());
|
|
assertNull(page2.getHeader("Content-Length"));
|
|
assertNull(page2.getHeader("Content-Type"));
|
|
assertNull(page2.getHeader("Transfer-Encoding"));
|
|
}
|
|
|
|
@Test public void response305UseProxy() throws Exception {
|
|
server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_USE_PROXY)
|
|
.addHeader("Location: " + server.url("/").url())
|
|
.setBody("This page has moved!"));
|
|
server.enqueue(new MockResponse().setBody("Proxy Response"));
|
|
|
|
connection = urlFactory.open(server.url("/foo").url());
|
|
// Fails on the RI, which gets "Proxy Response"
|
|
assertEquals("This page has moved!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
|
|
|
|
RecordedRequest page1 = server.takeRequest();
|
|
assertEquals("GET /foo HTTP/1.1", page1.getRequestLine());
|
|
assertEquals(1, server.getRequestCount());
|
|
}
|
|
|
|
@Test public void response307WithGet() throws Exception {
|
|
testRedirect(true, "GET");
|
|
}
|
|
|
|
@Test public void response307WithHead() throws Exception {
|
|
testRedirect(true, "HEAD");
|
|
}
|
|
|
|
@Test public void response307WithOptions() throws Exception {
|
|
testRedirect(true, "OPTIONS");
|
|
}
|
|
|
|
@Test public void response307WithPost() throws Exception {
|
|
testRedirect(true, "POST");
|
|
}
|
|
|
|
@Test public void response308WithGet() throws Exception {
|
|
testRedirect(false, "GET");
|
|
}
|
|
|
|
@Test public void response308WithHead() throws Exception {
|
|
testRedirect(false, "HEAD");
|
|
}
|
|
|
|
@Test public void response308WithOptions() throws Exception {
|
|
testRedirect(false, "OPTIONS");
|
|
}
|
|
|
|
@Test public void response308WithPost() throws Exception {
|
|
testRedirect(false, "POST");
|
|
}
|
|
|
|
private void testRedirect(boolean temporary, String method) throws Exception {
|
|
MockResponse response1 = new MockResponse()
|
|
.setResponseCode(temporary ? HTTP_TEMP_REDIRECT : HTTP_PERM_REDIRECT)
|
|
.addHeader("Location: /page2");
|
|
if (!method.equals("HEAD")) {
|
|
response1.setBody("This page has moved!");
|
|
}
|
|
server.enqueue(response1);
|
|
server.enqueue(new MockResponse().setBody("Page 2"));
|
|
|
|
connection = urlFactory.open(server.url("/page1").url());
|
|
connection.setRequestMethod(method);
|
|
byte[] requestBody = {'A', 'B', 'C', 'D'};
|
|
if (method.equals("POST")) {
|
|
connection.setDoOutput(true);
|
|
OutputStream outputStream = connection.getOutputStream();
|
|
outputStream.write(requestBody);
|
|
outputStream.close();
|
|
}
|
|
|
|
String response = readAscii(connection.getInputStream(), Integer.MAX_VALUE);
|
|
|
|
RecordedRequest page1 = server.takeRequest();
|
|
assertEquals(method + " /page1 HTTP/1.1", page1.getRequestLine());
|
|
|
|
if (method.equals("GET")) {
|
|
assertEquals("Page 2", response);
|
|
} else if (method.equals("HEAD")) {
|
|
assertEquals("", response);
|
|
} else {
|
|
// Methods other than GET/HEAD shouldn't follow the redirect
|
|
if (method.equals("POST")) {
|
|
assertTrue(connection.getDoOutput());
|
|
assertEquals("ABCD", page1.getBody().readUtf8());
|
|
}
|
|
assertEquals(1, server.getRequestCount());
|
|
assertEquals("This page has moved!", response);
|
|
return;
|
|
}
|
|
|
|
// GET/HEAD requests should have followed the redirect with the same method
|
|
assertFalse(connection.getDoOutput());
|
|
assertEquals(2, server.getRequestCount());
|
|
RecordedRequest page2 = server.takeRequest();
|
|
assertEquals(method + " /page2 HTTP/1.1", page2.getRequestLine());
|
|
}
|
|
|
|
@Test public void follow20Redirects() throws Exception {
|
|
for (int i = 0; i < 20; i++) {
|
|
server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
|
|
.addHeader("Location: /" + (i + 1))
|
|
.setBody("Redirecting to /" + (i + 1)));
|
|
}
|
|
server.enqueue(new MockResponse().setBody("Success!"));
|
|
|
|
connection = urlFactory.open(server.url("/0").url());
|
|
assertContent("Success!", connection);
|
|
assertEquals(server.url("/20").url(), connection.getURL());
|
|
}
|
|
|
|
@Test public void doesNotFollow21Redirects() throws Exception {
|
|
for (int i = 0; i < 21; i++) {
|
|
server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
|
|
.addHeader("Location: /" + (i + 1))
|
|
.setBody("Redirecting to /" + (i + 1)));
|
|
}
|
|
|
|
connection = urlFactory.open(server.url("/0").url());
|
|
try {
|
|
connection.getInputStream();
|
|
fail();
|
|
} catch (ProtocolException expected) {
|
|
assertEquals(HttpURLConnection.HTTP_MOVED_TEMP, connection.getResponseCode());
|
|
assertEquals("Too many follow-up requests: 21", expected.getMessage());
|
|
assertEquals(server.url("/20").url(), connection.getURL());
|
|
}
|
|
}
|
|
|
|
@Test public void httpsWithCustomTrustManager() throws Exception {
|
|
RecordingHostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
|
|
RecordingTrustManager trustManager = new RecordingTrustManager(sslClient.trustManager);
|
|
SSLContext sslContext = SSLContext.getInstance("TLS");
|
|
sslContext.init(null, new TrustManager[] { trustManager }, null);
|
|
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.hostnameVerifier(hostnameVerifier)
|
|
.sslSocketFactory(sslContext.getSocketFactory(), trustManager)
|
|
.build());
|
|
server.useHttps(sslClient.socketFactory, false);
|
|
server.enqueue(new MockResponse().setBody("ABC"));
|
|
server.enqueue(new MockResponse().setBody("DEF"));
|
|
server.enqueue(new MockResponse().setBody("GHI"));
|
|
|
|
URL url = server.url("/").url();
|
|
assertContent("ABC", urlFactory.open(url));
|
|
assertContent("DEF", urlFactory.open(url));
|
|
assertContent("GHI", urlFactory.open(url));
|
|
|
|
assertEquals(Arrays.asList("verify " + server.getHostName()), hostnameVerifier.calls);
|
|
assertEquals(Arrays.asList("checkServerTrusted [CN=" + server.getHostName() + " 1]"),
|
|
trustManager.calls);
|
|
}
|
|
|
|
@Test public void getClientRequestTimeout() throws Exception {
|
|
enqueueClientRequestTimeoutResponses();
|
|
|
|
HttpURLConnection connection = urlFactory.open(server.url("/").url());
|
|
|
|
assertEquals(200, connection.getResponseCode());
|
|
assertEquals("Body", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
|
|
}
|
|
|
|
private void enqueueClientRequestTimeoutResponses() {
|
|
server.enqueue(new MockResponse()
|
|
.setSocketPolicy(SocketPolicy.DISCONNECT_AT_END)
|
|
.setResponseCode(HttpURLConnection.HTTP_CLIENT_TIMEOUT)
|
|
.setHeader("Connection", "Close")
|
|
.setBody("You took too long!"));
|
|
server.enqueue(new MockResponse().setBody("Body"));
|
|
}
|
|
|
|
@Test public void bufferedBodyWithClientRequestTimeout() throws Exception {
|
|
enqueueClientRequestTimeoutResponses();
|
|
|
|
HttpURLConnection connection = urlFactory.open(server.url("/").url());
|
|
connection.setRequestMethod("POST");
|
|
connection.getOutputStream().write("Hello".getBytes("UTF-8"));
|
|
|
|
assertEquals(200, connection.getResponseCode());
|
|
assertEquals("Body", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
|
|
|
|
RecordedRequest request1 = server.takeRequest();
|
|
assertEquals("Hello", request1.getBody().readUtf8());
|
|
|
|
RecordedRequest request2 = server.takeRequest();
|
|
assertEquals("Hello", request2.getBody().readUtf8());
|
|
}
|
|
|
|
@Test public void streamedBodyWithClientRequestTimeout() throws Exception {
|
|
enqueueClientRequestTimeoutResponses();
|
|
|
|
HttpURLConnection connection = urlFactory.open(server.url("/").url());
|
|
connection.setRequestMethod("POST");
|
|
connection.setChunkedStreamingMode(0);
|
|
connection.getOutputStream().write("Hello".getBytes("UTF-8"));
|
|
|
|
assertEquals(408, connection.getResponseCode());
|
|
assertEquals(1, server.getRequestCount());
|
|
}
|
|
|
|
@Test public void readTimeouts() throws IOException {
|
|
// This relies on the fact that MockWebServer doesn't close the
|
|
// connection after a response has been sent. This causes the client to
|
|
// try to read more bytes than are sent, which results in a timeout.
|
|
MockResponse timeout =
|
|
new MockResponse().setBody("ABC").clearHeaders().addHeader("Content-Length: 4");
|
|
server.enqueue(timeout);
|
|
server.enqueue(new MockResponse().setBody("unused")); // to keep the server alive
|
|
|
|
URLConnection connection = urlFactory.open(server.url("/").url());
|
|
connection.setReadTimeout(1000);
|
|
InputStream in = connection.getInputStream();
|
|
assertEquals('A', in.read());
|
|
assertEquals('B', in.read());
|
|
assertEquals('C', in.read());
|
|
try {
|
|
in.read(); // if Content-Length was accurate, this would return -1 immediately
|
|
fail();
|
|
} catch (SocketTimeoutException expected) {
|
|
}
|
|
in.close();
|
|
}
|
|
|
|
/** Confirm that an unacknowledged write times out. */
|
|
@Test public void writeTimeouts() throws IOException {
|
|
MockWebServer server = new MockWebServer();
|
|
// Sockets on some platforms can have large buffers that mean writes do not block when
|
|
// required. These socket factories explicitly set the buffer sizes on sockets created.
|
|
final int SOCKET_BUFFER_SIZE = 4 * 1024;
|
|
server.setServerSocketFactory(
|
|
new DelegatingServerSocketFactory(ServerSocketFactory.getDefault()) {
|
|
@Override
|
|
protected ServerSocket configureServerSocket(ServerSocket serverSocket)
|
|
throws IOException {
|
|
serverSocket.setReceiveBufferSize(SOCKET_BUFFER_SIZE);
|
|
return serverSocket;
|
|
}
|
|
});
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.socketFactory(new DelegatingSocketFactory(SocketFactory.getDefault()) {
|
|
@Override protected Socket configureSocket(Socket socket) throws IOException {
|
|
socket.setReceiveBufferSize(SOCKET_BUFFER_SIZE);
|
|
socket.setSendBufferSize(SOCKET_BUFFER_SIZE);
|
|
return socket;
|
|
}
|
|
})
|
|
.writeTimeout(500, TimeUnit.MILLISECONDS)
|
|
.build());
|
|
|
|
server.start();
|
|
server.enqueue(new MockResponse()
|
|
.throttleBody(1, 1, TimeUnit.SECONDS)); // Prevent the server from reading!
|
|
|
|
connection = urlFactory.open(server.url("/").url());
|
|
connection.setDoOutput(true);
|
|
connection.setChunkedStreamingMode(0);
|
|
OutputStream out = connection.getOutputStream();
|
|
try {
|
|
byte[] data = new byte[2 * 1024 * 1024]; // 2 MiB.
|
|
out.write(data);
|
|
fail();
|
|
} catch (SocketTimeoutException expected) {
|
|
}
|
|
}
|
|
|
|
@Test public void setChunkedEncodingAsRequestProperty() throws IOException, InterruptedException {
|
|
server.enqueue(new MockResponse());
|
|
|
|
connection = urlFactory.open(server.url("/").url());
|
|
connection.setRequestProperty("Transfer-encoding", "chunked");
|
|
connection.setDoOutput(true);
|
|
connection.getOutputStream().write("ABC".getBytes("UTF-8"));
|
|
assertEquals(200, connection.getResponseCode());
|
|
|
|
RecordedRequest request = server.takeRequest();
|
|
assertEquals("ABC", request.getBody().readUtf8());
|
|
}
|
|
|
|
@Test public void connectionCloseInRequest() throws IOException, InterruptedException {
|
|
server.enqueue(new MockResponse()); // server doesn't honor the connection: close header!
|
|
server.enqueue(new MockResponse());
|
|
|
|
HttpURLConnection a = urlFactory.open(server.url("/").url());
|
|
a.setRequestProperty("Connection", "close");
|
|
assertEquals(200, a.getResponseCode());
|
|
|
|
HttpURLConnection b = urlFactory.open(server.url("/").url());
|
|
assertEquals(200, b.getResponseCode());
|
|
|
|
assertEquals(0, server.takeRequest().getSequenceNumber());
|
|
assertEquals("When connection: close is used, each request should get its own connection", 0,
|
|
server.takeRequest().getSequenceNumber());
|
|
}
|
|
|
|
@Test public void connectionCloseInResponse() throws IOException, InterruptedException {
|
|
server.enqueue(new MockResponse().addHeader("Connection: close"));
|
|
server.enqueue(new MockResponse());
|
|
|
|
HttpURLConnection a = urlFactory.open(server.url("/").url());
|
|
assertEquals(200, a.getResponseCode());
|
|
|
|
HttpURLConnection b = urlFactory.open(server.url("/").url());
|
|
assertEquals(200, b.getResponseCode());
|
|
|
|
assertEquals(0, server.takeRequest().getSequenceNumber());
|
|
assertEquals("When connection: close is used, each request should get its own connection", 0,
|
|
server.takeRequest().getSequenceNumber());
|
|
}
|
|
|
|
@Test public void connectionCloseWithRedirect() throws IOException, InterruptedException {
|
|
MockResponse response = new MockResponse()
|
|
.setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
|
|
.addHeader("Location: /foo")
|
|
.addHeader("Connection: close");
|
|
server.enqueue(response);
|
|
server.enqueue(new MockResponse()
|
|
.setBody("This is the new location!"));
|
|
|
|
URLConnection connection = urlFactory.open(server.url("/").url());
|
|
assertEquals("This is the new location!",
|
|
readAscii(connection.getInputStream(), Integer.MAX_VALUE));
|
|
|
|
assertEquals(0, server.takeRequest().getSequenceNumber());
|
|
assertEquals("When connection: close is used, each request should get its own connection", 0,
|
|
server.takeRequest().getSequenceNumber());
|
|
}
|
|
|
|
/**
|
|
* Retry redirects if the socket is closed.
|
|
* https://code.google.com/p/android/issues/detail?id=41576
|
|
*/
|
|
@Test public void sameConnectionRedirectAndReuse() throws Exception {
|
|
// TODO(jwilson): this behavior shouldn't rely on having another IP address to attempt.
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.dns(new DoubleInetAddressDns())
|
|
.build());
|
|
server.enqueue(new MockResponse()
|
|
.setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
|
|
.setSocketPolicy(SHUTDOWN_INPUT_AT_END)
|
|
.addHeader("Location: /foo"));
|
|
server.enqueue(new MockResponse()
|
|
.setBody("This is the new page!"));
|
|
|
|
assertContent("This is the new page!", urlFactory.open(server.url("/").url()));
|
|
|
|
assertEquals(0, server.takeRequest().getSequenceNumber());
|
|
assertEquals(0, server.takeRequest().getSequenceNumber());
|
|
}
|
|
|
|
@Test public void responseCodeDisagreesWithHeaders() throws IOException, InterruptedException {
|
|
server.enqueue(new MockResponse()
|
|
.setResponseCode(HttpURLConnection.HTTP_NO_CONTENT)
|
|
.setBody("This body is not allowed!"));
|
|
|
|
URLConnection connection = urlFactory.open(server.url("/").url());
|
|
try {
|
|
connection.getInputStream();
|
|
fail();
|
|
} catch (IOException expected) {
|
|
assertEquals("HTTP 204 had non-zero Content-Length: 25", expected.getMessage());
|
|
}
|
|
}
|
|
|
|
@Test public void singleByteReadIsSigned() throws IOException {
|
|
server.enqueue(new MockResponse().setBody(new Buffer().writeByte(-2).writeByte(-1)));
|
|
|
|
connection = urlFactory.open(server.url("/").url());
|
|
InputStream in = connection.getInputStream();
|
|
assertEquals(254, in.read());
|
|
assertEquals(255, in.read());
|
|
assertEquals(-1, in.read());
|
|
}
|
|
|
|
@Test public void flushAfterStreamTransmittedWithChunkedEncoding() throws IOException {
|
|
testFlushAfterStreamTransmitted(TransferKind.CHUNKED);
|
|
}
|
|
|
|
@Test public void flushAfterStreamTransmittedWithFixedLength() throws IOException {
|
|
testFlushAfterStreamTransmitted(TransferKind.FIXED_LENGTH);
|
|
}
|
|
|
|
@Test public void flushAfterStreamTransmittedWithNoLengthHeaders() throws IOException {
|
|
testFlushAfterStreamTransmitted(TransferKind.END_OF_STREAM);
|
|
}
|
|
|
|
/**
|
|
* We explicitly permit apps to close the upload stream even after it has been transmitted. We
|
|
* also permit flush so that buffered streams can do a no-op flush when they are closed.
|
|
* http://b/3038470
|
|
*/
|
|
private void testFlushAfterStreamTransmitted(TransferKind transferKind) throws IOException {
|
|
server.enqueue(new MockResponse().setBody("abc"));
|
|
|
|
connection = urlFactory.open(server.url("/").url());
|
|
connection.setDoOutput(true);
|
|
byte[] upload = "def".getBytes("UTF-8");
|
|
|
|
if (transferKind == TransferKind.CHUNKED) {
|
|
connection.setChunkedStreamingMode(0);
|
|
} else if (transferKind == TransferKind.FIXED_LENGTH) {
|
|
connection.setFixedLengthStreamingMode(upload.length);
|
|
}
|
|
|
|
OutputStream out = connection.getOutputStream();
|
|
out.write(upload);
|
|
assertEquals("abc", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
|
|
|
|
out.flush(); // Dubious but permitted.
|
|
try {
|
|
out.write("ghi".getBytes("UTF-8"));
|
|
fail();
|
|
} catch (IOException expected) {
|
|
}
|
|
}
|
|
|
|
@Test public void getHeadersThrows() throws IOException {
|
|
server.enqueue(new MockResponse().setSocketPolicy(DISCONNECT_AT_START));
|
|
|
|
connection = urlFactory.open(server.url("/").url());
|
|
try {
|
|
connection.getInputStream();
|
|
fail();
|
|
} catch (IOException expected) {
|
|
}
|
|
|
|
try {
|
|
connection.getInputStream();
|
|
fail();
|
|
} catch (IOException expected) {
|
|
}
|
|
}
|
|
|
|
@Test public void dnsFailureThrowsIOException() throws IOException {
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.dns(new FakeDns())
|
|
.build());
|
|
connection = urlFactory.open(new URL("http://host.unlikelytld"));
|
|
try {
|
|
connection.connect();
|
|
fail();
|
|
} catch (IOException expected) {
|
|
}
|
|
}
|
|
|
|
@Test public void malformedUrlThrowsUnknownHostException() throws IOException {
|
|
connection = urlFactory.open(new URL("http://./foo.html"));
|
|
try {
|
|
connection.connect();
|
|
fail();
|
|
} catch (UnknownHostException expected) {
|
|
}
|
|
}
|
|
|
|
@Test public void getKeepAlive() throws Exception {
|
|
server.enqueue(new MockResponse().setBody("ABC"));
|
|
|
|
// The request should work once and then fail
|
|
HttpURLConnection connection1 = urlFactory.open(server.url("/").url());
|
|
connection1.setReadTimeout(100);
|
|
InputStream input = connection1.getInputStream();
|
|
assertEquals("ABC", readAscii(input, Integer.MAX_VALUE));
|
|
server.shutdown();
|
|
try {
|
|
HttpURLConnection connection2 = urlFactory.open(server.url("").url());
|
|
connection2.setReadTimeout(100);
|
|
connection2.getInputStream();
|
|
fail();
|
|
} catch (ConnectException expected) {
|
|
}
|
|
}
|
|
|
|
/** http://code.google.com/p/android/issues/detail?id=14562 */
|
|
@Test public void readAfterLastByte() throws Exception {
|
|
server.enqueue(new MockResponse().setBody("ABC")
|
|
.clearHeaders()
|
|
.addHeader("Connection: close")
|
|
.setSocketPolicy(DISCONNECT_AT_END));
|
|
|
|
connection = urlFactory.open(server.url("/").url());
|
|
InputStream in = connection.getInputStream();
|
|
assertEquals("ABC", readAscii(in, 3));
|
|
assertEquals(-1, in.read());
|
|
assertEquals(-1, in.read()); // throws IOException in Gingerbread
|
|
}
|
|
|
|
@Test public void getContent() throws Exception {
|
|
server.enqueue(new MockResponse().addHeader("Content-Type: text/plain").setBody("A"));
|
|
connection = urlFactory.open(server.url("/").url());
|
|
InputStream in = (InputStream) connection.getContent();
|
|
assertEquals("A", readAscii(in, Integer.MAX_VALUE));
|
|
}
|
|
|
|
@Test public void getContentOfType() throws Exception {
|
|
server.enqueue(new MockResponse().addHeader("Content-Type: text/plain").setBody("A"));
|
|
connection = urlFactory.open(server.url("/").url());
|
|
try {
|
|
connection.getContent(null);
|
|
fail();
|
|
} catch (NullPointerException expected) {
|
|
}
|
|
try {
|
|
connection.getContent(new Class[] {null});
|
|
fail();
|
|
} catch (NullPointerException expected) {
|
|
}
|
|
assertNull(connection.getContent(new Class[] {getClass()}));
|
|
connection.getInputStream().close();
|
|
}
|
|
|
|
@Test public void getOutputStreamOnGetFails() throws Exception {
|
|
server.enqueue(new MockResponse());
|
|
connection = urlFactory.open(server.url("/").url());
|
|
try {
|
|
connection.getOutputStream();
|
|
fail();
|
|
} catch (ProtocolException expected) {
|
|
}
|
|
connection.getInputStream().close();
|
|
}
|
|
|
|
@Test public void getOutputAfterGetInputStreamFails() throws Exception {
|
|
server.enqueue(new MockResponse());
|
|
connection = urlFactory.open(server.url("/").url());
|
|
connection.setDoOutput(true);
|
|
try {
|
|
connection.getInputStream();
|
|
connection.getOutputStream();
|
|
fail();
|
|
} catch (ProtocolException expected) {
|
|
}
|
|
}
|
|
|
|
@Test public void setDoOutputOrDoInputAfterConnectFails() throws Exception {
|
|
server.enqueue(new MockResponse());
|
|
connection = urlFactory.open(server.url("/").url());
|
|
connection.connect();
|
|
try {
|
|
connection.setDoOutput(true);
|
|
fail();
|
|
} catch (IllegalStateException expected) {
|
|
}
|
|
try {
|
|
connection.setDoInput(true);
|
|
fail();
|
|
} catch (IllegalStateException expected) {
|
|
}
|
|
connection.getInputStream().close();
|
|
}
|
|
|
|
@Test public void clientSendsContentLength() throws Exception {
|
|
server.enqueue(new MockResponse().setBody("A"));
|
|
connection = urlFactory.open(server.url("/").url());
|
|
connection.setDoOutput(true);
|
|
OutputStream out = connection.getOutputStream();
|
|
out.write(new byte[] {'A', 'B', 'C'});
|
|
out.close();
|
|
assertEquals("A", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
|
|
RecordedRequest request = server.takeRequest();
|
|
assertEquals("3", request.getHeader("Content-Length"));
|
|
connection.getInputStream().close();
|
|
}
|
|
|
|
@Test public void getContentLengthConnects() throws Exception {
|
|
server.enqueue(new MockResponse().setBody("ABC"));
|
|
connection = urlFactory.open(server.url("/").url());
|
|
assertEquals(3, connection.getContentLength());
|
|
connection.getInputStream().close();
|
|
}
|
|
|
|
@Test public void getContentTypeConnects() throws Exception {
|
|
server.enqueue(new MockResponse().addHeader("Content-Type: text/plain").setBody("ABC"));
|
|
connection = urlFactory.open(server.url("/").url());
|
|
assertEquals("text/plain", connection.getContentType());
|
|
connection.getInputStream().close();
|
|
}
|
|
|
|
@Test public void getContentEncodingConnects() throws Exception {
|
|
server.enqueue(new MockResponse().addHeader("Content-Encoding: identity").setBody("ABC"));
|
|
connection = urlFactory.open(server.url("/").url());
|
|
assertEquals("identity", connection.getContentEncoding());
|
|
connection.getInputStream().close();
|
|
}
|
|
|
|
// http://b/4361656
|
|
@Test public void urlContainsQueryButNoPath() throws Exception {
|
|
server.enqueue(new MockResponse().setBody("A"));
|
|
|
|
URL url = new URL("http", server.getHostName(), server.getPort(), "?query");
|
|
assertEquals("A", readAscii(urlFactory.open(url).getInputStream(), Integer.MAX_VALUE));
|
|
RecordedRequest request = server.takeRequest();
|
|
assertEquals("GET /?query HTTP/1.1", request.getRequestLine());
|
|
}
|
|
|
|
@Test public void doOutputForMethodThatDoesntSupportOutput() throws Exception {
|
|
connection = urlFactory.open(server.url("/").url());
|
|
connection.setRequestMethod("HEAD");
|
|
connection.setDoOutput(true);
|
|
try {
|
|
connection.connect();
|
|
fail();
|
|
} catch (IOException expected) {
|
|
}
|
|
}
|
|
|
|
// http://code.google.com/p/android/issues/detail?id=20442
|
|
@Test public void inputStreamAvailableWithChunkedEncoding() throws Exception {
|
|
testInputStreamAvailable(TransferKind.CHUNKED);
|
|
}
|
|
|
|
@Test public void inputStreamAvailableWithContentLengthHeader() throws Exception {
|
|
testInputStreamAvailable(TransferKind.FIXED_LENGTH);
|
|
}
|
|
|
|
@Test public void inputStreamAvailableWithNoLengthHeaders() throws Exception {
|
|
testInputStreamAvailable(TransferKind.END_OF_STREAM);
|
|
}
|
|
|
|
private void testInputStreamAvailable(TransferKind transferKind) throws IOException {
|
|
String body = "ABCDEFGH";
|
|
MockResponse response = new MockResponse();
|
|
transferKind.setBody(response, body, 4);
|
|
server.enqueue(response);
|
|
connection = urlFactory.open(server.url("/").url());
|
|
InputStream in = connection.getInputStream();
|
|
for (int i = 0; i < body.length(); i++) {
|
|
assertTrue(in.available() >= 0);
|
|
assertEquals(body.charAt(i), in.read());
|
|
}
|
|
assertEquals(0, in.available());
|
|
assertEquals(-1, in.read());
|
|
}
|
|
|
|
@Test public void postFailsWithBufferedRequestForSmallRequest() throws Exception {
|
|
reusedConnectionFailsWithPost(TransferKind.END_OF_STREAM, 1024);
|
|
}
|
|
|
|
@Test public void postFailsWithBufferedRequestForLargeRequest() throws Exception {
|
|
reusedConnectionFailsWithPost(TransferKind.END_OF_STREAM, 16384);
|
|
}
|
|
|
|
@Test public void postFailsWithChunkedRequestForSmallRequest() throws Exception {
|
|
reusedConnectionFailsWithPost(TransferKind.CHUNKED, 1024);
|
|
}
|
|
|
|
@Test public void postFailsWithChunkedRequestForLargeRequest() throws Exception {
|
|
reusedConnectionFailsWithPost(TransferKind.CHUNKED, 16384);
|
|
}
|
|
|
|
@Test public void postFailsWithFixedLengthRequestForSmallRequest() throws Exception {
|
|
reusedConnectionFailsWithPost(TransferKind.FIXED_LENGTH, 1024);
|
|
}
|
|
|
|
@Test public void postFailsWithFixedLengthRequestForLargeRequest() throws Exception {
|
|
reusedConnectionFailsWithPost(TransferKind.FIXED_LENGTH, 16384);
|
|
}
|
|
|
|
private void reusedConnectionFailsWithPost(TransferKind transferKind, int requestSize)
|
|
throws Exception {
|
|
server.enqueue(new MockResponse().setBody("A").setSocketPolicy(DISCONNECT_AT_END));
|
|
server.enqueue(new MockResponse().setBody("B"));
|
|
server.enqueue(new MockResponse().setBody("C"));
|
|
|
|
assertContent("A", urlFactory.open(server.url("/a").url()));
|
|
|
|
// Give the server time to disconnect.
|
|
Thread.sleep(500);
|
|
|
|
// If the request body is larger than OkHttp's replay buffer, the failure may still occur.
|
|
byte[] requestBody = new byte[requestSize];
|
|
new Random(0).nextBytes(requestBody);
|
|
|
|
for (int j = 0; j < 2; j++) {
|
|
try {
|
|
connection = urlFactory.open(server.url("/b").url());
|
|
connection.setRequestMethod("POST");
|
|
transferKind.setForRequest(connection, requestBody.length);
|
|
for (int i = 0; i < requestBody.length; i += 1024) {
|
|
connection.getOutputStream().write(requestBody, i, 1024);
|
|
}
|
|
connection.getOutputStream().close();
|
|
assertContent("B", connection);
|
|
break;
|
|
} catch (IOException socketException) {
|
|
// If there's a socket exception, this must have a streamed request body.
|
|
assertEquals(0, j);
|
|
assertTrue(transferKind == TransferKind.CHUNKED
|
|
|| transferKind == TransferKind.FIXED_LENGTH);
|
|
}
|
|
}
|
|
|
|
RecordedRequest requestA = server.takeRequest();
|
|
assertEquals("/a", requestA.getPath());
|
|
RecordedRequest requestB = server.takeRequest();
|
|
assertEquals("/b", requestB.getPath());
|
|
assertEquals(Arrays.toString(requestBody), Arrays.toString(requestB.getBody().readByteArray()));
|
|
}
|
|
|
|
@Test public void postBodyRetransmittedOnFailureRecovery() throws Exception {
|
|
server.enqueue(new MockResponse().setBody("abc"));
|
|
server.enqueue(new MockResponse().setSocketPolicy(DISCONNECT_AFTER_REQUEST));
|
|
server.enqueue(new MockResponse().setBody("def"));
|
|
|
|
// Seed the connection pool so we have something that can fail.
|
|
assertContent("abc", urlFactory.open(server.url("/").url()));
|
|
|
|
HttpURLConnection post = urlFactory.open(server.url("/").url());
|
|
post.setDoOutput(true);
|
|
post.getOutputStream().write("body!".getBytes(UTF_8));
|
|
assertContent("def", post);
|
|
|
|
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 fullyBufferedPostIsTooShort() throws Exception {
|
|
server.enqueue(new MockResponse().setBody("A"));
|
|
|
|
connection = urlFactory.open(server.url("/b").url());
|
|
connection.setRequestProperty("Content-Length", "4");
|
|
connection.setRequestMethod("POST");
|
|
OutputStream out = connection.getOutputStream();
|
|
out.write('a');
|
|
out.write('b');
|
|
out.write('c');
|
|
try {
|
|
out.close();
|
|
fail();
|
|
} catch (IOException expected) {
|
|
}
|
|
}
|
|
|
|
@Test public void fullyBufferedPostIsTooLong() throws Exception {
|
|
server.enqueue(new MockResponse().setBody("A"));
|
|
|
|
connection = urlFactory.open(server.url("/b").url());
|
|
connection.setRequestProperty("Content-Length", "3");
|
|
connection.setRequestMethod("POST");
|
|
OutputStream out = connection.getOutputStream();
|
|
out.write('a');
|
|
out.write('b');
|
|
out.write('c');
|
|
try {
|
|
out.write('d');
|
|
out.flush();
|
|
fail();
|
|
} catch (IOException expected) {
|
|
}
|
|
}
|
|
|
|
@Test @Ignore public void testPooledConnectionsDetectHttp10() {
|
|
// TODO: write a test that shows pooled connections detect HTTP/1.0 (vs. HTTP/1.1)
|
|
fail("TODO");
|
|
}
|
|
|
|
@Test @Ignore public void postBodiesRetransmittedOnAuthProblems() {
|
|
fail("TODO");
|
|
}
|
|
|
|
@Test @Ignore public void cookiesAndTrailers() {
|
|
// Do cookie headers get processed too many times?
|
|
fail("TODO");
|
|
}
|
|
|
|
@Test @Ignore public void headerNamesContainingNullCharacter() {
|
|
// This is relevant for SPDY
|
|
fail("TODO");
|
|
}
|
|
|
|
@Test @Ignore public void headerValuesContainingNullCharacter() {
|
|
// This is relevant for SPDY
|
|
fail("TODO");
|
|
}
|
|
|
|
@Test public void emptyRequestHeaderValueIsAllowed() throws Exception {
|
|
server.enqueue(new MockResponse().setBody("body"));
|
|
connection = urlFactory.open(server.url("/").url());
|
|
connection.addRequestProperty("B", "");
|
|
assertContent("body", connection);
|
|
assertEquals("", connection.getRequestProperty("B"));
|
|
}
|
|
|
|
@Test public void emptyResponseHeaderValueIsAllowed() throws Exception {
|
|
server.enqueue(new MockResponse().addHeader("A:").setBody("body"));
|
|
connection = urlFactory.open(server.url("/").url());
|
|
assertContent("body", connection);
|
|
assertEquals("", connection.getHeaderField("A"));
|
|
}
|
|
|
|
@Test public void emptyRequestHeaderNameIsStrict() throws Exception {
|
|
server.enqueue(new MockResponse().setBody("body"));
|
|
connection = urlFactory.open(server.url("/").url());
|
|
try {
|
|
connection.setRequestProperty("", "A");
|
|
fail();
|
|
} catch (IllegalArgumentException expected) {
|
|
}
|
|
}
|
|
|
|
@Test public void emptyResponseHeaderNameIsLenient() throws Exception {
|
|
Headers.Builder headers = new Headers.Builder();
|
|
Internal.instance.addLenient(headers, ":A");
|
|
server.enqueue(new MockResponse().setHeaders(headers.build()).setBody("body"));
|
|
connection = urlFactory.open(server.url("/").url());
|
|
connection.getResponseCode();
|
|
assertEquals("A", connection.getHeaderField(""));
|
|
connection.getInputStream().close();
|
|
}
|
|
|
|
@Test public void requestHeaderValidationIsStrict() throws Exception {
|
|
connection = urlFactory.open(server.url("/").url());
|
|
try {
|
|
connection.addRequestProperty("a\tb", "Value");
|
|
fail();
|
|
} catch (IllegalArgumentException expected) {
|
|
}
|
|
try {
|
|
connection.addRequestProperty("Name", "c\u007fd");
|
|
fail();
|
|
} catch (IllegalArgumentException expected) {
|
|
}
|
|
try {
|
|
connection.addRequestProperty("", "Value");
|
|
fail();
|
|
} catch (IllegalArgumentException expected) {
|
|
}
|
|
try {
|
|
connection.addRequestProperty("\ud83c\udf69", "Value");
|
|
fail();
|
|
} catch (IllegalArgumentException expected) {
|
|
}
|
|
try {
|
|
connection.addRequestProperty("Name", "\u2615\ufe0f");
|
|
fail();
|
|
} catch (IllegalArgumentException expected) {
|
|
}
|
|
}
|
|
|
|
@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));
|
|
|
|
connection = urlFactory.open(server.url("/").url());
|
|
connection.getResponseCode();
|
|
assertEquals("c\u007fd", connection.getHeaderField("a\tb"));
|
|
assertEquals("\u2615\ufe0f", connection.getHeaderField("\ud83c\udf69"));
|
|
assertEquals("ef", connection.getHeaderField(""));
|
|
}
|
|
|
|
@Test @Ignore public void deflateCompression() {
|
|
fail("TODO");
|
|
}
|
|
|
|
@Test @Ignore public void postBodiesRetransmittedOnIpAddressProblems() {
|
|
fail("TODO");
|
|
}
|
|
|
|
@Test @Ignore public void pooledConnectionProblemsNotReportedToProxySelector() {
|
|
fail("TODO");
|
|
}
|
|
|
|
@Test public void customBasicAuthenticator() throws Exception {
|
|
MockResponse pleaseAuthenticate = new MockResponse().setResponseCode(401)
|
|
.addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
|
|
.setBody("Please authenticate.");
|
|
server.enqueue(pleaseAuthenticate);
|
|
server.enqueue(new MockResponse().setBody("A"));
|
|
|
|
String credential = Credentials.basic("jesse", "peanutbutter");
|
|
RecordingOkAuthenticator authenticator = new RecordingOkAuthenticator(credential);
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.authenticator(authenticator)
|
|
.build());
|
|
assertContent("A", urlFactory.open(server.url("/private").url()));
|
|
|
|
assertNull(server.takeRequest().getHeader("Authorization"));
|
|
assertEquals(credential, server.takeRequest().getHeader("Authorization"));
|
|
|
|
assertEquals(Proxy.NO_PROXY, authenticator.onlyProxy());
|
|
Response response = authenticator.onlyResponse();
|
|
assertEquals("/private", response.request().url().url().getPath());
|
|
assertEquals(Arrays.asList(new Challenge("Basic", "protected area")), response.challenges());
|
|
}
|
|
|
|
@Test public void customTokenAuthenticator() throws Exception {
|
|
MockResponse pleaseAuthenticate = new MockResponse().setResponseCode(401)
|
|
.addHeader("WWW-Authenticate: Bearer realm=\"oauthed\"")
|
|
.setBody("Please authenticate.");
|
|
server.enqueue(pleaseAuthenticate);
|
|
server.enqueue(new MockResponse().setBody("A"));
|
|
|
|
RecordingOkAuthenticator authenticator = new RecordingOkAuthenticator("oauthed abc123");
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.authenticator(authenticator)
|
|
.build());
|
|
assertContent("A", urlFactory.open(server.url("/private").url()));
|
|
|
|
assertNull(server.takeRequest().getHeader("Authorization"));
|
|
assertEquals("oauthed abc123", server.takeRequest().getHeader("Authorization"));
|
|
|
|
Response response = authenticator.onlyResponse();
|
|
assertEquals("/private", response.request().url().url().getPath());
|
|
assertEquals(Arrays.asList(new Challenge("Bearer", "oauthed")), response.challenges());
|
|
}
|
|
|
|
@Test public void authenticateCallsTrackedAsRedirects() throws Exception {
|
|
server.enqueue(new MockResponse()
|
|
.setResponseCode(302)
|
|
.addHeader("Location: /b"));
|
|
server.enqueue(new MockResponse()
|
|
.setResponseCode(401)
|
|
.addHeader("WWW-Authenticate: Basic realm=\"protected area\""));
|
|
server.enqueue(new MockResponse().setBody("c"));
|
|
|
|
RecordingOkAuthenticator authenticator = new RecordingOkAuthenticator(
|
|
Credentials.basic("jesse", "peanutbutter"));
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.authenticator(authenticator)
|
|
.build());
|
|
assertContent("c", urlFactory.open(server.url("/a").url()));
|
|
|
|
Response challengeResponse = authenticator.responses.get(0);
|
|
assertEquals("/b", challengeResponse.request().url().url().getPath());
|
|
|
|
Response redirectedBy = challengeResponse.priorResponse();
|
|
assertEquals("/a", redirectedBy.request().url().url().getPath());
|
|
}
|
|
|
|
@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", "peanutbutter");
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.authenticator(new RecordingOkAuthenticator(credential))
|
|
.build());
|
|
|
|
connection = urlFactory.open(server.url("/0").url());
|
|
assertContent("Success!", connection);
|
|
}
|
|
|
|
@Test public void doesNotAttemptAuthorization21Times() throws Exception {
|
|
for (int i = 0; i < 21; i++) {
|
|
server.enqueue(new MockResponse().setResponseCode(401));
|
|
}
|
|
|
|
String credential = Credentials.basic("jesse", "peanutbutter");
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.authenticator(new RecordingOkAuthenticator(credential))
|
|
.build());
|
|
|
|
connection = urlFactory.open(server.url("/").url());
|
|
try {
|
|
connection.getInputStream();
|
|
fail();
|
|
} catch (ProtocolException expected) {
|
|
assertEquals(401, connection.getResponseCode());
|
|
assertEquals("Too many follow-up requests: 21", expected.getMessage());
|
|
}
|
|
}
|
|
|
|
@Test public void setsNegotiatedProtocolHeader_SPDY_3() throws Exception {
|
|
setsNegotiatedProtocolHeader(Protocol.SPDY_3);
|
|
}
|
|
|
|
@Test public void setsNegotiatedProtocolHeader_HTTP_2() throws Exception {
|
|
setsNegotiatedProtocolHeader(Protocol.HTTP_2);
|
|
}
|
|
|
|
private void setsNegotiatedProtocolHeader(Protocol protocol) throws IOException {
|
|
enableProtocol(protocol);
|
|
server.enqueue(new MockResponse().setBody("A"));
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.protocols(Arrays.asList(protocol, Protocol.HTTP_1_1))
|
|
.build());
|
|
connection = urlFactory.open(server.url("/").url());
|
|
List<String> protocolValues = connection.getHeaderFields().get(SELECTED_PROTOCOL);
|
|
assertEquals(Arrays.asList(protocol.toString()), protocolValues);
|
|
assertContent("A", connection);
|
|
}
|
|
|
|
@Test public void http10SelectedProtocol() throws Exception {
|
|
server.enqueue(new MockResponse().setStatus("HTTP/1.0 200 OK"));
|
|
connection = urlFactory.open(server.url("/").url());
|
|
List<String> protocolValues = connection.getHeaderFields().get(SELECTED_PROTOCOL);
|
|
assertEquals(Arrays.asList("http/1.0"), protocolValues);
|
|
}
|
|
|
|
@Test public void http11SelectedProtocol() throws Exception {
|
|
server.enqueue(new MockResponse().setStatus("HTTP/1.1 200 OK"));
|
|
connection = urlFactory.open(server.url("/").url());
|
|
List<String> protocolValues = connection.getHeaderFields().get(SELECTED_PROTOCOL);
|
|
assertEquals(Arrays.asList("http/1.1"), protocolValues);
|
|
}
|
|
|
|
/** For example, empty Protobuf RPC messages end up as a zero-length POST. */
|
|
@Test public void zeroLengthPost() throws IOException, InterruptedException {
|
|
zeroLengthPayload("POST");
|
|
}
|
|
|
|
@Test public void zeroLengthPost_SPDY_3() throws Exception {
|
|
enableProtocol(Protocol.SPDY_3);
|
|
zeroLengthPost();
|
|
}
|
|
|
|
@Test public void zeroLengthPost_HTTP_2() throws Exception {
|
|
enableProtocol(Protocol.HTTP_2);
|
|
zeroLengthPost();
|
|
}
|
|
|
|
/** For example, creating an Amazon S3 bucket ends up as a zero-length POST. */
|
|
@Test public void zeroLengthPut() throws IOException, InterruptedException {
|
|
zeroLengthPayload("PUT");
|
|
}
|
|
|
|
@Test public void zeroLengthPut_SPDY_3() throws Exception {
|
|
enableProtocol(Protocol.SPDY_3);
|
|
zeroLengthPut();
|
|
}
|
|
|
|
@Test public void zeroLengthPut_HTTP_2() throws Exception {
|
|
enableProtocol(Protocol.HTTP_2);
|
|
zeroLengthPut();
|
|
}
|
|
|
|
private void zeroLengthPayload(String method)
|
|
throws IOException, InterruptedException {
|
|
server.enqueue(new MockResponse());
|
|
connection = urlFactory.open(server.url("/").url());
|
|
connection.setRequestProperty("Content-Length", "0");
|
|
connection.setRequestMethod(method);
|
|
connection.setFixedLengthStreamingMode(0);
|
|
connection.setDoOutput(true);
|
|
assertContent("", connection);
|
|
RecordedRequest zeroLengthPayload = server.takeRequest();
|
|
assertEquals(method, zeroLengthPayload.getMethod());
|
|
assertEquals("0", zeroLengthPayload.getHeader("content-length"));
|
|
assertEquals(0L, zeroLengthPayload.getBodySize());
|
|
}
|
|
|
|
@Test public void unspecifiedRequestBodyContentTypeGetsDefault() throws Exception {
|
|
server.enqueue(new MockResponse());
|
|
|
|
connection = urlFactory.open(server.url("/").url());
|
|
connection.setDoOutput(true);
|
|
connection.getOutputStream().write("abc".getBytes(UTF_8));
|
|
assertEquals(200, connection.getResponseCode());
|
|
|
|
RecordedRequest request = server.takeRequest();
|
|
assertEquals("application/x-www-form-urlencoded", request.getHeader("Content-Type"));
|
|
assertEquals("3", request.getHeader("Content-Length"));
|
|
assertEquals("abc", request.getBody().readUtf8());
|
|
}
|
|
|
|
@Test public void setProtocols() throws Exception {
|
|
server.enqueue(new MockResponse().setBody("A"));
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.protocols(Arrays.asList(Protocol.HTTP_1_1))
|
|
.build());
|
|
assertContent("A", urlFactory.open(server.url("/").url()));
|
|
}
|
|
|
|
@Test public void setProtocolsWithoutHttp11() throws Exception {
|
|
try {
|
|
new OkHttpClient.Builder().protocols(Arrays.asList(Protocol.SPDY_3));
|
|
fail();
|
|
} catch (IllegalArgumentException expected) {
|
|
}
|
|
}
|
|
|
|
@Test public void setProtocolsWithNull() throws Exception {
|
|
try {
|
|
new OkHttpClient.Builder().protocols(Arrays.asList(Protocol.HTTP_1_1, null));
|
|
fail();
|
|
} catch (IllegalArgumentException expected) {
|
|
}
|
|
}
|
|
|
|
@Test public void veryLargeFixedLengthRequest() throws Exception {
|
|
server.setBodyLimit(0);
|
|
server.enqueue(new MockResponse());
|
|
|
|
connection = urlFactory.open(server.url("/").url());
|
|
connection.setDoOutput(true);
|
|
long contentLength = Integer.MAX_VALUE + 1L;
|
|
connection.setFixedLengthStreamingMode(contentLength);
|
|
OutputStream out = connection.getOutputStream();
|
|
byte[] buffer = new byte[1024 * 1024];
|
|
for (long bytesWritten = 0; bytesWritten < contentLength; ) {
|
|
int byteCount = (int) Math.min(buffer.length, contentLength - bytesWritten);
|
|
out.write(buffer, 0, byteCount);
|
|
bytesWritten += byteCount;
|
|
}
|
|
assertContent("", connection);
|
|
|
|
RecordedRequest request = server.takeRequest();
|
|
assertEquals(Long.toString(contentLength), request.getHeader("Content-Length"));
|
|
}
|
|
|
|
/**
|
|
* We had a bug where we attempted to gunzip responses that didn't have a body. This only came up
|
|
* with 304s since that response code can include headers (like "Content-Encoding") without any
|
|
* content to go along with it. https://github.com/square/okhttp/issues/358
|
|
*/
|
|
@Test public void noTransparentGzipFor304NotModified() throws Exception {
|
|
server.enqueue(new MockResponse()
|
|
.clearHeaders()
|
|
.setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED)
|
|
.addHeader("Content-Encoding: gzip"));
|
|
server.enqueue(new MockResponse().setBody("b"));
|
|
|
|
HttpURLConnection connection1 = urlFactory.open(server.url("/").url());
|
|
assertEquals(HttpURLConnection.HTTP_NOT_MODIFIED, connection1.getResponseCode());
|
|
assertContent("", connection1);
|
|
|
|
HttpURLConnection connection2 = urlFactory.open(server.url("/").url());
|
|
assertEquals(HttpURLConnection.HTTP_OK, connection2.getResponseCode());
|
|
assertContent("b", connection2);
|
|
|
|
RecordedRequest requestA = server.takeRequest();
|
|
assertEquals(0, requestA.getSequenceNumber());
|
|
|
|
RecordedRequest requestB = server.takeRequest();
|
|
assertEquals(1, requestB.getSequenceNumber());
|
|
}
|
|
|
|
/**
|
|
* We had a bug where we weren't closing Gzip streams on redirects.
|
|
* https://github.com/square/okhttp/issues/441
|
|
*/
|
|
@Test public void gzipWithRedirectAndConnectionReuse() throws Exception {
|
|
server.enqueue(new MockResponse()
|
|
.setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
|
|
.addHeader("Location: /foo")
|
|
.addHeader("Content-Encoding: gzip")
|
|
.setBody(gzip("Moved! Moved! Moved!")));
|
|
server.enqueue(new MockResponse().setBody("This is the new page!"));
|
|
|
|
HttpURLConnection connection = urlFactory.open(server.url("/").url());
|
|
assertContent("This is the new page!", connection);
|
|
|
|
RecordedRequest requestA = server.takeRequest();
|
|
assertEquals(0, requestA.getSequenceNumber());
|
|
|
|
RecordedRequest requestB = server.takeRequest();
|
|
assertEquals(1, requestB.getSequenceNumber());
|
|
}
|
|
|
|
/**
|
|
* The RFC is unclear in this regard as it only specifies that this should invalidate the cache
|
|
* entry (if any).
|
|
*/
|
|
@Test public void bodyPermittedOnDelete() throws Exception {
|
|
server.enqueue(new MockResponse());
|
|
|
|
HttpURLConnection connection = urlFactory.open(server.url("/").url());
|
|
connection.setRequestMethod("DELETE");
|
|
connection.setDoOutput(true);
|
|
connection.getOutputStream().write("BODY".getBytes(UTF_8));
|
|
assertEquals(200, connection.getResponseCode());
|
|
|
|
RecordedRequest request = server.takeRequest();
|
|
assertEquals("DELETE", request.getMethod());
|
|
assertEquals("BODY", request.getBody().readUtf8());
|
|
}
|
|
|
|
@Test public void userAgentPicksUpHttpAgentSystemProperty() throws Exception {
|
|
server.enqueue(new MockResponse().setBody("abc"));
|
|
|
|
System.setProperty("http.agent", "foo");
|
|
assertContent("abc", urlFactory.open(server.url("/").url()));
|
|
|
|
RecordedRequest request = server.takeRequest();
|
|
assertEquals("foo", request.getHeader("User-Agent"));
|
|
}
|
|
|
|
/** https://github.com/square/okhttp/issues/891 */
|
|
@Test public void userAgentSystemPropertyIsNotAscii() throws Exception {
|
|
server.enqueue(new MockResponse().setBody("abc"));
|
|
|
|
System.setProperty("http.agent", "a\nb\ud83c\udf69c\ud83c\udf68d\u007fe");
|
|
assertContent("abc", urlFactory.open(server.url("/").url()));
|
|
|
|
RecordedRequest request = server.takeRequest();
|
|
assertEquals("a?b?c?d?e", request.getHeader("User-Agent"));
|
|
}
|
|
|
|
@Test public void userAgentDefaultsToOkHttpVersion() throws Exception {
|
|
server.enqueue(new MockResponse().setBody("abc"));
|
|
|
|
assertContent("abc", urlFactory.open(server.url("/").url()));
|
|
|
|
RecordedRequest request = server.takeRequest();
|
|
assertEquals(Version.userAgent(), request.getHeader("User-Agent"));
|
|
}
|
|
|
|
@Test public void interceptorsNotInvoked() throws Exception {
|
|
Interceptor interceptor = new Interceptor() {
|
|
@Override public Response intercept(Chain chain) throws IOException {
|
|
throw new AssertionError();
|
|
}
|
|
};
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.addInterceptor(interceptor)
|
|
.addNetworkInterceptor(interceptor)
|
|
.build());
|
|
|
|
server.enqueue(new MockResponse().setBody("abc"));
|
|
assertContent("abc", urlFactory.open(server.url("/").url()));
|
|
}
|
|
|
|
@Test public void urlWithSpaceInHost() throws Exception {
|
|
URLConnection urlConnection = urlFactory.open(new URL("http://and roid.com/"));
|
|
try {
|
|
urlConnection.getInputStream();
|
|
fail();
|
|
} catch (UnknownHostException expected) {
|
|
}
|
|
}
|
|
|
|
@Test public void urlWithSpaceInHostViaHttpProxy() throws Exception {
|
|
server.enqueue(new MockResponse());
|
|
URLConnection urlConnection =
|
|
urlFactory.open(new URL("http://and roid.com/"), server.toProxyAddress());
|
|
|
|
try {
|
|
// This test is to check that a NullPointerException is not thrown.
|
|
urlConnection.getInputStream();
|
|
fail(); // the RI makes a bogus proxy request for "GET http://and roid.com/ HTTP/1.1"
|
|
} catch (UnknownHostException expected) {
|
|
}
|
|
}
|
|
|
|
@Test public void urlHostWithNul() throws Exception {
|
|
URLConnection urlConnection = urlFactory.open(new URL("http://host\u0000/"));
|
|
try {
|
|
urlConnection.getInputStream();
|
|
fail();
|
|
} catch (UnknownHostException expected) {
|
|
}
|
|
}
|
|
|
|
@Test public void urlRedirectToHostWithNul() throws Exception {
|
|
String redirectUrl = "http://host\u0000/";
|
|
server.enqueue(new MockResponse().setResponseCode(302)
|
|
.addHeaderLenient("Location", redirectUrl));
|
|
|
|
HttpURLConnection urlConnection = urlFactory.open(server.url("/").url());
|
|
assertEquals(302, urlConnection.getResponseCode());
|
|
assertEquals(redirectUrl, urlConnection.getHeaderField("Location"));
|
|
}
|
|
|
|
@Test public void urlWithBadAsciiHost() throws Exception {
|
|
URLConnection urlConnection = urlFactory.open(new URL("http://host\u0001/"));
|
|
try {
|
|
urlConnection.getInputStream();
|
|
fail();
|
|
} catch (UnknownHostException expected) {
|
|
}
|
|
}
|
|
|
|
@Test public void instanceFollowsRedirects() throws Exception {
|
|
testInstanceFollowsRedirects("http://www.google.com/");
|
|
testInstanceFollowsRedirects("https://www.google.com/");
|
|
}
|
|
|
|
@Test public void setSslSocketFactoryFailsOnJdk9() throws Exception {
|
|
assumeTrue(getPlatform().equals("jdk9"));
|
|
|
|
URL url = server.url("/").url();
|
|
HttpsURLConnection connection = (HttpsURLConnection) urlFactory.open(url);
|
|
try {
|
|
connection.setSSLSocketFactory(sslClient.socketFactory);
|
|
fail();
|
|
} catch (UnsupportedOperationException expected) {
|
|
}
|
|
}
|
|
|
|
/** Confirm that runtime exceptions thrown inside of OkHttp propagate to the caller. */
|
|
@Test public void unexpectedExceptionSync() throws Exception {
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.dns(new Dns() {
|
|
@Override public List<InetAddress> lookup(String hostname) {
|
|
throw new RuntimeException("boom!");
|
|
}
|
|
})
|
|
.build());
|
|
|
|
server.enqueue(new MockResponse());
|
|
|
|
HttpURLConnection connection = urlFactory.open(server.url("/").url());
|
|
try {
|
|
connection.getResponseCode(); // Use the synchronous implementation.
|
|
fail();
|
|
} catch (RuntimeException expected) {
|
|
assertEquals("boom!", expected.getMessage());
|
|
}
|
|
}
|
|
|
|
/** Confirm that runtime exceptions thrown inside of OkHttp propagate to the caller. */
|
|
@Test public void unexpectedExceptionAsync() throws Exception {
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.dns(new Dns() {
|
|
@Override public List<InetAddress> lookup(String hostname) {
|
|
throw new RuntimeException("boom!");
|
|
}
|
|
})
|
|
.build());
|
|
|
|
server.enqueue(new MockResponse());
|
|
|
|
HttpURLConnection connection = urlFactory.open(server.url("/").url());
|
|
try {
|
|
connection.connect(); // Force the async implementation.
|
|
fail();
|
|
} catch (RuntimeException expected) {
|
|
assertEquals("boom!", expected.getMessage());
|
|
}
|
|
}
|
|
|
|
private void testInstanceFollowsRedirects(String spec) throws Exception {
|
|
URL url = new URL(spec);
|
|
HttpURLConnection urlConnection = urlFactory.open(url);
|
|
urlConnection.setInstanceFollowRedirects(true);
|
|
assertTrue(urlConnection.getInstanceFollowRedirects());
|
|
urlConnection.setInstanceFollowRedirects(false);
|
|
assertFalse(urlConnection.getInstanceFollowRedirects());
|
|
}
|
|
|
|
/** Returns a gzipped copy of {@code bytes}. */
|
|
public Buffer gzip(String data) throws IOException {
|
|
Buffer result = new Buffer();
|
|
BufferedSink gzipSink = Okio.buffer(new GzipSink(result));
|
|
gzipSink.writeUtf8(data);
|
|
gzipSink.close();
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Reads at most {@code limit} characters from {@code in} and asserts that content equals {@code
|
|
* expected}.
|
|
*/
|
|
private void assertContent(String expected, HttpURLConnection connection, int limit)
|
|
throws IOException {
|
|
connection.connect();
|
|
assertEquals(expected, readAscii(connection.getInputStream(), limit));
|
|
}
|
|
|
|
private void assertContent(String expected, HttpURLConnection connection) throws IOException {
|
|
assertContent(expected, connection, Integer.MAX_VALUE);
|
|
}
|
|
|
|
private Set<String> newSet(String... elements) {
|
|
return new LinkedHashSet<>(Arrays.asList(elements));
|
|
}
|
|
|
|
enum TransferKind {
|
|
CHUNKED() {
|
|
@Override void setBody(MockResponse response, Buffer content, int chunkSize)
|
|
throws IOException {
|
|
response.setChunkedBody(content, chunkSize);
|
|
}
|
|
|
|
@Override void setForRequest(HttpURLConnection connection, int contentLength) {
|
|
connection.setChunkedStreamingMode(5);
|
|
}
|
|
},
|
|
FIXED_LENGTH() {
|
|
@Override void setBody(MockResponse response, Buffer content, int chunkSize) {
|
|
response.setBody(content);
|
|
}
|
|
|
|
@Override void setForRequest(HttpURLConnection connection, int contentLength) {
|
|
connection.setFixedLengthStreamingMode(contentLength);
|
|
}
|
|
},
|
|
END_OF_STREAM() {
|
|
@Override void setBody(MockResponse response, Buffer content, int chunkSize) {
|
|
response.setBody(content);
|
|
response.setSocketPolicy(DISCONNECT_AT_END);
|
|
response.removeHeader("Content-Length");
|
|
}
|
|
|
|
@Override void setForRequest(HttpURLConnection connection, int contentLength) {
|
|
}
|
|
};
|
|
|
|
abstract void setBody(MockResponse response, Buffer content, int chunkSize) throws IOException;
|
|
|
|
abstract void setForRequest(HttpURLConnection connection, int contentLength);
|
|
|
|
void setBody(MockResponse response, String content, int chunkSize) throws IOException {
|
|
setBody(response, new Buffer().writeUtf8(content), chunkSize);
|
|
}
|
|
}
|
|
|
|
enum ProxyConfig {
|
|
NO_PROXY() {
|
|
@Override public HttpURLConnection connect(
|
|
MockWebServer server, OkUrlFactory streamHandlerFactory, URL url)
|
|
throws IOException {
|
|
streamHandlerFactory.setClient(streamHandlerFactory.client().newBuilder()
|
|
.proxy(Proxy.NO_PROXY)
|
|
.build());
|
|
return streamHandlerFactory.open(url);
|
|
}
|
|
},
|
|
|
|
CREATE_ARG() {
|
|
@Override public HttpURLConnection connect(
|
|
MockWebServer server, OkUrlFactory streamHandlerFactory, URL url)
|
|
throws IOException {
|
|
streamHandlerFactory.setClient(streamHandlerFactory.client().newBuilder()
|
|
.proxy(server.toProxyAddress())
|
|
.build());
|
|
return streamHandlerFactory.open(url);
|
|
}
|
|
},
|
|
|
|
PROXY_SYSTEM_PROPERTY() {
|
|
@Override public HttpURLConnection connect(
|
|
MockWebServer server, OkUrlFactory streamHandlerFactory, URL url)
|
|
throws IOException {
|
|
System.setProperty("proxyHost", server.getHostName());
|
|
System.setProperty("proxyPort", Integer.toString(server.getPort()));
|
|
return streamHandlerFactory.open(url);
|
|
}
|
|
},
|
|
|
|
HTTP_PROXY_SYSTEM_PROPERTY() {
|
|
@Override public HttpURLConnection connect(
|
|
MockWebServer server, OkUrlFactory streamHandlerFactory, URL url)
|
|
throws IOException {
|
|
System.setProperty("http.proxyHost", server.getHostName());
|
|
System.setProperty("http.proxyPort", Integer.toString(server.getPort()));
|
|
return streamHandlerFactory.open(url);
|
|
}
|
|
},
|
|
|
|
HTTPS_PROXY_SYSTEM_PROPERTY() {
|
|
@Override public HttpURLConnection connect(
|
|
MockWebServer server, OkUrlFactory streamHandlerFactory, URL url)
|
|
throws IOException {
|
|
System.setProperty("https.proxyHost", server.getHostName());
|
|
System.setProperty("https.proxyPort", Integer.toString(server.getPort()));
|
|
return streamHandlerFactory.open(url);
|
|
}
|
|
};
|
|
|
|
public abstract HttpURLConnection connect(
|
|
MockWebServer server, OkUrlFactory streamHandlerFactory, URL url)
|
|
throws IOException;
|
|
}
|
|
|
|
private static class RecordingTrustManager implements X509TrustManager {
|
|
private final List<String> calls = new ArrayList<String>();
|
|
private final X509TrustManager delegate;
|
|
|
|
public RecordingTrustManager(X509TrustManager delegate) {
|
|
this.delegate = delegate;
|
|
}
|
|
|
|
public X509Certificate[] getAcceptedIssuers() {
|
|
return delegate.getAcceptedIssuers();
|
|
}
|
|
|
|
public void checkClientTrusted(X509Certificate[] chain, String authType)
|
|
throws CertificateException {
|
|
calls.add("checkClientTrusted " + certificatesToString(chain));
|
|
}
|
|
|
|
public void checkServerTrusted(X509Certificate[] chain, String authType)
|
|
throws CertificateException {
|
|
calls.add("checkServerTrusted " + certificatesToString(chain));
|
|
}
|
|
|
|
private String certificatesToString(X509Certificate[] certificates) {
|
|
List<String> result = new ArrayList<String>();
|
|
for (X509Certificate certificate : certificates) {
|
|
result.add(certificate.getSubjectDN() + " " + certificate.getSerialNumber());
|
|
}
|
|
return result.toString();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
urlFactory.setClient(urlFactory.client().newBuilder()
|
|
.sslSocketFactory(sslClient.socketFactory, sslClient.trustManager)
|
|
.hostnameVerifier(new RecordingHostnameVerifier())
|
|
.protocols(Arrays.asList(protocol, Protocol.HTTP_1_1))
|
|
.build());
|
|
server.useHttps(sslClient.socketFactory, false);
|
|
server.setProtocolNegotiationEnabled(true);
|
|
server.setProtocols(urlFactory.client().protocols());
|
|
}
|
|
|
|
/**
|
|
* Used during tests that involve TLS connection fallback attempts. OkHttp includes the
|
|
* TLS_FALLBACK_SCSV cipher on fallback connections. See {@link FallbackTestClientSocketFactory}
|
|
* for details.
|
|
*/
|
|
private FallbackTestClientSocketFactory suppressTlsFallbackClientSocketFactory() {
|
|
return new FallbackTestClientSocketFactory(sslClient.socketFactory);
|
|
}
|
|
|
|
private String getPlatform() {
|
|
return System.getProperty("okhttp.platform", "platform");
|
|
}
|
|
}
|