okhttp的连接过程是什么样的?
由前章可知,RealConnection是在StreamAllocation中查找可用连接是生成的。连接过程主要分为两步,这个过程是个死循环,直到建立可用的连接为止。
if (route.requiresTunnel()) {
connectTunnel(connectTimeout, readTimeout, writeTimeout);
} else {
connectSocket(connectTimeout, readTimeout);
}
establishProtocol(connectionSpecSelector);
1.如果路由需要通道,则连接通道,否则创建原始socket
2.建立协议,如果涉及到tls,则包含tls握手连接等。
关于tunnel:https://tools.ietf.org/html/rfc2817#section-5.2
For example, proxy authentication might be used to establish the
authority to create a tunnel:
CONNECT server.example.com:80 HTTP/1.1
Host: server.example.com:80
Proxy-Authorization: basic aGVsbG86d29ybGQ=
与任何其他流水线式HTTP/1.1请求一样,要传输的数据可以在空行之后立即发送。
通常的警告也适用:如果最终响应为负数,数据可能会被丢弃;如果有多个TCP段未完成,连接可能会重置而不响应。
先看下route
是由RouteSelector生成的,即sun.net.spi.DefaultProxySelector
.select(url.uri())
尝试代理服务
/** Prepares the proxy servers to try. */
private void resetNextProxy(HttpUrl url, Proxy proxy) {
if (proxy != null) {
// If the user specifies a proxy, try that and only that.
proxies = Collections.singletonList(proxy);
} else {
// Try each of the ProxySelector choices until one connection succeeds.
List<Proxy> proxiesOrNull = address.proxySelector().select(url.uri());
proxies = proxiesOrNull != null && !proxiesOrNull.isEmpty()
? Util.immutableList(proxiesOrNull)
: Util.immutableList(Proxy.NO_PROXY);
}
nextProxyIndex = 0;
}
/** Returns the next proxy to try. May be PROXY.NO_PROXY but never null. */
private Proxy nextProxy() throws IOException {
if (!hasNextProxy()) {
throw new SocketException("No route to " + address.url().host()
+ "; exhausted proxy configurations: " + proxies);
}
Proxy result = proxies.get(nextProxyIndex++);
resetNextInetSocketAddress(result);
return result;
}
/** Prepares the socket addresses to attempt for the current proxy or host. */
private void resetNextInetSocketAddress(Proxy proxy) throws IOException {
// Clear the addresses. Necessary if getAllByName() below throws!
inetSocketAddresses = new ArrayList<>();
String socketHost;
int socketPort;
if (proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.SOCKS) {
socketHost = address.url().host();
socketPort = address.url().port();
} else {
SocketAddress proxyAddress = proxy.address();
if (!(proxyAddress instanceof InetSocketAddress)) {
throw new IllegalArgumentException(
"Proxy.address() is not an " + "InetSocketAddress: " + proxyAddress.getClass());
}
InetSocketAddress proxySocketAddress = (InetSocketAddress) proxyAddress;
socketHost = getHostString(proxySocketAddress);
socketPort = proxySocketAddress.getPort();
}
if (socketPort < 1 || socketPort > 65535) {
throw new SocketException("No route to " + socketHost + ":" + socketPort
+ "; port is out of range");
}
if (proxy.type() == Proxy.Type.SOCKS) {
inetSocketAddresses.add(InetSocketAddress.createUnresolved(socketHost, socketPort));
} else {
// Try each address for best behavior in mixed IPv4/IPv6 environments.
List<InetAddress> addresses = address.dns().lookup(socketHost);
if (addresses.isEmpty()) {
throw new UnknownHostException(address.dns() + " returned no addresses for " + socketHost);
}
for (int i = 0, size = addresses.size(); i < size; i++) {
InetAddress inetAddress = addresses.get(i);
inetSocketAddresses.add(new InetSocketAddress(inetAddress, socketPort));
}
}
nextInetSocketAddressIndex = 0;
}
RouteDatabase:失败的路由会被记录。
ConnectionSpecSelector:
处理连接规范回退策略:当安全套接字连接由于握手/协议问题而失败时,可以使用不同的协议重试连接。实例是有状态的,应该创建并用于单个连接尝试。
private void establishProtocol(ConnectionSpecSelector connectionSpecSelector) throws IOException {
if (route.address().sslSocketFactory() == null) {
protocol = Protocol.HTTP_1_1;
socket = rawSocket;
return;
}
connectTls(connectionSpecSelector);
if (protocol == Protocol.HTTP_2) {
socket.setSoTimeout(0); // HTTP/2 connection timeouts are set per-stream.
http2Connection = new Http2Connection.Builder(true)
.socket(socket, route.address().url().host(), source, sink)
.listener(this)
.build();
http2Connection.start();
}
}
建立协议的过程,如果路由的address().sslSocketFactory为null,就跳过ssl层,直接使用原始socket,否则使用sslsocket。
http2默认必须要使用ssl套接字。
private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IOException {
Address address = route.address();
SSLSocketFactory sslSocketFactory = address.sslSocketFactory();
boolean success = false;
SSLSocket sslSocket = null;
try {
// Create the wrapper over the connected socket.
sslSocket = (SSLSocket) sslSocketFactory.createSocket(
rawSocket, address.url().host(), address.url().port(), true /* autoClose */);
// Configure the socket's ciphers, TLS versions, and extensions.
ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
if (connectionSpec.supportsTlsExtensions()) {
Platform.get().configureTlsExtensions(
sslSocket, address.url().host(), address.protocols());
}
// Force handshake. This can throw!
sslSocket.startHandshake();
Handshake unverifiedHandshake = Handshake.get(sslSocket.getSession());
// Verify that the socket's certificates are acceptable for the target host.
if (!address.hostnameVerifier().verify(address.url().host(), sslSocket.getSession())) {
X509Certificate cert = (X509Certificate) unverifiedHandshake.peerCertificates().get(0);
throw new SSLPeerUnverifiedException("Hostname " + address.url().host() + " not verified:"
+ "\n certificate: " + CertificatePinner.pin(cert)
+ "\n DN: " + cert.getSubjectDN().getName()
+ "\n subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert));
}
// Check that the certificate pinner is satisfied by the certificates presented.
address.certificatePinner().check(address.url().host(),
unverifiedHandshake.peerCertificates());
// Success! Save the handshake and the ALPN protocol.
String maybeProtocol = connectionSpec.supportsTlsExtensions()
? Platform.get().getSelectedProtocol(sslSocket)
: null;
socket = sslSocket;
source = Okio.buffer(Okio.source(socket));
sink = Okio.buffer(Okio.sink(socket));
handshake = unverifiedHandshake;
protocol = maybeProtocol != null
? Protocol.get(maybeProtocol)
: Protocol.HTTP_1_1;
success = true;
} catch (AssertionError e) {
if (Util.isAndroidGetsocknameError(e)) throw new IOException(e);
throw e;
} finally {
if (sslSocket != null) {
Platform.get().afterHandshake(sslSocket);
}
if (!success) {
closeQuietly(sslSocket);
}
}
}
上面的额步骤,
1.创建ssl套接字。
2.配置密码套件,tls版本, extensions等,
如果connectionSpec支持tls扩展,Android或java平台配置configureTlsExtensions
NOTE: 安全套接字重要的API:
socket.getEnabledProtocols,socket.getEnabledCipherSuites
3.开始握手
sslsocket.startHandshake()
javax.net.ssl.SSLSocket::
* @throws IOException on a network level error
* @see #addHandshakeCompletedListener(HandshakeCompletedListener)
*/
public abstract void startHandshake() throws IOException;
在此连接上启动SSL握手。常见原因包括需要使用新的加密密钥、更改密码套件或启动新会话。若要强制完全重新验证,当前会话可能会在开始此握手之前失效。
如果已经在连接上发送了数据,那么在这个握手过程中数据将继续流动。当握手完成时,将用一个事件发出信号。
对于连接上的初始握手,此方法是同步的,并在协商握手完成时返回。某些协议可能不支持现有套接字上的多个握手,并可能引发IOException。
public static Handshake get(SSLSession session) {
String cipherSuiteString = session.getCipherSuite();
if (cipherSuiteString == null) throw new IllegalStateException("cipherSuite == null");
CipherSuite cipherSuite = CipherSuite.forJavaName(cipherSuiteString);
String tlsVersionString = session.getProtocol();
if (tlsVersionString == null) throw new IllegalStateException("tlsVersion == null");
TlsVersion tlsVersion = TlsVersion.forJavaName(tlsVersionString);
Certificate[] peerCertificates;
try {
peerCertificates = session.getPeerCertificates();
} catch (SSLPeerUnverifiedException ignored) {
peerCertificates = null;
}
List<Certificate> peerCertificatesList = peerCertificates != null
? Util.immutableList(peerCertificates)
: Collections.<Certificate>emptyList();
Certificate[] localCertificates = session.getLocalCertificates();
List<Certificate> localCertificatesList = localCertificates != null
? Util.immutableList(localCertificates)
: Collections.<Certificate>emptyList();
return new Handshake(tlsVersion, cipherSuite, peerCertificatesList, localCertificatesList);
}
4.通过sslSocket创建的SSLSession会话,生成握手
// Verify that the socket's certificates are acceptable for the target host.
5.校验certificate pinner
6.支持的协议
public static Protocol get(String protocol) throws IOException {
// Unroll the loop over values() to save an allocation.
if (protocol.equals(HTTP_1_0.protocol)) return HTTP_1_0;
if (protocol.equals(HTTP_1_1.protocol)) return HTTP_1_1;
if (protocol.equals(HTTP_2.protocol)) return HTTP_2;
if (protocol.equals(SPDY_3.protocol)) return SPDY_3;
throw new IOException("Unexpected protocol: " + protocol);
}
安卓平台的协议
OptionalMethod(Class<?> returnType, String methodName, Class... methodParams) {
this.returnType = returnType;
this.methodName = methodName;
this.methodParams = methodParams;
}
getAlpnSelectedProtocol =new OptionalMethod<>(byte[].class, "getAlpnSelectedProtocol");
@Override public String getSelectedProtocol(SSLSocket socket) {
if (getAlpnSelectedProtocol == null) return null;
if (!getAlpnSelectedProtocol.isSupported(socket)) return null;
byte[] alpnResult = (byte[]) getAlpnSelectedProtocol.invokeWithoutCheckedException(socket);
return alpnResult != null ? new String(alpnResult, Util.UTF_8) : null;
}
网友评论