okhttp3 源码解析笔记整理——拦截器与请求的实现

在第一篇文章 okhttp 源码解析笔记整理——同步、异步请求 中可以知道,不管是同步请求还是异步请求,都最终是通过

RealCall # getResponseWithInterceptorChain()

来实现真实的请求的。

Response getResponseWithInterceptorChain() throws IOException {
  // Build a full stack of interceptors.
  // 收集拦截器的临时列表
  List<Interceptor> interceptors = new ArrayList<>();
  // 先添加通过 OkHttpClient.Builder # addInterceptor() 方法添加的拦截器
  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 (!forWebSocket) {
    // 如果不是 WebSocket 请求,
    // 则添加通过 OkHttpClient.Builder # addNetworkInterceptor() 方法添加的拦截器
    interceptors.addAll(client.networkInterceptors());
  }
  interceptors.add(new CallServerInterceptor(forWebSocket));
  Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
      originalRequest, this, eventListener, client.connectTimeoutMillis(),
      client.readTimeoutMillis(), client.writeTimeoutMillis());
  return chain.proceed(originalRequest);
}

从上面的方法可知,除了自定义的拦截器外,固定的会添加 5 个系统拦截器,来组成拦截器链(InterceptorChain) 来实现请求。


首先来说一下拦截器链的本质,就是 责任链模式

通过构建一系列的拦截器,来一层一层的处理请求时涉及到的逻辑,分工明确,从而实现了最终的请求。

其大概的代码模型如下:

其中 InterceptorChainproceed() 方法会根据 index 取出 interceptors 拦截器链中的目标拦截器来进行处理,而拦截器就是通过 intercept() 方法来处理请求的。

class RealTestInterceptorChain(private val interceptors: List<TestInterceptor>, private val index: Int): TestInterceptor.TestChain {

    override fun proceed(): TestResponse {

        if (index >= interceptors.size) throw AssertionError()
        
        val next = RealTestInterceptorChain(interceptors, index + 1)
        val testInterceptor: TestInterceptor = interceptors[index]
        val response = testInterceptor.intercept(next)

        return response
    }
}

可以看到,在 RealTestInterceptor 类型的拦截器中,又是通过调用 chain.proceed() 来获取下一级拦截器的 response

class RealTestInterceptor(private val curIndex: Int) : TestInterceptor {
    override fun intercept(chain: TestInterceptor.TestChain): TestResponse {
        // 在 chain.proceed() 之前,即请求之前,对 request 进行处理
        val response = chain.proceed()
        // 在 chain.proceed() 之后,即请求之后,对 response 进行处理
        return response
    }
}

就是这样,通过 InterceptorChain 来将对请求的处理传递到下一级拦截器,而当前拦截器 RealTestInterceptor 会借由 InterceptorChain 来获得下一级拦截器处理的后的结果,即 response

当然,这里还需要注意,拦截器是不可能无限调用的,总会有一个终点,而这个终点就是最后一个拦截器,在该拦截器里,不会继续再使用 chain.proceed(),而是会构造出最原始的 response 来返回给之前的拦截器。就像下面的示例代码一样。

class RealTestFinalInterceptor(private val curIndex: Int) : TestInterceptor {
    override fun intercept(chain: TestInterceptor.TestChain): TestResponse {
        // 请求前,对 request 进行加工
        val response = buildOriginalResponse()
        // 请求后,对 response 进行加工
        return response
    }
}

最后附上最外层的调用代码:

val interceptors = ArrayList<TestInterceptor>()
interceptors.add(RealTestInterceptor(0))
interceptors.add(RealTestInterceptor(1))
interceptors.add(RealTestInterceptor(2))
interceptors.add(RealTestInterceptor(3))
interceptors.add(RealTestInterceptor(4))
// 最后一个 Interceptor 为特殊的
// 在里面不会直接再进行 chain.proceed() 的逻辑
// 而是会构造原始的 response 进行返回
// 这样就能够避免需要无限调用 chain.proceed() 的情况
interceptors.add(RealTestFinalInterceptor(5))
val chain = RealTestInterceptorChain(interceptors, 0)
val response = chain.proceed()

讲解了拦截器链的大致实现原理之后,就需要介绍一个前面提到的 5 个原始的拦截器了。

(1) RetryAndFollowUpInterceptor
对失败的请求进行恢复,或者进行重定向。

(2) BridgeInterceptor

