OkHttp3源码解析--拦截器ConnectInterceptor-处理流程

依旧再看一下ConnectInterceptor的intercept方法:

public final class ConnectInterceptor implements Interceptor {
  public final OkHttpClient client;//已经创建的OkHttpClient对象
  //构造器中传入OkHttpClient对象
  public ConnectInterceptor(OkHttpClient client) {
    this.client = client;
  }@Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;//上一个拦截器中创建的拦截器链
    Request request = realChain.request();//前面传过来的Request对象
    StreamAllocation streamAllocation = realChain.streamAllocation();//StreamAllocation对象,这个对象,已经在RetryAndFollowUpInterceptor中创建。// We need the network to satisfy this request. Possibly for validating a conditional GET.
    //如果不是GET方法,需要做额外的状态检测
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    //创建一个新的Http Stream。用来编码HTTP requests和解码HTTP responses
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    //获取一个RealConnection,即网络连接,可以从连接池中获取,也可以创建一个新的链接。
    RealConnection connection = streamAllocation.connection();
    //执行下一个拦截器,也就是CallServerInterceptor,进行网络请求和结果传递操作
    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }
}

上面代码可以看出,intercept方法的流程有:

  • 获取Request,StreamAllocation对象;

  • 创建一个新流HttpCodec,用来编码HTTP requests和解码HTTP responses;

  • 创建一个新的网络连接RealConnection;

  • 继续执行下一个拦截器,推动拦截器链的执行。

根据上面的流程,看一下代码执行:

StreamAllocation对象创建

//RetryAndFollowUpInterceptor
public final class RetryAndFollowUpInterceptor implements Interceptor {
  ......
    
  private StreamAllocation streamAllocation;
  
  @Override public Response intercept(Chain chain) throws IOException {
    ......
    //创建StreamAllocation对象
    streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()),
        call, eventListener, callStackTrace);
  }
}
//StreamAllocation
public final class StreamAllocation {
  public final Address address;
  private RouteSelector.Selection routeSelection;
  private Route route;
  private final ConnectionPool connectionPool;
  public final Call call;
  public final EventListener eventListener;
  private final Object callStackTrace;
  // State guarded by connectionPool.
  private final RouteSelector routeSelector;
  //上面的属性在StreamAllocation构造其中初始化
  
  
  private int refusedStreamCount;
  private RealConnection connection;
  private boolean reportedAcquired;
  private boolean released;
  private boolean canceled;
  private HttpCodec codec;
  //构造器
  public StreamAllocation(ConnectionPool connectionPool, Address address, Call call,
      EventListener eventListener, Object callStackTrace) {
    this.connectionPool = connectionPool;
    this.address = address;
    this.call = call;
    this.eventListener = eventListener;
    this.routeSelector = new RouteSelector(address, routeDatabase(), call, eventListener);
    this.callStackTrace = callStackTrace;
  }
}

上述代码中创建StreamAllocation对象,参数有:

  • 连接池,在OkHttpClient中创建;

  • Address对象,通过Request的URL内容创建Address对象;

  • Call对象,前面的RealCall对象;

创建HttpCodec对象

需要到StreamAllocation类中查看newStream方法:

StreamAllocation#newStream

HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);//创建新的Http Stream
//传入OkHttpClient对象,和拦截器链,是否需要对连接进行额外检测
public HttpCodec newStream(
      OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
    int connectTimeout = chain.connectTimeoutMillis();//连接超时时间
    int readTimeout = chain.readTimeoutMillis();//读超时时间
    int writeTimeout = chain.writeTimeoutMillis();//写超时时间
    boolean connectionRetryEnabled = client.retryOnConnectionFailure();//连接失败是否可以重试,用户可以自己配置try {
      //调用findHealthyConnection方法,查找一个状态良好的网络连接,有可能是从连接池中获取到的,或者直接创建了一个新的,并添加到连接池中。
      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
          writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
      //基于获取到的RealConnection对象,创建一个新的HttpCodec对象。
      HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
      //返回创建的HttpCodec对象。
      synchronized (connectionPool) {
        codec = resultCodec;
        return resultCodec;
      }
    } catch (IOException e) {
      throw new RouteException(e);
    }
  }

上面代码流程为:

  • 获取一个状态良好的网络连接对象;

  • 基于上面的网络连接对象,创建一个新的Http流对象HttpCodec;

  • 返回创建的HttpCodec对象。

再看一下是如何获取一个获取一个状态可用的连接的:

