4、深刻理解OkHttp:CallServerIntercepter

1、前言

通過前面幾章的準備工做,咱們終於能夠和服務器進行正式的交流了。而與服務器進行正式的數據通訊就發生在最後一個攔截器:ServerIntercepter.java。在解析這個類以前,須要先看一下其餘的類:ExchangeCodec,這個類在上篇文章中就有涉及到,不過沒有仔細講下去。在這章中會講一下。java

2、ExchangeCodec

【2.1】簡介

ExchageCodec是一個接口,他是來規範網絡請求的編碼行爲和網絡回覆的解碼行爲。他有2個子類 Http1ExchangeCodec 和 Http2ExchangeCodec。從名字上一看就知道,他們分別對應了Http1協議和Http2協議。下面是ExchageCodec主要的接口規範:緩存

public interface ExchangeCodec {
  ...
  
  /** 將請求體轉化爲輸出流*/
  Sink createRequestBody(Request request, long contentLength) throws IOException;

  /** 寫請求頭*/
  void writeRequestHeaders(Request request) throws IOException;

  /** 將在緩存區的請求刷新到輸出流 */
  void flushRequest() throws IOException;

  /** 通知已經完成請求動做 */
  void finishRequest() throws IOException;

    
 /** 讀取響應體 */
  Source openResponseBodySource(Response response) throws IOException;

  /** 讀取響應頭 */
  @Nullable Response.Builder readResponseHeaders(boolean expectContinue) throws IOException;
  
  ...

  /** 取消請求 */
  void cancel();
 
}
  
複製代碼

總結: 總的來講,ExchageCodec.java規範了網絡交互過程當中的寫請求和讀響應的動做。具體的以下:bash

  1. 將請求體轉化爲輸出流。
  2. 寫請求頭。
  3. 將在請求刷新到底層的Socket。
  4. 通知完成請求動做。
  5. 讀響應頭。
  6. 讀響應體。
  7. 取消請求。

3、Http1ExchangeCodec

【3.1】createRequestBody()

Http1ExchageCodec.java
@Override public Sink createRequestBody(Request request, long contentLength) throws IOException {
    if (request.body() != null && request.body().isDuplex()) {
      throw new ProtocolException("Duplex connections are not supported for HTTP/1");
    }

    //建立一個不知長度的輸出流。
    if ("chunked".equalsIgnoreCase(request.header("Transfer-Encoding"))) {
      return newChunkedSink();
    }

    //建立一個知道長度的輸出流。
    if (contentLength != -1L) {
      return newKnownLengthSink();
    }

    throw new IllegalStateException(
        "Cannot stream a request body without chunked encoding or a known content length!");
  }
複製代碼

總結: 該方法總的來講就是根據請求的長度的肯定性生成響應的流類型服務器

【3.2】 writeRequestHeaders():寫入請求頭

@Override public void writeRequestHeaders(Request request) throws IOException {
    String requestLine = RequestLine.get(
        request, realConnection.route().proxy().type());
    writeRequest(request.headers(), requestLine);
  }
  
  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;
  }
複製代碼

【3.3】flushRequest()/finishRequest()

@Override public void flushRequest() throws IOException {
    sink.flush();
  }
  
  
@Override public void finishRequest() throws IOException {
    sink.flush();
  }
複製代碼

總結: 他們調用的都是flush方法。因此作的都是同一件是,把緩存區的數據刷新到底層Socket。網絡

【3.4】openResponseBodySource():讀取響應體

@Override public Source openResponseBodySource(Response response) {
    //1. 若是沒有響應體,那麼構建一個讀取長度爲0的輸入流
    if (!HttpHeaders.hasBody(response)) {
      return newFixedLengthSource(0);
    }

    //2. 不肯定長度的輸入流
    if ("chunked".equalsIgnoreCase(response.header("Transfer-Encoding"))) {
      return newChunkedSource(response.request().url());
    }

    //3. 肯定長度的輸入流
    long contentLength = HttpHeaders.contentLength(response);
    if (contentLength != -1) {
      return newFixedLengthSource(contentLength);
    }

    return newUnknownLengthSource();
  }
複製代碼

【3.5】readResponseHeaders():讀取響應頭

@Override public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException {
    ...

    try {
    //解析響應頭的String。
      StatusLine statusLine = StatusLine.parse(readHeaderLine());

      //構建響應體
      Response.Builder responseBuilder = new Response.Builder()
          .protocol(statusLine.protocol)
          .code(statusLine.code)
          .message(statusLine.message)
          .headers(readHeaders());

      if (expectContinue && statusLine.code == HTTP_CONTINUE) {
        return null;
      } else if (statusLine.code == HTTP_CONTINUE) {
        state = STATE_READ_RESPONSE_HEADERS;
        return responseBuilder;
      }

      state = STATE_OPEN_RESPONSE_BODY;
      return responseBuilder;
    } catch (EOFException e) {
      ...
    }
  }
  
  ///讀取響應頭輸入流
 private String readHeaderLine() throws IOException {
    String line = source.readUtf8LineStrict(headerLimit);
    headerLimit -= line.length();
    return line;
  }
