在OkHttp3中,ProxySelector
对象由OkHttpClient维护。
public class OkHttpClient implements Cloneable, Call.Factory {
......
final ProxySelector proxySelector;
private OkHttpClient(Builder builder) {
this.dispatcher = builder.dispatcher;
this.proxy = builder.proxy;
this.protocols = builder.protocols;
this.connectionSpecs = builder.connectionSpecs;
this.interceptors = Util.immutableList(builder.interceptors);
this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
this.proxySelector = builder.proxySelector;
......
public ProxySelector proxySelector() {
return proxySelector;
}
......
public Builder() {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
proxySelector = ProxySelector.getDefault();
......
Builder(OkHttpClient okHttpClient) {
this.dispatcher = okHttpClient.dispatcher;
this.proxy = okHttpClient.proxy;
this.protocols = okHttpClient.protocols;
this.connectionSpecs = okHttpClient.connectionSpecs;
this.interceptors.addAll(okHttpClient.interceptors);
this.networkInterceptors.addAll(okHttpClient.networkInterceptors);
this.proxySelector = okHttpClient.proxySelector;
在创建OkHttpClient时,可以通过为OkHttpClient.Builder设置ProxySelector
来定制ProxySelector
。若没有指定,则使用系统默认的ProxySelector
。OpenJDK 1.8版默认的ProxySelector
为sun.net.spi.DefaultProxySelector
:
public abstract class ProxySelector {
/**
* The system wide proxy selector that selects the proxy server to
* use, if any, when connecting to a remote object referenced by
* an URL.
*
* @see #setDefault(ProxySelector)
*/
private static ProxySelector theProxySelector;
static {
try {
Class<?> c = Class.forName("sun.net.spi.DefaultProxySelector");
if (c != null && ProxySelector.class.isAssignableFrom(c)) {
theProxySelector = (ProxySelector) c.newInstance();
}
} catch (Exception e) {
theProxySelector = null;
}
}
/**
* Gets the system-wide proxy selector.
*
* @throws SecurityException
* If a security manager has been installed and it denies
* {@link NetPermission}{@code ("getProxySelector")}
* @see #setDefault(ProxySelector)
* @return the system-wide {@code ProxySelector}
* @since 1.5
*/
public static ProxySelector getDefault() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(SecurityConstants.GET_PROXYSELECTOR_PERMISSION);
}
return theProxySelector;
}
在Android平台上,默认ProxySelector
所用的则是另外的实现:
public abstract class ProxySelector {
private static ProxySelector defaultSelector = new ProxySelectorImpl();
/**
* Returns the default proxy selector, or null if none exists.
*/
public static ProxySelector getDefault() {
return defaultSelector;
}
/**
* Sets the default proxy selector. If {@code selector} is null, the current
* proxy selector will be removed.
*/
public static void setDefault(ProxySelector selector) {
defaultSelector = selector;
}
Android平台下,默认的ProxySelector
ProxySelectorImpl,其实现 (不同Android版本实现不同,这里以android-6.0.1_r61为例) 如下:
package java.net;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
final class ProxySelectorImpl extends ProxySelector {
@Override public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
if (uri == null || sa == null || ioe == null) {
throw new IllegalArgumentException();
}
}
@Override public List<Proxy> select(URI uri) {
return Collections.singletonList(selectOneProxy(uri));
}
private Proxy selectOneProxy(URI uri) {
if (uri == null) {
throw new IllegalArgumentException("uri == null");
}
String scheme = uri.getScheme();
if (scheme == null) {
throw new IllegalArgumentException("scheme == null");
}
int port = -1;
Proxy proxy = null;
String nonProxyHostsKey = null;
boolean httpProxyOkay = true;
if ("http".equalsIgnoreCase(scheme)) {
port = 80;
nonProxyHostsKey = "http.nonProxyHosts";
proxy = lookupProxy("http.proxyHost", "http.proxyPort", Proxy.Type.HTTP, port);
} else if ("https".equalsIgnoreCase(scheme)) {
port = 443;
nonProxyHostsKey = "https.nonProxyHosts"; // RI doesn't support this
proxy = lookupProxy("https.proxyHost", "https.proxyPort", Proxy.Type.HTTP, port);
} else if ("ftp".equalsIgnoreCase(scheme)) {
port = 80; // not 21 as you might guess
nonProxyHostsKey = "ftp.nonProxyHosts";
proxy = lookupProxy("ftp.proxyHost", "ftp.proxyPort", Proxy.Type.HTTP, port);
} else if ("socket".equalsIgnoreCase(scheme)) {
httpProxyOkay = false;
} else {
return Proxy.NO_PROXY;
}
if (nonProxyHostsKey != null
&& isNonProxyHost(uri.getHost(), System.getProperty(nonProxyHostsKey))) {
return Proxy.NO_PROXY;
}
if (proxy != null) {
return proxy;
}
if (httpProxyOkay) {
proxy = lookupProxy("proxyHost", "proxyPort", Proxy.Type.HTTP, port);
if (proxy != null) {
return proxy;
}
}
proxy = lookupProxy("socksProxyHost", "socksProxyPort", Proxy.Type.SOCKS, 1080);
if (proxy != null) {
return proxy;
}
return Proxy.NO_PROXY;
}
/**
* Returns the proxy identified by the {@code hostKey} system property, or
* null.
*/
private Proxy lookupProxy(String hostKey, String portKey, Proxy.Type type, int defaultPort) {
String host = System.getProperty(hostKey);
if (host == null || host.isEmpty()) {
return null;
}
int port = getSystemPropertyInt(portKey, defaultPort);
return new Proxy(type, InetSocketAddress.createUnresolved(host, port));
}
private int getSystemPropertyInt(String key, int defaultValue) {
String string = System.getProperty(key);
if (string != null) {
try {
return Integer.parseInt(string);
} catch (NumberFormatException ignored) {
}
}
return defaultValue;
}
/**
* Returns true if the {@code nonProxyHosts} system property pattern exists
* and matches {@code host}.
*/
private boolean isNonProxyHost(String host, String nonProxyHosts) {
if (host == null || nonProxyHosts == null) {
return false;
}
// construct pattern
StringBuilder patternBuilder = new StringBuilder();
for (int i = 0; i < nonProxyHosts.length(); i++) {
char c = nonProxyHosts.charAt(i);
switch (c) {
case '.':
patternBuilder.append("\\.");
break;
case '*':
patternBuilder.append(".*");
break;
default:
patternBuilder.append(c);
}
}
// check whether the host is the nonProxyHosts.
String pattern = patternBuilder.toString();
return host.matches(pattern);
}
}
在Android平台上,主要是从系统属性System properties中获取代理服务器的配置信息,这里会过滤掉不能进行代理的主机的访问。
前面我们看到 RouteSelector
通过 Address
提供的Proxy和ProxySelector来收集Proxy信息及连接的目标地址信息。OkHttp3中用 Address
描述建立连接所需的配置信息,包括HTTP服务器的地址,DNS,SocketFactory,Proxy,ProxySelector及TLS所需的一些设施等等:
public final class Address {
final HttpUrl url;
final Dns dns;
final SocketFactory socketFactory;
final Authenticator proxyAuthenticator;
final List<Protocol> protocols;
final List<ConnectionSpec> connectionSpecs;
final ProxySelector proxySelector;
final Proxy proxy;
final SSLSocketFactory sslSocketFactory;
final HostnameVerifier hostnameVerifier;
final CertificatePinner certificatePinner;
public Address(String uriHost, int uriPort, Dns dns, SocketFactory socketFactory,
SSLSocketFactory sslSocketFactory, HostnameVerifier hostnameVerifier,
CertificatePinner certificatePinner, Authenticator proxyAuthenticator, Proxy proxy,
List<Protocol> protocols, List<ConnectionSpec> connectionSpecs, ProxySelector proxySelector) {
this.url = new HttpUrl.Builder()
.scheme(sslSocketFactory != null ? "https" : "http")
.host(uriHost)
.port(uriPort)
.build();
if (dns == null) throw new NullPointerException("dns == null");
this.dns = dns;
if (socketFactory == null) throw new NullPointerException("socketFactory == null");
this.socketFactory = socketFactory;
if (proxyAuthenticator == null) {
throw new NullPointerException("proxyAuthenticator == null");
}
this.proxyAuthenticator = proxyAuthenticator;
if (protocols == null) throw new NullPointerException("protocols == null");
this.protocols = Util.immutableList(protocols);
if (connectionSpecs == null) throw new NullPointerException("connectionSpecs == null");
this.connectionSpecs = Util.immutableList(connectionSpecs);
if (proxySelector == null) throw new NullPointerException("proxySelector == null");
this.proxySelector = proxySelector;
this.proxy = proxy;
this.sslSocketFactory = sslSocketFactory;
this.hostnameVerifier = hostnameVerifier;
this.certificatePinner = certificatePinner;
}
/**
* Returns a URL with the hostname and port of the origin server. The path, query, and fragment of
* this URL are always empty, since they are not significant for planning a route.
*/
public HttpUrl url() {
return url;
}
/** Returns the service that will be used to resolve IP addresses for hostnames. */
public Dns dns() {
return dns;
}
/** Returns the socket factory for new connections. */
public SocketFactory socketFactory() {
return socketFactory;
}
/** Returns the client's proxy authenticator. */
public Authenticator proxyAuthenticator() {
return proxyAuthenticator;
}
/**
* Returns the protocols the client supports. This method always returns a non-null list that
* contains minimally {@link Protocol#HTTP_1_1}.
*/
public List<Protocol> protocols() {
return protocols;
}
public List<ConnectionSpec> connectionSpecs() {
return connectionSpecs;
}
/**
* Returns this address's proxy selector. Only used if the proxy is null. If none of this
* selector's proxies are reachable, a direct connection will be attempted.
*/
public ProxySelector proxySelector() {
return proxySelector;
}
/**
* Returns this address's explicitly-specified HTTP proxy, or null to delegate to the {@linkplain
* #proxySelector proxy selector}.
*/
public Proxy proxy() {
return proxy;
}
/** Returns the SSL socket factory, or null if this is not an HTTPS address. */
public SSLSocketFactory sslSocketFactory() {
return sslSocketFactory;
}
/** Returns the hostname verifier, or null if this is not an HTTPS address. */
public HostnameVerifier hostnameVerifier() {
return hostnameVerifier;
}
/** Returns this address's certificate pinner, or null if this is not an HTTPS address. */
public CertificatePinner certificatePinner() {
return certificatePinner;
}
......
}
OkHttp3中通过职责链执行HTTP请求。在其中的RetryAndFollowUpInterceptor里创建Address对象时,从OkHttpClient对象获取ProxySelector。Address对象会被用于创建StreamAllocation对象。StreamAllocation在建立连接时,从Address对象中获取ProxySelector以选择路由。
public final class RetryAndFollowUpInterceptor implements Interceptor {
......
private Address createAddress(HttpUrl url) {
SSLSocketFactory sslSocketFactory = null;
HostnameVerifier hostnameVerifier = null;
CertificatePinner certificatePinner = null;
if (url.isHttps()) {
sslSocketFactory = client.sslSocketFactory();
hostnameVerifier = client.hostnameVerifier();
certificatePinner = client.certificatePinner();
}
return new Address(url.host(), url.port(), client.dns(), client.socketFactory(),
sslSocketFactory, hostnameVerifier, certificatePinner, client.proxyAuthenticator(),
client.proxy(), client.protocols(), client.connectionSpecs(), client.proxySelector());
}
在StreamAllocation中,Address对象会被用于创建 RouteSelector
对象:
public final class StreamAllocation {
......
public StreamAllocation(ConnectionPool connectionPool, Address address) {
this.connectionPool = connectionPool;
this.address = address;
this.routeSelector = new RouteSelector(address, routeDatabase());
}
如我们在 OkHttp3 HTTP请求执行流程分析 中看到的,OkHttp3对HTTP请求是通过Interceptor链来处理的。 RetryAndFollowUpInterceptor
创建StreamAllocation
对象,处理http的重定向及出错重试。对后续Interceptor的执行的影响为修改Request并创建StreamAllocation对象。 BridgeInterceptor
补全缺失的一些http header。对后续Interceptor的执行的影响主要为修改了Request。 CacheInterceptor
处理http缓存。对后续Interceptor的执行的影响为,若缓存中有所需请求的响应,则后续Interceptor不再执行。 ConnectInterceptor
借助于前面分配的StreamAllocation
对象建立与服务器之间的连接,并选定交互所用的协议是HTTP 1.1还是HTTP 2。对后续Interceptor的执行的影响为,创建了HttpStream和connection。 CallServerInterceptor
作为Interceptor链中的最后一个Interceptor,用于处理IO,与服务器进行数据交换。
在OkHttp3中,收集的路由信息,是在ConnectInterceptor
中建立连接时用到的。ConnectInterceptor
借助于 StreamAllocation
完成整个连接的建立,包括TCP连接建立,代理协议所要求的协商,以及SSL/TLS协议的协商,如ALPN等。我们暂时略过整个连接建立的完整过程,主要关注TCP连接建立及代理协议的协商过程的部分。
StreamAllocation
的findConnection()用来为某次特定的网络请求寻找一个可用的连接。
/**
* Returns a connection to host a new stream. This prefers the existing connection if it exists,
* then the pool, finally building a new connection.
*/
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
boolean connectionRetryEnabled) throws IOException {
Route selectedRoute;
synchronized (connectionPool) {
if (released) throw new IllegalStateException("released");
if (codec != null) throw new IllegalStateException("codec != null");
if (canceled) throw new IOException("Canceled");
RealConnection allocatedConnection = this.connection;
if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
return allocatedConnection;
}
// Attempt to get a connection from the pool.
RealConnection pooledConnection = Internal.instance.get(connectionPool, address, this);
if (pooledConnection != null) {
this.connection = pooledConnection;
return pooledConnection;
}
selectedRoute = route;
}
if (selectedRoute == null) {
selectedRoute = routeSelector.next();
synchronized (connectionPool) {
route = selectedRoute;
refusedStreamCount = 0;
}
}
RealConnection newConnection = new RealConnection(selectedRoute);
acquire(newConnection);
synchronized (connectionPool) {
Internal.instance.put(connectionPool, newConnection);
this.connection = newConnection;
if (canceled) throw new IOException("Canceled");
}
newConnection.connect(connectTimeout, readTimeout, writeTimeout, address.connectionSpecs(),
connectionRetryEnabled);
routeDatabase().connected(newConnection.route());
return newConnection;
}
OkHttp3中有一套连接池的机制,这里先尝试从连接池中寻找可用的连接,找不到时才会新建连接。新建连接的过程是:
RealConnection
连接对象。
RealConnection
中建立连接的过程是这样的:
public final class RealConnection extends Http2Connection.Listener implements Connection {
private final Route route;
/** The low-level TCP socket. */
private Socket rawSocket;
/**
* The application layer socket. Either an {@link SSLSocket} layered over {@link #rawSocket}, or
* {@link #rawSocket} itself if this connection does not use SSL.
*/
public Socket socket;
private Handshake handshake;
private Protocol protocol;
public volatile Http2Connection http2Connection;
public int successCount;
public BufferedSource source;
public BufferedSink sink;
public int allocationLimit;
public final List<Reference<StreamAllocation>> allocations = new ArrayList<>();
public boolean noNewStreams;
public long idleAtNanos = Long.MAX_VALUE;
public RealConnection(Route route) {
this.route = route;
}
public void connect(int connectTimeout, int readTimeout, int writeTimeout,
List<ConnectionSpec> connectionSpecs, boolean connectionRetryEnabled) {
if (protocol != null) throw new IllegalStateException("already connected");
RouteException routeException = null;
ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);
if (route.address().sslSocketFactory() == null) {
if (!connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
throw new RouteException(new UnknownServiceException(
"CLEARTEXT communication not enabled for client"));
}
String host = route.address().url().host();
if (!Platform.get().isCleartextTrafficPermitted(host)) {
throw new RouteException(new UnknownServiceException(
"CLEARTEXT communication to " + host + " not permitted by network security policy"));
}
}
while (protocol == null) {
try {
if (route.requiresTunnel()) {
buildTunneledConnection(connectTimeout, readTimeout, writeTimeout,
connectionSpecSelector);
} else {
buildConnection(connectTimeout, readTimeout, writeTimeout, connectionSpecSelector);
}
} catch (IOException e) {
closeQuietly(socket);
closeQuietly(rawSocket);
socket = null;
rawSocket = null;
source = null;
sink = null;
handshake = null;
protocol = null;
if (routeException == null) {
routeException = new RouteException(e);
} else {
routeException.addConnectException(e);
}
if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
throw routeException;
}
}
}
}
在这个方法中,SSLSocketFactory为空,也就是要求请求/响应明文传输时,先做安全性检查,以确认系统允许明文传输,允许以请求的域名做明文传输。
然后根据路由的具体情况,执行不同的连接建立过程。对于需要创建隧道连接的路由,执行buildTunneledConnection(),对于其它情况,则执行buildConnection()。
判断是否要建立隧道连接的依据是代理的类型,以及连接的类型:
/**
* Returns true if this route tunnels HTTPS through an HTTP proxy. See <a
* href="http://www.ietf.org/rfc/rfc2817.txt">RFC 2817, Section 5.2</a>.
*/
public boolean requiresTunnel() {
return address.sslSocketFactory != null && proxy.type() == Proxy.Type.HTTP;
}
如果是HTTP代理,且请求建立SSL/TLS加密通道 (http/1.1的https和http2) ,则需要建立隧道连接。其它情形不需要建立隧道连接。
网易云新用户大礼包:https://www.163yun.com/gift
本文来自网易实践者社区,经作者韩鹏飞授权发布。