OkHttp-ConnectInterceptor源碼解析

ConnectInterceptor源碼解析

本文基於okhttp3.10.0

1. 概述

ConnectInterceptor主要是用於建立連接,並再連接成功後將流封裝成對象傳遞給下一個攔截器CallServerInterceptor與遠端進行讀寫操作。

這個過程中會涉及比較多類我們簡述下每個類的作用

  • StreamAllocation:類似一個工廠用來創建連接RealConnection和與遠端通信的流的封裝對象HttpCodec
  • ConnectionPool:連接池用來存儲可用的連接,在條件符合的情況下進行連接複用
  • HttpCodec:對輸入輸出流的封裝對象,對於http1和2實現不同
  • RealConnection:對tcp連接的封裝

2. 源碼解析

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

    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();

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

可以看到代碼非常少,主要就是從realChain中拿出StreamAllocation然後調用StreamAllocation#newStream()獲取流的封裝對象HttpCodec、StreamAllocation#connection()拿到連接對象RealConnection,然後將它們傳遞給下一個攔截器。

很顯然主要邏輯是在StreamAllocation中實現的,而StreamAllocation是在第一個攔截器retryAndFollowUpInterceptor創建的直到ConnectInterceptor才使用

#RetryAndFollowUpInterceptor
@Override public Response intercept(Chain chain) throws IOException {
		    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Call call = realChain.call();
    EventListener eventListener = realChain.eventListener();

    StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
        createAddress(request.url()), call, eventListener, callStackTrace);
    ...
}

可以看到構造方法一共傳入了5個參數,我們只需要關注前三個即可

  • client.connectionPool():okhttp連接池
  • createAddress(request.url()):將url解析爲Address對象
  • call:請求對象Call

2.1 ConnectionPool

連接池通過OkHttpClient實例的connectionPool()方法獲取,默認初始化是在OkHttpClient.Builder構造函數

主要功能是用來緩存連接,當符合條件的時候進行連接複用,內部通過一個隊列去緩存連接,當超過緩存時間後會自動清理過期連接。

先來看下ConnectionPool構造方法

private final int maxIdleConnections;//最大閒置連接數
private final long keepAliveDurationNs;//每個連接最大緩存時間

public ConnectionPool() {
    this(5, 5, TimeUnit.MINUTES);//默認最大緩存5個閒置連接,過期時間5分鐘
  }

  public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
    this.maxIdleConnections = maxIdleConnections;
    this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);

    // Put a floor on the keep alive duration, otherwise cleanup will spin loop.
    if (keepAliveDuration <= 0) {
      throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
    }
  }

默認最大緩存5個連接,過期時間爲5分鐘,存儲連接是通過ConnectionPool#put()方法

private final Deque<RealConnection> connections = new ArrayDeque<>();//緩存隊列
boolean cleanupRunning;//開啓清理過期連接任務標記位
private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
      Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
      new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));

void put(RealConnection connection) {
    assert (Thread.holdsLock(this));
    if (!cleanupRunning) {
      cleanupRunning = true;
      executor.execute(cleanupRunnable);//在線程池中開啓清理過期連接任務
    }
    connections.add(connection);//添加到緩存隊列
  }

存儲連接的時候,如果沒開啓清理任務則開啓清理過期連接任務並緩存新的連接

  private final Runnable cleanupRunnable = new Runnable() {
    @Override public void run() {
      while (true) {
        long waitNanos = cleanup(System.nanoTime());//獲取最近一個即將過期連接的倒計時
        if (waitNanos == -1) return;//-1則代表沒有緩存連接了直接return
        if (waitNanos > 0) {
          long waitMillis = waitNanos / 1000000L;
          waitNanos -= (waitMillis * 1000000L);
          synchronized (ConnectionPool.this) {
            try {
              ConnectionPool.this.wait(waitMillis, (int) waitNanos);//wait最近一個即將過期連接的倒計時後在進行檢測
            } catch (InterruptedException ignored) {
            }
          }
        }
      }
    }
  };

