关于 Okhttp3(七)-ConnectInterceptor

1,867 阅读5分钟

上篇我们将来缓存拦截器,鉴于本系列是将Okhttp的所以就没有将太多关于缓存相关的东西,后面又机会在开一个系列。今天我们讲讲倒数第二个拦截器—连接拦截器,这才是真正的开始向服务端发起进攻,同志们是不是已经急不可耐了,哈哈,马上到来。

源码

  1. 获取到最初实例化的通道对象
  2. 得到一个httpcodec
  3. 得到一个连接对象
@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, doExtensiveHealthChecks);
  RealConnection connection = streamAllocation.connection();
  return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }

此处的逻辑很简单,因为已经转嫁到其他的类对象上去了。前面我们提到过RealInterceptorChain构造函数有四个重要的属性(Request、StreamAllocation, HttpCodec、Connection)第一个不用说一开始就必须有,第二个是在重试拦截器中实例化的,第三、第四就是在本节中腰实例化出来的。

获取httpcodec

通过StreamAllocation的newStream()方法可以的得到一个HttpCodec。

public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
 // 略
  try {
    // 1.查找是否有完好的连接,仅在http2.0有用,如果没有将会实例化一个
    RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
        writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
    // 2.实例化httpcodec,如果是http2.0实例化Http2Codec否则Http2Codec
    HttpCodec resultCodec = resultConnection.newCodec(client, this);
    synchronized (connectionPool) {
      codec = resultCodec;
      return resultCodec;
    }
  } catch (IOException e) {
    throw new RouteException(e);
  }
  }
  1. 对于健康连接的判断
    1. socket没有关闭
    2. 输入流没有关闭
    3. 输出流没有关闭
    4. http2时连接没有关闭

获取连接并连接

流程:

  1. 上一个连接是否完好,是,直接使用
  2. 缓存中有没有可以用的,有,使用
  3. 自己创建
  4. 握手、连接
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");
    // 使用已存在的连接
    // Attempt to use an already-allocated connection.
    RealConnection allocatedConnection = this.connection;
    if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
      return allocatedConnection;
    }
    // 从缓存中获取
    // Attempt to get a connection from the pool.
    Internal.instance.get(connectionPool, address, this);
    if (connection != null) {
      return connection;
    }
    selectedRoute = route;
  }
  // 线路的选择,多ip的支持
  // If we need a route, make one. This is a blocking operation.
  if (selectedRoute == null) {
    // 里面又个神奇的递归
    selectedRoute = routeSelector.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 result;
  synchronized (connectionPool) {
    route = selectedRoute;
    refusedStreamCount = 0;
    result = new RealConnection(connectionPool, selectedRoute);
    acquire(result);
    if (canceled) throw new IOException("Canceled");
  }
  // 连接 并握手
  // Do TCP + TLS handshakes. This is a blocking operation.
  result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
  routeDatabase().connected(result.route());
  Socket socket = null;
  // 放到缓存中
  synchronized (connectionPool) {
    // Pool the connection.
    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);
  return result;
  }

连接

建立连接是比较重要的一步了。如果是Https还有证书步骤

