OKHTTP拦截器ConnectInterceptor的简单分析

OKHTTP异步和同步请求简单分析
OKHTTP拦截器缓存策略CacheInterceptor的简单分析
OKHTTP拦截器ConnectInterceptor的简单分析
OKHTTP拦截器CallServerInterceptor的简单分析
OKHTTP拦截器BridgeInterceptor的简单分析
OKHTTP拦截器RetryAndFollowUpInterceptor的简单分析
OKHTTP结合官网示例分析两种自定义拦截器的区别

先前分析了 Interceptor 拦截器中其中的一步 OKHTTP 缓存策略简单分析

下面分析的是 OKHTTP 是如何与服务端建立连接操作的(仅分析 HTTP1)。在 OKHTTP 底层是通过 SOCKET 的方式于服务端进行连接的,并且在连接建立之后会通过 OKIO 获取通向 server 端的输入流 Source 和输出流 Sink。

这个过程就在 ConnectInterceptor 拦截器中完成的,下面就来关注这个拦截器的具体实现。

拦截器的作用

在 RealCall 内部维护了一个 interceptors 的集合,通过以下方法 getResponseWithInterceptorChain 去对原生的 originalRequest 进行处理,内部会通过 chain.procee(originalRequest) 最终得到一个 Response 对象。而 Intercaptor.Chain 内部会封装当前需要处理的 interceptor 对象。其内部会调用 interceptor.intercept() 方法去处理这个 request 请求最终得到 response 。

只有执行完 intercaptors 内部所有的 interceptor 才能得到 repsonse 对象。这个执行过程是 intercept() 方法执行的过程,这个过程是一个递归方法调用过程。

private Response getResponseWithInterceptorChain() throws IOException {
  // Build a full stack of interceptors.
  List<Interceptor> interceptors = new ArrayList<>();
  interceptors.addAll(client.interceptors());
  interceptors.add(retryAndFollowUpInterceptor);
  interceptors.add(new BridgeInterceptor(client.cookieJar()));
  interceptors.add(new CacheInterceptor(client.internalCache()));
  interceptors.add(new ConnectInterceptor(client));
  if (!retryAndFollowUpInterceptor.isForWebSocket()) {
    interceptors.addAll(client.networkInterceptors());
  }
  interceptors.add(new CallServerInterceptor(
      retryAndFollowUpInterceptor.isForWebSocket()));
  Interceptor.Chain chain = new RealInterceptorChain(
      interceptors, null, null, null, 0, originalRequest);
  return chain.proceed(originalRequest);
}

ConnectInterceptor

这篇 BLOG 分析其中一个拦截器 ConnectInterceptor ,这的作用就是与服务端 建立连接,并且获得通向服务端的输入和输出流对象。

而 ConnectInterceptor 是 Interceptor 的实现类,关注父亲的功能就可知道它大体具备什么功能。在 Interceptor 接口中只有一个 intercept 方法,他是负责拦截操作的,具体这一层具体的拦截操作就可以在该方法内部实现,最后将 Response 进行返回即可。

Response intercept(Chain chain) throws IOException;

ConnectInterceptor 的功能实现

@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");
  HttpStream httpStream = streamAllocation.newStream(client, doExtensiveHealthChecks);
  RealConnection connection = streamAllocation.connection();
  return realChain.proceed(request, streamAllocation, httpStream, connection);
}

在上面的代码实现可以知道主要做了几件事:

  • StreamAllocation streamAllocation = realChain.streamAllocation();得到 StreamAllocation 对象,纵观对 interceptors 的调用过程可以知道 StreamAllocation 这个对象是在第一个拦截器 RetryAndFollowUpInterceptor 中内部创建的,这个对象创建的比较早,但是到这个拦截才被使用。

  • streamAllocation.newStream 通过这个方法得到一个 HttpStream 这个接口有两个实现类分别是 Http1xStream 和 Http2xStream 现在只分析 Http1xStream ,这个 Http1xStream 流是通过 SOCKET 与服务端建立连接之后,通向服务端的输入和输出流的封装。

  • streamAllocation.connection(); 得到一个连接,这个连接是在 newStream 内部就已经完成建立了的。

  • realChain.proceed 告知下一个拦截器开始去执行。

newStream 获取一个 HttpStream 对象

public HttpStream newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
  //读取从 OkHttpClient 配置的超时时间
  int connectTimeout = client.connectTimeoutMillis();
  int readTimeout = client.readTimeoutMillis();
  int writeTimeout = client.writeTimeoutMillis();
  //连接重试
  boolean connectionRetryEnabled = client.retryOnConnectionFailure();
  try {
    //寻找一个连接(在连接池中寻找或者在新创建一个连接)
    RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
        writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
    HttpStream resultStream;
    if (resultConnection.framedConnection != null) {
      resultStream = new Http2xStream(client, this, resultConnection.framedConnection);
    } else {
      //给 SOCKET 设置超时时间
      resultConnection.socket().setSoTimeout(readTimeout);
      //给输入输出流设置读写超时时间
      resultConnection.source.timeout().timeout(readTimeout, MILLISECONDS);
      resultConnection.sink.timeout().timeout(writeTimeout, MILLISECONDS);
      //构建一个 Http1xStream 对象
      resultStream = new Http1xStream(
          client, this, resultConnection.source, resultConnection.sink);
    }
    synchronized (connectionPool) {
      stream = resultStream;
      return resultStream;
    }
  } catch (IOException e) {
    throw new RouteException(e);
  }
}

