OKHttp3 源碼解析之攔截器(二)

在上一篇文章中,主要梳理了 OKHttp 請求的整體流程,了解到攔截器在其中有非常重要的意義,那么本篇文章就重點介紹一下 OKHttp 中的精髓 ---- 攔截器。本文中涉及到的自定義類的源碼都在 Github 上的 OkHttpPractice 工程中。

  1. 攔截器的使用
  2. 源碼中攔截器的應用及分析

1. 攔截器的使用

攔截器是 OKHttp 設計的精髓所在,每個攔截器負責不同的功能,使用責任鏈模式,通過鏈式調用執行所有的攔截器對象中的 Response intercept(Chain chain) 方法。攔截器在某種程度上也借鑒了網絡協議中的分層思想,請求時從最上層到最下層,響應時從最下層到最上層。

一個攔截器可以攔截請求和響應,獲取或修改其中的信息,這在編程中是非常有用的。不僅在源碼中攔截器使用的很廣泛,開發者也可以根據自己的需求自定義攔截器,并將其加入到 OkHttpClient 對象中。

1.1 自定義攔截器

攔截器的源碼如下:

/**
 * Observes, modifies, and potentially short-circuits requests going out and the corresponding
 * responses coming back in. Typically interceptors add, remove, or transform headers on the request
 * or response.
 */
public interface Interceptor {
  Response intercept(Chain chain) throws IOException;

  interface Chain {
    Request request();

    Response proceed(Request request) throws IOException;

    /**
     * Returns the connection the request will be executed on. This is only available in the chains
     * of network interceptors; for application interceptors this is always null.
     */
    @Nullable Connection connection();
  }
}

其中最重要的方法便是 Response intercept(Chain chain) 方法。自定義攔截器只要實現 interceptor 接口,重寫其中的 Response intercept(Chain chain) 方法便基本完成。

在開發中,經常想查看服務器響應中的內容,如果使用攔截器的話,則可以非常方便的實現,只要自定義攔截器并加入到 OKHttpClient 對象中,那么使用此 OKHttpClient 對象進行的網絡請求都會將其響應信息打印出來。自定義的攔截器 LogInterceptor,源碼如下所示:

public class LogInterceptor implements Interceptor {

    private static final Charset UTF8 = Charset.forName("UTF-8");

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response response;
        long startTime = System.currentTimeMillis();
        response = chain.proceed(request);
        long endTime = System.currentTimeMillis();
        long duration = endTime - startTime;
        BufferedSource source = response.body().source();
        source.request(Long.MAX_VALUE);
        Buffer buffer = source.buffer();
        String log = "\n================="
                .concat("\nnetwork code ==== " + response.code())
                .concat("\nnetwork url ===== " + request.url())
                .concat("\nduration ======== " + duration)
                .concat("\nrequest duration ============ " + (response.receivedResponseAtMillis() - response.sentRequestAtMillis()))
                .concat("\nrequest header == " + request.headers())
                .concat("\nrequest ========= " + bodyToString(request.body()))
                .concat("\nbody ============ " + buffer.clone().readString(UTF8));
        Log.i("lijk", "log is " + log);
        return response;
    }

    /**
     * 請求體轉String
     *
     * @param request 請求體
     * @return String 類型的請求體
     */
    private static String bodyToString(final RequestBody request) {
        try {
            final Buffer buffer = new Buffer();
            request.writeTo(buffer);
            return buffer.readUtf8();
        } catch (final Exception e) {
            return "did not work";
        }
    }
}

1.2 注意事項

這里有個坑需要注意一下:response.body().string(); 方法只能被調用一次,如果多次調用 response.body().string(); 則會拋出如下異常:

exception.png

可以看到 string() 的源碼如下所示:

  /**
   * Returns the response as a string decoded with the charset of the Content-Type header. If that
   * header is either absent or lacks a charset, this will attempt to decode the response body in
   * accordance to <a >its BOM</a> or UTF-8.
   * Closes {@link ResponseBody} automatically.
   *
   * <p>This method loads entire response body into memory. If the response body is very large this
   * may trigger an {@link OutOfMemoryError}. Prefer to stream the response body if this is a
   * possibility for your response.
   */
  public final String string() throws IOException {
    BufferedSource source = source();
    try {
      Charset charset = Util.bomAwareCharset(source, charset());
      return source.readString(charset);
    } finally {
      Util.closeQuietly(source);
    }
  }

