1
0
mirror of https://github.com/square/okhttp.git synced 2026-01-25 16:01:38 +03:00

Optionally follow redirects from HTTPS to HTTP and vice versa.

The API is imperfect because HttpURLConnection doesn't expose
any SSL information, and because HttpsURLConnection won't necessarily
have a secure final request.
This commit is contained in:
jwilson
2013-02-18 04:18:53 -05:00
parent 6741a8f51f
commit 38575673e4
5 changed files with 256 additions and 163 deletions

View File

@@ -36,6 +36,7 @@ public final class OkHttpClient {
private SSLSocketFactory sslSocketFactory;
private HostnameVerifier hostnameVerifier;
private ConnectionPool connectionPool;
private boolean followProtocolRedirects = true;
/**
* Sets the HTTP proxy that will be used by connections created by this
@@ -48,6 +49,10 @@ public final class OkHttpClient {
return this;
}
public Proxy getProxy() {
return proxy;
}
/**
* Sets the proxy selection policy to be used if no {@link #setProxy proxy}
* is specified explicitly. The proxy selector may return multiple proxies;
@@ -62,6 +67,10 @@ public final class OkHttpClient {
return this;
}
public ProxySelector getProxySelector() {
return proxySelector;
}
/**
* Sets the cookie handler to be used to read outgoing cookies and write
* incoming cookies.
@@ -74,6 +83,10 @@ public final class OkHttpClient {
return this;
}
public CookieHandler getCookieHandler() {
return cookieHandler;
}
/**
* Sets the response cache to be used to read and write cached responses.
*
@@ -85,6 +98,10 @@ public final class OkHttpClient {
return this;
}
public ResponseCache getResponseCache() {
return responseCache;
}
/**
* Sets the socket factory used to secure HTTPS connections.
*
@@ -96,6 +113,10 @@ public final class OkHttpClient {
return this;
}
public SSLSocketFactory getSslSocketFactory() {
return sslSocketFactory;
}
/**
* Sets the verifier used to confirm that response certificates apply to
* requested hostnames for HTTPS connections.
@@ -108,6 +129,10 @@ public final class OkHttpClient {
return this;
}
public HostnameVerifier getHostnameVerifier() {
return hostnameVerifier;
}
/**
* Sets the connection pool used to recycle HTTP and HTTPS connections.
*
@@ -119,31 +144,55 @@ public final class OkHttpClient {
return this;
}
public HttpURLConnection open(URL url) {
ProxySelector proxySelector =
this.proxySelector != null ? this.proxySelector : ProxySelector.getDefault();
CookieHandler cookieHandler =
this.cookieHandler != null ? this.cookieHandler : CookieHandler.getDefault();
ResponseCache responseCache =
this.responseCache != null ? this.responseCache : ResponseCache.getDefault();
ConnectionPool connectionPool =
this.connectionPool != null ? this.connectionPool : ConnectionPool.getDefault();
public ConnectionPool getConnectionPool() {
return connectionPool;
}
/**
* Configure this client to follow redirects from HTTPS to HTTP and from HTTP
* to HTTPS.
*
* <p>If unset, protocol redirects will be followed. This is different than
* the built-in {@code HttpURLConnection}'s default.
*/
public OkHttpClient setFollowProtocolRedirects(boolean followProtocolRedirects) {
this.followProtocolRedirects = followProtocolRedirects;
return this;
}
public boolean getFollowProtocolRedirects() {
return followProtocolRedirects;
}
public HttpURLConnection open(URL url) {
String protocol = url.getProtocol();
if (protocol.equals("http")) {
return new HttpURLConnectionImpl(url, 80, proxy, proxySelector, cookieHandler, responseCache,
connectionPool);
return new HttpURLConnectionImpl(url, copyWithDefaults());
} else if (protocol.equals("https")) {
HttpsURLConnectionImpl result =
new HttpsURLConnectionImpl(url, 443, proxy, proxySelector, cookieHandler, responseCache,
connectionPool);
result.setSSLSocketFactory(this.sslSocketFactory != null ? this.sslSocketFactory
: HttpsURLConnection.getDefaultSSLSocketFactory());
result.setHostnameVerifier(this.hostnameVerifier != null ? this.hostnameVerifier
: HttpsURLConnection.getDefaultHostnameVerifier());
return result;
return new HttpsURLConnectionImpl(url, copyWithDefaults());
} else {
throw new IllegalArgumentException("Unexpected protocol: " + protocol);
}
}
/**
* Returns a copy of this OkHttpClient that uses the system-wide default for
* each field that hasn't been explicitly configured.
*/
private OkHttpClient copyWithDefaults() {
OkHttpClient result = new OkHttpClient();
result.proxy = proxy;
result.proxySelector = proxySelector != null ? proxySelector : ProxySelector.getDefault();
result.cookieHandler = cookieHandler != null ? cookieHandler : CookieHandler.getDefault();
result.responseCache = responseCache != null ? responseCache : ResponseCache.getDefault();
result.sslSocketFactory = sslSocketFactory != null
? sslSocketFactory
: HttpsURLConnection.getDefaultSSLSocketFactory();
result.hostnameVerifier = hostnameVerifier != null
? hostnameVerifier
: HttpsURLConnection.getDefaultHostnameVerifier();
result.connectionPool = connectionPool != null ? connectionPool : ConnectionPool.getDefault();
result.followProtocolRedirects = followProtocolRedirects;
return result;
}
}

