关于Okhttp3(七)-ConnectInterceptor

上篇我们将来缓存拦截器,鉴于本系列是将Okhttp的所以就没有将太多关于缓存相关的东西,后面又机会在开一个系列。今天我们讲讲倒数第二个拦截器—连接拦截器,这才是真正的开始向服务端发起进攻,同志们是不是已经急不可耐了,哈哈,马上到来。

源码

  1. 获取到最初实例化的通道对象
  2. 得到一个httpcodec
  3. 得到一个连接对象
1
2
3
4
5
6
7
8
9
10
11
12
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
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, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();

return realChain.proceed(request, streamAllocation, httpCodec, connection);
}

此处的逻辑很简单,因为已经转嫁到其他的类对象上去了。前面我们提到过RealInterceptorChain构造函数有四个重要的属性(Request、StreamAllocation, HttpCodec、Connection)第一个不用说一开始就必须有,第二个是在重试拦截器中实例化的,第三、第四就是在本节中腰实例化出来的。

获取httpcodec

通过StreamAllocation的newStream()方法可以的得到一个HttpCodec。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
// 略
try {
// 1.查找是否有完好的连接,仅在http2.0有用,如果没有将会实例化一个
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
// 2.实例化httpcodec,如果是http2.0实例化Http2Codec否则Http2Codec
HttpCodec resultCodec = resultConnection.newCodec(client, this);

synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
  1. 对于健康连接的判断
    1. socket没有关闭
    2. 输入流没有关闭
    3. 输出流没有关闭
    4. http2时连接没有关闭

获取连接并连接

流程:

  1. 上一个连接是否完好,是,直接使用
  2. 缓存中有没有可以用的,有,使用
  3. 自己创建
  4. 握手、连接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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");

// 使用已存在的连接
// Attempt to use an already-allocated connection.
RealConnection allocatedConnection = this.connection;
if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
return allocatedConnection;
}

// 从缓存中获取
// Attempt to get a connection from the pool.
Internal.instance.get(connectionPool, address, this);
if (connection != null) {
return connection;
}

selectedRoute = route;
}

// 线路的选择,多ip的支持
// If we need a route, make one. This is a blocking operation.
if (selectedRoute == null) {
// 里面又个神奇的递归
selectedRoute = routeSelector.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.
// 以上都不符合,创建一个连接
RealConnection result;
synchronized (connectionPool) {
route = selectedRoute;
refusedStreamCount = 0;
result = new RealConnection(connectionPool, selectedRoute);
acquire(result);
if (canceled) throw new IOException("Canceled");
}

// 连接 并握手
// Do TCP + TLS handshakes. This is a blocking operation.
result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
routeDatabase().connected(result.route());

Socket socket = null;
// 放到缓存中
synchronized (connectionPool) {
// 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);

return result;
}

连接

建立连接是比较重要的一步了。如果是Https还有证书步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public void connect(
int connectTimeout, int readTimeout, int writeTimeout, boolean connectionRetryEnabled)
{

if (protocol != null) throw new IllegalStateException("already connected");
// 线路的选择
RouteException routeException = null;
List<ConnectionSpec> connectionSpecs = route.address().connectionSpecs();
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 (true) {
try {
// 如果要求通道模式,建立通道连接,通常不是这种
if (route.requiresTunnel()) {
connectTunnel(connectTimeout, readTimeout, writeTimeout);
} else {
// 一般都走这条逻辑了,实际上很简单就是socket的连接
connectSocket(connectTimeout, readTimeout);
}
// https的建立
establishProtocol(connectionSpecSelector);
break;
} catch (IOException e) {
closeQuietly(socket);
closeQuietly(rawSocket);
socket = null;
rawSocket = null;
source = null;
sink = null;
handshake = null;
protocol = null;
http2Connection = null;

if (routeException == null) {
routeException = new RouteException(e);
} else {
routeException.addConnectException(e);
}

if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
throw routeException;
}
}
}

if (http2Connection != null) {
synchronized (connectionPool) {
allocationLimit = http2Connection.maxConcurrentStreams();
}
}
}

我们进入connectSocket()函数看看,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private void connectSocket(int connectTimeout, int readTimeout) throws IOException {
Proxy proxy = route.proxy();
Address address = route.address();

// 根据代理类型处理socket,为true
rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
? address.socketFactory().createSocket()
: new Socket(proxy);

rawSocket.setSoTimeout(readTimeout);
try {
// 连接socket,之所以这样写是因为支持不同的平台
/**
* 里面实际上是 socket.connect(address, connectTimeout);
*
*/

Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
} catch (ConnectException e) {
ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress());
ce.initCause(e);
throw ce;
}
// 得到输入/输出流
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
}

如果使用的是https协议,并且配置有真实将会协议升级。

Https协议的建立

如果使用的是https协议socket连接完成后还有一步,就是Tls的处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IOException {
Address address = route.address();
SSLSocketFactory sslSocketFactory = address.sslSocketFactory();
boolean success = false;
SSLSocket sslSocket = null;
try {
// 在原来socket上加一层ssl
// 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);
}
}
}

如果对https熟悉的话,应该知道,https就是在http的基础上加了一层。

到此链接完成RealConnection实例化完成。

然后就是进行下一拦截器。

总结

这个拦截器原理流程都比较简单,重点是要把Socket和Http协议相关知识理解清楚。此文知识抛砖引玉,里面的一些细节没有讲,建议读者更具这系列文章自己去梳理。

系列文章

  1. 关于Okhttp(一)-基本使用
  2. 关于Okhttp(二)-如何下载查看源码
  3. 关于Okhttp3(三)-请求流程
  4. 关于Okhttp3(四)-RetryAndFollowUpInterceptor
  5. 关于Okhttp3(五)-BridgeInterceptor
  6. 关于Okhttp3(六)-CacheInterceptor
  7. 关于Okhttp3(七)-ConnectInterceptor
  8. 关于Okhttp3(八)-CallServerInterceptor
坚持原创技术分享,您的支持将鼓励我继续创作!