清理過期連接runnable則是通過cleanup()方法獲取最近一個即將過期連接的倒計時,爲-1則代表緩存已經清空了直接return退出,否則wait一個即將超時的時間後在進行檢查

  long cleanup(long now) {
    int inUseConnectionCount = 0;//使用的連接數
    int idleConnectionCount = 0;//閒置的連接數
    RealConnection longestIdleConnection = null;//閒置時間最長的連接
    long longestIdleDurationNs = Long.MIN_VALUE;

    synchronized (this) {
      for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
        RealConnection connection = i.next();//遍歷緩存的連接

        if (pruneAndGetAllocationCount(connection, now) > 0) {//如果該連接有人使用
          inUseConnectionCount++;//使用連接數++
          continue;
        }

        idleConnectionCount++;//否則閒置連接數++

        long idleDurationNs = now - connection.idleAtNanos;//當前連接閒置時間
        if (idleDurationNs > longestIdleDurationNs) {//找到閒置最久的連接
          longestIdleDurationNs = idleDurationNs;
          longestIdleConnection = connection;
        }
      }

      if (longestIdleDurationNs >= this.keepAliveDurationNs
          || idleConnectionCount > this.maxIdleConnections) {//閒置最久的連接時間超過5分鐘或者閒置連接數大於5
        connections.remove(longestIdleConnection);//移除當前連接
      } else if (idleConnectionCount > 0) {//閒置連接數大於0
        return keepAliveDurationNs - longestIdleDurationNs;//return緩存最久連接剩餘過期時間
      } else if (inUseConnectionCount > 0) {//如果使用連接數大於0
        return keepAliveDurationNs;//return最大緩存時間
      } else {//沒有緩存連接了
        cleanupRunning = false;//把標記位置爲false
        return -1;//返回-1停止檢測
      }
    }

遍歷緩存的連接,找到閒置最久的連接,和使用與閒置連接數量

  1. 當閒置最久的連接閒置時間大於最大閒置時間或者閒置連接數大於最大閒置連接數,直接移除該連接
  2. 當閒置連接數大於0則計算閒置最久的連接過期時間作爲倒計時返回
  3. 當使用連接數大於0,返回最大緩存時間作爲倒計時
  4. 沒有緩存連接了把標誌位cleanupRunning置爲false並返回-1停止清理任務。

這裏需要注意下的是對於連接是否在使用的判斷pruneAndGetAllocationCount()方法

  private int pruneAndGetAllocationCount(RealConnection connection, long now) {
    List<Reference<StreamAllocation>> references = connection.allocations;//拿到RealConnection中StreamAllocation引用
    for (int i = 0; i < references.size(); ) {//遍歷
      Reference<StreamAllocation> reference = references.get(i);

      if (reference.get() != null) {//如果持有StreamAllocation則代表有使用
        i++;
        continue;
      }

      references.remove(i);//如果reference.get() == null則代表內存泄露了,移除該引用
      connection.noNewStreams = true;//該連接不再創建新的流

      if (references.isEmpty()) {//如果StreamAllocation引用爲空
        connection.idleAtNanos = now - keepAliveDurationNs;//將當前閒置時間設置爲now - keepAliveDurationNs即滿足清除條件會被直接從緩存移除
        return 0;
      }
    }

    return references.size();//返回當前連接使用數
  }

對於連接是否在使用是通過RealConnection中的成員變量allocations來判斷,如果有請求正在使用則會將StreamAllocation對象的引用存儲到allocations中,具體存儲的代碼是在StreamAllocation中後面會說到

看完put再來看get

  @Nullable RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
    assert (Thread.holdsLock(this));
    for (RealConnection connection : connections) {//遍歷緩存連接
      if (connection.isEligible(address, route)) {//滿足條件
        streamAllocation.acquire(connection, true);//複用連接
        return connection;
      }
    }
    return null;
  }

