OkHttp - Interceptors(三)

weixin_33912445發表於2017-04-09

本文中原始碼基於OkHttp 3.6.0

這篇文章主要分析 ConnectInterceptor ,它是 OkHttp 請求鏈上的倒數第二個節點,其主要任務就是建立與伺服器的連線。

- ConnectInterceptor

先來看其 intercept 方法。

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

這裡的程式碼比較少,建立了一個 HTTP 編解碼器HttpCodec和一個連線物件RealConnection,並將它們加入到請求鏈中,其中HttpCodec主要用於在CallServerInterceptor中向伺服器發起 Request 和解析 Response。構建這兩個物件的主要邏輯全部封裝在StreamAllocation中,它在請求鏈的第一個節點RetryAndFollowupInterceptor中建立的。

來看其 newStream() 方法。

private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
    int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
    throws IOException {
  while (true) {
    RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
        connectionRetryEnabled);

    // 如果 connection 是一個新建立的,那麼不用進行健康性測試
    synchronized (connectionPool) {
      if (candidate.successCount == 0) {
        return candidate;
      }
    }

    // 健康檢查主要是判斷 socket 是否關閉,輸入輸出流是否關閉等。
    // 如果連線不健康,則斷開連線,並從 connectionPool 中移除。
    if (!candidate.isHealthy(doExtensiveHealthChecks)) {
      noNewStreams();
      continue;
    }

    return candidate;
  }
}

通過 findHealthyConnection() 找到一條可用的連線。

private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
    int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
    throws IOException {
  while (true) {
    RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
        connectionRetryEnabled);

    // 如果 connection 是一個新建立的,那麼不用進行健康性測試
    synchronized (connectionPool) {
      if (candidate.successCount == 0) {
        return candidate;
      }
    }

    // 健康檢查主要是判斷 socket 是否關閉,輸入輸出流是否關閉等。
    // 如果連線不健康,則斷開連線,並從 connectionPool 中移除。
    if (!candidate.isHealthy(doExtensiveHealthChecks)) {
      noNewStreams();
      continue;
    }

    return candidate;
  }
}

繼續進入 findConnection()。

private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
    boolean connectionRetryEnabled) throws IOException {
  Route selectedRoute;
  synchronized (connectionPool) {
    if (released) throw new IllegalStateException("released");
    if (codec != null) throw new IllegalStateException("codec != null");
    if (canceled) throw new IOException("Canceled");

    // StreamAllocation 會保持了一個當前的連線,如果這個連線允許繼續使用,則直接複用這個連線
    RealConnection allocatedConnection = this.connection;
    if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
      return allocatedConnection;
    }

    // 從連線池中找到一個可以複用的連線
    Internal.instance.get(connectionPool, address, this);
    if (connection != null) {
      return connection;
    }

    selectedRoute = route;
  }

  // 如果沒有合適的連線,那麼就建立一個新的
  // 尋找一個合適的路由,如果沒有找到就丟擲異常,由 RetryAndFollowUpInterceptor 負責捕獲並重試
  if (selectedRoute == null) {
    selectedRoute = routeSelector.next();
  }

  RealConnection result;
  synchronized (connectionPool) {
    route = selectedRoute;
    refusedStreamCount = 0;
    result = new RealConnection(connectionPool, selectedRoute);
    acquire(result);
    if (canceled) throw new IOException("Canceled");
  }

  // 執行三次握手,建立與伺服器的連線
  result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
  routeDatabase().connected(result.route());

  Socket socket = null;
  synchronized (connectionPool) {
    // 把這個連線放進連線池,供以後複用
    Internal.instance.put(connectionPool, result);

    if (result.isMultiplexed()) {
      socket = Internal.instance.deduplicate(connectionPool, address, this);
      result = connection;
    }
  }
  closeQuietly(socket);

  return result;
}

在執行 connect() 的時候,使用 Socket 建立連線,如果請求是 HTTPS,則使用 SSLSocket 進行包裝。

那麼,至此客戶端與伺服器之間的連線就已經建立成功了,如果 OkHttpClient 中設定了 networkInterceptor 的話,下面就該把任務交給它們處理了,這裡就可以看出 networkInterceptor 和 Interceptor 之間的區別,networkInterceptor 處理請求時已經建立好連線。如果沒有設定 networkInterceptor,下面就該 CallServerInterceptor 出場了。

相關文章