Okhttp之ConnectInterceptor拦截器原理及解析

因为Okhttp中拦截器都是责任链设计模式,这里直接看intercept()方法即可。
先来献上高清无码图,方便更好的理解其原理

《Okhttp之ConnectInterceptor拦截器原理及解析》 image.png

1. ConnectInterceptor核心代码:

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

    boolean doExtensiveHealthChecks = !request.method().equals("GET");

    //看来核心代码是newStream();获取到httpCodec
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    //按照字面意思是获取一个连接
    RealConnection connection = streamAllocation.connection();

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

可以看出它的核心方法为:streamAllocation.newStream(client, chain, doExtensiveHealthChecks) 那么该方法到底做了什么事?

2. 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();
    boolean connectionRetryEnabled = client.retryOnConnectionFailure();

    RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
          writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
    HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
        ...
    return resultCodec;
      }
 }
  • 2.1 首先看到的是在这里设置读写和连接时间,和获取client中的配置,是否请求重试设置,最关键的事 findHealthyConnection(….),通过字面意思,是获取一个有效/健康的连接,看看findHealthyConnection()的代码,则看到有一个while(true)的死循环,他会一直去找一个连接,知道找到为止。
    while (true) {
      RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
          pingIntervalMillis, connectionRetryEnabled);

      // If this is a brand new connection, we can skip the extensive health checks.
      synchronized (connectionPool) {
        if (candidate.successCount == 0) {
          return candidate;
        }
      }
      ...
      return candidate;
    }

到这里还是没有发现具体怎么找到的连接,可以看到上面有一个findConnection()方法,再来看看这个方法是具体如何找到连接的?

    RealConnection result = null;
    Route selectedRoute = null;
    Connection releasedConnection;
    ...    
    //从连接池中获取可用的连接
    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;
      }
    }
   if (result != null) {
      // If we found an already-allocated or pooled connection, we're done.
      return result;
    }
    ...
    synchronized (connectionPool) {
      ...
      if (newRouteSelection) {
        //获取路由表中的信息,并且缓存起来
        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;
            break;
          }
        }
      }
      ...
        //如果从缓存中更没有找到连接,则new 一个新的连接
        result = new RealConnection(connectionPool, selectedRoute);
        acquire(result, false);
      }
    }
    //http的三次握手操作 Do TCP + TLS handshakes. This is a blocking operation.
    result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
        connectionRetryEnabled, call, eventListener);
    routeDatabase().connected(result.route());

    Socket socket = null;
    synchronized (connectionPool) {
      reportedAcquired = true;
      //将连接放入连接池 Pool the connection.
      Internal.instance.put(connectionPool, result);

    }
    ...
    return result;
  }

即如果找到从连接池中获取可用的连接,如果没有则创建,并且重新缓存到连接池中。

  • 2.2 再来看看,获取到连接以后,okhttp做了什么事?
 HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);

可以看到newCodec中所做的事,可以看出是将streamAllocation和source,sink做了封装,可以看到这里用到了okio中的流的封装。

  public HttpCodec newCodec(OkHttpClient client, Interceptor.Chain chain,
      StreamAllocation streamAllocation) throws SocketException {
    if (http2Connection != null) {
      return new Http2Codec(client, chain, streamAllocation, http2Connection);
    } else {
      socket.setSoTimeout(chain.readTimeoutMillis());
      source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);
      sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);
      return new Http1Codec(client, streamAllocation, source, sink);
    }
  }

3.new RealConnection(connectionPool, selectedRoute)中的实现

RealConnection result = new RealConnection(connectionPool, selectedRoute);

new RealConnection()做了什么事,可以从源码中看出,里面有好几个connetXX的方法和地址的封装,但是connetXX方法中都会有socket和okio的封装,即如下代码connectTls()方法实现:

private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IOException {
    Address address = route.address();
    SSLSocketFactory sslSocketFactory = address.sslSocketFactory();
    boolean success = false;
    SSLSocket sslSocket = null;

      // Create the wrapper over the connected socket.
      sslSocket = (SSLSocket) sslSocketFactory.createSocket(
          rawSocket, address.url().host(), address.url().port(), true /* autoClose */);

      // Configure the socket's ciphers, TLS versions, and extensions.
      ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
      if (connectionSpec.supportsTlsExtensions()) {
        Platform.get().configureTlsExtensions(
            sslSocket, address.url().host(), address.protocols());
      }

      // Force handshake. This can throw!
      sslSocket.startHandshake();
      // block for session establishment
      SSLSession sslSocketSession = sslSocket.getSession();
      Handshake unverifiedHandshake = Handshake.get(sslSocketSession);

      // Check that the certificate pinner is satisfied by the certificates presented.
      address.certificatePinner().check(address.url().host(),
          unverifiedHandshake.peerCertificates());

      // Success! Save the handshake and the ALPN protocol.
      String maybeProtocol = connectionSpec.supportsTlsExtensions()
          ? Platform.get().getSelectedProtocol(sslSocket)
          : null;
      socket = sslSocket;
      source = Okio.buffer(Okio.source(socket));
      sink = Okio.buffer(Okio.sink(socket));
      handshake = unverifiedHandshake;
      protocol = maybeProtocol != null
          ? Protocol.get(maybeProtocol)
          : Protocol.HTTP_1_1;
      success = true;
}

到这里可以得出如下结论:OkHttp 是基于原生的 Socket + okio(原生IO的封装)
HttpCodec 里面封装了 okio 的 Source(输入) 和 Sink (输出),我们通过 HttpCodec 就可以操作 Socket的输入输出,可以向服务器写数据和读取返回数据

总结

ConnectionInterceptor是获取一个RealConnection对象,然后创建Socket链接,封装地址信息,然后调用CallServerInterceptor 来完成Okhttp的整个操作。

    原文作者:OneXzgj
    原文地址: https://www.jianshu.com/p/c725102b3b7e
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