因為在執行完讀取數據之后,IO 流被關閉,如果再次調用此方法,就會拋出上面的異常。

而且從注釋中可以看到,此方法將響應報文中的主體全部都讀到了內存中,如果響應報文主體較大,可能會導致 OOM 異常。所以更推薦使用流的方式獲取響應體的內容。如下所示:

  BufferedSource source = response.body().source();
  source.request(Long.MAX_VALUE);
  Buffer buffer = source.buffer();
  String response = buffer.clone().readString(UTF8);

2. 源碼中攔截器的應用及分析

在上一篇分析整體流程的文章中,在 RealCall 中有一個方法非常重要,不論是異步請求還是同步請求,都是通過該方法獲取服務器響應的,源碼如下所示:

Response getResponseWithInterceptorChain() throws IOException {
  // Build a full stack of interceptors.
  List<Interceptor> interceptors = new ArrayList<>();
  // 用戶自定義攔截器
  interceptors.addAll(client.interceptors());
  // 重試重定向攔截器
  interceptors.add(retryAndFollowUpInterceptor);
  // 橋接攔截器
  interceptors.add(new BridgeInterceptor(client.cookieJar()));
  // 緩存攔截器
  interceptors.add(new CacheInterceptor(client.internalCache()));
  // 連接服務器攔截器
  interceptors.add(new ConnectInterceptor(client));
  if (!forWebSocket) {
    // 用戶定義的網絡攔截器
    interceptors.addAll(client.networkInterceptors());
  }
  // 請求服務器攔截器
  interceptors.add(new CallServerInterceptor(forWebSocket));

  Interceptor.Chain chain = new RealInterceptorChain(
      interceptors, null, null, null, 0, originalRequest);
  // 通過 RealInterceptorChain 對象鏈式的調用攔截器,從而得到響應。
  return chain.proceed(originalRequest);
}

其中的每個攔截器負責具體不同的功能,接下來就分析每個攔截器的功能,因為攔截器中最重要的方法便是 Response intercept(Chain chain),所以我們也重點分析 Response intercept(Chain chain) 方法的實現。

2.1 RetryAndFollowUpInterceptor

RetryAndFollowUpInterceptor 攔截器主要負責實現 HTTP 協議中的認證質詢、重定向和超時重試等協議機制。

RetryAndFollowUpInterceptor 攔截器的主要功能如下所示:

  1. 初始化連接對象 StreamAllocation
  2. 通過 RealInterceptorChain 調用鏈對象得到響應
  3. 通過得到的響應,根據 HTTP 協議做認證質詢、重定向和超時重試等處理,通過 followUpRequest() 方法創建后續新的請求
  4. 若沒有后續請求,即 followUpRequest() 方法返回為 null,則說明當前請求結束,返回響應
    ,若后續請求不為空,則繼續進行請求

源碼如下:

