OkHttp源码学习之5 ConnectInterceptor

Response getResponseWithInterceptorChain() throws IOException {
    // 构建一整套拦截器
    List interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());//1
    interceptors.add(retryAndFollowUpInterceptor);//2
    interceptors.add(new BridgeInterceptor(client.cookieJar()));//3
    interceptors.add(new CacheInterceptor(client.internalCache()));//4
    interceptors.add(new ConnectInterceptor(client));//5
    //构建一个RealCall的时候我们传入的forWebSocket是false
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());//6
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));//7
    //构建拦截器链
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, 
    null, 0,originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());
    //拦截器链处理请求
    return chain.proceed(originalRequest);
}

CacheInterceptor(缓存拦截器)的intercept方法中调用了RealInterceptorChain的proceed方法,内部会调用ConnectInterceptor的intercept方法。

ConnectInterceptor的intercept方法。

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();
        //注释1处,获取StreamAllocation
        StreamAllocation streamAllocation = realChain.streamAllocation();

        // We need the network to satisfy this request. Possibly for validating a conditional GET.
        boolean doExtensiveHealthChecks = !request.method().equals("GET");
        //注释2处,获取HttpCodec对象
        HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
        //注释3处,获取RealConnection对象
        RealConnection connection = streamAllocation.connection();
        //交给下一个拦截器处理
        return realChain.proceed(request, streamAllocation, httpCodec, connection);
    }
}

注释1处,获取StreamAllocation,我们在RetryAndFollowUpInterceptor的intercept方法中创建了StreamAllocation实例。

注释2处,获取HttpCodec对象,StreamAllocation的newStream方法。

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();
    //默认是true
    boolean connectionRetryEnabled = client.retryOnConnectionFailure();

    try {
        //注释1处,获取健康的连接
        RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
        writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
        //注释2处,返回HttpCodec对象。
        HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);

        synchronized (connectionPool) {
            //保存生成的HttpCodec对象
            codec = resultCodec;
            return resultCodec;
        }
    } catch (IOException e) {
        throw new RouteException(e);
    }
}

注释1处,获取健康的连接。

private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
        int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
        boolean doExtensiveHealthChecks) throws IOException {
    //死循环,直到找到一个连接
    while (true) {
        //注释1处,调用findConnection方法。
        RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
        pingIntervalMillis, connectionRetryEnabled);

        // 如果这是一个全新的连接,跳过额外的健康检查。
        synchronized (connectionPool) {
            if (candidate.successCount == 0) {
                return candidate;
            }
        }

        //执行(可能较慢)检查以确认缓存的连接仍然良好。如果不是,把它从缓存中移除,然后重新while循环,获取健康的连接。
        if (!candidate.isHealthy(doExtensiveHealthChecks)) {
            noNewStreams();
            continue;
        }

        return candidate;
    }
}

findHealthyConnection方法的注释1处,StreamAllocation的findConnection方法。

/**
 * 返回一个连接来承载新的stream。这会优先选择现有连接(如果存在),然后是缓存池里的连接,最后构建一个新连接。
 */
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
    Socket toClose;
    synchronized (connectionPool) {
        if (released) throw new IllegalStateException("released");
        if (codec != null) throw new IllegalStateException("codec != null");
        if (canceled) throw new IOException("Canceled");

        //先尝试使用一个已分配的连接。这里我们需要小心一点,
        //因为我们已分配的连接可能已经禁止创建新的stream了。
        releasedConnection = this.connection;
        //注释1处,看看是否要释放当前的连接。如果要释放的话,toClose不为null,connection为null。
        toClose = releaseIfNoNewStreams();
        if (this.connection != null) {
            //注释2处,connection不为null,说明我们有一个已分配的连接,并且是健康可用的。
            result = this.connection;
            releasedConnection = null;
        }
        if (!reportedAcquired) {
            // If the connection was never reported acquired, don't report it as released!
            releasedConnection = null;
        }

        if (result == null) {
            // 注释3处,connection为null,则尝试从连接池里获取连接。如果从连接池获取到了连接,connection不为null
            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) {
        //注释4处,返回已分配的连接或者从连接池中获取的连接。
        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) {
            // 注释5处,如果有多个ip地址,更换路由,继续从连接池里查找。这个不是太了解。。。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.
            List 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.
            route = selectedRoute;
            refusedStreamCount = 0;
            //注释6处,最终没有找到的话,新建一个连接,并在acquire方法中,会把创建的连接赋值给connection
            result = new RealConnection(connectionPool, selectedRoute);
            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;
    }

    //注释7处,进行 TCP + TLS 握手。这是一个阻塞操作。
    result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
        connectionRetryEnabled, call, eventListener);
    routeDatabase().connected(result.route());

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

        //注释8处,将连接放进连接池。
        Internal.instance.put(connectionPool, result);

        //注释9处,如果同时创建了另一个到同一地址的多路复用连接,则释放当前连接并获取另外创建的连接。
        if (result.isMultiplexed()) {
            socket = Internal.instance.deduplicate(connectionPool, address, this);
            result = connection;
        }
    }
    closeQuietly(socket);

    eventListener.connectionAcquired(call, result);
    //注释10处,最后返回结果
    return result;
}

