系列文章
OKHttp原始碼解析(2)----攔截器RetryAndFollowUpInterceptor
OKHttp原始碼解析(3)----攔截器BridgeInterceptor
OKHttp原始碼解析(4)----攔截器CacheInterceptor
OKHttp原始碼解析(5)----攔截器ConnectInterceptor
OKHttp原始碼解析(6)----攔截器CallServerInterceptor
1.簡介
This is the last interceptor in the chain. It makes a network call to the server. 這是鏈中最後一個攔截器,它向伺服器發起了一次網路訪問
請求服務攔截器,負責向伺服器傳送請求資料、從伺服器讀取響應資料
2.原始碼解析
2.1流程
@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;
// 檢查是否為有 body 的請求方法
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
// If there is a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
// Continue" response before transmitting the request body. If we do not get that, return what
// we did get (such as a 4xx response) without ever transmitting the request body.
// 如果有 Expect: 100-continue在請求頭中,那麼要等伺服器的響應。100-continue協議需要在post資料前,
徵詢伺服器情況,看伺服器是否處理POST的資料
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
//傳送100-continue的請求
httpCodec.flushRequest();
//如果有中間級的響應,返回null
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();
// 如果為 web socket 且狀態碼是 101 ,那麼 body 為空
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 {
//設定響應body,此時並沒有讀取socket中的輸入流
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
// 如果請求頭中有 close 那麼斷開連線
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;
}
複製程式碼
首先我們要明確Source和Sink是什麼: 在JAVA IO中有輸入流和輸出流,在OKio的IO體系中,Sourc就表示輸入流,Sink表示輸出流。OKio的IO體系我們將在新的一章中進行講解,本章不進行展開。
- Source: 輸入流,這是一個介面類,其子類有:BufferedSource、PeekSource、ForwardingSource、GzipSource、InputStreamSource、InflaterSource
- Sink: 輸出流 ,介面類,其子類有:BufferedSink、ForwardingSink、GzipSink、OutputStreamSink、DeflaterSink、BlackholeSink
在本流程中,主幹邏輯有以下4步:
- httpCodec.writeRequestHeaders(request)
- httpCodec.createRequestBody(request, contentLength)
- httpCodec.readResponseHeaders(true)
- httpCodec.openResponseBody(response)
以上4步分別完成了請求頭的寫入,請求體的寫入,響應頭的讀取,響應體的讀取。
2.2流的讀取
當時看這裡的時候,有個疑惑點,網路請求的資料及輸入流中的資料在哪裡,其實在第4步中獲取到響應的body資訊,只是獲取一個流物件,只有在應用程式碼中呼叫流物件的讀方法或者response.body().string()方法等,才會從socket的輸入流中讀取資訊到應用的記憶體中使用。下面我們來分析一下流的讀取過程。
response.body().string()
複製程式碼
ResponseBody:
public final String string() throws IOException {
BufferedSource source = source();
try {
Charset charset = Util.bomAwareCharset(source, charset());
return source.readString(charset);
} finally {
Util.closeQuietly(source);
}
}
複製程式碼
從原始碼可以看到source.readString(charset);將流轉換為String進行輸出。下面我們分兩步來討論:
- source的來源
- source.readString()之後的過程
2.2.1 source的來源
source來自於方法source(),ResponseBody中source()是個抽象方法:
public abstract BufferedSource source();
複製程式碼
其實現類為RealResponseBody:
@Override public BufferedSource source() {
return source;
}
複製程式碼
方法中的source為構造方法中傳遞過來的,建立RealResponseBody的是CallServerInterceptor原始碼中的httpCodec.openResponseBody(response),看一下httpCodec.openResponseBody()的原始碼:
@Override public ResponseBody openResponseBody(Response response) throws IOException {
......
if (!HttpHeaders.hasBody(response)) {
Source source = newFixedLengthSource(0);
return new RealResponseBody(contentType, 0, Okio.buffer(source));
}
......
}
複製程式碼
檢視newFixedLengthSource(0)方法:
public Source newFixedLengthSource(long length) throws IOException {
if (state != STATE_OPEN_RESPONSE_BODY) throw new IllegalStateException("state: " + state);
state = STATE_READING_RESPONSE_BODY;
return new FixedLengthSource(length);
}
複製程式碼
檢視FixedLengthSource()類的read方法:
@Override public long read(Buffer sink, long byteCount) throws IOException {
if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
if (closed) throw new IllegalStateException("closed");
if (bytesRemaining == 0) return -1;
long read = super.read(sink, Math.min(bytesRemaining, byteCount));
if (read == -1) {
ProtocolException e = new ProtocolException("unexpected end of stream");
endOfInput(false, e); // The server did not supply the promised content length.
throw e;
}
bytesRemaining -= read;
if (bytesRemaining == 0) {
endOfInput(true, null);
}
return read;
}
複製程式碼
read方法是將source資料寫入Buffer中,我們來看source的來源,繼續檢視父類AbstractSource中的super.read():
@Override public long read(Buffer sink, long byteCount) throws IOException {
try {
long read = source.read(sink, byteCount);
if (read > 0) {
bytesRead += read;
}
return read;
} catch (IOException e) {
endOfInput(false, e);
throw e;
}
}
複製程式碼
查詢source.read(sink, byteCount)中的source的來源,這個source是Http1Codec構造方法傳遞進行的:
public Http1Codec(OkHttpClient client, StreamAllocation streamAllocation, BufferedSource source,
BufferedSink sink) {
this.client = client;
this.streamAllocation = streamAllocation;
this.source = source;
this.sink = sink;
}
複製程式碼
經過上溯程式碼,我們發現建立Http1Codec是在ConnectInterceptor中開始的,流程如下:
@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, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
複製程式碼
streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
public HttpCodec newStream(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
....
try {
//建立連線connection,根據socket建立輸入流sink和輸出流source
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
//建立HttpCodec,有Http1Codec和Http2Codec兩個子類
HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
...
}
}
複製程式碼
其中findHealthyConnection建立了source,resultConnection.newCodec建立了Http1Codec:
-
findHealthyConnection-->findConnection-->result.connect(RealConnection)-->connectSocket()--> source = Okio.buffer(Okio.source(rawSocket))
-
newCodec
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);
}
}
複製程式碼
小結:
okhttp在ConnectInterceptor連線攔截器中,根據socket建立了網路設施connection,輸出流sink和輸入流source。並在CallServerInterceptor請求伺服器攔截器中完成了請求的傳送和響應頭的讀取,此時並沒有讀取響應體即我們關心的網路請求結果。響應體的讀取是使用者拿到response後,使用response.body().string()從socket中讀取的。
2.2.2 source.readString()之後的過程:
source我們找到了,是RealBufferedSource類包含的引用了socket的source,那麼我們在RealBufferedSource()中檢視方法readString():
override fun readString(charset: Charset): String {
buffer.writeAll(source)
return buffer.readString(charset)
}
複製程式碼
流的讀取分為兩步:
- 將soucre寫入快取buffer
- 從快取中把資料讀取出來,返回。
本章在追原始碼,寫的有點亂,開發者在看的時候,還是需要去讀一下原始碼,自己跟蹤一下。下一章將學習一下OKio的IO體系,結合OKio將對okhttp有更深的理解。OKio原始碼解析