在请求之前,根据用户的 request 构建一个网路请求对应的 request,在请求返回之后,根据网络响应的 response 构建一个用户 response。

(3) CacheInterceptor

@Override public Response intercept(Chain chain) throws IOException {
  // 根据 request 得到缓存的 response(可能为 null)
  Response cacheCandidate = cache != null
      ? cache.get(chain.request())
      : null;
  long now = System.currentTimeMillis();
  
  // 根据 request 以及前面得到的 cacheCandidate(即 缓存的 response)生成一个缓存策略对象
  CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get()
  // 得到缓存对象 strategy 的成员变量 networkRequest 和 cacheResponse
  // 这两个成员变量的值是在 CacheStrategy.Factory#get()方法中根据传入的 now, chain.request(), cacheCandidate 等参数动态设置的
  // 用于在后面进行各种情况的判断
  Request networkRequest = strategy.networkRequest;
  Response cacheResponse = strategy.cacheResponse;
  if (cache != null) {
    cache.trackResponse(strategy);
  }
  if (cacheCandidate != null && cacheResponse == null) {
    closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
  }
  
  // 如果不使用网络,且没有缓存的 response,则返回 504
  // If we're forbidden from using the network and the cache is insufficient, fail.
  if (networkRequest == null && cacheResponse == null) {
    return new Response.Builder()
        .request(chain.request())
        .protocol(Protocol.HTTP_1_1)
        .code(504)
        .message("Unsatisfiable Request (only-if-cached)")
        .body(Util.EMPTY_RESPONSE)
        .sentRequestAtMillis(-1L)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();
  }
  
  // 如果不使用网络,且有缓存的 response,则直接使用缓存的 response
  // If we don't need the network, we're done.
  if (networkRequest == null) {
    return cacheResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .build();
  }
  Response networkResponse = null;
  try {
    // 前面都不是的话,则进行网络请求,得到网络响应的 response
    networkResponse = chain.proceed(networkRequest);
  } finally {
    // If we're crashing on I/O or otherwise, don't leak the cache body.
    if (networkResponse == null && cacheCandidate != null) {
      closeQuietly(cacheCandidate.body());
    }
  }
  // If we have a cache response too, then we're doing a conditional get.
  if (cacheResponse != null) {
    //	如果缓存的 response 不为空,且网络响应的 response 的状态码为 304,
    // 则由两者组合构成一个新的 response
    // (304 表示服务端资源未发生改变,可直接使用客户端未过期的缓存)
    if (networkResponse.code() == HTTP_NOT_MODIFIED) {
      Response response = cacheResponse.newBuilder()
          .headers(combine(cacheResponse.headers(), networkResponse.headers()))
          .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
          .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
          .cacheResponse(stripBody(cacheResponse))
          .networkResponse(stripBody(networkResponse))
          .build();
      networkResponse.body().close();
      // Update the cache after combining headers but before stripping the
      // Content-Encoding header (as performed by initContentStream()).
      cache.trackConditionalCacheHit();
      cache.update(cacheResponse, response);// 更新缓存
      return response;
    } else {
      closeQuietly(cacheResponse.body());
    }
  }
  
  // 否则直接由网络响应的 response 构成一个新的 response
  // 与前面状态码为 304 时的还是有区别的
  Response response = networkResponse.newBuilder()
      .cacheResponse(stripBody(cacheResponse))
      .networkResponse(stripBody(networkResponse))
      .build();
       
  if (cache != null) {
    // 如果设置的 cache 且新的 response 有报文主体,且是可以缓存的,则进行缓存
    if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
      // Offer this request to the cache.
      CacheRequest cacheRequest = cache.put(response);
      return cacheWritingResponse(cacheRequest, response);
    }
    
    // 如果请求方法不是 GET,则移除该网络请求对应的缓存
    if (HttpMethod.invalidatesCache(networkRequest.method())) {
      try {
        cache.remove(networkRequest);
      } catch (IOException ignored) {
        // The cache cannot be written.
      }
    }
  }
  return response;
}
public static boolean invalidatesCache(String method) {
  return method.equals("POST")
      || method.equals("PATCH")
      || method.equals("PUT")
      || method.equals("DELETE")
      || method.equals("MOVE");     // WebDAV
}

补充文章:OKHttp源码解析(4)----拦截器CacheInterceptor

(4) ConnectInterceptor