注释1处,看看是否要释放当前的连接connection。如果要释放的话,toClose不为null,StreamAllocation的成员变量connection为null。

/**
 *如果当前连接禁止创建新的stream,则释放当前持有的连接并返回一个需要被关闭的socket。对于`HTTP/2`来说的,多个请求共享一个连接,所以在重定向请求的时候,我们的连接禁止创建新的stream。
 */
private Socket releaseIfNoNewStreams() {
    assert (Thread.holdsLock(connectionPool));
    RealConnection allocatedConnection = this.connection;
    //noNewStreams为true,表示该连接不能创建新的Stream了,就释放该连接。
    if (allocatedConnection != null && allocatedConnection.noNewStreams) {
      return deallocate(false, false, true);
    }
    return null;
}

StreamAllocation的deallocate方法。

/**
 * 释放当前StreamAllocation持有的资源。如果分配了足够的资源,将断开或关闭连接。调用者必须持有连接池的锁。
 * 返回一个Closeable对象,调用者应该在同步代码块结束的时候传递给 {@link Util#closeQuietly} 方法。
 */
private Socket deallocate(boolean noNewStreams, boolean released, boolean streamFinished) {
    assert (Thread.holdsLock(connectionPool));

    if (streamFinished) {
      //将codec置为null
      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)) {
        //注释1处,释放连接,会将connection置为null,并关闭socket连接。
        release(connection);
        if (connection.allocations.isEmpty()) {
          connection.idleAtNanos = System.nanoTime();
          if (Internal.instance.connectionBecameIdle(connectionPool, connection)) {
            //连接从连接池里被移除了,并且应该被关闭。返回该连接的socket,调用者会关闭这个socket
            socket = connection.socket();
          }
        }
        //注意这里将connection置为null
        connection = null;
      }
    }
    return socket;
}

我们回到StreamAllocation的findConnection方法的注释2处,connection不为null,说明我们有一个已分配的连接,并且是健康可用的。

注释3处,connection为null,则尝试从连接池里获取连接。最终是调用ConnectionPool的get方法来获取的。

@Nullable
RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
    assert (Thread.holdsLock(this));
    for (RealConnection connection : connections) {
        //注释1处,如果存在合适的连接,就返回。调用StreamAllocation注意这里会给StreamAllocation的connection赋值。
        if (connection.isEligible(address, route)) {
            streamAllocation.acquire(connection, true);
            return connection;
        }
    }
    return null;
}

StreamAllocation的acquire方法。

public void acquire(RealConnection connection, boolean reportedAcquired) {
    assert (Thread.holdsLock(connectionPool));
    if (this.connection != null) throw new IllegalStateException();
    //给connection赋值
    this.connection = connection;
    this.reportedAcquired = reportedAcquired;
    connection.allocations.add(new StreamAllocation.StreamAllocationReference(this, callStackTrace));
}

我们回到StreamAllocation的findConnection方法的注释4处,返回已分配的连接(就是StreamAllocation 的 connection成员变量)或者从连接池中获取的连接。

注释5处,如果有多个ip地址,更换路由,继续从连接池里查找。这个不是太了解。。。暂时略过。

注释6处,最终没有找到的话,新建一个连接,并在acquire方法中,会把创建的连接赋值给connection。

注释7处,进行 TCP + TLS 握手。这是一个阻塞操作。 这里就是发生三次握手的地方。(八股文:能说一下三次握手和四次挥手的原理吗,哈哈。)

注释8处,将连接放进连接池ConnectionPool。

注释9处,如果同时创建了另一个到同一地址的多路复用连接,则释放当前连接并获取另外创建的连接。

注释10处,最后返回结果。

我们回到StreamAllocation的newStream方法的注释2处,返回HttpCodec对象。

ConnectInterceptor的intercept方法的注释3处,获取StreamAllocation的connection对象交给下一个拦截器进行处理。

你可能感兴趣的:(OkHttp源码学习之5 ConnectInterceptor)