OKHttp3-- 請求服務器攔截器CallServerInterceptor源碼分析 【十一】

系列

OKHttp3–詳細使用及源碼分析系列之初步介紹【一】
OKHttp3–流程分析 核心類介紹 同步異步請求源碼分析【二】
OKHttp3–Dispatcher分發器源碼解析【三】
OKHttp3–調用對象RealCall源碼解析【四】
OKHttp3–攔截器鏈RealInterceptorChain源碼解析【五】
OKHttp3–重試及重定向攔截器RetryAndFollowUpInterceptor源碼解析【六】
OKHttp3–橋接攔截器BridgeInterceptor源碼解析及相關http請求頭字段解析【七】
OKHttp3–緩存攔截器CacheInterceptor源碼解析【八】
OKHttp3-- HTTP緩存機制解析 緩存處理類Cache和緩存策略類CacheStrategy源碼分析 【九】
通過ConnectInterceptor源碼掌握OKHttp3網絡連接原理 嘔心瀝血第十彈【十】

前言

這篇文章將對OKHttp最後一個攔截器進行解析,總算快要結束了;上一篇文章講到連接攔截器,即客戶端已經與服務端進行了連接,那接下來的操作自然就是發送接收數據了,看看官網的註釋

這是攔截器鏈上的最後一個攔截器,向服務器發起網絡訪問

那接下來就從源碼看看它是如何實現發送請求數據,接收響應數據的

CallServerInterceptor

  @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;
    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.
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        httpCodec.flushRequest();
        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();
    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 {
      response = response.newBuilder()
          .body(httpCodec.openResponseBody(response))
          .build();
    }

    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;
  }

這裏代碼我們分開看

第一部分:獲取網絡組件

    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    HttpCodec httpCodec = realChain.httpStream();
    StreamAllocation streamAllocation = realChain.streamAllocation();
    RealConnection connection = (RealConnection) realChain.connection();
    Request request = realChain.request();

這裏通過攔截器鏈拿到了四個對象

  • HttpCodec : 封裝I/O操作的對象,根據HTTP/1.x,HTTP/2版本,有兩個對應的實現類Http1Codec、Http2Codec,可以簡單理解爲編碼我們的請求,解碼返回的響應
  • StreamAllocation:封裝了一次網絡操作需要的組件,爲一個請求分配一個stream
  • RealConnection: 封裝了一次Socket與服務器連接的對象,在OKHttp中將一次連接抽象爲Connection,而它是其實現類
  • Request:封裝用戶的請求信息的對象

第二部分:發送請求

    long sentRequestMillis = System.currentTimeMillis();
    httpCodec.writeRequestHeaders(request);

    Response.Builder responseBuilder = null;
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        httpCodec.flushRequest();
        responseBuilder = httpCodec.readResponseHeaders(true);
      }

      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()) {
        streamAllocation.noNewStreams();
      }
    }

    httpCodec.finishRequest();

這部分主要是用來發送請求

  • 獲取發送請求的時間戳,並將請求頭信息寫入
  • 檢測是否爲有body的請求方法,接下來有兩個判斷,分開講述

第一個if判斷的意思是:如果請求頭中包含【Expect: 100- Continue】,那麼就發送100-continue的請求,然後獲取服務器的響應,看服務器是否同意接受請求體,如果服務器的響應碼是HTTP_CONTINUE,即100,那這個responseBuilder 即爲null

第二個判斷:上面一個判斷可以知道如果服務器同意接受請求體數據,那麼responseBuilder 爲null,接下來就構建輸出流BufferedSink對象,將請求體數據寫到其緩衝區

第三個判斷:也就是responseBuilder 不爲null,意思是服務器不同意接受該請求體,且同時這個連接不支持多路複用,也就是說這是一個Http/1.x的請求,那麼我們就需要標記該連接不能再被複用,同時關閉相關的Socket

接下來就是將緩存區的數據寫到Socket中,發送出去,到這裏發送請求已經完成

第三部分:構建響應

    if (responseBuilder == null) {
      responseBuilder = httpCodec.readResponseHeaders(false);
    }

    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

首先獲取響應頭部信息,然後構建響應Response 對象

第四部分:處理響應

    int code = response.code();
    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 {
      response = response.newBuilder()
          .body(httpCodec.openResponseBody(response))
          .build();
    }

    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;
  • 如果使用websocket且響應碼是101,那麼構建一個空響應體的response 對象;否則就根據響應體構建真正的響應對象
  • 如果請求頭或者響應頭部包含【Connection: close】,意味着該連接不需要保活,那就關閉該連接以及釋放相應的資源
  • 如果響應碼是204或者205,且響應體長度大於0,也就意味着該次請求成功,但是服務器只返回了響應行和響應頭信息,沒有響應體,可是這裏拿到的響應體長度居然大於0,那就說明出問題了,所以需要拋出一個協議異常

總結

可以看到最後一個攔截器的代碼比較簡單,當然了你要是沒有看前面的系列文章,可能會覺得有點懵;攔截器鏈上的五個攔截器分析完了,後續的文章將會介紹下OKHttp中有關WebSocket的知識點

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