@Override public Response intercept(Chain chain) throws IOException {
  Request request = chain.request();
  // 創建一個 StreamAllocation 對象
  streamAllocation = new StreamAllocation(
      client.connectionPool(), createAddress(request.url()), callStackTrace);

  int followUpCount = 0;
  Response priorResponse = null;
  // 啟動一個 While 死循環
  while (true) {
    // 判斷是否已經取消,若已取消則拋出 IO 異常
    if (canceled) {
      streamAllocation.release();
      throw new IOException("Canceled");
    }

    Response response = null;
    boolean releaseConnection = true;
    try {
      // 通過 RealInterceptorChain 對象調用下一個攔截器,并從中得到響應
      response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
      releaseConnection = false;
    } catch (RouteException e) {
      // The attempt to connect via a route failed. The request will not have been sent.
      // 如果進入 RouteException 路由異常,則嘗試是否可以重新進行請求,若可以則從頭開始新的請求
      if (!recover(e.getLastConnectException(), false, request)) {
        throw e.getLastConnectException();
      }
      releaseConnection = false;
      continue;
    } catch (IOException e) {
      // An attempt to communicate with a server failed. The request may have been sent.
      // 若是進入 IOException IO異常,若可以重新嘗試請求,則從頭開始新的請求
      boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
      if (!recover(e, requestSendStarted, request)) throw e;
      releaseConnection = false;
      continue;
    } finally {
      // We're throwing an unchecked exception. Release any resources.
      // 如果沒有拋出異常,則釋放資源。
      if (releaseConnection) {
        streamAllocation.streamFailed(null);
        streamAllocation.release();
      }
    }

    // Attach the prior response if it exists. Such responses never have a body.
    // 如果之前發生過重定向,并且 priorResponse 不為空,則創建新的 響應對象,并將其 body 置位空
    if (priorResponse != null) {
      response = response.newBuilder()
          .priorResponse(priorResponse.newBuilder()
                  .body(null)
                  .build())
          .build();
    }

    // 添加認證需要的頭部,處理重定向或超時重試,得到新的請求
    // followUpRequest() 方法很重要,涉及到 HTTP 中認證質詢、重定向和重試等協議的實現
    Request followUp = followUpRequest(response);

    // 若 followUp 重試請求為空,則當前請求結束,并返回當前的響應
    if (followUp == null) {
      if (!forWebSocket) {
        streamAllocation.release();
      }
      return response;
    }

    // 關閉響應結果
    closeQuietly(response.body());

    // 若重定向、認證質詢、重試次數超過 MAX_FOLLOW_UPS,則拋出 ProtocolException 異常
    if (++followUpCount > MAX_FOLLOW_UPS) {
      streamAllocation.release();
      throw new ProtocolException("Too many follow-up requests: " + followUpCount);
    }

    // 若請求中的主體為 UnrepeatableRequestBody 不可被重復使用的請求體類型,則拋出異常
    if (followUp.body() instanceof UnrepeatableRequestBody) {
      streamAllocation.release();
      throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
    }

    // 判斷是否是相同的連接,若不相同則釋放 streamAllocation,并重新創建新的 streamAllocation 對象
    if (!sameConnection(response, followUp.url())) {
      streamAllocation.release();
      streamAllocation = new StreamAllocation(
          client.connectionPool(), createAddress(followUp.url()), callStackTrace);
    } else if (streamAllocation.codec() != null) {
      throw new IllegalStateException("Closing the body of " + response
          + " didn't close its backing stream. Bad interceptor?");
    }

    request = followUp;
    priorResponse = response;
  }
}

2.2 BridgeInterceptor

BridgeInterceptor 攔截器主要功能是:

  • 設置一些請求和響應首部,如:Content-TypeContent-LengthHost 等常見的請求和響應首部。
  • 處理 HTTP 請求和響應中的 Cookie
  • 如果在請求中設置了編碼,要從響應流中解碼

源碼如下:

@Override public Response intercept(Chain chain) throws IOException {
  Request userRequest = chain.request();
  Request.Builder requestBuilder = userRequest.newBuilder();

  RequestBody body = userRequest.body();
  if (body != null) {
    // 在請求中設置實體首部 Content-Type
    MediaType contentType = body.contentType();
    if (contentType != null) {
      requestBuilder.header("Content-Type", contentType.toString());
    }

    // 在請求中設置實體首部 Content-Length 和 Transfer-Encoding
    long contentLength = body.contentLength();
    if (contentLength != -1) {
      requestBuilder.header("Content-Length", Long.toString(contentLength));
      requestBuilder.removeHeader("Transfer-Encoding");
    } else {
      requestBuilder.header("Transfer-Encoding", "chunked");
      requestBuilder.removeHeader("Content-Length");
    }
  }

  // 設置請求首部 `Host`
  if (userRequest.header("Host") == null) {
    requestBuilder.header("Host", hostHeader(userRequest.url(), false));
  }

  // 設置請求首部 Connection,若 `Connection` 為空,則打開長連接
  if (userRequest.header("Connection") == null) {
    requestBuilder.header("Connection", "Keep-Alive");
  }

  // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
  // the transfer stream.
  // 如果在請求中設置了 "Accept-Encoding: gzip",要記得從響應流中解碼
  boolean transparentGzip = false;
  if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
    transparentGzip = true;
    requestBuilder.header("Accept-Encoding", "gzip");
  }

  // 為請求添加 Cookie
  List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
  if (!cookies.isEmpty()) {
    requestBuilder.header("Cookie", cookieHeader(cookies));
  }

  // 設置請求首部 "User-Agent"
  if (userRequest.header("User-Agent") == null) {
    requestBuilder.header("User-Agent", Version.userAgent());
  }

  Response networkResponse = chain.proceed(requestBuilder.build());

  // 從響應中得到 Cookie,并交給傳入的 CookieJar 對象處理
  HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

  Response.Builder responseBuilder = networkResponse.newBuilder()
      .request(userRequest);

  // 如果之前在請求中設置了 "Accept-Encoding: gzip" 編碼,則需要對響應流進行解碼操作并移除響應中的首部字段 “Content-Encoding” 和 “Content-Length”
  if (transparentGzip
      && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
      && HttpHeaders.hasBody(networkResponse)) {
    GzipSource responseBody = new GzipSource(networkResponse.body().source());
    Headers strippedHeaders = networkResponse.headers().newBuilder()
        .removeAll("Content-Encoding")
        .removeAll("Content-Length")
        .build();
    responseBuilder.headers(strippedHeaders);
    responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
  }

  return responseBuilder.build();
}