StreamAllocation#findHealthyConnection

  /**
   * Finds a connection and returns it if it is healthy. If it is unhealthy the process is repeated
   * until a healthy connection is found.
   */
  //查找一个可用的连接。一直找到一个可用的连接这个过程才会停止。
  private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
      int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
      throws IOException {
    //无限循环,直到获取一个可用的连接
    while (true) {
      //调用findConnection方法,查找一个链接
      RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
          connectionRetryEnabled);// If this is a brand new connection, we can skip the extensive health checks.
      //RealConnection的成员属性successCount表示成功传输Http流的次数,如果为0,表示这是一个新的连接。可以不用在进行范围更广的状态监测。
      synchronized (connectionPool) {
        if (candidate.successCount == 0) {
          return candidate;//直接返回这个连接
        }
      }// Do a (potentially slow) check to confirm that the pooled connection is still good. If it
      // isn't, take it out of the pool and start again.
      //进行更广范围的状态监测,如果当前链接的状态已经不可用,
      //调用noNewStreams()方法,标记RealConnection的noNewStreams为false,表示这个连接不再创建新的Http流。
      //接下来请回清除这个连接。
      //继续下一个循环
      if (!candidate.isHealthy(doExtensiveHealthChecks)) {
        noNewStreams();
        continue;
      }
      //当前连接RealConnection可用,返回使用
      return candidate;
    }
  }

上述代码的执行流程如下:

  • 开始while循环,无限循环的查找一个连接

  • 调用findConnection获取连接;

  • 判断查找到的这个RealConnection对象是不是新的连接,通过successCount是否为0来判断。如果是新的连接,直接返回使用这个连接,不用再往下执行状态检测;

  • 对这个连接RealConnection进行状态检测:

    • 如果这个连接的状态不可用,调用noNewStreams()方法,标记RealConnection的noNewStreams为false,表示这个连接不再创建新的Http流;

    • 继续下一个循环,查找下一个链接。

  • 返回状态可用的连接RealConnection对象。

下面继续看一下findConnection方法是什么流程:

StreamAllocation#findConnection

  /**
   * Returns a connection to host a new stream. This prefers the existing connection if it exists,
   * then the pool, finally building a new connection.
   */
  //返回一个连接来承载一个新的流。如果连接池中存在可用的连接,就使用这个连接,如果没有就创建一个新的连接,然后存入连接池中。  
  private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
      boolean connectionRetryEnabled) throws IOException {
    boolean foundPooledConnection = false;
    RealConnection result = null;
    Route selectedRoute = null;
    Connection releasedConnection;
    Socket toClose;
    synchronized (connectionPool) {
      if (released) throw new IllegalStateException("released");//资源已经被释放
      if (codec != null) throw new IllegalStateException("codec != null");//codec不为空,说明当前有正在处理的流
      if (canceled) throw new IOException("Canceled");//该请求已经被取消// Attempt to use an already-allocated connection. We need to be careful here because our
      // already-allocated connection may have been restricted from creating new streams.
      //先尝试使用之前已经分配的链接,但是这个链接有可能已经不能分配新的Http流
      releasedConnection = this.connection;
      toClose = releaseIfNoNewStreams();//如果当前连接已经不能分配新的流,就释放资源,并返回当前连接的Socket对象,并会把this.connection置为null。
      if (this.connection != null) {//this.connection依旧不为空,说明这个connection可用
        // We had an already-allocated connection and it's good.
        result = this.connection;
        releasedConnection = null;
      }
      if (!reportedAcquired) {
        // If the connection was never reported acquired, don't report it as released!
        releasedConnection = null;
      }
      //this.connection为null,就需要从连接池中获取链接
      if (result == null) {
        // Attempt to get a connection from the pool.
        //获取链接
        Internal.instance.get(connectionPool, address, this, null);
        //找到了一个连接
        if (connection != null) {
          foundPooledConnection = true;
          result = connection;
        } else {
          selectedRoute = route;
        }
      }
    }
    //关闭前面的Socket
    closeQuietly(toClose);if (releasedConnection != null) {
      eventListener.connectionReleased(call, releasedConnection);
    }
    if (foundPooledConnection) {
      eventListener.connectionAcquired(call, result);
    }
    if (result != null) {
      // If we found an already-allocated or pooled connection, we're done.
      //返回链接
      return result;
    }
    //能运行到这里,说明从连接池中没有找到合适的链接。
    //路由选择
    // If we need a route selection, make one. This is a blocking operation.
    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) {
        // Now that we have a set of IP addresses, make another attempt at getting a connection from
        // the pool. This could match due to connection coalescing.
        //现在有了一系列的IP地址,再次尝试从连接池中查找链接。
        List<Route> routes = routeSelection.getAll();
        for (int i = 0, size = routes.size(); i < size; i++) {
          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();
        }// Create a connection and assign it to this allocation immediately. This makes it possible
        // for an asynchronous cancel() to interrupt the handshake we're about to do.
        //经过上面的查找,没有能够从连接池中查找到合适的链接。就需要创建一个新的RealConnection对象。
        route = selectedRoute;
        refusedStreamCount = 0;
        //创建新的RealConnection对象。
        result = new RealConnection(connectionPool, selectedRoute);
        //调用acquire方法,表示就是要使用这个链接
        acquire(result, false);
      }
    }// If we found a pooled connection on the 2nd time around, we're done.
    if (foundPooledConnection) {
      eventListener.connectionAcquired(call, result);
      return result;
    }// Do TCP + TLS handshakes. This is a blocking operation.
    //进行TCP链接,并开始进行handshakes过程,
    result.connect(
        connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled, call, eventListener);
    //更改路由信息数据库
    routeDatabase().connected(result.route());
