在okhttp发起请求的调用链中,在发起请求之前需要建立连接,负责建立连接的就是ConnectInterceptor.intercept了
@Override
public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
// 在RetryAndFollowUpInterceptor中new出来的,传递到这里
// streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()),
// call, eventListener, callStackTrace);
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
// 重点就是这个方法了
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
代码很简单,就是调用streamAllocation.newStream建立连接,一共两步,建立连接的是第一步
public HttpCodec newStream(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
// ...
// 1. 找到可以使用的链接,可能是复用的,也可能是新建的
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
// 2. 创建HttpCodec实例,用于后续的请求
HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
// ...
}
findHealthyConnection主要是调用findConnection来建立连接,直接看findConnection的实现
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
boolean connectionRetryEnabled) throws IOException {
// ...
synchronized (connectionPool) {
// 1. 首先尽量寻找可以用的连接,做到尽量的复用
releasedConnection = this.connection;
// 判断当前连接(如果有的话)是否需要释放,有的连接不允许复用,通过noNewStreams变量来标识
toClose = releaseIfNoNewStreams();
if (this.connection != null) {
// We had an already-allocated connection and it's good.
result = this.connection;
releasedConnection = null;
}
if (!reportedAcquired) {
// If the connection was never reported acquired, don't report it as released!
releasedConnection = null;
}
if (result == null) {
// 先去看看okhttpClient.connectPool里面有没有可以复用的连接
Internal.instance.get(connectionPool, address, this, null);
if (connection != null) {
foundPooledConnection = true;
result = connection;
} else {
selectedRoute = route;
}
}
}
closeQuietly(toClose);
if (releasedConnection != null) {
eventListener.connectionReleased(call, releasedConnection);
}
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
}
// 如果在okhttpClient.connectPool里找到了可用的连接,就可以直接返回了
if (result != null) {
// If we found an already-allocated or pooled connection, we're done.
return result;
}
// 如果是第一次进来的话,selectedRoute和routeSelection肯定都是空的
// 经过赋值以后,这里的routeSelection就是在RouteSelector里new出来的,如下,包含了本次请求的信息
// new Route(address, proxy, inetSocketAddresses.get(i));
// new Selection(routes)
boolean newRouteSelection = false;
if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
newRouteSelection = true;
routeSelection = routeSelector.next();
}
synchronized (connectionPool) {
if (canceled) throw new IOException("Canceled");
if (newRouteSelection) {
// 再次尝试一下复用老连接
List<Route> routes = routeSelection.getAll();
for (int i = 0, size = routes.size(); i < size; i++) {
Route route = routes.get(i);
Internal.instance.get(connectionPool, address, this, route);
if (connection != null) {
foundPooledConnection = true;
result = connection;
this.route = route;
break;
}
}
}
// 如果经过上面的流程都没有找到可以复用的连接,那么就new一个新的
if (!foundPooledConnection) {
if (selectedRoute == null) {
selectedRoute = routeSelection.next();
}
// Create a connection and assign it to this allocation immediately. This makes it possible
// for an asynchronous cancel() to interrupt the handshake we're about to do.
route = selectedRoute;
refusedStreamCount = 0;
result = new RealConnection(connectionPool, selectedRoute);
acquire(result, false);
}
}
// If we found a pooled connection on the 2nd time around, we're done.
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
return result;
}
// 2. 经过前面的过程,找到(或者new)了一个可用的连接,这里做tcp握手
result.connect(
connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled, call, eventListener);
routeDatabase().connected(result.route());
// 3. 把可用连接保存在connectPool中
Socket socket = null;
synchronized (connectionPool) {
reportedAcquired = true;
// Pool the connection.
Internal.instance.put(connectionPool, result);
// If another multiplexed connection to the same address was created concurrently, then
// release this connection and acquire that one.
if (result.isMultiplexed()) {
socket = Internal.instance.deduplicate(connectionPool, address, this);
result = connection;
}
}
closeQuietly(socket);
eventListener.connectionAcquired(call, result);
return result;
}
这个方法有点长,但是逻辑很清晰,如代码中注释的那样,主要是3部分
- 找到可以复用的连接,如果找不到,就new一个
- 用这个连接进行握手
- 存储这个连接,供后续其他请求复用
连接的真正处理部分是RealConnection.connect方法,看看它的处理
public void connect(int connectTimeout, int readTimeout, int writeTimeout,
boolean connectionRetryEnabled, Call call, EventListener eventListener) {
RouteException routeException = null;
// ConnectionSpec是连接支持的加密算法、支持的协议的一个描述,okHttpClient里的默认值是
// static final List<ConnectionSpec> DEFAULT_CONNECTION_SPECS = Util.immutableList(
// ConnectionSpec.MODERN_TLS, ConnectionSpec.CLEARTEXT);
// ConnectionSpec.MODERN_TLS表示TLS,其中包括支持的加密算法,可以在CipherSuite类中找到定义,支持的TLS协议,包括从SSLv3到TLSv1.3
// ConnectionSpec.CLEARTEXT表示HTTP,不支持任何加密算法和TLS
List<ConnectionSpec> connectionSpecs = route.address().connectionSpecs();
ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);
if (route.address().sslSocketFactory() == null) {
if (!connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
// 如果不需要ssl,但是connectionSpecs禁用了http,那么抛出异常
}
// 这里的Platform.get()得到的是AnroidPlatform
String host = route.address().url().host();
// 通过反射调用android系统类NetworkSecurityPolicy,检查android是否允许下列协议
// Returns whether cleartext network traffic (e.g. HTTP, FTP, WebSockets, XMPP, IMAP, SMTP --
// * without TLS or STARTTLS) is permitted for this process.
if (!Platform.get().isCleartextTrafficPermitted(host)) {
// 不允许就直接抛出异常
}
}
while (true) {
try {
if (route.requiresTunnel()) {
connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
if (rawSocket == null) {
// We were unable to connect the tunnel but properly closed down our resources.
break;
}
} else {
connectSocket(connectTimeout, readTimeout, call, eventListener);
}
establishProtocol(connectionSpecSelector, call, eventListener);
eventListener.connectEnd(call, route.socketAddress(), route.proxy(), protocol);
break;
} catch (IOException e) {
// 异常处理
}
}
// 异常处理和http2.0处理
}
- 首先进行异常检查
- 根据是否需要建立隧道调用不同的方法
- 异常处理
这里聚焦于连接的方法,当请求是https但是代理是http的时候,就需要建立隧道
不需要建立隧道
private void connectSocket(int connectTimeout, int readTimeout, Call call,
EventListener eventListener) throws IOException {
Proxy proxy = route.proxy();
Address address = route.address();
// 没有代理的时候,代理类型是DIRECT,表示直接连接,这里的address.socketFactory().createSocket()
// 默认就是new Socket,需要代理的话就是new Socket(proxy)
rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
? address.socketFactory().createSocket()
: new Socket(proxy);
eventListener.connectStart(call, route.socketAddress(), proxy);
rawSocket.setSoTimeout(readTimeout);
try {
// 这里调用的是AndroidPlatform,实际调用的方法就是rawSocket.connect
Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
} catch (ConnectException e) {
// ...
}
try {
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
} catch (NullPointerException npe) {
// ...
}
}
可以看到建立连接的方式很简单,就是调用Socket.connect
需要建立隧道
private void connectTunnel(int connectTimeout, int readTimeout, int writeTimeout, Call call,
EventListener eventListener) throws IOException {
// 1. 首先创建tunnelRequest,仅包含url/host/Proxy-Connection/User-Agent的request
// 因为跟http代理之间的请求是未加密的,所以header里面尽可能少的包含信息
Request tunnelRequest = createTunnelRequest();
HttpUrl url = tunnelRequest.url();
for (int i = 0; i < MAX_TUNNEL_ATTEMPTS; i++) {
// 2. 建立跟代理之间的socket连接
connectSocket(connectTimeout, readTimeout, call, eventListener);
// 3. 使用CONNECT请求建立一条隧道
tunnelRequest = createTunnel(readTimeout, writeTimeout, tunnelRequest, url);
// 4. 建立成功调处循环
if (tunnelRequest == null) break; // Tunnel successfully created.
// 5. 清理工作,为下次重试做准备
// ...
}
}
逻辑也是比较清楚的
连接后进行握手
在建立连接之后要进行握手,握手过程可以参考, RealConnection通过establishProtocol方法完成,该方法又调用了connectTls
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());
}
// 这里得到的SSLSocketFactory和SSLSocket分别是OpenSSLSocketFactoryImpl和OpenSSLSessionImpl
// 代码都在android源码external/conscrypt中
// 握手执行的native代码也在该工程中,对应代码文件org_conscrypt_NativeCrypto.cpp
// 在握手过程中,会回调验证证书,验证不过直接抛出异常
sslSocket.startHandshake();
Handshake unverifiedHandshake = Handshake.get(sslSocket.getSession());
// 证书验证通过之后校验域名
if (!address.hostnameVerifier().verify(address.url().host(), sslSocket.getSession())) {
// ....
}
// 指纹校验,默认是空,也就是不校验。
// 指纹校验可以参考https://www.jianshu.com/p/1fdbcfcc962c
// 和https://imququ.com/post/http-public-key-pinning.html
address.certificatePinner().check(address.url().host(),
unverifiedHandshake.peerCertificates());
// ...
} catch (AssertionError e) {
// ...
} finally {
// ...
}
}
至此,连接建立完毕,回到开始的StreamAllocation.newStream方法,创建HttpCodec实例后ConnectInterceptor的工作就完成了