複製代碼

3、CallServerIntercepter.java: 最後一個攔截器

@Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Exchange exchange = realChain.exchange();
    Request request = realChain.request();
    long sentRequestMillis = System.currentTimeMillis();

    //1. 寫入請求頭
    exchange.writeRequestHeaders(request);

    boolean responseHeadersStarted = false;
    Response.Builder responseBuilder = null;
    
    //2. 是否爲有請求體的請求
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      //3. 若請求頭裏有"100-continue",表明先只有請求頭的向服務器請求。
      // 須要等待服務器的響應頭再進一步請求。
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        exchange.flushRequest();
        responseHeadersStarted = true;
        exchange.responseHeadersStart();
        responseBuilder = exchange.readResponseHeaders(true);
      }
      
      //4. 能夠繼續發出請求數據
      if (responseBuilder == null) {
        //5. 將請求體寫入socket
        if (request.body().isDuplex()) {
          exchange.flushRequest();
          BufferedSink bufferedRequestBody = Okio.buffer(
              exchange.createRequestBody(request, true));
          request.body().writeTo(bufferedRequestBody);
        } else {
          BufferedSink bufferedRequestBody = Okio.buffer(
              exchange.createRequestBody(request, false));
          request.body().writeTo(bufferedRequestBody);
          bufferedRequestBody.close();
        }
      } else {
        exchange.noRequestBody();
        if (!exchange.connection().isMultiplexed()) {
          
          exchange.noNewExchangesOnConnection();
        }
      }
    } else {
      exchange.noRequestBody();
    }

    //6. 通知結束請求。
    if (request.body() == null || !request.body().isDuplex()) {
      exchange.finishRequest();
    }

    if (!responseHeadersStarted) {
      exchange.responseHeadersStart();
    }

    //7. 讀取響應頭
    if (responseBuilder == null) {
      responseBuilder = exchange.readResponseHeaders(false);
    }

    //8. 構建響應體
    Response response = responseBuilder
        .request(request)
        .handshake(exchange.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    //9. 若是響應碼=100,須要再請求一次。
    int code = response.code();
    if (code == 100) {
      response = exchange.readResponseHeaders(false)
          .request(request)
          .handshake(exchange.connection().handshake())
          .sentRequestAtMillis(sentRequestMillis)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();

      code = response.code();
    }

    exchange.responseHeadersEnd(response);

    if (forWebSocket && code == 101) {
      //空鏈接
      response = response.newBuilder()
          .body(Util.EMPTY_RESPONSE)
          .build();
    } else {
      //10.讀取響應體詳細
      response = response.newBuilder()
          .body(exchange.openResponseBody(response))
          .build();
    }

    //11. 若是有close頭,那麼關閉鏈接。
    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      exchange.noNewExchangesOnConnection();
    }
    ...
    
    //12. 返回請求
    return response;
  }
複製代碼

總結: CallServerIntercepter的攔截邏輯很簡單,總的來講就是將請求頭,請求體寫入Socket,而後讀取Socket的響應頭和響應體。而具體的IO操做,OkHttp是採用的okio,這是個優秀的IO庫,具體的邏輯這裏就不深挖了。具體的流程以下:架構

  1. 寫入請求頭。
  2. 若是請求頭裏有"100-continue", 表明先將請求頭髮送給服務器,看服務器的響應決定是否進行下一步請求體的發送。
  3. 寫入請求體,併發送請求。
  4. 讀取響應體,並構建一個Resonse
  5. 若是響應碼爲100,須要再請求一次。
  6. 讀取詳細的響應體。
  7. 若是響應頭有「close」,那麼關閉這條鏈接。
  8. 返回響應。

OkHttp的幾個重要部分講解就到這裏所有結束了。回顧一下,咱們從網絡的同步/異步請求,降到它的攔截鏈模式。而後着重講了幾個重要的攔截器:cacheIntercepter、ConnectInterpcet和CallServerIntercepter。這幾篇文章是本人在自學中,總結記錄。有不對的地方歡迎指出。最後,放上一張整體架構圖,有助於總體理解:併發

( 圖片來源感謝:yq.aliyun.com/articles/78…)

最後,在這裏須要鳴謝如下博文:
www.jianshu.com/p/82f74db14…
www.jianshu.com/p/7624b45fb…
www.jianshu.com/p/227cee9c8…
本文引用的圖片若有涉權,請聯繫本人刪除,謝謝!異步

相關文章
相關標籤/搜索