​
    Socket socket = null;
    synchronized (connectionPool) {
      reportedAcquired = true;// Pool the connection.
      //需要将新创建的RealConnection放入到连接池中
      Internal.instance.put(connectionPool, result);// If another multiplexed connection to the same address was created concurrently, then
      // release this connection and acquire that one.
      //如果同时创建了到同一地址的另一个多路复用连接,则释放此连接并获取该连接。
      if (result.isMultiplexed()) {
        socket = Internal.instance.deduplicate(connectionPool, address, this);
        result = connection;
      }
    }
    closeQuietly(socket);
​
    eventListener.connectionAcquired(call, result);
    //返回连接
    return result;
  }

代码比较长,上述代码流程如下:

  • 如果已经分配过了连接,即this.connection不为null。先尝试使用这个连接,但是这个连接有可能已经不能创建新的Http Stream;

  • 如果不能使用上面的连接,就需要释放这个链接,调用releaseIfNoNewStreams;如果可用,就直接返回这个链接使用;

  • 尝试在连接池中查找可用的链接,调用get方法;

  • 如果在连接池中没有找到,更改路由配置,再次从连接池中查找,调用get方法;

  • 如果依旧没有找到,就需要创建一个新的RealConnection对象,开始建立TCP连接,调用connect方法;调用acquire方法表示使用这个RealConnection对象;

  • 将新创建的RealConnection对象加入到连接池中,调用put方法;

  • 如果当前针对这个地址的链接是一个多路复用的连接,需要去重,只保留一个连接就够用了,调用deduplicate方法。

  • 我们已经获取了可用的链接,返回即可。

所以,我们需要在深入到上面提到的方法中,了解更多的细节:

  • releaseIfNoNewStreams方法;

  • 连接池get方法;

  • acquire方法;

  • 连接池put方法;

  • deduplicate方法;

StreamAllocation#releaseIfNoNewStreams–deallocate

  /**
   * Releases the currently held connection and returns a socket to close if the held connection
   * restricts new streams from being created. With HTTP/2 multiple requests share the same
   * connection so it's possible that our connection is restricted from creating new streams during
   * a follow-up request.
   */
  //如果这个connection对象已经不能创建新的streams,就需要释放持有的connection对象,并把这个connection的socket对象返回前面去关闭它;
  //由于HTTP/2多路请求可以共享同一个连接connection对象,所以有可能后续的请求在这个链接上不能创建新的Stream。
  private Socket releaseIfNoNewStreams() {
    assert (Thread.holdsLock(connectionPool));
    RealConnection allocatedConnection = this.connection;
    if (allocatedConnection != null && allocatedConnection.noNewStreams) {
      return deallocate(false, false, true);//解除分配
    }
    return null;
  }/**
   * Releases resources held by this allocation. If sufficient resources are allocated, the
   * connection will be detached or closed. Callers must be synchronized on the connection pool.
   */
  //释放这个分配占用的资源。  
  //解除分配
  private Socket deallocate(boolean noNewStreams, boolean released, boolean streamFinished) {
    assert (Thread.holdsLock(connectionPool));if (streamFinished) {
      this.codec = null;
    }
    if (released) {
      this.released = true;
    }
    Socket socket = null;
    if (connection != null) {
      if (noNewStreams) {
        connection.noNewStreams = true;
      }
      if (this.codec == null && (this.released || connection.noNewStreams)) {
        release(connection);//释放connection
        if (connection.allocations.isEmpty()) {//如果当前connection的分配记录列表为空了,表示没有流占用了
          connection.idleAtNanos = System.nanoTime();
          //connection置为空闲状态,调用connectionPool的connectionBecameIdle方法
          if (Internal.instance.connectionBecameIdle(connectionPool, connection)) {
            socket = connection.socket();//获取这个连接的socket对象
          }
        }
        connection = null;//StreamAllocation的connection属性置为null
      }
    }
    return socket;
  }/** Remove this allocation from the connection's list of allocations. */