遍歷緩存連接,如果滿足條件則複用該連接

  public boolean isEligible(Address address, @Nullable Route route) {
    if (allocations.size() >= allocationLimit || noNewStreams) return false;//如果當前連接流使用數>=最大併發或者不能創建新流了則return false

    if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;//地址中除了host有不相同的返回false

    if (address.url().host().equals(this.route().address().url().host())) {//如果host相同則返回true
      return true; 
    }

    if (http2Connection == null) return false;//如果上面條件都不滿足並且不是http2則返回false http2不再本次源碼討論範圍下面就不分析了
    if (route == null) return false;
    if (route.proxy().type() != Proxy.Type.DIRECT) return false;
    if (this.route.proxy().type() != Proxy.Type.DIRECT) return false;
    if (!this.route.socketAddress().equals(route.socketAddress())) return false;

    if (route.address().hostnameVerifier() != OkHostnameVerifier.INSTANCE) return false;
    if (!supportsUrl(address.url())) return false;

    try {
      address.certificatePinner().check(address.url().host(), handshake().peerCertificates());
    } catch (SSLPeerUnverifiedException e) {
      return false;
    }

可以看到在滿足如下三個條件的時候http1連接可以服用

  1. 小於當前連接最大併發數,http1最大併發爲1並且可以創建新的流
  2. 請求路徑中除了host以外的全部相同
  3. host也相同

滿足以上三個條件即可複用緩存的連接

複用是通過streamAllocation.acquire(connection, true)方法實現

  public void acquire(RealConnection connection, boolean reportedAcquired) {
    assert (Thread.holdsLock(connectionPool));
    if (this.connection != null) throw new IllegalStateException();

    this.connection = connection;
    this.reportedAcquired = reportedAcquired;
    connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
  }

即通過成員變量存儲connection,並connection.allocations.add(new StreamAllocationReference(this, callStackTrace));給當前連接添加上StreamAllocation引用代表有一個流正在使用該連接。

那麼總結下ConnectionPool是用來緩存http連接的,當條件滿足的時候複用連接,默認每個連接最大緩存時長5分鐘,最大緩存數量5個。

2.2 Address

在RetryAndFollowUpInterceptor#createAddress()方法創建

  private Address createAddress(HttpUrl url) {
    SSLSocketFactory sslSocketFactory = null;
    HostnameVerifier hostnameVerifier = null;
    CertificatePinner certificatePinner = null;
    if (url.isHttps()) {//請求是https
      sslSocketFactory = client.sslSocketFactory();
      hostnameVerifier = client.hostnameVerifier();
      certificatePinner = client.certificatePinner();
    }

    return new Address(url.host(), url.port(), client.dns(), client.socketFactory(),
        sslSocketFactory, hostnameVerifier, certificatePinner, client.proxyAuthenticator(),
        client.proxy(), client.protocols(), client.connectionSpecs(), client.proxySelector());
  }

當是https的時候SSLSocketFactory、HostnameVerifier、CertificatePinner纔會被賦值,後面會通過SSLSocketFactory != null這個判斷請求是不是https,構造的時候只有host和port是從url中取得,其他都是在OkHttpClient的配置。

  public Address(String uriHost, int uriPort, Dns dns, SocketFactory socketFactory,
      @Nullable SSLSocketFactory sslSocketFactory, @Nullable HostnameVerifier hostnameVerifier,
      @Nullable CertificatePinner certificatePinner, Authenticator proxyAuthenticator,
      @Nullable Proxy proxy, List<Protocol> protocols, List<ConnectionSpec> connectionSpecs,
      ProxySelector proxySelector) {
    this.url = new HttpUrl.Builder()
        .scheme(sslSocketFactory != null ? "https" : "http")
        .host(uriHost)
        .port(uriPort)
        .build();

    if (dns == null) throw new NullPointerException("dns == null");
    this.dns = dns;

    if (socketFactory == null) throw new NullPointerException("socketFactory == null");
    this.socketFactory = socketFactory;

    if (proxyAuthenticator == null) {
      throw new NullPointerException("proxyAuthenticator == null");
    }
    this.proxyAuthenticator = proxyAuthenticator;

    if (protocols == null) throw new NullPointerException("protocols == null");
    this.protocols = Util.immutableList(protocols);

    if (connectionSpecs == null) throw new NullPointerException("connectionSpecs == null");
    this.connectionSpecs = Util.immutableList(connectionSpecs);

    if (proxySelector == null) throw new NullPointerException("proxySelector == null");
    this.proxySelector = proxySelector;

    this.proxy = proxy;
    this.sslSocketFactory = sslSocketFactory;
    this.hostnameVerifier = hostnameVerifier;
    this.certificatePinner = certificatePinner;
  }

可以看到構造函數只是將傳入的屬性通過成員變量存儲起來。

總結下Address就是將url分解,然後通過成員變量存儲不同的數據供後面使用。

2.3 StreamAllocation

上面把兩個重要的對象說完了,那麼回到ConnectInterceptor#intercept

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

    boolean doExtensiveHealthChecks = !request.method().equals("GET");//非get請求需要額外的檢查
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);//獲取流的封裝對象
    RealConnection connection = streamAllocation.connection();//獲取連接

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

