Okhttp之ConnectInterceptor攔截器原理及解析
因為Okhttp中攔截器都是責任鏈設計模式,這裡直接看ittercept()方法即可。
先來獻上高清無碼圖,方便更好的理解其原理

image.png
1. ConnectInterceptor核心程式碼:
@Override public Response intercept(Chain chain) throws IOException { RealInterceptorChain realChain = (RealInterceptorChain) chain; Request request = realChain.request(); //RetryAndFollowUpInterceptor中建立 StreamAllocation streamAllocation = realChain.streamAllocation(); boolean doExtensiveHealthChecks = !request.method().equals("GET"); //看來核心程式碼是newStream();獲取到httpCodec HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks); //按照字面意思是獲取一個連線 RealConnection connection = streamAllocation.connection(); return realChain.proceed(request, streamAllocation, httpCodec, connection); } }
可以看出它的核心方法為:streamAllocation.newStream(client, chain, doExtensiveHealthChecks) 那麼該方法到底做了什麼事?
2. StreamAllocation中的newStream()核心程式碼:
public HttpCodec newStream( OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) { int connectTimeout = chain.connectTimeoutMillis(); int readTimeout = chain.readTimeoutMillis(); int writeTimeout = chain.writeTimeoutMillis(); int pingIntervalMillis = client.pingIntervalMillis(); boolean connectionRetryEnabled = client.retryOnConnectionFailure(); RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks); HttpCodec resultCodec = resultConnection.newCodec(client, chain, this); ... return resultCodec; } }
- 2.1 首先看到的是在這裡設定讀寫和連線時間,和獲取client中的配置,是否請求重試設定,最關鍵的事 findHealthyConnection(....),通過字面意思,是獲取一個有效/健康的連線,看看findHealthyConnection()的程式碼,則看到有一個while(true)的死迴圈,他會一直去找一個連線,知道找到為止。
while (true) { RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled); // If this is a brand new connection, we can skip the extensive health checks. synchronized (connectionPool) { if (candidate.successCount == 0) { return candidate; } } ... return candidate; }
到這裡還是沒有發現具體怎麼找到的連線,可以看到上面有一個findConnection()方法,再來看看這個方法是具體如何找到連線的?
RealConnection result = null; Route selectedRoute = null; Connection releasedConnection; ... //從連線池中獲取可用的連線 if (result == null) { // Attempt to get a connection from the pool. Internal.instance.get(connectionPool, address, this, null); if (connection != null) { foundPooledConnection = true; result = connection; } else { selectedRoute = route; } } if (result != null) { // If we found an already-allocated or pooled connection, we're done. return result; } ... synchronized (connectionPool) { ... 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; break; } } } ... //如果從快取中更沒有找到連線,則new 一個新的連線 result = new RealConnection(connectionPool, selectedRoute); acquire(result, false); } } //http的三次握手操作 Do TCP + TLS handshakes. This is a blocking operation. result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled, call, eventListener); routeDatabase().connected(result.route()); Socket socket = null; synchronized (connectionPool) { reportedAcquired = true; //將連線放入連線池 Pool the connection. Internal.instance.put(connectionPool, result); } ... return result; }
即如果找到從連線池中獲取可用的連線,如果沒有則建立,並且重新快取到連線池中。
- 2.2 再來看看,獲取到連線以後,okhttp做了什麼事?
HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
可以看到newCodec中所做的事,可以看出是將streamAllocation和source,sink做了封裝,可以看到這裡用到了okio中的流的封裝。
public HttpCodec newCodec(OkHttpClient client, Interceptor.Chain chain, StreamAllocation streamAllocation) throws SocketException { if (http2Connection != null) { return new Http2Codec(client, chain, streamAllocation, http2Connection); } else { socket.setSoTimeout(chain.readTimeoutMillis()); source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS); sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS); return new Http1Codec(client, streamAllocation, source, sink); } }
3.new RealConnection(connectionPool, selectedRoute)中的實現
RealConnection result = new RealConnection(connectionPool, selectedRoute);
new RealConnection()做了什麼事,可以從原始碼中看出,裡面有好幾個connetXX的方法和地址的封裝,但是connetXX方法中都會有socket和okio的封裝,即如下程式碼connectTls()方法實現:
private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IOException { Address address = route.address(); SSLSocketFactory sslSocketFactory = address.sslSocketFactory(); boolean success = false; SSLSocket sslSocket = null; // 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(); // block for session establishment SSLSession sslSocketSession = sslSocket.getSession(); Handshake unverifiedHandshake = Handshake.get(sslSocketSession); // 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; }
到這裡可以得出如下結論:OkHttp 是基於原生的 Socket + okio(原生IO的封裝)
HttpCodec 裡面封裝了 okio 的 Source(輸入) 和 Sink (輸出),我們通過 HttpCodec 就可以操作 Socket的輸入輸出,可以向伺服器寫資料和讀取返回資料
總結
ConnectionInterceptor是獲取一個RealConnection物件,然後建立Socket連結,封裝地址資訊,然後呼叫CallServerInterceptor 來完成Okhttp的整個操作。