//connection.allocations表示当前connection被分配的记录
  private void release(RealConnection connection) {
    for (int i = 0, size = connection.allocations.size(); i < size; i++) {
      Reference<StreamAllocation> reference = connection.allocations.get(i);
      if (reference.get() == this) {//是否是这个分配记录
        connection.allocations.remove(i);//从列表中删除
        return;
      }
    }
    throw new IllegalStateException();
  }


来看一下ConnectionPool的connectionBecameIdle方法:

ConnectionPool#connectionBecameIdle

/**
   * Notify this pool that {@code connection} has become idle. Returns true if the connection has
   * been removed from the pool and should be closed.
   */
  //通知连接池这个connection变成空闲状态。如果连接connection被删除了且被关闭了就返回true。
  boolean connectionBecameIdle(RealConnection connection) {
    assert (Thread.holdsLock(this));
    if (connection.noNewStreams || maxIdleConnections == 0) {
      connections.remove(connection);//从列表中删除这个链接
      return true;
    } else {
      notifyAll(); // Awake the cleanup thread: we may have exceeded the idle connection limit.
      return false;
    }
  }

ConnectionPool#get

/**
   * Returns a recycled connection to {@code address}, or null if no such connection exists. The
   * route is null if the address has not yet been routed.
   */
  //根据Address返回一个循环使用的链接,如果没有返回null
  @Nullable RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
    assert (Thread.holdsLock(this));
    //循环遍历连接池中的connections
    for (RealConnection connection : connections) {
      //判断是否符合条件。判断Address内容中非主机部分和主机部分是否都符合
      if (connection.isEligible(address, route)) {
        streamAllocation.acquire(connection, true);//connection符合条件,使用这个connection,并且添加connection的分配记录。
        return connection;//返回这个connection。
      }
    }
    return null;
  }


RealConnection#isEligible

  //如果这个连接可以承载一个这个address对应的Http 流,就返回true,代表这个这个连接可用
  public boolean isEligible(Address address, @Nullable Route route) {
    // If this connection is not accepting new streams, we're done.
    //如果当前链接被分配次数allocations已经达到最高次数,或者noNewStreams = true,都表示这个链接不能再分配新的流了。
    if (allocations.size() >= allocationLimit || noNewStreams) return false;
    // If the non-host fields of the address don't overlap, we're done.
    //如果地址Address内容的非主机部分不匹配,表示这个连接也不能使用
    if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;
    // If the host exactly matches, we're done: this connection can carry the address.
    //主机部分匹配的话,就返回true
    if (address.url().host().equals(this.route().address().url().host())) {
      return true; // This connection is a perfect match.
    }
    
......
  
  }

StreamAllocation#acquire

  /**
   * Use this allocation to hold {@code connection}. Each call to this must be paired with a call to
   * {@link #release} on the same connection.
   */
  //使用这个分配的connection。
  public void acquire(RealConnection connection, boolean reportedAcquired) {
    assert (Thread.holdsLock(connectionPool));
    if (this.connection != null) throw new IllegalStateException();
​
    this.connection = connection;
    this.reportedAcquired = reportedAcquired;
    //这个链接的allocations添加StreamAllocation。StreamAllocationReference是WeakReference子类
    connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
  }

acquire方法其实就是connection的allocations分配记录中添加一条分配记录。

ConnectionPool#put

//没有从连接池中获取所需要的RealConnection对象,创建了一个新的RealConnection对象,放入到连接池中
void put(RealConnection connection) {
    assert (Thread.holdsLock(this));
  //cleanupRunning表示清理链接的任务是否正在进行中。如果没在进行中,需要先重新开始清理任务
    if (!cleanupRunning) {
      cleanupRunning = true;
      executor.execute(cleanupRunnable);
    }
  //放入到连接池中
    connections.add(connection);
  }

ConnectionPool#deduplicate

/**
   * Replaces the connection held by {@code streamAllocation} with a shared connection if possible.
   * This recovers when multiple multiplexed connections are created concurrently.
   */
  //当多个可以多路复中的链接同时被创建,需要去除重复的链接
  @Nullable Socket deduplicate(Address address, StreamAllocation streamAllocation) {
    assert (Thread.holdsLock(this));
    for (RealConnection connection : connections) {//循环遍历
      if (connection.isEligible(address, null)
          && connection.isMultiplexed()
          && connection != streamAllocation.connection()) {
        return streamAllocation.releaseAndAcquire(connection);
      }
    }
    return null;
  }

