CallServerInterceptor的主要功能就是—向伺服器傳送請求,並最終返回Response物件供客戶端使用。
上原始碼:
public Response intercept(Chain chain) throws IOException {
// 省略部分程式碼
// 獲取HttpCodec
HttpCodec httpCodec = realChain.httpStream();
// 省略部分程式碼
Request request = realChain.request();
//向伺服器傳送請求
httpCodec.writeRequestHeaders(request);
Response.Builder responseBuilder = null;
// 檢測是否有請求body
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();
//構建responseBuilder物件
responseBuilder = httpCodec.readResponseHeaders(true);
}
//如果伺服器允許傳送請求body傳送
if (responseBuilder == null) {
Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
} else if (!connection.isMultiplexed()) {
//省略部分程式碼
}
}
//結束請求
httpCodec.finishRequest();
//構建請求buidder物件
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) {
//省略部分程式碼
} else {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
//省略部分程式碼
return response;
}
複製程式碼
該方法首先是獲取了httpCodec物件,該物件的主要功能就是對不同http協議(http1.1和http/2)的請求和響應做處理,該物件的初始化是在ConnectIntercepor的intercept裡面:
HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);複製程式碼
最終httpCodec的初始化又是在StreamAllocation的newStream方法(詳見《 原始碼分析三:OkHttp—ConnectInterceptor》):
ublic HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
//:省略部分程式碼;
HttpCodec resultCodec = resultConnection.newCodec(client, this);
}
public HttpCodec newCodec(
OkHttpClient client, StreamAllocation streamAllocation) throws SocketException {
if (http2Connection != null) {
return new Http2Codec(client, streamAllocation, http2Connection);
} else {
//設定socket的讀超時時間
socket.setSoTimeout(client.readTimeoutMillis());
//InputStream的超時時間
source.timeout().timeout(client.readTimeoutMillis(), MILLISECONDS);
//OutputStream的超時時間
sink.timeout().timeout(client.writeTimeoutMillis(), MILLISECONDS);
return new Http1Codec(client, streamAllocation, source, sink);
}
}
複製程式碼
可以發現Okhttp的提供了兩種HttpCodec的實現類,如果使用了http2協議則返回Http2Codec,否則返回Http1Codec!並且設定了超時時間,本篇就以Http1Codec物件來進行分析。
我們知道Http傳送網路請求前兩個步驟是:
1、建立TCP連結
2、客戶端向web伺服器傳送請求命令:形如GET /login/login.jsp?username=android&password=123 HTTP/1.1的資訊
在Okhttp中ConnectInterceptor負責第一個步驟,那麼第二個步驟是如何實現的呢?答案就是httpCodec物件的writeRequestHeaders方法。(該方法在CallserverInterceptor的intercept裡面呼叫,見上面程式碼)
public void writeRequestHeaders(Request request) throws IOException {
//RequestLine.get用來構建形如GET xx HTTP/1.1的字串
String requestLine = RequestLine.get(
request, streamAllocation.connection().route().proxy().type());
//像伺服器傳送請求,形如GET xxx HTTP/1.1
writeRequest(request.headers(), requestLine);
}
複製程式碼
可以發現Okhttp通過OkIO的Sink物件(該物件可以看做Socket的OutputStream物件)的writeRequest來向伺服器傳送請求的。
public void writeRequest(Headers headers, String requestLine) throws IOException {
if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);
sink.writeUtf8(requestLine).writeUtf8("\r\n");
for (int i = 0, size = headers.size(); i < size; i++) {
sink.writeUtf8(headers.name(i))
.writeUtf8(": ")
.writeUtf8(headers.value(i))
.writeUtf8("\r\n");
}
sink.writeUtf8("\r\n");
state = STATE_OPEN_REQUEST_BODY;
}123456789101112複製程式碼
我們知道HTTP支援post,delete,get,put等方法,而post,put等方法是需要請求體的(在Okhttp中用RequestBody來表示)。所以接著writeRequestHeaders之後Okhttp對請求體也做了響應的處理:
//如果當前request請求需要請求體
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
//詢問Server使用願意接受資料
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();
//構建responseBuilder物件
responseBuilder = httpCodec.readResponseHeaders(true);
}
//向伺服器傳送請求體
if (responseBuilder == null) {
//傳送請求體,詳見下文描述
} else if (!connection.isMultiplexed()) {
//省略部分程式碼
}
}123456789101112131415161718複製程式碼
通過上面的程式碼可以發現Okhttp對Expect頭部也做了支援,上面程式碼對客戶端是否使用該頭部做了判斷,“100 continue”的作用就是:客戶端有一個RequestBody(比如post或者PUT方法)要發給伺服器,但是客戶端希望在傳送RequestBody之前檢視伺服器是否接受這個body,服務端在接受到這個請求後必須進行響應。客戶端通過Expect首部來傳送這個訊息,當然如果客戶端沒有實體傳送,就不應該傳送100 continue 首部,因為這樣會使伺服器誤以為客戶端有body要傳送。所以okhttp在傳送這個之前要permitsRequestBody來判斷。當然常規的get請求是不會走這個方法的。
如果伺服器允許傳送ReqeustBody,那麼就通過下面這三行程式碼來傳送請求體:
//構建請求體物件組成的輸入流
Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
//傳送請求體
request.body().writeTo(bufferedRequestBody);12345複製程式碼
最終呼叫ReqeustBody的writeTo方法來傳送請求體,實際上是呼叫bufferedRequestBody物件的write方法,簡單例項如下(當然實際可能是FormBody或者是自定義的ReqeustBody):
ublic static RequestBody create(
final @Nullable MediaType contentType, final ByteString content) {
return new RequestBody() {
//省略部分程式碼
@Override public void writeTo(BufferedSink sink) throws IOException {
sink.write(content);
}
};
}123456789複製程式碼
到現在為止,客戶端向服務端傳送請求的部分已經講解完畢,下面就剩下讀取伺服器響應然後構建Response物件了:
//構建請求buider物件
if (responseBuilder == null) {
responseBuilder = httpCodec.readResponseHeaders(false);
}
//構建response物件
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
if (forWebSocket && code == 101) {
//返回空的即無效的響應
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
12345678910111213141516171819202122232425複製程式碼
上面的程式碼做了三個工作:
1、呼叫HttpCodec的readResponseHeaders方法讀取伺服器響應的資料,構建Response.Builder物件(以Hppt1Codec分析):
public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException {
//省略部分程式碼
//讀取伺服器
StatusLine statusLine = StatusLine.parse(source.readUtf8LineStrict());
Response.Builder responseBuilder = new Response.Builder()
.protocol(statusLine.protocol)//http協議版本
.code(statusLine.code)//http響應狀態碼
//http的message :like "OK" or "Not Modified"
.message(statusLine.message)
.headers(readHeaders());//http響應header
//省略部分程式碼
return responseBuilder;
}12345678910111213141516複製程式碼
2、通過ResopnseBuilder物件來最終建立Response物件,並返回。
最關鍵的是伺服器的響應體或者響應內容是如果傳給Response的,程式碼如下:
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();123複製程式碼
Response的body通過httpCodec物件的openResponseBody傳進來,進入Http1Codec物件的openResponseBody方法看看都做了些神馬:
public ResponseBody openResponseBody(Response response) throws IOException {
Source source = getTransferStream(response);
return new RealResponseBody(response.headers(), Okio.buffer(source));
}1234複製程式碼
很簡單,openResponseBody將Socket的輸入流InputStream物件交給OkIo的Source物件(在本篇博文中只需簡單的將Sink作為Socket的輸入流,Source作為Socket的輸入流看待即可,詳細的分析可參考OKIO),然後封裝成RealResponseBody(該類是ResponseBody的子類)作為Response的body.
那麼我們怎麼通過這個body來獲取伺服器傳送過來的字串呢?ResponseBody提供了string()方法:
public final String string() throws IOException {
BufferedSource source = source();
try {
Charset charset = Util.bomAwareCharset(source, charset());
//InputStream 讀取資料
return source.readString(charset);
} finally {
Util.closeQuietly(source);
}
}12345678910複製程式碼
string()方法也很簡單,就是通過一些處理然後讓呼叫source.readString來讀取伺服器的資料。需要注意的是該方法最後呼叫closeQuietly來關閉了當前請求的InputStream輸入流,所以string()方法只能呼叫一次,再次呼叫的話會報錯,畢竟輸入流已經關閉了,你還怎麼讀取資料呢?
到此為止CallServerInterceptor簡單分析完畢,總結下主要做了如下工作:
1、獲取HttpCodec物件,對<=Http1.1之前的或者http/2不同協議的http請求處理。
2、傳送http請求資料,構建Resposne.Builder物件,然後構建Response並返回。
到此為止Okhttp從發起請求到響應請求生成Response物件的流程已經分析完畢。