OKhttp源碼學習(八)—— CallServerInterceptor

源碼地址:https://github.com/square/okhttp

不知不覺已經來到了最後一個攔截器,前面做了各種處理,也建立了連接。接下來的CallServerInterceptor,應該就是對數據進行交換、讀取以及構建結果的類了。

不過其實在這個攔截器之前,還可以自定義networkInterceptors。
在Okhttp的攔截器鏈條裏面有兩個地方可以自定義攔截:

  1. 最開始的時候(Interceptors):對發出去的請求做最初的處理,以及在拿到最後Reponse時候做最後的處理;

  2. 最後數據交換前(networkInterceptors):對發出去的請求做最後的處理,以及在拿到結果時候做最初的處理。

我們可以自定義攔截器,去處理我們需要做的事情。

下面言歸正傳:

intercept

和其他攔截器一樣,最重要的方法就是這個intercept方法,攔截方法。

這個攔截方法主要步驟:

  1. 獲取幾個前面已經創建的重要類(httpCodec, streamAllocation, request);

  2. 先向sink(OutputStream)中寫頭信息(sink, 在創建連接時候已經創建好);

  3. 判斷是否有請求體,如有,走4,5的操作,沒有直接到6;

  4. 如果頭部添加了"100-continue", 相對於一次見到的握手操作,只有拿到服務的結果再繼續;

  5. 當“100-continue”成功或者不需要這個簡單握手的,寫入請求實體;

  6. finishRequest( 實際是調用了 sink.flush(), 來刷數據 )

  7. 讀取頭部信息(通過source(InputStream), 讀取頭部信息,狀態碼等)

  8. 構建Response, 寫入原請求,握手情況,請求時間,得到的結果時間

  9. 通過Response 狀態碼判斷以及是否webSocket判斷,是否返回一個空的body, 或者讀取Body信息(通過 source(InputStream) 讀取)

  10. 讀取到請求時的連接close ,或者服務器返回的 close, 進行斷開操作;

  11. 對於204,205的特殊狀態碼進行處理。

** 有兩個比較重要的類,就是sink以及source, 這個兩個是Okio裏面的一個重要概念,相當於我們熟悉的 OutputStream 以及 InputStream輸入輸出流。** 這裏就不展開,有興趣的可以瞭解一下 Okio這個 I/O 庫。這兩個類都是在創建連接的時候就已經建立,而在個攔截器裏面就是對數據進行交換。

下面是源碼閱讀:

  @Override public Response intercept(Chain chain) throws IOException {

    // 1.獲取幾個前面攔截方法創建的,重要類
    HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStream();
    StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
    Request request = chain.request();
    long sentRequestMillis = System.currentTimeMillis();

    //2. 先向sink(OutputStream)中寫頭信息
    httpCodec.writeRequestHeaders(request);

    Response.Builder responseBuilder = null;

   // 3.判斷是否有請求實體的請求,用method判斷
    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.
   
      //4. 如果頭部添加了"100-continue", 相對於一次見到的握手操作,只有拿到服務的結果再繼續
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        httpCodec.flushRequest();
        responseBuilder = httpCodec.readResponseHeaders(true);
      }

      //5. 當前面的"100-continue",需要握手,但又握手失敗,這個時候responseBuilder不是空的
      // Write the request body, unless an "Expect: 100-continue" expectation failed.
      if (responseBuilder == null) {
        Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);

        // 回調RequestBody的writeTo,寫相應的數據
        request.body().writeTo(bufferedRequestBody);
        bufferedRequestBody.close();
      }
    }

    //6. 這裏也是調用了一次 sink.flush()
    httpCodec.finishRequest();

    //7. 讀取頭部信息,狀態碼,信息等
    if (responseBuilder == null) {
      responseBuilder = httpCodec.readResponseHeaders(false);
    }

    //8. 構建Response, 寫入原請求,握手情況,請求時間,得到的結果時間
    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    int code = response.code();
    //9. 通過狀態碼判斷以及是否webSocket判斷,是否返回一個空的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信息
      response = response.newBuilder()
          .body(httpCodec.openResponseBody(response))
          .build();
    }

    //10 .如果設置了連接 close ,斷開連接
    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      streamAllocation.noNewStreams();
    }

    //11. HTTP 204(no content) 代表響應報文中包含若干首部和一個狀態行,但是沒有實體的主體內容。
    //HTTP 205(reset content) 表示響應執行成功,重置頁面(Form表單),方便用戶下次輸入
    //這裏做了同樣的處理,就是拋出協議異常。
    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. HttpCodec是一個接口,其實有兩個實現類,分別是HttpCodec1、HttpCodec2, 對應着http1 的讀寫數據和 http2的 讀寫數據。兩者是有差異的;

  2. 還有前面提到的Okio,可以去了解一下。

系列(簡書地址):
OKhttp源碼學習(一)—— 基本請求流程
OKhttp源碼學習(二)—— OkHttpClient
OKhttp源碼學習(三)—— Request, RealCall
OKhttp源碼學習(四)—— RetryAndFollowUpInterceptor攔截器
OKhttp源碼學習(五)—— BridgeInterceptor攔截器
OKhttp源碼學習(六)—— CacheInterceptor攔截器
OKhttp源碼學習(七)—— ConnectInterceptor攔截器
OKhttp源碼學習(九)—— 任務管理(Dispatcher)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章