okHttp连接流程

在okhttp发起请求的调用链中,在发起请求之前需要建立连接,负责建立连接的就是ConnectInterceptor.intercept了

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

  // 在RetryAndFollowUpInterceptor中new出来的,传递到这里
  // streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()),
  //      call, eventListener, callStackTrace);
  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);
}

代码很简单,就是调用streamAllocation.newStream建立连接,一共两步,建立连接的是第一步

public HttpCodec newStream(
    OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
  // ...
  // 1. 找到可以使用的链接,可能是复用的,也可能是新建的
  RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
      writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
  // 2. 创建HttpCodec实例,用于后续的请求
  HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
  // ...
}

findHealthyConnection主要是调用findConnection来建立连接,直接看findConnection的实现

private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
    boolean connectionRetryEnabled) throws IOException {
  // ...
  synchronized (connectionPool) {
    // 1. 首先尽量寻找可以用的连接,做到尽量的复用
    releasedConnection = this.connection;
    // 判断当前连接(如果有的话)是否需要释放,有的连接不允许复用,通过noNewStreams变量来标识
    toClose = releaseIfNoNewStreams();
    if (this.connection != null) {
      // 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;
    }

    if (result == null) {
      // 先去看看okhttpClient.connectPool里面有没有可以复用的连接
      Internal.instance.get(connectionPool, address, this, null);
      if (connection != null) {
        foundPooledConnection = true;
        result = connection;
      } else {
        selectedRoute = route;
      }
    }
  }
  closeQuietly(toClose);

  if (releasedConnection != null) {
    eventListener.connectionReleased(call, releasedConnection);
  }
  if (foundPooledConnection) {
    eventListener.connectionAcquired(call, result);
  }
  // 如果在okhttpClient.connectPool里找到了可用的连接,就可以直接返回了
  if (result != null) {
    // If we found an already-allocated or pooled connection, we're done.
    return result;
  }

  // 如果是第一次进来的话,selectedRoute和routeSelection肯定都是空的
  // 经过赋值以后,这里的routeSelection就是在RouteSelector里new出来的,如下,包含了本次请求的信息
  // new Route(address, proxy, inetSocketAddresses.get(i));
  // new Selection(routes)
  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++) {
        Route route = routes.get(i);
        Internal.instance.get(connectionPool, address, this, route);
        if (connection != null) {
          foundPooledConnection = true;
          result = connection;
          this.route = route;
          break;
        }
      }
    }

    // 如果经过上面的流程都没有找到可以复用的连接,那么就new一个新的
    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;
      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;
  }

  // 2. 经过前面的过程,找到(或者new)了一个可用的连接,这里做tcp握手
  result.connect(
      connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled, call, eventListener);
  routeDatabase().connected(result.route());

  // 3. 把可用连接保存在connectPool中
  Socket socket = null;
  synchronized (connectionPool) {
    reportedAcquired = true;

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

  eventListener.connectionAcquired(call, result);
  return result;
}

这个方法有点长,但是逻辑很清晰,如代码中注释的那样,主要是3部分
- 找到可以复用的连接,如果找不到,就new一个
- 用这个连接进行握手
- 存储这个连接,供后续其他请求复用

连接的真正处理部分是RealConnection.connect方法,看看它的处理

public void connect(int connectTimeout, int readTimeout, int writeTimeout,
    boolean connectionRetryEnabled, Call call, EventListener eventListener) {

  RouteException routeException = null;
  // ConnectionSpec是连接支持的加密算法、支持的协议的一个描述,okHttpClient里的默认值是
  // static final List<ConnectionSpec> DEFAULT_CONNECTION_SPECS = Util.immutableList(
  //    ConnectionSpec.MODERN_TLS, ConnectionSpec.CLEARTEXT);
  // ConnectionSpec.MODERN_TLS表示TLS,其中包括支持的加密算法,可以在CipherSuite类中找到定义,支持的TLS协议,包括从SSLv3到TLSv1.3
  // ConnectionSpec.CLEARTEXT表示HTTP,不支持任何加密算法和TLS
  List<ConnectionSpec> connectionSpecs = route.address().connectionSpecs();
  ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);

  if (route.address().sslSocketFactory() == null) {
    if (!connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
      // 如果不需要ssl,但是connectionSpecs禁用了http,那么抛出异常
    }
    // 这里的Platform.get()得到的是AnroidPlatform
    String host = route.address().url().host();
    // 通过反射调用android系统类NetworkSecurityPolicy,检查android是否允许下列协议
    // Returns whether cleartext network traffic (e.g. HTTP, FTP, WebSockets, XMPP, IMAP, SMTP --
    // * without TLS or STARTTLS) is permitted for this process.
    if (!Platform.get().isCleartextTrafficPermitted(host)) {
      // 不允许就直接抛出异常
    }
  }

  while (true) {
    try {
      if (route.requiresTunnel()) {
        connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
        if (rawSocket == null) {
          // We were unable to connect the tunnel but properly closed down our resources.
          break;
        }
      } else {
        connectSocket(connectTimeout, readTimeout, call, eventListener);
      }
      establishProtocol(connectionSpecSelector, call, eventListener);
      eventListener.connectEnd(call, route.socketAddress(), route.proxy(), protocol);
      break;
    } catch (IOException e) {
      // 异常处理
    }
  }
  // 异常处理和http2.0处理
}
  • 首先进行异常检查
  • 根据是否需要建立隧道调用不同的方法
  • 异常处理

