Okhttp之CallServerInterceptor攔截器原理及解析
在開始之前拓展一個http的知識:
1、關鍵字100-continue介紹
http 100-continue用於客戶端在傳送POST資料給伺服器前,徵詢伺服器情況,看伺服器是否處理POST的資料,如果不處理,客戶端則不上傳POST資料,如果處理,則POST上傳資料。在現實應用中,通過在POST大資料時,才會使用100-continue協議。
2、客戶端策略
- 如果客戶端有POST資料要上傳,可以考慮使用100-continue協議。加入頭{"Expect":"100-continue"}
- 如果沒有POST資料,不能使用100-continue協議,因為這會讓服務端造成誤解。
- 並不是所有的Server都會正確實現100-continue協議,如果Client傳送Expect:100-continue訊息後,在timeout時間內無響應,Client需要立馬上傳POST資料。
- 有些Server會錯誤實現100-continue協議,在不需要此協議時返回100,此時客戶端應該忽略。
3、服務端策略
- 正確情況下,收到請求後,返回100或錯誤碼。
- 如果在傳送100-continue前收到了POST資料(客戶端提前傳送POST資料),則不傳送100響應碼(略去)。
回到CallServerInterceptor核心Intercept方法:
@Override public Response intercept(Chain chain) throws IOException { ... //封裝請求頭,即conent-length,method,編碼等等 realChain.eventListener().requestHeadersStart(realChain.call()); httpCodec.writeRequestHeaders(request); realChain.eventListener().requestHeadersEnd(realChain.call(), request); //當前request需要請求體,即post請求等方式,如果有,則進行封裝 Response.Builder responseBuilder = null; if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) { // 判斷伺服器是否允許傳送body if ("100-continue".equalsIgnoreCase(request.header("Expect"))) { httpCodec.flushRequest(); realChain.eventListener().responseHeadersStart(realChain.call()); responseBuilder = httpCodec.readResponseHeaders(true); } if (responseBuilder == null) { // 向伺服器傳送requestbody, realChain.eventListener().requestBodyStart(realChain.call()); long contentLength = request.body().contentLength(); CountingSink requestBodyOut = new CountingSink(httpCodec.createRequestBody(request, contentLength)); BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut); request.body().writeTo(bufferedRequestBody); bufferedRequestBody.close(); realChain.eventListener() .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount); .... } //結束請求 httpCodec.finishRequest(); if (responseBuilder == null) { realChain.eventListener().responseHeadersStart(realChain.call()); responseBuilder = httpCodec.readResponseHeaders(false); } //返回response Response response = responseBuilder .request(request) .handshake(streamAllocation.connection().handshake()) .sentRequestAtMillis(sentRequestMillis) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); //100的狀態碼的處理繼續傳送請求,繼續接受資料 int code = response.code(); if (code == 100) { responseBuilder = httpCodec.readResponseHeaders(false); response = responseBuilder .request(request) .handshake(streamAllocation.connection().handshake()) .sentRequestAtMillis(sentRequestMillis) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); code = response.code(); } ... //返回為空的處理 if ((code == 204 || code == 205) && response.body().contentLength() > 0) { throw new ProtocolException( "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength()); } return response; }
簡單概述一下流程:
1、獲取到 ConnectInterceptor 攔截器中的httpCodec和realChain,至於ConnectInterceptor中做了什麼,可以看之前寫的《Okhttp之ConnectInterceptor攔截器原理及解析》
2、寫入http請求頭資訊,判斷是否允許上傳requestBody
-
判斷header中Expect域是否為100-continue,這個請求頭欄位的作用是在傳送RequestBody前向伺服器確認是否接受RequestBody,如果沒有,則正常請求。如果有,則相當於一次簡單的握手操作,則等待伺服器返回的ResponseHeaders之後再繼續,如果伺服器接收RequestBody,會返回null。
-
根據3的判斷,如果RequestBuilder為null,說明Expect不為100-continue或者伺服器同意接收RequestBody。這時就開始向流中寫入RequestBody。
-
讀取響應頭資訊構建Response,寫入原請求、握手情況、請求時間、得到結果的時間等
-
針對204/205狀態碼處理
-
返回Response