調用StreamAllocation#newStream()獲取流的封裝對象HttpCodec、調用StreamAllocation#connection()拿到連接對象RealConnection,然後將它們傳遞給下一個攔截器。

  public synchronized RealConnection connection() {
    return connection;//獲取連接
  }  

	public HttpCodec newStream(
      OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
    int connectTimeout = chain.connectTimeoutMillis();
    int readTimeout = chain.readTimeoutMillis();
    int writeTimeout = chain.writeTimeoutMillis();
    int pingIntervalMillis = client.pingIntervalMillis();
    boolean connectionRetryEnabled = client.retryOnConnectionFailure();

    try {
      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
          writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);//找到一個可用的連接
      HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);//創建流封裝對象

      synchronized (connectionPool) {
        codec = resultCodec;
        return resultCodec;
      }
    } catch (IOException e) {
      throw new RouteException(e);
    }
  }

StreamAllocation#connection()獲取成員變量connection返回,主要流程是在newStream()獲取可用的連接並創建一個流的封裝對象

  private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
      int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
      boolean doExtensiveHealthChecks) throws IOException {
    while (true) {
      RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
          pingIntervalMillis, connectionRetryEnabled);//找一個可用的連接

        synchronized (connectionPool) {
        if (candidate.successCount == 0) {//如果successCount == 0代表是個新連接直接返回
          return candidate;
        }
      }

      if (!candidate.isHealthy(doExtensiveHealthChecks)) {//不爲新連接則檢查連接是否可用
        noNewStreams();//不可用釋放連接
        continue;
      }

      return candidate;
    }
  }

while循環找到一個可用的連接則返回,如果是新連接則直接返回,否則檢查連接是否可用,不可用的釋放連接。

  private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
      int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
    boolean foundPooledConnection = false;
    RealConnection result = null;
    Route selectedRoute = null;
    Connection releasedConnection;
    Socket toClose;
    synchronized (connectionPool) {
      releasedConnection = this.connection;
      toClose = releaseIfNoNewStreams();
      if (this.connection != null) {//1.連接不爲空則直接使用
        result = this.connection;
        releasedConnection = null;
      }

      if (result == null) {//2.從連接池中找是否有可以複用的連接
        Internal.instance.get(connectionPool, address, this, null);
        if (connection != null) {
          foundPooledConnection = true;
          result = connection;
        } else {
          selectedRoute = route;
        }
      }
    }
    closeQuietly(toClose);
    if (result != null) {
      return result;
    }

    boolean newRouteSelection = false;
    if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {//切換新的路由
      newRouteSelection = true;
      routeSelection = routeSelector.next();
    }

    synchronized (connectionPool) {
      if (canceled) throw new IOException("Canceled");

      if (newRouteSelection) {
        List<Route> routes = routeSelection.getAll();
        for (int i = 0, size = routes.size(); i < size; i++) {//3.用新的路由再次看能不能複用連接
          Route route = routes.get(i);
          Internal.instance.get(connectionPool, address, this, route);
          if (connection != null) {
            foundPooledConnection = true;
            result = connection;
            this.route = route;
            break;
          }
        }
      }

      if (!foundPooledConnection) {
        if (selectedRoute == null) {
          selectedRoute = routeSelection.next();
        }

        route = selectedRoute;
        refusedStreamCount = 0;
        result = new RealConnection(connectionPool, selectedRoute);//4.創建新的連接
        acquire(result, false);//成員變量記錄下來
      }
    }
    if (foundPooledConnection) {
      eventListener.connectionAcquired(call, result);
      return result;
    }

    // Do TCP + TLS handshakes. This is a blocking operation.
    result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
        connectionRetryEnabled, call, eventListener);//建立tcp連接
    routeDatabase().connected(result.route());

    Socket socket = null;
    synchronized (connectionPool) {
      reportedAcquired = true;

      // Pool the connection.
      Internal.instance.put(connectionPool, result);//緩存連接
    }
    closeQuietly(socket);
    return result;
  }