View File

@@ -32,7 +32,6 @@ import java.io.OutputStream;
import java.net.CacheRequest;
import java.net.CacheResponse;
import java.net.CookieHandler;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URI;
import java.net.URISyntaxException;
@@ -272,8 +271,14 @@ public class HttpEngine {
if (uriHost == null) {
throw new UnknownHostException(uri.toString());
}
Address address = new Address(uriHost, getEffectivePort(uri), getSslSocketFactory(),
getHostnameVerifier(), policy.requestedProxy);
SSLSocketFactory sslSocketFactory = null;
HostnameVerifier hostnameVerifier = null;
if (uri.getScheme().equalsIgnoreCase("https")) {
sslSocketFactory = policy.sslSocketFactory;
hostnameVerifier = policy.hostnameVerifier;
}
Address address = new Address(uriHost, getEffectivePort(uri), sslSocketFactory,
hostnameVerifier, policy.requestedProxy);
routeSelector =
new RouteSelector(address, uri, policy.proxySelector, policy.connectionPool, Dns.DEFAULT);
}
@@ -391,11 +396,7 @@ public class HttpEngine {
}
// Offer this request to the cache.
cacheRequest = policy.responseCache.put(uri, getHttpConnectionToCache());
}
protected HttpURLConnection getHttpConnectionToCache() {
return policy;
cacheRequest = policy.responseCache.put(uri, policy.getHttpConnectionToCache());
}
/**
@@ -576,22 +577,6 @@ public class HttpEngine {
: connection.getProxy().type() == Proxy.Type.HTTP; // A proxy was selected.
}
/**
* Returns the SSL configuration for connections created by this engine.
* We cannot reuse HTTPS connections if the socket factory has changed.
*/
protected SSLSocketFactory getSslSocketFactory() {
return null;
}
/**
* Returns the hostname verifier for connections created by this engine. We
* cannot reuse HTTPS connections if the hostname verifier has changed.
*/
protected HostnameVerifier getHostnameVerifier() {
return null;
}
public static String getDefaultUserAgent() {
String agent = System.getProperty("http.agent");
return agent != null ? agent : ("Java" + System.getProperty("java.version"));
@@ -651,7 +636,7 @@ public class HttpEngine {
if (policy.responseCache instanceof OkResponseCache) {
OkResponseCache httpResponseCache = (OkResponseCache) policy.responseCache;
httpResponseCache.trackConditionalCacheHit();
httpResponseCache.update(cacheResponse, getHttpConnectionToCache());
httpResponseCache.update(cacheResponse, policy.getHttpConnectionToCache());
}
return;
} else {

View File

@@ -19,6 +19,7 @@ package com.squareup.okhttp.internal.http;
import com.squareup.okhttp.Connection;
import com.squareup.okhttp.ConnectionPool;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.internal.Util;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -38,7 +39,9 @@ import java.security.Permission;
import java.security.cert.CertificateException;
import java.util.List;
import java.util.Map;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSocketFactory;
import static com.squareup.okhttp.internal.Util.getEffectivePort;
@@ -63,7 +66,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
*/
private static final int MAX_REDIRECTS = 20;
private final int defaultPort;
private final boolean followProtocolRedirects;
/** The proxy requested by the client, or null for a proxy to be selected automatically. */
final Proxy requestedProxy;
@@ -72,6 +75,9 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
final CookieHandler cookieHandler;
final ResponseCache responseCache;
final ConnectionPool connectionPool;
/* SSL configuration; necessary for HTTP requests that get redirected to HTTPS. */
SSLSocketFactory sslSocketFactory;
HostnameVerifier hostnameVerifier;
private final RawHeaders rawRequestHeaders = new RawHeaders();
@@ -80,15 +86,16 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
protected IOException httpEngineFailure;
protected HttpEngine httpEngine;
public HttpURLConnectionImpl(URL url, int defaultPort, Proxy proxy, ProxySelector proxySelector,
CookieHandler cookieHandler, ResponseCache responseCache, ConnectionPool connectionPool) {
public HttpURLConnectionImpl(URL url, OkHttpClient client) {
super(url);
this.defaultPort = defaultPort;
this.requestedProxy = proxy;
this.proxySelector = proxySelector;
this.cookieHandler = cookieHandler;
this.responseCache = responseCache;
this.connectionPool = connectionPool;
this.followProtocolRedirects = client.getFollowProtocolRedirects();
this.requestedProxy = client.getProxy();
this.proxySelector = client.getProxySelector();
this.cookieHandler = client.getCookieHandler();
this.responseCache = client.getResponseCache();
this.connectionPool = client.getConnectionPool();
this.sslSocketFactory = client.getSslSocketFactory();
this.hostnameVerifier = client.getHostnameVerifier();
}
@Override public final void connect() throws IOException {
@@ -258,13 +265,20 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
}
}
/**
* Create a new HTTP engine. This hook method is non-final so it can be
* overridden by HttpsURLConnectionImpl.
*/
protected HttpEngine newHttpEngine(String method, RawHeaders requestHeaders,
protected HttpURLConnection getHttpConnectionToCache() {
return this;
}
private HttpEngine newHttpEngine(String method, RawHeaders requestHeaders,
Connection connection, RetryableOutputStream requestBody) throws IOException {
return new HttpEngine(this, method, requestHeaders, connection, requestBody);
if (url.getProtocol().equals("http")) {
return new HttpEngine(this, method, requestHeaders, connection, requestBody);
} else if (url.getProtocol().equals("https")) {
return new HttpsURLConnectionImpl.HttpsEngine(
this, method, requestHeaders, connection, requestBody);
} else {
throw new AssertionError();
}
}
/**
@@ -413,11 +427,16 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
}
URL previousUrl = url;
url = new URL(previousUrl, location);
if (!previousUrl.getProtocol().equals(url.getProtocol())) {
return Retry.NONE; // the scheme changed; don't retry.
if (!url.getProtocol().equals("https") && !url.getProtocol().equals("http")) {
return Retry.NONE; // Don't follow redirects to unsupported protocols.
}
if (previousUrl.getHost().equals(url.getHost())
&& getEffectivePort(previousUrl) == getEffectivePort(url)) {
boolean sameProtocol = previousUrl.getProtocol().equals(url.getProtocol());
if (!sameProtocol && !followProtocolRedirects) {
return Retry.NONE; // This client doesn't follow redirects across protocols.
}
boolean sameHost = previousUrl.getHost().equals(url.getHost());
boolean samePort = getEffectivePort(previousUrl) == getEffectivePort(url);
if (sameHost && samePort && sameProtocol) {
return Retry.SAME_CONNECTION;
} else {
return Retry.DIFFERENT_CONNECTION;
@@ -428,10 +447,6 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
}
}
final int getDefaultPort() {
return defaultPort;
}
/** @see java.net.HttpURLConnection#setFixedLengthStreamingMode(int) */
final int getFixedContentLength() {
return fixedContentLength;

View File

@@ -17,18 +17,14 @@
package com.squareup.okhttp.internal.http;
import com.squareup.okhttp.Connection;
import com.squareup.okhttp.ConnectionPool;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.TunnelRequest;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.CacheResponse;
import java.net.CookieHandler;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.ResponseCache;
import java.net.SecureCacheResponse;
import java.net.URL;
import java.security.Permission;
@@ -49,73 +45,84 @@ public final class HttpsURLConnectionImpl extends HttpsURLConnection {
/** HttpUrlConnectionDelegate allows reuse of HttpURLConnectionImpl. */
private final HttpUrlConnectionDelegate delegate;
public HttpsURLConnectionImpl(URL url, int defaultPort, Proxy proxy, ProxySelector proxySelector,
CookieHandler cookieHandler, ResponseCache responseCache, ConnectionPool connectionPool) {
public HttpsURLConnectionImpl(URL url, OkHttpClient client) {
super(url);
delegate = new HttpUrlConnectionDelegate(url, defaultPort, proxy, proxySelector, cookieHandler,
responseCache, connectionPool);
delegate = new HttpUrlConnectionDelegate(url, client);
}
private void checkConnected() {
if (delegate.getSSLSocket() == null) {
throw new IllegalStateException("Connection has not yet been established");
@Override public String getCipherSuite() {
SecureCacheResponse cacheResponse = delegate.getSecureCacheResponse();
if (cacheResponse != null) {
return cacheResponse.getCipherSuite();
}
SSLSocket sslSocket = getSslSocket();
if (sslSocket != null) {
return sslSocket.getSession().getCipherSuite();
}
return null;
}
@Override public Certificate[] getLocalCertificates() {
SecureCacheResponse cacheResponse = delegate.getSecureCacheResponse();
if (cacheResponse != null) {
List<Certificate> result = cacheResponse.getLocalCertificateChain();
return result != null ? result.toArray(new Certificate[result.size()]) : null;
}
SSLSocket sslSocket = getSslSocket();
if (sslSocket != null) {
return sslSocket.getSession().getLocalCertificates();
}
return null;
}
@Override public Certificate[] getServerCertificates() throws SSLPeerUnverifiedException {
SecureCacheResponse cacheResponse = delegate.getSecureCacheResponse();
if (cacheResponse != null) {
List<Certificate> result = cacheResponse.getServerCertificateChain();
return result != null ? result.toArray(new Certificate[result.size()]) : null;
}
SSLSocket sslSocket = getSslSocket();
if (sslSocket != null) {
return sslSocket.getSession().getPeerCertificates();
}
return null;
}
@Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
SecureCacheResponse cacheResponse = delegate.getSecureCacheResponse();
if (cacheResponse != null) {
return cacheResponse.getPeerPrincipal();
}
SSLSocket sslSocket = getSslSocket();
if (sslSocket != null) {
return sslSocket.getSession().getPeerPrincipal();
}
return null;
}
@Override public Principal getLocalPrincipal() {
SecureCacheResponse cacheResponse = delegate.getSecureCacheResponse();
if (cacheResponse != null) {
return cacheResponse.getLocalPrincipal();
}
SSLSocket sslSocket = getSslSocket();
if (sslSocket != null) {
return sslSocket.getSession().getLocalPrincipal();
}
return null;
}
HttpEngine getHttpEngine() {
return delegate.getHttpEngine();
}
@Override
public String getCipherSuite() {
SecureCacheResponse cacheResponse = delegate.getCacheResponse();
if (cacheResponse != null) {
return cacheResponse.getCipherSuite();
private SSLSocket getSslSocket() {
if (delegate.httpEngine == null || delegate.httpEngine.sentRequestMillis == -1) {
throw new IllegalStateException("Connection has not yet been established");
}
checkConnected();
return delegate.getSSLSocket().getSession().getCipherSuite();
}
@Override
public Certificate[] getLocalCertificates() {
SecureCacheResponse cacheResponse = delegate.getCacheResponse();
if (cacheResponse != null) {
List<Certificate> result = cacheResponse.getLocalCertificateChain();
return result != null ? result.toArray(new Certificate[result.size()]) : null;
}
checkConnected();
return delegate.getSSLSocket().getSession().getLocalCertificates();
}
@Override
public Certificate[] getServerCertificates() throws SSLPeerUnverifiedException {
SecureCacheResponse cacheResponse = delegate.getCacheResponse();
if (cacheResponse != null) {
List<Certificate> result = cacheResponse.getServerCertificateChain();
return result != null ? result.toArray(new Certificate[result.size()]) : null;
}
checkConnected();
return delegate.getSSLSocket().getSession().getPeerCertificates();
}
@Override
public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
SecureCacheResponse cacheResponse = delegate.getCacheResponse();
if (cacheResponse != null) {
return cacheResponse.getPeerPrincipal();
}
checkConnected();
return delegate.getSSLSocket().getSession().getPeerPrincipal();
}
@Override
public Principal getLocalPrincipal() {
SecureCacheResponse cacheResponse = delegate.getCacheResponse();
if (cacheResponse != null) {
return cacheResponse.getLocalPrincipal();
}
checkConnected();
return delegate.getSSLSocket().getSession().getLocalPrincipal();
return delegate.httpEngine instanceof HttpsEngine
? ((HttpsEngine) delegate.httpEngine).sslSocket
: null; // Not HTTPS! Probably an https:// to http:// redirect.
}
@Override
@@ -375,49 +382,53 @@ public final class HttpsURLConnectionImpl extends HttpsURLConnection {
delegate.setChunkedStreamingMode(chunkLength);
}
@Override public void setHostnameVerifier(HostnameVerifier hostnameVerifier) {
delegate.hostnameVerifier = hostnameVerifier;
}
@Override public HostnameVerifier getHostnameVerifier() {
return delegate.hostnameVerifier;
}
@Override public void setSSLSocketFactory(SSLSocketFactory sslSocketFactory) {
delegate.sslSocketFactory = sslSocketFactory;
}
@Override public SSLSocketFactory getSSLSocketFactory() {
return delegate.sslSocketFactory;
}
private final class HttpUrlConnectionDelegate extends HttpURLConnectionImpl {
private HttpUrlConnectionDelegate(URL url, int defaultPort, Proxy proxy,
ProxySelector proxySelector, CookieHandler cookieHandler, ResponseCache responseCache,
ConnectionPool connectionPool) {
super(url, defaultPort, proxy, proxySelector, cookieHandler, responseCache, connectionPool);
private HttpUrlConnectionDelegate(URL url, OkHttpClient client) {
super(url, client);
}
@Override protected HttpEngine newHttpEngine(String method, RawHeaders requestHeaders,
Connection connection, RetryableOutputStream requestBody) throws IOException {
return new HttpsEngine(this, method, requestHeaders, connection, requestBody,
HttpsURLConnectionImpl.this);
@Override protected HttpURLConnection getHttpConnectionToCache() {
return HttpsURLConnectionImpl.this;
}
public SecureCacheResponse getCacheResponse() {
HttpsEngine engine = (HttpsEngine) httpEngine;
return engine != null ? (SecureCacheResponse) engine.getCacheResponse() : null;
}
public SSLSocket getSSLSocket() {
HttpsEngine engine = (HttpsEngine) httpEngine;
return engine != null ? engine.sslSocket : null;
public SecureCacheResponse getSecureCacheResponse() {
return httpEngine instanceof HttpsEngine
? (SecureCacheResponse) httpEngine.getCacheResponse()
: null;
}
}
private static final class HttpsEngine extends HttpEngine {
public static final class HttpsEngine extends HttpEngine {
/**
* Stash of HttpsEngine.connection.socket to implement requests like
* {@link #getCipherSuite} even after the connection has been recycled.
*/
private SSLSocket sslSocket;
private final HttpsURLConnectionImpl enclosing;
/**
* @param policy the HttpURLConnectionImpl with connection configuration
* @param enclosing the HttpsURLConnection with HTTPS features
*/
private HttpsEngine(HttpURLConnectionImpl policy, String method, RawHeaders requestHeaders,
Connection connection, RetryableOutputStream requestBody, HttpsURLConnectionImpl enclosing)
public HttpsEngine(HttpURLConnectionImpl policy, String method, RawHeaders requestHeaders,
Connection connection, RetryableOutputStream requestBody)
throws IOException {
super(policy, method, requestHeaders, connection, requestBody);
this.sslSocket = connection != null ? (SSLSocket) connection.getSocket() : null;
this.enclosing = enclosing;
}
@Override protected void connected(Connection connection) {
@@ -433,18 +444,6 @@ public final class HttpsURLConnectionImpl extends HttpsURLConnection {
return false;
}
@Override protected SSLSocketFactory getSslSocketFactory() {
return enclosing.getSSLSocketFactory();
}
@Override protected HostnameVerifier getHostnameVerifier() {
return enclosing.getHostnameVerifier();
}
@Override protected HttpURLConnection getHttpConnectionToCache() {
return enclosing;
}
@Override protected TunnelRequest getTunnelConfig() {
String userAgent = requestHeaders.getUserAgent();
if (userAgent == null) {

View File

@@ -61,6 +61,7 @@ import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
@@ -1540,6 +1541,7 @@ public final class URLConnectionTest {
.setBody("This page has moved!"));
server.play();
client.setFollowProtocolRedirects(false);
client.setSSLSocketFactory(sslContext.getSocketFactory());
client.setHostnameVerifier(new RecordingHostnameVerifier());
HttpURLConnection connection = client.open(server.getUrl("/"));
@@ -1552,10 +1554,53 @@ public final class URLConnectionTest {
.setBody("This page has moved!"));
server.play();
client.setFollowProtocolRedirects(false);
HttpURLConnection connection = client.open(server.getUrl("/"));
assertEquals("This page has moved!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
}
@Test public void redirectedFromHttpsToHttpFollowingProtocolRedirects() throws Exception {
server2 = new MockWebServer();
server2.enqueue(new MockResponse().setBody("This is insecure HTTP!"));
server2.play();
server.useHttps(sslContext.getSocketFactory(), false);
server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
.addHeader("Location: " + server2.getUrl("/"))
.setBody("This page has moved!"));
server.play();
client.setSSLSocketFactory(sslContext.getSocketFactory());
client.setHostnameVerifier(new RecordingHostnameVerifier());
client.setFollowProtocolRedirects(true);
HttpsURLConnection connection = (HttpsURLConnection) client.open(server.getUrl("/"));
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 = new MockWebServer();
server2.useHttps(sslContext.getSocketFactory(), false);
server2.enqueue(new MockResponse().setBody("This is secure HTTPS!"));
server2.play();
server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
.addHeader("Location: " + server2.getUrl("/"))
.setBody("This page has moved!"));
server.play();
client.setSSLSocketFactory(sslContext.getSocketFactory());
client.setHostnameVerifier(new RecordingHostnameVerifier());
client.setFollowProtocolRedirects(true);
HttpURLConnection connection = client.open(server.getUrl("/"));
assertContent("This is secure HTTPS!", connection);
assertFalse(connection instanceof HttpsURLConnection);
}
@Test public void redirectToAnotherOriginServer() throws Exception {
redirectToAnotherOriginServer(false);
}