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 进行与服务端的连接已经分析完毕...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容