這段我簡化了不少代碼,主要流程分爲四步

  1. 如果當前StreamAllocation有Connection成員變量則直接使用(這個對應的是重定向的url路徑和當前請求匹配的情況下)
  2. 從連接池中找可以複用的連接
  3. 切換路由,再次從連接池中找複用連接(關於路由這塊說實話我不太懂,哪位大佬懂的話歡迎指導)
  4. 創建新的連接

對於第一個情況我們簡單說下StreamAllocation是在RetryAndFollowUpInterceptor攔截器創建的,也就是說每次請求都會創建,所以大多數情況下StreamAllocation#connection成員變量爲null,但是當收到response爲重定向的時候RetryAndFollowUpInterceptor的while循環中會調用sameConnection()判斷重定向的請求和當前請求連接是否相同如果相同則不會再創建StreamAllocation,也就會進入我們上面第一個條件了。

  private boolean sameConnection(Response response, HttpUrl followUp) {
    HttpUrl url = response.request().url();
    return url.host().equals(followUp.host())
        && url.port() == followUp.port()
        && url.scheme().equals(followUp.scheme());
  }

即滿足host相同port相同scheme相同則可以使用同一個連接,條件還是比較苛刻的。

回到正題如果連接是新創建的則

  1. StreamAllocation#acquire(),streamAllocation記錄下連接,給connection.allocations添加streamAllocation引用
  2. RealConnection#connect()建立tcp連接
  3. 緩存到連接池
  public void acquire(RealConnection connection, boolean reportedAcquired) {
    assert (Thread.holdsLock(connectionPool));
    if (this.connection != null) throw new IllegalStateException();

    this.connection = connection;//記錄connection
    this.reportedAcquired = reportedAcquired;
    connection.allocations.add(new StreamAllocationReference(this, callStackTrace));//給connection.allocations添加streamAllocation引用代表有請求在使用該連接
  }

connection.allocations.add(new StreamAllocationReference(this, callStackTrace));給當前連接添加上StreamAllocation引用代表有一個流正在使用該連接。

2.4 RealConnection

  public void connect(int connectTimeout, int readTimeout, int writeTimeout,
      int pingIntervalMillis, boolean connectionRetryEnabled, Call call,
      EventListener eventListener) {
    while (true) {
      try {
        if (route.requiresTunnel()) {//如果是隧道請求
          connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);//建立隧道
          if (rawSocket == null) {
            break;
          }
        } else {
          connectSocket(connectTimeout, readTimeout, call, eventListener);//建立http連接
        }
        establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);//完成協議操作比如https
        eventListener.connectEnd(call, route.socketAddress(), route.proxy(), protocol);
        break;
      } catch (IOException e) {
        ....
      }
    }

    if (route.requiresTunnel() && rawSocket == null) {
      ProtocolException exception = new ProtocolException("Too many tunnel connections attempted: "
          + MAX_TUNNEL_ATTEMPTS);
      throw new RouteException(exception);
    }

    if (http2Connection != null) {//如果是http2請求
      synchronized (connectionPool) {
        allocationLimit = http2Connection.maxConcurrentStreams();//修改當前連接支持的最大併發數
      }
    }
  }

