1
0
mirror of https://github.com/square/okhttp.git synced 2026-01-27 04:22:07 +03:00

Non-static authenticators.

This avoids the problems of HttpURLConnection's authenticator
where multiple concurrent uses of HTTP require a single shared
authenticator.
This commit is contained in:
jwilson
2013-04-27 16:25:29 -04:00
parent 01ca44b830
commit cb7ade556c
11 changed files with 333 additions and 105 deletions

View File

@@ -38,16 +38,20 @@ public final class Address {
final int uriPort;
final SSLSocketFactory sslSocketFactory;
final HostnameVerifier hostnameVerifier;
final OkAuthenticator authenticator;
public Address(String uriHost, int uriPort, SSLSocketFactory sslSocketFactory,
HostnameVerifier hostnameVerifier, Proxy proxy) throws UnknownHostException {
HostnameVerifier hostnameVerifier, OkAuthenticator authenticator, Proxy proxy)
throws UnknownHostException {
if (uriHost == null) throw new NullPointerException("uriHost == null");
if (uriPort <= 0) throw new IllegalArgumentException("uriPort <= 0: " + uriPort);
if (authenticator == null) throw new IllegalArgumentException("authenticator == null");
this.proxy = proxy;
this.uriHost = uriHost;
this.uriPort = uriPort;
this.sslSocketFactory = sslSocketFactory;
this.hostnameVerifier = hostnameVerifier;
this.authenticator = authenticator;
}
/** Returns the hostname of the origin server. */
@@ -79,6 +83,14 @@ public final class Address {
return hostnameVerifier;
}
/**
* Returns the client's authenticator. This method never returns null.
*/
public OkAuthenticator getAuthenticator() {
return authenticator;
}
/**
* Returns this address's explicitly-specified HTTP proxy, or null to
* delegate to the HTTP client's proxy selector.
@@ -94,7 +106,8 @@ public final class Address {
&& this.uriHost.equals(that.uriHost)
&& this.uriPort == that.uriPort
&& equal(this.sslSocketFactory, that.sslSocketFactory)
&& equal(this.hostnameVerifier, that.hostnameVerifier);
&& equal(this.hostnameVerifier, that.hostnameVerifier)
&& equal(this.authenticator, that.authenticator);
}
return false;
}
@@ -105,6 +118,7 @@ public final class Address {
result = 31 * result + uriPort;
result = 31 * result + (sslSocketFactory != null ? sslSocketFactory.hashCode() : 0);
result = 31 * result + (hostnameVerifier != null ? hostnameVerifier.hashCode() : 0);
result = 31 * result + (authenticator != null ? authenticator.hashCode() : 0);
result = 31 * result + (proxy != null ? proxy.hashCode() : 0);
return result;
}

View File

@@ -275,8 +275,9 @@ public final class Connection implements Closeable {
case HTTP_PROXY_AUTH:
requestHeaders = new RawHeaders(requestHeaders);
URL url = new URL("https", tunnelRequest.host, tunnelRequest.port, "/");
boolean credentialsFound = HttpAuthenticator.processAuthHeader(HTTP_PROXY_AUTH,
responseHeaders, requestHeaders, route.proxy, url);
boolean credentialsFound = HttpAuthenticator.processAuthHeader(
route.address.authenticator, HTTP_PROXY_AUTH, responseHeaders, requestHeaders,
route.proxy, url);
if (credentialsFound) {
continue;
} else {

View File

@@ -0,0 +1,123 @@
/*
* Copyright (C) 2013 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.okhttp;
import com.squareup.okhttp.internal.Base64;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.Proxy;
import java.net.URL;
import java.util.List;
/**
* Responds to authentication challenges from the remote web or proxy server by
* returning credentials.
*/
public interface OkAuthenticator {
/**
* Returns a credential that satisfies the authentication challenge made by
* {@code url}. Returns null if the challenge cannot be satisfied. This method
* is called in response to an HTTP 401 unauthorized status code sent by the
* origin server.
*
* @param challenges parsed "WWW-Authenticate" challenge headers from the HTTP
* response.
*/
Credential authenticate(Proxy proxy, URL url, List<Challenge> challenges) throws IOException;
/**
* Returns a credential that satisfies the authentication challenge made by
* {@code proxy}. Returns null if the challenge cannot be satisfied. This
* method is called in response to an HTTP 401 unauthorized status code sent
* by the proxy server.
*
* @param challenges parsed "Proxy-Authenticate" challenge headers from the
* HTTP response.
*/
Credential authenticateProxy(Proxy proxy, URL url, List<Challenge> challenges) throws IOException;
/** An RFC 2617 challenge. */
public final class Challenge {
private final String scheme;
private final String realm;
public Challenge(String scheme, String realm) {
this.scheme = scheme;
this.realm = realm;
}
/** Returns the authentication scheme, like {@code Basic}. */
public String getScheme() {
return scheme;
}
/** Returns the protection space. */
public String getRealm() {
return realm;
}
@Override public boolean equals(Object o) {
return o instanceof Challenge
&& ((Challenge) o).scheme.equals(scheme)
&& ((Challenge) o).realm.equals(realm);
}
@Override public int hashCode() {
return scheme.hashCode() + 31 * realm.hashCode();
}
@Override public String toString() {
return scheme + " realm=\"" + realm + "\"";
}
}
/** An RFC 2617 credential. */
public final class Credential {
private final String headerValue;
private Credential(String headerValue) {
this.headerValue = headerValue;
}
/** Returns an auth credential for the Basic scheme. */
public static Credential basic(String userName, String password) {
try {
String usernameAndPassword = userName + ":" + password;
byte[] bytes = usernameAndPassword.getBytes("ISO-8859-1");
String encoded = Base64.encode(bytes);
return new Credential("Basic " + encoded);
} catch (UnsupportedEncodingException e) {
throw new AssertionError();
}
}
public String getHeaderValue() {
return headerValue;
}
@Override public boolean equals(Object o) {
return o instanceof Credential && ((Credential) o).headerValue.equals(headerValue);
}
@Override public int hashCode() {
return headerValue.hashCode();
}
@Override public String toString() {
return headerValue;
}
}
}

View File

@@ -15,10 +15,12 @@
*/
package com.squareup.okhttp;
import com.squareup.okhttp.internal.http.HttpAuthenticator;
import com.squareup.okhttp.internal.http.HttpURLConnectionImpl;
import com.squareup.okhttp.internal.http.HttpsURLConnectionImpl;
import com.squareup.okhttp.internal.http.OkResponseCache;
import com.squareup.okhttp.internal.http.OkResponseCacheAdapter;
import java.net.Authenticator;
import java.net.CookieHandler;
import java.net.HttpURLConnection;
import java.net.Proxy;
@@ -41,6 +43,7 @@ public final class OkHttpClient {
private ResponseCache responseCache;
private SSLSocketFactory sslSocketFactory;
private HostnameVerifier hostnameVerifier;
private OkAuthenticator authenticator;
private ConnectionPool connectionPool;
private boolean followProtocolRedirects = true;
@@ -149,6 +152,22 @@ public final class OkHttpClient {
return hostnameVerifier;
}
/**
* Sets the authenticator used to respond to challenges from the remote web
* server or proxy server.
*
* <p>If unset, the {@link Authenticator#setDefault system-wide default}
* authenticator will be used.
*/
public OkHttpClient setAuthenticator(OkAuthenticator authenticator) {
this.authenticator = authenticator;
return this;
}
public OkAuthenticator getAuthenticator() {
return authenticator;
}
/**
* Sets the connection pool used to recycle HTTP and HTTPS connections.
*
@@ -209,6 +228,9 @@ public final class OkHttpClient {
result.hostnameVerifier = hostnameVerifier != null
? hostnameVerifier
: HttpsURLConnection.getDefaultHostnameVerifier();
result.authenticator = authenticator != null
? authenticator
: HttpAuthenticator.SYSTEM_DEFAULT;
result.connectionPool = connectionPool != null ? connectionPool : ConnectionPool.getDefault();
result.followProtocolRedirects = followProtocolRedirects;
return result;

View File

@@ -16,7 +16,8 @@
*/
package com.squareup.okhttp.internal.http;
import com.squareup.okhttp.internal.Base64;
import com.squareup.okhttp.OkAuthenticator;
import com.squareup.okhttp.OkAuthenticator.Challenge;
import java.io.IOException;
import java.net.Authenticator;
import java.net.InetAddress;
@@ -27,11 +28,49 @@ import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import static com.squareup.okhttp.OkAuthenticator.Credential;
import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
/** Handles HTTP authentication headers from origin and proxy servers. */
public final class HttpAuthenticator {
/** Uses the global authenticator to get the password. */
public static final OkAuthenticator SYSTEM_DEFAULT = new OkAuthenticator() {
@Override public Credential authenticate(
Proxy proxy, URL url, List<Challenge> challenges) throws IOException {
for (Challenge challenge : challenges) {
PasswordAuthentication auth = Authenticator.requestPasswordAuthentication(url.getHost(),
getConnectToInetAddress(proxy, url), url.getPort(), url.getProtocol(),
challenge.getRealm(), challenge.getScheme(), url, Authenticator.RequestorType.SERVER);
if (auth != null) {
return Credential.basic(auth.getUserName(), new String(auth.getPassword()));
}
}
return null;
}
@Override public Credential authenticateProxy(
Proxy proxy, URL url, List<Challenge> challenges) throws IOException {
for (Challenge challenge : challenges) {
InetSocketAddress proxyAddress = (InetSocketAddress) proxy.address();
PasswordAuthentication auth = Authenticator.requestPasswordAuthentication(
proxyAddress.getHostName(), getConnectToInetAddress(proxy, url), proxyAddress.getPort(),
url.getProtocol(), challenge.getRealm(), challenge.getScheme(), url,
Authenticator.RequestorType.PROXY);
if (auth != null) {
return Credential.basic(auth.getUserName(), new String(auth.getPassword()));
}
}
return null;
}
private InetAddress getConnectToInetAddress(Proxy proxy, URL url) throws IOException {
return (proxy != null && proxy.type() != Proxy.Type.DIRECT)
? ((InetSocketAddress) proxy.address()).getAddress()
: InetAddress.getByName(url.getHost());
}
};
private HttpAuthenticator() {
}
@@ -41,68 +80,33 @@ public final class HttpAuthenticator {
* @return true if credentials have been added to successorRequestHeaders
* and another request should be attempted.
*/
public static boolean processAuthHeader(int responseCode, RawHeaders responseHeaders,
RawHeaders successorRequestHeaders, Proxy proxy, URL url) throws IOException {
if (responseCode != HTTP_PROXY_AUTH && responseCode != HTTP_UNAUTHORIZED) {
throw new IllegalArgumentException();
public static boolean processAuthHeader(OkAuthenticator authenticator, int responseCode,
RawHeaders responseHeaders, RawHeaders successorRequestHeaders, Proxy proxy, URL url)
throws IOException {
String responseField;
String requestField;
if (responseCode == HTTP_UNAUTHORIZED) {
responseField = "WWW-Authenticate";
requestField = "Authorization";
} else if (responseCode == HTTP_PROXY_AUTH) {
responseField = "Proxy-Authenticate";
requestField = "Proxy-Authorization";
} else {
throw new IllegalArgumentException(); // TODO: ProtocolException?
}
// Keep asking for username/password until authorized.
String challengeHeader =
responseCode == HTTP_PROXY_AUTH ? "Proxy-Authenticate" : "WWW-Authenticate";
String credentials = getCredentials(responseHeaders, challengeHeader, proxy, url);
if (credentials == null) {
return false; // Could not find credentials so end the request cycle.
}
// Add authorization credentials, bypassing the already-connected check.
String fieldName = responseCode == HTTP_PROXY_AUTH ? "Proxy-Authorization" : "Authorization";
successorRequestHeaders.set(fieldName, credentials);
return true;
}
/**
* Returns the authorization credentials that may satisfy the challenge.
* Returns null if a challenge header was not provided or if credentials
* were not available.
*/
private static String getCredentials(RawHeaders responseHeaders, String challengeHeader,
Proxy proxy, URL url) throws IOException {
List<Challenge> challenges = parseChallenges(responseHeaders, challengeHeader);
List<Challenge> challenges = parseChallenges(responseHeaders, responseField);
if (challenges.isEmpty()) {
return null;
return false; // Could not find a challenge so end the request cycle.
}
for (Challenge challenge : challenges) {
// Use the global authenticator to get the password.
PasswordAuthentication auth;
if (responseHeaders.getResponseCode() == HTTP_PROXY_AUTH) {
InetSocketAddress proxyAddress = (InetSocketAddress) proxy.address();
auth = Authenticator.requestPasswordAuthentication(proxyAddress.getHostName(),
getConnectToInetAddress(proxy, url), proxyAddress.getPort(), url.getProtocol(),
challenge.realm, challenge.scheme, url, Authenticator.RequestorType.PROXY);
} else {
auth = Authenticator.requestPasswordAuthentication(url.getHost(),
getConnectToInetAddress(proxy, url), url.getPort(), url.getProtocol(), challenge.realm,
challenge.scheme, url, Authenticator.RequestorType.SERVER);
}
if (auth == null) {
continue;
}
// Use base64 to encode the username and password.
String usernameAndPassword = auth.getUserName() + ":" + new String(auth.getPassword());
byte[] bytes = usernameAndPassword.getBytes("ISO-8859-1");
String encoded = Base64.encode(bytes);
return challenge.scheme + " " + encoded;
Credential credential = responseHeaders.getResponseCode() == HTTP_PROXY_AUTH
? authenticator.authenticateProxy(proxy, url, challenges)
: authenticator.authenticate(proxy, url, challenges);
if (credential == null) {
return false; // Could not satisfy the challenge so end the request cycle.
}
return null;
}
private static InetAddress getConnectToInetAddress(Proxy proxy, URL url) throws IOException {
return (proxy != null && proxy.type() != Proxy.Type.DIRECT)
? ((InetSocketAddress) proxy.address()).getAddress() : InetAddress.getByName(url.getHost());
// Add authorization credentials, bypassing the already-connected check.
successorRequestHeaders.set(requestField, credential.getHeaderValue());
return true;
}
/**
@@ -151,25 +155,4 @@ public final class HttpAuthenticator {
}
return result;
}
/** An RFC 2617 challenge. */
private static final class Challenge {
final String scheme;
final String realm;
Challenge(String scheme, String realm) {
this.scheme = scheme;
this.realm = realm;
}
@Override public boolean equals(Object o) {
return o instanceof Challenge
&& ((Challenge) o).scheme.equals(scheme)
&& ((Challenge) o).realm.equals(realm);
}
@Override public int hashCode() {
return scheme.hashCode() + 31 * realm.hashCode();
}
}
}

View File

@@ -278,7 +278,7 @@ public class HttpEngine {
hostnameVerifier = policy.hostnameVerifier;
}
Address address = new Address(uriHost, getEffectivePort(uri), sslSocketFactory,
hostnameVerifier, policy.requestedProxy);
hostnameVerifier, policy.authenticator, policy.requestedProxy);
routeSelector = new RouteSelector(address, uri, policy.proxySelector, policy.connectionPool,
Dns.DEFAULT, policy.getFailedRoutes());
}

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.OkAuthenticator;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Route;
import com.squareup.okhttp.internal.Util;
@@ -83,6 +84,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
/* SSL configuration; necessary for HTTP requests that get redirected to HTTPS. */
SSLSocketFactory sslSocketFactory;
HostnameVerifier hostnameVerifier;
OkAuthenticator authenticator;
final Set<Route> failedRoutes;
@@ -104,6 +106,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
this.connectionPool = client.getConnectionPool();
this.sslSocketFactory = client.getSslSocketFactory();
this.hostnameVerifier = client.getHostnameVerifier();
this.authenticator = client.getAuthenticator();
this.responseCache = responseCache;
}
@@ -421,8 +424,9 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
}
// fall-through
case HTTP_UNAUTHORIZED:
boolean credentialsFound = HttpAuthenticator.processAuthHeader(getResponseCode(),
httpEngine.getResponseHeaders().getHeaders(), rawRequestHeaders, selectedProxy, url);
boolean credentialsFound = HttpAuthenticator.processAuthHeader(authenticator,
getResponseCode(), httpEngine.getResponseHeaders().getHeaders(), rawRequestHeaders,
selectedProxy, url);
return credentialsFound ? Retry.SAME_CONNECTION : Retry.NONE;
case HTTP_MULT_CHOICE:

View File

@@ -19,6 +19,7 @@ import com.google.mockwebserver.MockWebServer;
import com.squareup.okhttp.internal.RecordingHostnameVerifier;
import com.squareup.okhttp.internal.SslContextBuilder;
import com.squareup.okhttp.internal.Util;
import com.squareup.okhttp.internal.http.HttpAuthenticator;
import com.squareup.okhttp.internal.mockspdyserver.MockSpdyServer;
import java.io.IOException;
import java.net.InetAddress;
@@ -70,14 +71,15 @@ public final class ConnectionPoolTest {
@Before public void setUp() throws Exception {
httpServer.play();
httpAddress = new Address(httpServer.getHostName(), httpServer.getPort(), null, null, null);
httpAddress = new Address(httpServer.getHostName(), httpServer.getPort(), null, null,
HttpAuthenticator.SYSTEM_DEFAULT, null);
httpSocketAddress = new InetSocketAddress(InetAddress.getByName(httpServer.getHostName()),
httpServer.getPort());
spdyServer.play();
spdyAddress =
new Address(spdyServer.getHostName(), spdyServer.getPort(), sslContext.getSocketFactory(),
new RecordingHostnameVerifier(), null);
new RecordingHostnameVerifier(), HttpAuthenticator.SYSTEM_DEFAULT, null);
spdySocketAddress = new InetSocketAddress(InetAddress.getByName(spdyServer.getHostName()),
spdyServer.getPort());

View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) 2013 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.okhttp.internal;
import com.squareup.okhttp.OkAuthenticator;
import java.io.IOException;
import java.net.Proxy;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
public final class RecordingOkAuthenticator implements OkAuthenticator {
public final List<String> calls = new ArrayList<String>();
public final Credential credential;
public RecordingOkAuthenticator(Credential credential) {
this.credential = credential;
}
@Override public Credential authenticate(Proxy proxy, URL url, List<Challenge> challenges)
throws IOException {
calls.add("authenticate"
+ " proxy=" + proxy.type()
+ " url=" + url
+ " challenges=" + challenges);
return credential;
}
@Override public Credential authenticateProxy(Proxy proxy, URL url, List<Challenge> challenges)
throws IOException {
calls.add("authenticateProxy"
+ " proxy=" + proxy.type()
+ " url=" + url
+ " challenges=" + challenges);
return credential;
}
}

View File

@@ -18,6 +18,7 @@ package com.squareup.okhttp.internal.http;
import com.squareup.okhttp.Address;
import com.squareup.okhttp.Connection;
import com.squareup.okhttp.ConnectionPool;
import com.squareup.okhttp.OkAuthenticator;
import com.squareup.okhttp.Route;
import com.squareup.okhttp.internal.Dns;
import com.squareup.okhttp.internal.SslContextBuilder;
@@ -78,11 +79,12 @@ public final class RouteSelectorTest {
}
}
private final OkAuthenticator authenticator = HttpAuthenticator.SYSTEM_DEFAULT;
private final FakeDns dns = new FakeDns();
private final FakeProxySelector proxySelector = new FakeProxySelector();
@Test public void singleRoute() throws Exception {
Address address = new Address(uriHost, uriPort, null, null, null);
Address address = new Address(uriHost, uriPort, null, null, authenticator, null);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);
@@ -100,7 +102,7 @@ public final class RouteSelectorTest {
}
@Test public void singleRouteReturnsFailedRoute() throws Exception {
Address address = new Address(uriHost, uriPort, null, null, null);
Address address = new Address(uriHost, uriPort, null, null, authenticator, null);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);
@@ -121,7 +123,7 @@ public final class RouteSelectorTest {
}
@Test public void explicitProxyTriesThatProxiesAddressesOnly() throws Exception {
Address address = new Address(uriHost, uriPort, null, null, proxyA);
Address address = new Address(uriHost, uriPort, null, null, authenticator, proxyA);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);
@@ -138,7 +140,7 @@ public final class RouteSelectorTest {
}
@Test public void explicitDirectProxy() throws Exception {
Address address = new Address(uriHost, uriPort, null, null, NO_PROXY);
Address address = new Address(uriHost, uriPort, null, null, authenticator, NO_PROXY);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);
@@ -153,7 +155,7 @@ public final class RouteSelectorTest {
}
@Test public void proxySelectorReturnsNull() throws Exception {
Address address = new Address(uriHost, uriPort, null, null, null);
Address address = new Address(uriHost, uriPort, null, null, authenticator, null);
proxySelector.proxies = null;
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
@@ -169,7 +171,7 @@ public final class RouteSelectorTest {
}
@Test public void proxySelectorReturnsNoProxies() throws Exception {
Address address = new Address(uriHost, uriPort, null, null, null);
Address address = new Address(uriHost, uriPort, null, null, authenticator, null);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);
@@ -184,7 +186,7 @@ public final class RouteSelectorTest {
}
@Test public void proxySelectorReturnsMultipleProxies() throws Exception {
Address address = new Address(uriHost, uriPort, null, null, null);
Address address = new Address(uriHost, uriPort, null, null, authenticator, null);
proxySelector.proxies.add(proxyA);
proxySelector.proxies.add(proxyB);
@@ -218,7 +220,7 @@ public final class RouteSelectorTest {
}
@Test public void proxySelectorDirectConnectionsAreSkipped() throws Exception {
Address address = new Address(uriHost, uriPort, null, null, null);
Address address = new Address(uriHost, uriPort, null, null, authenticator, null);
proxySelector.proxies.add(NO_PROXY);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
@@ -235,7 +237,7 @@ public final class RouteSelectorTest {
}
@Test public void proxyDnsFailureContinuesToNextProxy() throws Exception {
Address address = new Address(uriHost, uriPort, null, null, null);
Address address = new Address(uriHost, uriPort, null, null, authenticator, null);
proxySelector.proxies.add(proxyA);
proxySelector.proxies.add(proxyB);
@@ -274,8 +276,8 @@ public final class RouteSelectorTest {
}
@Test public void nonSslErrorAddsAllTlsModesToFailedRoute() throws Exception {
Address address =
new Address(uriHost, uriPort, socketFactory, hostnameVerifier, Proxy.NO_PROXY);
Address address = new Address(uriHost, uriPort, socketFactory, hostnameVerifier, authenticator,
Proxy.NO_PROXY);
Set<Route> failedRoutes = new LinkedHashSet<Route>();
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
failedRoutes);
@@ -287,8 +289,8 @@ public final class RouteSelectorTest {
}
@Test public void sslErrorAddsOnlyFailedTlsModeToFailedRoute() throws Exception {
Address address =
new Address(uriHost, uriPort, socketFactory, hostnameVerifier, Proxy.NO_PROXY);
Address address = new Address(uriHost, uriPort, socketFactory, hostnameVerifier, authenticator,
Proxy.NO_PROXY);
Set<Route> failedRoutes = new LinkedHashSet<Route>();
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
failedRoutes);
@@ -300,7 +302,8 @@ public final class RouteSelectorTest {
}
@Test public void multipleProxiesMultipleInetAddressesMultipleTlsModes() throws Exception {
Address address = new Address(uriHost, uriPort, socketFactory, hostnameVerifier, null);
Address address = new Address(uriHost, uriPort, socketFactory, hostnameVerifier, authenticator,
null);
proxySelector.proxies.add(proxyA);
proxySelector.proxies.add(proxyB);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
@@ -338,8 +341,8 @@ public final class RouteSelectorTest {
}
@Test public void failedRoutesAreLast() throws Exception {
Address address =
new Address(uriHost, uriPort, socketFactory, hostnameVerifier, Proxy.NO_PROXY);
Address address = new Address(uriHost, uriPort, socketFactory, hostnameVerifier, authenticator,
Proxy.NO_PROXY);
Set<Route> failedRoutes = new LinkedHashSet<Route>(1);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,

View File

@@ -24,6 +24,7 @@ import com.squareup.okhttp.HttpResponseCache;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.internal.RecordingAuthenticator;
import com.squareup.okhttp.internal.RecordingHostnameVerifier;
import com.squareup.okhttp.internal.RecordingOkAuthenticator;
import com.squareup.okhttp.internal.SslContextBuilder;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -78,6 +79,7 @@ import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_END;
import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_START;
import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_INPUT_AT_END;
import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_OUTPUT_AT_END;
import static com.squareup.okhttp.OkAuthenticator.Credential;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
@@ -2358,6 +2360,30 @@ public final class URLConnectionTest {
fail("TODO");
}
@Test public void customAuthenticator() 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"));
server.play();
Credential credential = Credential.basic("jesse", "peanutbutter");
RecordingOkAuthenticator authenticator = new RecordingOkAuthenticator(credential);
client.setAuthenticator(authenticator);
assertContent("A", client.open(server.getUrl("/private")));
assertContainsNoneMatching(server.takeRequest().getHeaders(), "Authorization: .*");
assertContains(server.takeRequest().getHeaders(),
"Authorization: " + credential.getHeaderValue());
assertEquals(1, authenticator.calls.size());
String call = authenticator.calls.get(0);
assertTrue(call, call.contains("proxy=DIRECT"));
assertTrue(call, call.contains("url=" + server.getUrl("/private")));
assertTrue(call, call.contains("challenges=[Basic realm=\"protected area\"]"));
}
/** Returns a gzipped copy of {@code bytes}. */
public byte[] gzip(byte[] bytes) throws IOException {
ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();