public void connect(
    int connectTimeout, int readTimeout, int writeTimeout, boolean connectionRetryEnabled) {
  if (protocol != null) throw new IllegalStateException("already connected");
  // 线路的选择
  RouteException routeException = null;
  List<ConnectionSpec> connectionSpecs = route.address().connectionSpecs();
  ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);
  if (route.address().sslSocketFactory() == null) {
    if (!connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
      throw new RouteException(new UnknownServiceException(
          "CLEARTEXT communication not enabled for client"));
    }
    String host = route.address().url().host();
    if (!Platform.get().isCleartextTrafficPermitted(host)) {
      throw new RouteException(new UnknownServiceException(
          "CLEARTEXT communication to " + host + " not permitted by network security policy"));
    }
  }
  // 连接开始
  while (true) {
    try {
    // 如果要求通道模式,建立通道连接,通常不是这种
      if (route.requiresTunnel()) {
        connectTunnel(connectTimeout, readTimeout, writeTimeout);
      } else {
      // 一般都走这条逻辑了,实际上很简单就是socket的连接
        connectSocket(connectTimeout, readTimeout);
      }
      // https的建立
      establishProtocol(connectionSpecSelector);
      break;
    } catch (IOException e) {
      closeQuietly(socket);
      closeQuietly(rawSocket);
      socket = null;
      rawSocket = null;
      source = null;
      sink = null;
      handshake = null;
      protocol = null;
      http2Connection = null;
      if (routeException == null) {
        routeException = new RouteException(e);
      } else {
        routeException.addConnectException(e);
      }
      if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
        throw routeException;
      }
    }
  }
  if (http2Connection != null) {
    synchronized (connectionPool) {
      allocationLimit = http2Connection.maxConcurrentStreams();
    }
  }
  }

我们进入connectSocket()函数看看,

private void connectSocket(int connectTimeout, int readTimeout) throws IOException {
  Proxy proxy = route.proxy();
  Address address = route.address();
  // 根据代理类型处理socket,为true
  rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
      ? address.socketFactory().createSocket()
      : new Socket(proxy);
  rawSocket.setSoTimeout(readTimeout);
  try {
    // 连接socket,之所以这样写是因为支持不同的平台
    /**
    * 里面实际上是  socket.connect(address, connectTimeout);
    *
    */
    Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
  } catch (ConnectException e) {
    ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress());
    ce.initCause(e);
    throw ce;
  }
  // 得到输入/输出流
  source = Okio.buffer(Okio.source(rawSocket));
  sink = Okio.buffer(Okio.sink(rawSocket));
  }

如果使用的是https协议,并且配置有真实将会协议升级。

Https协议的建立

如果使用的是https协议socket连接完成后还有一步,就是Tls的处理

private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IOException {
  Address address = route.address();
  SSLSocketFactory sslSocketFactory = address.sslSocketFactory();
  boolean success = false;
  SSLSocket sslSocket = null;
  try {
    // 在原来socket上加一层ssl
    // 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();
    Handshake unverifiedHandshake = Handshake.get(sslSocket.getSession());
    // Verify that the socket's certificates are acceptable for the target host.
    if (!address.hostnameVerifier().verify(address.url().host(), sslSocket.getSession())) {
      X509Certificate cert = (X509Certificate) unverifiedHandshake.peerCertificates().get(0);
      throw new SSLPeerUnverifiedException("Hostname " + address.url().host() + " not verified:"
          + "\n    certificate: " + CertificatePinner.pin(cert)
          + "\n    DN: " + cert.getSubjectDN().getName()
          + "\n    subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert));
    }
    // 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;
  } catch (AssertionError e) {
    if (Util.isAndroidGetsocknameError(e)) throw new IOException(e);
    throw e;
  } finally {
    if (sslSocket != null) {
      Platform.get().afterHandshake(sslSocket);
    }
    if (!success) {
      closeQuietly(sslSocket);
    }
  }
  }

如果对https熟悉的话,应该知道,https就是在http的基础上加了一层。

到此链接完成RealConnection实例化完成。

然后就是进行下一拦截器。

总结

这个拦截器原理流程都比较简单,重点是要把Socket和Http协议相关知识理解清楚。此文知识抛砖引玉,里面的一些细节没有讲,建议读者更具这系列文章自己去梳理。

系列文章

  1. 关于Okhttp(一)-基本使用
  2. 关于Okhttp(二)-如何下载查看源码
  3. 关于Okhttp3(三)-请求流程
  4. 关于Okhttp3(四)-RetryAndFollowUpInterceptor
  5. 关于Okhttp3(五)-BridgeInterceptor
  6. 关于Okhttp3(六)-CacheInterceptor
  7. 关于Okhttp3(七)-ConnectInterceptor
坚持原创技术分享,您的支持将鼓励我继续创作! 赏 lowett WeChat Pay

微信打赏