#得到一个可用的 Connection 连接

下面是得到一个可用的连接,并且通过 SOCKET 与服务端建立连接需要用到的 3 段代码。

  • 在 StreamAllocation 中的 newStream 方法内部调用第一段代码,获取一个连接 server 的 HttpStream ,它封装了输入输出流。
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
          writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
  • findHealthyConnection 是负责死循环去检测获取到的 Connection
    是否可用,如果是新创建的 Connection 那么就不需要进行检测了,当 Connection 不可用的话就继续去调用 findConnection 去重新获取一个连接。
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
    int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
    throws IOException {
  //死循环获取一个连接
  while (true) {
    RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
        connectionRetryEnabled);
    // If this is a brand new connection, we can skip the extensive health checks.
    //如果这个连接是新的连接,那么不需要进行可用新检查了,直接返回这个新的的连接
    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.
    //这个连接是从连接池中取出的,检测是否可用
    if (!candidate.isHealthy(doExtensiveHealthChecks)) {
      //不可用关闭这个连接,从新寻找。
      noNewStreams();
      continue;
    }
    return candidate;
  }
}
  • findConnection 真正去获取一个连接并且通过 SOCKET 建立连接的方法。
/**
 * 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 {
  Route selectedRoute;
  synchronized (connectionPool) {
    if (released) throw new IllegalStateException("released");
    if (stream != null) throw new IllegalStateException("stream != null");
    if (canceled) throw new IOException("Canceled");
    RealConnection allocatedConnection = this.connection;
    if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
      return allocatedConnection;
    }
    // Attempt to get a connection from the pool.
    RealConnection pooledConnection = Internal.instance.get(connectionPool, address, this);
    if (pooledConnection != null) {
      this.connection = pooledConnection;
      return pooledConnection;
    }
    selectedRoute = route;
  }
  if (selectedRoute == null) {
    selectedRoute = routeSelector.next();
    synchronized (connectionPool) {
      route = selectedRoute;
      refusedStreamCount = 0;
    }
  }
  //当在连接池中没有找到可用的连接时,创建一个新的连接。
  RealConnection newConnection = new RealConnection(selectedRoute);
  acquire(newConnection);
  synchronized (connectionPool) {
    Internal.instance.put(connectionPool, newConnection);
    this.connection = newConnection;
    if (canceled) throw new IOException("Canceled");
  }
  //通过 socket 建立连接的核心代码
  newConnection.connect(connectTimeout, readTimeout, writeTimeout, address.connectionSpecs(),
      connectionRetryEnabled);
  routeDatabase().connected(newConnection.route());
  return newConnection;
}

通过 Socket 建立连接

在上面 findConnection 方法中除了获取一个 Connection 之外还进行了 Connetion 的连接操作。下面就是从 findConnection 摘抄的连接服务器的核心代码。

 newConnection.connect(connectTimeout, readTimeout, writeTimeout, address.connectionSpecs(),
connectionRetryEnabled);

这个 newConnction 就是一个 RealConnction,connect 方法最终会调用 connectSocket 方法。下面是该方法的源码:

private void connectSocket(int connectTimeout, int readTimeout) throws IOException {
  Proxy proxy = route.proxy();
  Address address = route.address();
  rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
      ? address.socketFactory().createSocket()
      : new Socket(proxy);
  //设置超时时间
  rawSocket.setSoTimeout(readTimeout);
  try {
    //获取指定的平台去进行连接
    Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
  } catch (ConnectException e) {
    throw new ConnectException("Failed to connect to " + route.socketAddress());
  }
  //得到连接服务器的输入流对象
  source = Okio.buffer(Okio.source(rawSocket));
  //得到连接服务器的输出流对象
  sink = Okio.buffer(Okio.sink(rawSocket));
}

通过上面得到的 resultConnection 构建一个 HttpStream

在代码的最后一行可以看到, Http1xStream 内部封装了通过 connection 连接服务器所得到的输入输出流对象resultConnection.source, resultConnection.sink。

//给 SOCKET 设置超时时间
resultConnection.socket().setSoTimeout(readTimeout);
//给输入输出流设置读写超时时间
resultConnection.source.timeout().timeout(readTimeout, MILLISECONDS);
resultConnection.sink.timeout().timeout(writeTimeout, MILLISECONDS);
//构建一个 Http1xStream 对象
resultStream = new Http1xStream(
          client, this, resultConnection.source, resultConnection.sink);

对 ConnectionInterceptor 进行与服务端的连接已经分析完毕…

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