根据目标服务器建立一个连接(可能是一个新的连接,也可能是复用连接池中的连接),实际上建立连接就是调用了 streamAllocation.newStream() ,该方法创建了一个 HttpCodec 对象,它将在后面的步骤中被使用。

如果是复用之前的连接,则不需要再次进行三次握手的操作,如果是新建的连接,则需要进行三次握手操作,才是与服务器建立了真实的连接。

@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 用于编码 Http 的 request 与 response)
  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) {
  int connectTimeout = chain.connectTimeoutMillis();
  int readTimeout = chain.readTimeoutMillis();
  int writeTimeout = chain.writeTimeoutMillis();
  int pingIntervalMillis = client.pingIntervalMillis();
  boolean connectionRetryEnabled = client.retryOnConnectionFailure();
  try {
    // 通过 findHealthyConnection() 方法寻找一个有效的连接
    RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
        writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
    HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
    synchronized (connectionPool) {
      codec = resultCodec;
      return resultCodec;
    }
  } catch (IOException e) {
    throw new RouteException(e);
  }
}

/**
 * Finds a connection and returns it if it is healthy. If it is unhealthy the process is repeated
 * until a healthy connection is found.
 */
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
    int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
    boolean doExtensiveHealthChecks) throws IOException {
  while (true) {
    // 内部又是不断的调用 findConnection() 方法,直到找到一个有效的连接
    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;
      }
    }
    // 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;
  }
}

/**
 * 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,
    int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
  boolean foundPooledConnection = false;
  RealConnection result = null;
  Route selectedRoute = null;
  Connection releasedConnection;
  Socket toClose; // 涉及到了 socket
  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. We need to be careful here because our
    // already-allocated connection may have been restricted from creating new streams.
    releasedConnection = this.connection;
    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) {
      // Attempt to get a connection from the pool.
      Internal.instance.acquire(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);
  }
  // (1)前面的逻辑会尝试:
  // 1、用 this.connection(该成员属性的赋值可能会通过 acquire() 或者 releaseAndAcquire() 
  //	方法从外部传递进来,所以说是 already-allocated 的);
  // 2、从连接池中寻找到的连接
  // 如果通过前两步得到的 result 连接不为空,则直接返回该 result
  if (result != null) {
    // If we found an already-allocated or pooled connection, we're done.
    return result;
  }
  // If we need a route selection, make one. This is a blocking operation.
  boolean newRouteSelection = false;
  if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
    newRouteSelection = true;
    routeSelection = routeSelector.next();
  }
  synchronized (connectionPool) {
    if (canceled) throw new IOException("Canceled");
    // (2)如果通过前面的判断,newRouteSelection 为 true,则会再次尝试从连接池中寻找
    if (newRouteSelection) {
      // Now that we have a set of IP addresses, make another attempt at getting a connection from
      // the pool. This could match due to connection coalescing.
      List<Route> routes = routeSelection.getAll();
      for (int i = 0, size = routes.size(); i < size; i++) {
        Route route = routes.get(i);
        Internal.instance.acquire(connectionPool, address, this, route);
        if (connection != null) {
          foundPooledConnection = true;
          result = connection;
          this.route = route;
          break;
        }
      }
    }
	// (3)如果再次尝试依旧不行的话,则会新建一个连接,并放入连接池中
    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;
  }
  // Do TCP + TLS handshakes. This is a blocking operation.
  // 进行 TCP 与 TLS 握手(TLS 是关于 https 的)
  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);
    // If another multiplexed connection to the same address was created concurrently, then
    // release this connection and acquire that one.
    // 如果有另外一个多路复用的连接(多路复用是 http2.0 的概念),则直接使用那个,而释放掉刚才新建的
    if (result.isMultiplexed()) {
      socket = Internal.instance.deduplicate(connectionPool, address, this);
      result = connection;
    }
  }
  closeQuietly(socket);
  eventListener.connectionAcquired(call, result);
  return result;
}

(5) CallServerInterceptor
最后一个拦截器,向服务器发起一次网络访问(这里是发起网络访问,与服务器建立连接是在 ConnectInterceptor 中完成的),负责向服务器发送请求数据,从服务器读取响应数据。其实现是基于 Okio 的。

在最一个拦截器中,就没有调用与 chain.proceed(),而是真正的实现网络的请求,构造最原始的 response

补充文章:

1、OkHttp源码解析7:CallServerInterceptor流程

2、OKHttp源码解析(6)----拦截器CallServerInterceptor

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值