这里聚焦于连接的方法,当请求是https但是代理是http的时候,就需要建立隧道

不需要建立隧道
private void connectSocket(int connectTimeout, int readTimeout, Call call,
    EventListener eventListener) throws IOException {
  Proxy proxy = route.proxy();
  Address address = route.address();

  // 没有代理的时候,代理类型是DIRECT,表示直接连接,这里的address.socketFactory().createSocket()
  // 默认就是new Socket,需要代理的话就是new Socket(proxy)
  rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
      ? address.socketFactory().createSocket()
      : new Socket(proxy);

  eventListener.connectStart(call, route.socketAddress(), proxy);
  rawSocket.setSoTimeout(readTimeout);
  try {
    // 这里调用的是AndroidPlatform,实际调用的方法就是rawSocket.connect
    Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
  } catch (ConnectException e) {
    // ...
  }
  try {
    source = Okio.buffer(Okio.source(rawSocket));
    sink = Okio.buffer(Okio.sink(rawSocket));
  } catch (NullPointerException npe) {
    // ...
  }
}

可以看到建立连接的方式很简单,就是调用Socket.connect

需要建立隧道
private void connectTunnel(int connectTimeout, int readTimeout, int writeTimeout, Call call,
    EventListener eventListener) throws IOException {
  // 1. 首先创建tunnelRequest,仅包含url/host/Proxy-Connection/User-Agent的request
  // 因为跟http代理之间的请求是未加密的,所以header里面尽可能少的包含信息
  Request tunnelRequest = createTunnelRequest();
  HttpUrl url = tunnelRequest.url();
  for (int i = 0; i < MAX_TUNNEL_ATTEMPTS; i++) {
    // 2. 建立跟代理之间的socket连接
    connectSocket(connectTimeout, readTimeout, call, eventListener);
    // 3. 使用CONNECT请求建立一条隧道
    tunnelRequest = createTunnel(readTimeout, writeTimeout, tunnelRequest, url);
    // 4. 建立成功调处循环
    if (tunnelRequest == null) break; // Tunnel successfully created.
    // 5. 清理工作,为下次重试做准备
    // ...
  }
}

逻辑也是比较清楚的

连接后进行握手

在建立连接之后要进行握手,握手过程可以参考, RealConnection通过establishProtocol方法完成,该方法又调用了connectTls

private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IOException {
  Address address = route.address();
  SSLSocketFactory sslSocketFactory = address.sslSocketFactory();
  boolean success = false;
  SSLSocket sslSocket = null;
  try {
    // 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());
    }

    // 这里得到的SSLSocketFactory和SSLSocket分别是OpenSSLSocketFactoryImpl和OpenSSLSessionImpl
    // 代码都在android源码external/conscrypt中
    // 握手执行的native代码也在该工程中,对应代码文件org_conscrypt_NativeCrypto.cpp
    // 在握手过程中,会回调验证证书,验证不过直接抛出异常
    sslSocket.startHandshake();
    Handshake unverifiedHandshake = Handshake.get(sslSocket.getSession());

    // 证书验证通过之后校验域名
    if (!address.hostnameVerifier().verify(address.url().host(), sslSocket.getSession())) {
      // ....
    }

    // 指纹校验,默认是空,也就是不校验。
    // 指纹校验可以参考https://www.jianshu.com/p/1fdbcfcc962c
    // 和https://imququ.com/post/http-public-key-pinning.html
    address.certificatePinner().check(address.url().host(),
        unverifiedHandshake.peerCertificates());

    // ...
  } catch (AssertionError e) {
    // ...
  } finally {
    // ...
  }
}

至此,连接建立完毕,回到开始的StreamAllocation.newStream方法,创建HttpCodec实例后ConnectInterceptor的工作就完成了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值