- 1.OkHttp源碼解析(一):OKHttp初階
- 2 OkHttp源碼解析(二):OkHttp連接的"前戲"——HTTP的那些事
- 3 OkHttp源碼解析(三):OKHttp中階之線程池和消息隊列
- 4 OkHttp源碼解析(四):OKHttp中階之攔截器及調(diào)用鏈
- 5 OkHttp源碼解析(五):OKHttp中階之OKio簡介
- 6 OkHttp源碼解析(六):OKHttp中階之緩存基礎(chǔ)
- 7 OkHttp源碼解析(七):OKHttp中階之緩存機制
- 8 OkHttp源碼解析(八):OKHttp中階之連接與請求值前奏
- 9 OkHttp源碼解析(九):OKHTTP連接中三個"核心"RealConnection、ConnectionPool、StreamAllocation
- 10 OkHttp源碼解析(十) OKHTTP中連接與請求
- 11 OkHttp的感謝
終于到了講解OkHttp中的連接與請求了,這部分內(nèi)容主要是在ConnectInterceptor與CallServerInterceptor中,所以本片文章主要分2部分
- 1、ConnectInterceptor
- 2、CallServerInterceptor
- 3、總結(jié)
一、ConnectInterceptor
顧名思義連接攔截器,這才是真行的開始向服務器發(fā)起器連接。
看下這個類的代碼
/** Opens a connection to the target server and proceeds to the next interceptor. */
public final class ConnectInterceptor implements Interceptor {
public final OkHttpClient client;
public ConnectInterceptor(OkHttpClient client) {
this.client = client;
}
@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);
}
}
主要看下ConnectInterceptor()方法,里面代碼已經(jīng)很簡單了,受限了通過streamAllocation的newStream方法獲取一個流(HttpCodec 是個接口,根據(jù)協(xié)議的不同,由具體的子類的去實現(xiàn)),第二步就是獲取對應的RealConnection,由于在上一篇文章已經(jīng)詳細解釋了RealConnection和streamAllocation類了,這里就不詳細說了是大概聊一下
StreamAllocation的newStream()內(nèi)部其實是通過findHealthyConnection()方法獲取一個RealConnection,而在findHealthyConnection()里面通過一個while(true)死循環(huán)不斷去調(diào)用findConnection()方法去找RealConnection.而在findConnection()里面其實是真正的尋找RealConnection,而上面提到的findHealthyConnection()里面主要就是調(diào)用findConnection()然后去驗證是否是"健康"的。在findConnection()里面主要是通過3重判斷:1如果有已知連接且可用,則直接返回,2如果在連接池有對應address的連接,則返回,3切換路由再在連接池里面找下,如果有則返回,如果上述三個條件都沒有滿足,則直接new一個RealConnection。然后開始握手,握手結(jié)束后,把連接加入連接池,如果在連接池有重復連接,和合并連接。
至此findHealthyConnection()就分析完畢,給大家看下大縮減后的代碼,如果大家想詳細了解,請看上一篇文章。
//StreamAllocation.java
public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
// 省略代碼
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
HttpCodec resultCodec = resultConnection.newCodec(client, this);
// 省略代碼
}
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
throws IOException {
while (true) {
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
connectionRetryEnabled);
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
noNewStreams();
continue;
}
return candidate;
}
}
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
boolean connectionRetryEnabled) throws IOException {
//省略部分代碼
//條件1如果有已知連接且可用,則直接返回
RealConnection allocatedConnection = this.connection;
if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
return allocatedConnection;
}
//條件2 如果在連接池有對應address的連接,則返回
Internal.instance.get(connectionPool, address, this, null);
if (connection != null) {
return connection;
}
selectedRoute = route;
}
// 條件3切換路由再在連接池里面找下,如果有則返回
if (selectedRoute == null) {
selectedRoute = routeSelector.next();
}
RealConnection result;
synchronized (connectionPool) {
if (canceled) throw new IOException("Canceled");
Internal.instance.get(connectionPool, address, this, selectedRoute);
if (connection != null) return connection;
route = selectedRoute;
refusedStreamCount = 0;
//以上條件都不滿足則new一個
result = new RealConnection(connectionPool, selectedRoute);
acquire(result);
}
// 開始握手
result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
//計入數(shù)據(jù)庫
routeDatabase().connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
//加入連接池
Internal.instance.put(connectionPool, result);
// 如果是多路復用,則合并
if (result.isMultiplexed()) {
socket = Internal.instance.deduplicate(connectionPool, address, this);
result = connection;
}
}
closeQuietly(socket);
return result;
}
這里再簡單的說下RealConnection的connect()因為這個方法也很重要。不過大家要注意RealConnection的connect()是StreamAllocation調(diào)用的。在RealConnection的connect()的方法里面也是一個while(true)的循環(huán),里面判斷是隧道連接還是普通連接,如果是隧道連接就走connectTunnel(),如果是普通連接則走connectSocket(),最后建立協(xié)議。隧道連接這里就不介紹了,如果大家有興趣就去上一篇文章去看。connectSocket()方噶里面就是通過okio獲取source與sink。establishProtocol()方法建立連接咱們說下,里面判斷是是HTTP/1.1還是HTTP/2.0。如果是HTTP/2.0則通過Builder來創(chuàng)建一個Http2Connection對象,并且調(diào)用Http2Connection對象的start()方法。所以判斷一個RealConnection是否是HTTP/2.0其實很簡單,判斷RealConnection對象的http2Connection屬性是否為null即可,因為只有HTTP/2的時候http2Connection才會被賦值。
代碼如下:
public void connect(
int connectTimeout, int readTimeout, int writeTimeout, boolean connectionRetryEnabled) {
//省略部分代碼
while (true) {
try {
if (route.requiresTunnel()) {
connectTunnel(connectTimeout, readTimeout, writeTimeout);
} else {
connectSocket(connectTimeout, readTimeout);
}
establishProtocol(connectionSpecSelector);
break;
} catch (IOException e) {
//省略部分代碼
}
}
if (http2Connection != null) {
synchronized (connectionPool) {
allocationLimit = http2Connection.maxConcurrentStreams();
}
}
}
private void connectSocket(int connectTimeout, int readTimeout) throws IOException {
//省略部分代碼
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
}
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();
}
}
這時候我們在回來看下findConnection()方法里面的一行代碼
acquire(result)
調(diào)用的是acquire()方法
public void acquire(RealConnection connection) {
assert (Thread.holdsLock(connectionPool));
if (this.connection != null) throw new IllegalStateException();
this.connection = connection;
connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
}
代碼簡單,這里解釋一下,每一個RealConnection對象都有一個字段即allocations
public final List<Reference<StreamAllocation>> allocations = new ArrayList<>();
connections中維護了一張在一個連接上的流的鏈表。該鏈表保存的是StreamAllocation的引用。如果connections字段為空,則說明該連接可以被回收,如果不為空,說明被引用,不能被回收。所以O(shè)kHttp使用了類似計數(shù)法與標記擦出法的混合使用。當連接空閑或者釋放的時候,StreamAllcocation的數(shù)量就會漸漸變成0。從而被線程池檢測并回收。
至此StreamAllocation的findHealthyConnection()就分析完畢了。那我們來看下
//StreamAllocation.java
HttpCodec resultCodec = resultConnection.newCodec(client, this);
其實是調(diào)用RealConnection的newCodec()方法
public HttpCodec newCodec(
OkHttpClient client, StreamAllocation streamAllocation) throws SocketException {
if (http2Connection != null) {
return new Http2Codec(client, streamAllocation, http2Connection);
} else {
socket.setSoTimeout(client.readTimeoutMillis());
source.timeout().timeout(client.readTimeoutMillis(), MILLISECONDS);
sink.timeout().timeout(client.writeTimeoutMillis(), MILLISECONDS);
return new Http1Codec(client, streamAllocation, source, sink);
}
}
上面主要分了HTTP/2和HTTP/1.x,如果是HTTP/2(http2Connection不為null)則構(gòu)建Http2Codec。如果是HTTP/1.x。則構(gòu)建Http1Codec,大家注意一下在構(gòu)建Http2Codec的時候并沒有傳入source和sink。這是為什么那?大家好好想一下,如果大家不知道為什么可以去看一下我前面的一篇介紹HTTP/2的文章,如果看了還不懂,請在下面留言,我給大家解釋下。
至此關(guān)于ConnectInterceptor已經(jīng)介紹完畢了。下面我們來介紹下CallServerInterceptor。最后一個Interceptor
二、CallServerInterceptor
上面我們已經(jīng)成功連接到服務器了,那接下來要做什么那?相信你已經(jīng)猜到了, 那就說發(fā)送數(shù)據(jù)了。
在OkHttp里面讀取數(shù)據(jù)主要是通過以下四個步驟來實現(xiàn)的
- 1 寫入請求頭
- 2 寫入請求體
- 3 讀取響應頭
- 4 讀取響應體
OkHttp的流程是完全獨立的。同樣讀寫數(shù)據(jù)月是交給相關(guān)的類來處理,就是HttpCodec(解碼器)來處理。
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
HttpCodec httpCodec = realChain.httpStream();
StreamAllocation streamAllocation = realChain.streamAllocation();
RealConnection connection = (RealConnection) realChain.connection();
Request request = realChain.request();
long sentRequestMillis = System.currentTimeMillis();
//寫入請求頭
httpCodec.writeRequestHeaders(request);
Response.Builder responseBuilder = null;
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
// If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
// Continue" response before transmitting the request body. If we don't get that, return what
// we did get (such as a 4xx response) without ever transmitting the request body.
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();
responseBuilder = httpCodec.readResponseHeaders(true);
}
//寫入請求體
if (responseBuilder == null) {
// Write the request body if the "Expect: 100-continue" expectation was met.
Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
} else if (!connection.isMultiplexed()) {
// If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection from
// being reused. Otherwise we're still obligated to transmit the request body to leave the
// connection in a consistent state.
streamAllocation.noNewStreams();
}
}
httpCodec.finishRequest();
//讀取響應頭
if (responseBuilder == null) {
responseBuilder = httpCodec.readResponseHeaders(false);
}
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
//讀取響應體
int code = response.code();
if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
自此整個流程已經(jīng)結(jié)束了。
那我們再來看下OkHttp網(wǎng)絡請求的整體接口圖(特別聲明:這個圖不是我畫的)
關(guān)于OkHttp就的解析馬上就要結(jié)束了,最后我們再來溫習一下整體的流程圖