這里涉及到 OKHttp 中對 Cookie 的處理,其中有一個接口十分重要 ---- CookieJar,源碼如下:

public interface CookieJar {
  /** A cookie jar that never accepts any cookies. */
  CookieJar NO_COOKIES = new CookieJar() {
    @Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
    }

    @Override public List<Cookie> loadForRequest(HttpUrl url) {
      return Collections.emptyList();
    }
  };

  // 從響應中獲取 Cookie
  void saveFromResponse(HttpUrl url, List<Cookie> cookies);

  // 為請求添加 Cookie
  List<Cookie> loadForRequest(HttpUrl url);
}

OKHttp 對 Cookie 的管理還是十分方便簡潔的。創建一個類實現 Cookiejar 接口,并將其對象設置給 OKHttpClient,那么使用此 OKHttpClient 對象進行的網絡請求都會自動處理 Cookie。這里有一個我實現的自動管理 Cookie 的類 CustomCookieManager,可以實現 Cookie 的持久化,Github 上還有其他很好的實現在 OKHttp 中管理 Cookie 的類,也可以參考。

2.3 CacheInterceptor

CacheInterceptor 實現了 HTTP 協議中的緩存機制,其主要功能如下:

  1. 從緩存中讀取緩存,并創建緩存策略對象
  2. 根據創建的緩存策略對象,從緩存、網絡獲取響應并生成最終的響應對象
  3. 更新緩存內容,并返回響應對象

源碼如下:

public final class CacheInterceptor implements Interceptor {
  final InternalCache cache;

  public CacheInterceptor(InternalCache cache) {
    // 從 OKHttpClient 中傳入的 InternalCache(內部緩存)對象
    this.cache = cache;
  }

  @Override public Response intercept(Chain chain) throws IOException {
    // 獲取候選緩存
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();

    // 創建緩存策略對象,并從中得到請求和響應
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;

    if (cache != null) {
      cache.trackResponse(strategy);
    }

    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

    // If we're forbidden from using the network and the cache is insufficient, fail.
    // 如果不走網絡進行請求,并且緩存響應為空,則創建狀態碼為 504 的響應并返回
    if (networkRequest == null && cacheResponse == null) {
      return new Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(Util.EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();
    }

    // If we don't need the network, we're done.
    // 如果不走網絡進行請求,并且緩存響應不為空,則返回從緩存中獲取的響應
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

    Response networkResponse = null;
    try {
      // 如果前面的條件都不滿足,從攔截器鏈中進行網絡請求并得到響應
      networkResponse = chain.proceed(networkRequest);
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }

    // If we have a cache response too, then we're doing a conditional get.
    if (cacheResponse != null) {
      // 如果緩存響應也不為空,并且網絡響應的狀態碼為 304,則根據緩存響應結果生成最終的響應并返回
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        Response response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
        networkResponse.body().close();

        // Update the cache after combining headers but before stripping the
        // Content-Encoding header (as performed by initContentStream()).
        cache.trackConditionalCacheHit();
        // 更新緩存中的響應內容
        cache.update(cacheResponse, response);
        return response;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }

    // 根據網絡請求響應生成最終的響應
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    // 如果緩存對象不為空,則將響應加入到緩存中
    if (cache != null) {
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }

      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }

    return response;
  }

  ......

}

2.4 ConnectInterceptor