如果請求需要建立隧道則建立隧道,否則就建立普通的tcp連接,然後進行協議相關操作,如果是http2的話會修改當前連接支持的最大併發數,默認爲1。

  private void connectSocket(int connectTimeout, int readTimeout, Call call,
      EventListener eventListener) throws IOException {
    Proxy proxy = route.proxy();
    Address address = route.address();

    rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
        ? address.socketFactory().createSocket()
        : new Socket(proxy);//創建Socket對象

    rawSocket.setSoTimeout(readTimeout);//設置讀取數據時阻塞鏈路的超時時間。
    try {
      Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);//調用 socket.connect()建立連接
    } catch (ConnectException e) {
      ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress());
      ce.initCause(e);
      throw ce;
    }
    try {
      //拿到流對象
      source = Okio.buffer(Okio.source(rawSocket));
      sink = Okio.buffer(Okio.sink(rawSocket));
    } catch (NullPointerException npe) {
      if (NPE_THROW_WITH_NULL.equals(npe.getMessage())) {
        throw new IOException(npe);
      }
    }
  }

可以看到內部封裝的是socket對象,然後通過socket.connect()進行連接,通過成員變量將輸入輸出流存儲起來

  private void establishProtocol(ConnectionSpecSelector connectionSpecSelector,
      int pingIntervalMillis, Call call, EventListener eventListener) throws IOException {
    if (route.address().sslSocketFactory() == null) {//如果不是https請求的話
      protocol = Protocol.HTTP_1_1;
      socket = rawSocket;
      return;
    }

    eventListener.secureConnectStart(call);
    connectTls(connectionSpecSelector);//建立ssl通道
    eventListener.secureConnectEnd(call, handshake);

    if (protocol == Protocol.HTTP_2) {//如果是http2請求
      socket.setSoTimeout(0); //創建http2實例
      http2Connection = new Http2Connection.Builder(true)
          .socket(socket, route.address().url().host(), source, sink)
          .listener(this)
          .pingIntervalMillis(pingIntervalMillis)
          .build();
      http2Connection.start();
    }
  }

這裏就看到前面說的通過route.address().sslSocketFactory() == null判斷當前請求是不是https請求,不是的話建立ssl通道,如果是http2的話創建Http2Connection實例,後面也會通過這個實例是否爲空判斷當前請求是不是http2

現在知道RealConnection怎麼創建的了再來看RealConnection#newCodec()

  public HttpCodec newCodec(OkHttpClient client, Interceptor.Chain chain,
      StreamAllocation streamAllocation) throws SocketException {
    if (http2Connection != null) {//如果是http2則創建ttp2Codec
      return new Http2Codec(client, chain, streamAllocation, http2Connection);
    } else {//否則創建Http1Codec
      socket.setSoTimeout(chain.readTimeoutMillis());
      source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);
      sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);
      return new Http1Codec(client, streamAllocation, source, sink);
    }
  }

可以發現就是判斷http2Connection實例是否不爲null來判斷當前請求是不是http2,一般情況下都是http1,也就是返回一個Http1Codec實例而Http1Codec和Http2Codec都是HttpCodec的實現類

public interface HttpCodec {
  int DISCARD_STREAM_TIMEOUT_MILLIS = 100;
  Sink createRequestBody(Request request, long contentLength);
  void writeRequestHeaders(Request request) throws IOException;
  void flushRequest() throws IOException;
  void finishRequest() throws IOException;
  Response.Builder readResponseHeaders(boolean expectContinue) throws IOException;
  ResponseBody openResponseBody(Response response) throws IOException;
  void cancel();
}

可以看到都是對流的操作,你猜的沒錯HttpCodec就是對流的再一次封裝。

到這裏ConnectInterceptor基本就分析完了

  @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, chain, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();

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

獲取可用的連接RealConnection和對流的封裝HttpCodec傳遞給下個攔截器,完成與遠端流的讀寫相關操作。

3. 總結

最後再來總結下ConnectInterceptor幾個對象幹嘛的

  • StreamAllocation:類似一個工廠用來創建連接RealConnection和與遠端通信的流的封裝對象HttpCodec
  • Address:將url分解,然後通過成員變量存儲不同的數據供其他地方使用
  • ConnectionPool:連接池用來存儲可用的連接,在條件符合的情況下進行連接複用
  • HttpCodec:對輸入輸出流的封裝對象,對於http1和2實現不同,方便後面攔截器使用
  • RealConnection:對tcp連接的封裝對象

對於ConnectInterceptor我覺得最特殊的地方就是有連接池緩存連接做連接複用

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