RealConnection#isMultiplexed

  /**
   * Returns true if this is an HTTP/2 connection. Such connections can be used in multiple HTTP
   * requests simultaneously.
   */
  //HTTP/2 connection可以作为多路复用的链接
  public boolean isMultiplexed() {
    return http2Connection != null;
  }

StreamAllocation#releaseAndAcquire

/**
   * Release the connection held by this connection and acquire {@code newConnection} instead. It is
   * only safe to call this if the held connection is newly connected but duplicated by {@code
   * newConnection}. Typically this occurs when concurrently connecting to an HTTP/2 webserver.
   *
   * 

Returns a closeable that the caller should pass to {@link Util#closeQuietly} upon completion * of the synchronized block. (We don't do I/O while synchronized on the connection pool.) */ public Socket releaseAndAcquire(RealConnection newConnection) { assert (Thread.holdsLock(connectionPool)); if (codec != null || connection.allocations.size() != 1) throw new IllegalStateException();// Release the old connection. //删除旧的connection Reference<StreamAllocation> onlyAllocation = connection.allocations.get(0); Socket socket = deallocate(true, false, false);// Acquire the new connection. //使用新的链接 this.connection = newConnection; newConnection.allocations.add(onlyAllocation);return socket; }

还需要再回到StreamAllocation#findHealthyConnection方法中。上述这么多方法会返回一个可用的RealConnection对象,在findHealthyConnection中还需要在进行isHealthy方法进行状态检测:

/** Returns true if this connection is ready to host new streams. */
  public boolean isHealthy(boolean doExtensiveChecks) {
    //如果socket已经被关闭,或者socket的输入流关闭,如果socket的输出流被关闭,都表示这个Connection不可用了
    if (socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) {
      return false;
    }
    //如果是http2Connection,判断是否Shutdown
    if (http2Connection != null) {
      return !http2Connection.isShutdown();
    }
    //做额外的检测
    if (doExtensiveChecks) {
      try {
        int readTimeout = socket.getSoTimeout();
        try {
          socket.setSoTimeout(1);
          if (source.exhausted()) {
            return false; // Stream is exhausted; socket is closed.
          }
          return true;
        } finally {
          socket.setSoTimeout(readTimeout);
        }
      } catch (SocketTimeoutException ignored) {
        // Read timed out; socket is good.
      } catch (IOException e) {
        return false; // Couldn't read; socket is closed.
      }
    }return true;
  }

上述代码主要是判断当前的链接状态是否可用:

  • socket是否关闭,socket的输入/输出流是否关闭;

  • 如果是http2Connection,判断是否shutdown;

  • 还需要再做额外的检测;

如果上述isHealthy返回false,则会执行noNewStreams方法,主要是释放资源,并将RealConnection的noNewStreams属性置为true,表示当前connection不在创建新的流。

再通过上面的链接状态监测,我们真正获得了一个可以使用的RealConnection对象。接下来,回到StreamAllocation#newStream方法中,还需要继续看一下:

RealConnection#newCodec

//创建HttpCodec对象
public HttpCodec newCodec(OkHttpClient client, Interceptor.Chain chain,
      StreamAllocation streamAllocation) throws SocketException {
    if (http2Connection != null) {//HTTP/2协议下的链接
      return new Http2Codec(client, chain, streamAllocation, http2Connection);
    } else {//HTTP/1协议下的链接
      socket.setSoTimeout(chain.readTimeoutMillis());
      source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);
      sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);
      return new Http1Codec(client, streamAllocation, source, sink);
    }
  }

这样,再回到ConnectInterceptor的intercept方法,我们就执行到了realChain.proceed方法,就执行到了写一个拦截器CallServerInterceptor。

为了更清晰的了解这个流程,画个流程图:

其实还有一些结论:

  • 一个StreamAllocation对应着一个HttpCodec对象,StreamAllocation可以根据Http流的状态来操作HttpCodec对象;即StreamAllocation负责创建和销毁HttpCodec对象;

  • StreamAllocation负责将HttpCodec和RealConnection对象绑定;

  • StreamAllocation负责在流传输完成后,也就是read Response的操作完成后,进行StreamFinish的操作,即进行资源的回收。这将驱动RealConnection和ConnectionPool进行资源清理;

下面我们看一下,流传输完成后,如何进行资源清理工作:

你可能感兴趣的:(okhttp)