ConnectInterceptor 攔截器的主要功能是:

  • 得到從 RetryAndFollowUpInterceptor 中創建的 StreamAllocation 對象
  • 通過 StreamAllocation 對象創建 HttpCodec 對象
  • 通過 StreamAllocation 對象創建 RealConnection 對象
  • 最終通過 RealInterceptorChainproceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,RealConnection connection) 方法得到響應對象并返回。

源碼如下:

/** Opens a connection to the target server and proceeds to the next interceptor. */
public final class ConnectInterceptor implements Interceptor {
  public final OkHttpClient client;

  public ConnectInterceptor(OkHttpClient client) {
    this.client = client;
  }

  @Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    StreamAllocation streamAllocation = realChain.streamAllocation();

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();

    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }
}

ConnectInterceptor 攔截器中用到了 StreamAllocation 類,此類創建了 HttpCodecRealConnection 對象,這兩個類在網絡請求中都是非常重要的類,會在后面文章中詳細分析

2.5 CallServerInterceptor

CallServerInterceptor 攔截器中最主要的功能就是:

  • 遵循 HTTP 協議規范,通過 HttpCodec 對象寫入請求頭、請求主體、讀取響應頭和響應主體
  • 生成最初的響應對象并返回

源碼如下所示:

@Override public Response intercept(Chain chain) throws IOException {
  // 得到 httpCodec、streamAllocation、connection 和 request 對象
  RealInterceptorChain realChain = (RealInterceptorChain) chain;
  HttpCodec httpCodec = realChain.httpStream();
  StreamAllocation streamAllocation = realChain.streamAllocation();
  RealConnection connection = (RealConnection) realChain.connection();
  Request request = realChain.request();

  // 向 HttpCodec 對象中寫入請求頭部信息
  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.
    // 若在請求頭部中存在 ”Expect: 100-continue“,先不發送請求主體,只有收到 ”100-continue“ 響應報文才會將請求主體發送出去。
    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();
  }

  // 如果響應的狀態碼為 204 和 205 并且響應體不為空,則拋出異常
  if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
    throw new ProtocolException(
        "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
  }

  return response;
}

在此攔截器中非常重要的一個對象是 HttpCodec,它是一個接口,具體的實現類有 Http1Codec 和 Http2Codec 兩個類,分別對應著 HTTP1.1 和 HTTP2。在 HttpCodec 內部是通過 sinksource 來實現的。

2.6 注意

關于 OKHttp 的攔截器需要注意的一點是,從 RealCallgetResponseWithInterceptorChain() 方法中可以看到開發自定義的攔截器有兩種類型的

Response getResponseWithInterceptorChain() throws IOException {
  // Build a full stack of interceptors.
  List<Interceptor> interceptors = new ArrayList<>();
  interceptors.addAll(client.interceptors());
  interceptors.add(retryAndFollowUpInterceptor);
  interceptors.add(new BridgeInterceptor(client.cookieJar()));
  interceptors.add(new CacheInterceptor(client.internalCache()));
  interceptors.add(new ConnectInterceptor(client));
  if (!forWebSocket) {
    interceptors.addAll(client.networkInterceptors());
  }
  interceptors.add(new CallServerInterceptor(forWebSocket));

  Interceptor.Chain chain = new RealInterceptorChain(
      interceptors, null, null, null, 0, originalRequest);
  return chain.proceed(originalRequest);
}

一種是在最開始,通過 client.interceptors() 方法添加的普通攔截器;另一種是通過 client.networkInterceptors() 方法添加的網絡請求攔截器,兩者的區別如下:

  • 普通攔截器:對發出去的請求做最初的處理,對最終得到的響應做處理
  • 網絡攔截器:對發出去的請求做最后的處理,對收到的響應做最初的處理

兩種攔截器都可以通過在初始化 OKHttpClient 對象的時候設置,如下所示:

OkHttpClient client = new OkHttpClient.Builder()
                .addInterceptor(new LogInterceptor())
                .addNetworkInterceptor(new LogInterceptor())
                .build();

雖然都是添加了 LogInterceptor 攔截器給 OKHttpClient 對象,但是從 RealCallgetResponseWithInterceptorChain() 方法中可以看出兩個攔截器調用的時機不同。


本文中涉及到的自定義類的源碼都在 Github 上的 OkHttpPractice 工程中。


參考資料:

OkHttp源碼解析 -- 俞其榮

OkHttp源碼解析 -- 高沛

OKhttp源碼學習(十)——寫在最后 -- 小禤大叔

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容