OkHttp源码与架构(三)

112 阅读12分钟

前几章从使用方法看了OkHttp源码,熟悉了从上层创建请求到OkHttp返回response,接下来将看看OkHttp的重点类。

1. Dispatcher

通过上一个章节,我们知道dispatcher是OkHttp中一个比较重要的类,主要用于协调各个队列之间的调度和释放资源。上一章我们从使用的角度分析了OkHttp源码,其中Dispatcher占了很大一部分。现在来详细看看Dispatcher类吧。

Dispatcher.java
  // 最大请求数目
  private int maxRequests = 64;
  // 每台主机的最大请求数
  private int maxRequestsPerHost = 5;
  ...
  /** Executes calls. Created lazily. */
  private ExecutorService executorService;

  /** Ready async calls in the order they'll be run. */
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

这里主要有三个类需要特别注意:

  1. readyAsyncCalls:等待中的异步请求队列。这个队列主要存储等待中异步请求。当正在运行的请求队列数量超过最大请求数目(64)时,会将新的call加入等待的异步请求队列;当正在运行的请求队列的长度小于最大请求数目(64)并且共享主机的请求数目小于每台主机的最大请求数目时,会从等待异步队列中将call取出,添加到正在运行的请求队列中。
  2. runningAsyncCalls:运行中的异步请求队列。
  3. runningSyncCalls:正在运行的同步请求队列。

接下来看看线程池的初始化:

Dispatcher.java

public synchronized ExecutorService executorService() {
  if (executorService == null) {
    executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
        new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
  }
  return executorService;
}

线程池executor初始化时使用了六个参数,这留个参数是:

  1. corePoolSize:0;线程池的核心线程数量。非核心线程会在线程池空闲的时候等待一段时间关闭,节省资源
  2. maximumPoolSize:Integer.MAX_VALUE(2147483647)。线程池中的最大线程数。
  3. keepAliveTime:60;空闲线程在线程池中的存活时间。超过这个时间,空闲线程就会被杀死。
  4. TimeUnit:TimeUnit.SECONDS; 时间的单位,例如此处表示60s。
  5. BlockingQueue:new SynchronousQueue<>();阻塞队列。
  6. ThreadFactory:Util.threadFactory("OkHttp Dispatcher", false);线程的创建工厂。

剩余的关键方法已经在上一章介绍清楚了,这里不做赘述。接下来看看OkHttp中最为重要的拦截器部分吧。

2. Intercepor

拦截器的入口在RealCall中。

RealCall.java

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

RealCallgetResponseWithInterceptorChain()方法中,依次添加了拦截器。然后初始化为RealInterceptorChain

RealInterceptorChain.java

public Response proceed(Request request, StreamAllocation streamAllocation, HttpStream httpStream,
      Connection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();
    ......
    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpStream, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);
    ......
    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    return response;
  }

并在RealInterceptorChain中依次调用刚才添加的五个拦截器的intercept()处理request,最终从上至下依次返回处理结果response。

image.png

以上都是温习之前讲过的部分,接下来依次看看各个拦截器在处理的时候做了什么工作吧。

2.1 RetryAndFollowUpInterceptor

先看看重试和重定向拦截器:

RetryAndFollowUpInterceptor.java

@Override public Response intercept(Chain chain) throws IOException {
  // 获取request
  Request request = chain.request();
  // 新建一个StreamAllocation
  streamAllocation = new StreamAllocation(
      client.connectionPool(), createAddress(request.url()));
  // 重定向次数
  int followUpCount = 0;
  Response priorResponse = null;
  while (true) {
    // 关闭连接
    if (canceled) {
      streamAllocation.release();
      throw new IOException("Canceled");
    }
    
    Response response = null;
    boolean releaseConnection = true;
    try {
      // 调用RealInterceptorChain处理请求,请求下一层interceptor处理
      response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
      releaseConnection = false;
    } catch (RouteException e) {
      // The attempt to connect via a route failed. The request will not have been sent.
      if (!recover(e.getLastConnectException(), true, request)) throw e.getLastConnectException();
      releaseConnection = false;
      continue;
    } catch (IOException e) {
      // An attempt to communicate with a server failed. The request may have been sent.
      if (!recover(e, false, request)) throw e;
      releaseConnection = false;
      continue;
    } finally {
      // We're throwing an unchecked exception. Release any resources.
      if (releaseConnection) {
        streamAllocation.streamFailed(null);
        streamAllocation.release();
      }
    }

    // Attach the prior response if it exists. Such responses never have a body.
    if (priorResponse != null) {
      response = response.newBuilder()
          .priorResponse(priorResponse.newBuilder()
              .body(null)
              .build())
          .build();
    }

    Request followUp = followUpRequest(response);

    if (followUp == null) {
      if (!forWebSocket) {
        streamAllocation.release();
      }
      return response;
    }

    closeQuietly(response.body());

    if (++followUpCount > MAX_FOLLOW_UPS) {
      streamAllocation.release();
      throw new ProtocolException("Too many follow-up requests: " + followUpCount);
    }

    if (followUp.body() instanceof UnrepeatableRequestBody) {
      throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
    }

    if (!sameConnection(response, followUp.url())) {
      streamAllocation.release();
      streamAllocation = new StreamAllocation(
          client.connectionPool(), createAddress(followUp.url()));
    } else if (streamAllocation.stream() != null) {
      throw new IllegalStateException("Closing the body of " + response
          + " didn't close its backing stream. Bad interceptor?");
    }

    request = followUp;
    priorResponse = response;
  }
}

在这个方法中,首先使用chain传来的request,将这个Request作为参数,创建一个StreamAllocation对象,这个StreamAllocation是用于协调Connections、Streams和Calls三个实体之间的关系,用于和服务器传递数据。接着尝试请求下一层interceptor进行处理,如果捕获到异常,就不会将这个请求发送给服务器,会在本地拦截下来,然后释放StreamAllocation资源,在while循环中下次循环重新处理。接着创建一个body为空的response,交给followUpRequest()进行处理。看看这个方法的具体作用:

RetryAndFollowUpInterceptor.java

private Request followUpRequest(Response userResponse) throws IOException {
    if (userResponse == null) throw new IllegalStateException();
    Connection connection = streamAllocation.connection();
    Route route = connection != null
        ? connection.route()
        : null;
    int responseCode = userResponse.code();

    final String method = userResponse.request().method();
    switch (responseCode) {
      case HTTP_PROXY_AUTH:
        Proxy selectedProxy = route != null
            ? route.proxy()
            : client.proxy();
        if (selectedProxy.type() != Proxy.Type.HTTP) {
          throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
        }
        return client.proxyAuthenticator().authenticate(route, userResponse);

      case HTTP_UNAUTHORIZED:
        return client.authenticator().authenticate(route, userResponse);

      case HTTP_PERM_REDIRECT:
      case HTTP_TEMP_REDIRECT:
        // "If the 307 or 308 status code is received in response to a request other than GET
        // or HEAD, the user agent MUST NOT automatically redirect the request"
        if (!method.equals("GET") && !method.equals("HEAD")) {
          return null;
        }
        // fall-through
      case HTTP_MULT_CHOICE:
      case HTTP_MOVED_PERM:
      case HTTP_MOVED_TEMP:
      case HTTP_SEE_OTHER:
        // Does the client allow redirects?
        if (!client.followRedirects()) return null;

        String location = userResponse.header("Location");
        if (location == null) return null;
        HttpUrl url = userResponse.request().url().resolve(location);

        // Don't follow redirects to unsupported protocols.
        if (url == null) return null;

        // If configured, don't follow redirects between SSL and non-SSL.
        boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
        if (!sameScheme && !client.followSslRedirects()) return null;

        // Redirects don't include a request body.
        Request.Builder requestBuilder = userResponse.request().newBuilder();
        if (HttpMethod.permitsRequestBody(method)) {
          if (HttpMethod.redirectsToGet(method)) {
            requestBuilder.method("GET", null);
          } else {
            requestBuilder.method(method, null);
          }
          requestBuilder.removeHeader("Transfer-Encoding");
          requestBuilder.removeHeader("Content-Length");
          requestBuilder.removeHeader("Content-Type");
        }

        // When redirecting across hosts, drop all authentication headers. This
        // is potentially annoying to the application layer since they have no
        // way to retain them.
        if (!sameConnection(userResponse, url)) {
          requestBuilder.removeHeader("Authorization");
        }

        return requestBuilder.url(url).build();

      case HTTP_CLIENT_TIMEOUT:
        // 408's are rare in practice, but some servers like HAProxy use this response code. The
        // spec says that we may repeat the request without modifications. Modern browsers also
        // repeat the request (even non-idempotent ones.)
        if (userResponse.request().body() instanceof UnrepeatableRequestBody) {
          return null;
        }

        return userResponse.request();

      default:
        return null;
    }
  }

仔细看看这个方法,其实就是对超时、重定向等返回值的处理。尤其看看重定向部分:

RetryAndFollowUpInterceptor.java

case HTTP_PERM_REDIRECT:
      case HTTP_TEMP_REDIRECT:
        // "If the 307 or 308 status code is received in response to a request other than GET
        // or HEAD, the user agent MUST NOT automatically redirect the request"
        if (!method.equals("GET") && !method.equals("HEAD")) {
          return null;
        }
        // fall-through
      case HTTP_MULT_CHOICE:
      case HTTP_MOVED_PERM:
      case HTTP_MOVED_TEMP:
      case HTTP_SEE_OTHER:
        // Does the client allow redirects?
        if (!client.followRedirects()) return null;

        String location = userResponse.header("Location");
        if (location == null) return null;
        HttpUrl url = userResponse.request().url().resolve(location);

        // Don't follow redirects to unsupported protocols.
        if (url == null) return null;

        // If configured, don't follow redirects between SSL and non-SSL.
        boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
        if (!sameScheme && !client.followSslRedirects()) return null;

        // Redirects don't include a request body.
        Request.Builder requestBuilder = userResponse.request().newBuilder();
        if (HttpMethod.permitsRequestBody(method)) {
          if (HttpMethod.redirectsToGet(method)) {
            requestBuilder.method("GET", null);
          } else {
            requestBuilder.method(method, null);
          }
          requestBuilder.removeHeader("Transfer-Encoding");
          requestBuilder.removeHeader("Content-Length");
          requestBuilder.removeHeader("Content-Type");
        }

        // When redirecting across hosts, drop all authentication headers. This
        // is potentially annoying to the application layer since they have no
        // way to retain them.
        if (!sameConnection(userResponse, url)) {
          requestBuilder.removeHeader("Authorization");
        }

        return requestBuilder.url(url).build();

其实就是在返回值是30X的时候,将重定位的url放在location的位置,然后重新构建一个request,返回这个新构建的request,用于下一次请求处理。

接着回去看看proceed()

RetryAndFollowUpInterceptor.java

Request followUp = followUpRequest(response);

      if (followUp == null) {
        if (!forWebSocket) {
          streamAllocation.release();
        }
        return response;
      }

      closeQuietly(response.body());

      if (++followUpCount > MAX_FOLLOW_UPS) {
        streamAllocation.release();
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }

      if (followUp.body() instanceof UnrepeatableRequestBody) {
        throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
      }

      if (!sameConnection(response, followUp.url())) {
        streamAllocation.release();
        streamAllocation = new StreamAllocation(
            client.connectionPool(), createAddress(followUp.url()));
      } else if (streamAllocation.stream() != null) {
        throw new IllegalStateException("Closing the body of " + response
            + " didn't close its backing stream. Bad interceptor?");
      }

      request = followUp;
      priorResponse = response;

如果followUp==null,并且不是socket连接,那么就释放streamAllocation资源。接着关闭刚才那个为了重定向问题创建的body为空的response。

接下来判断,如果重定向的次数超过最大重定向值(20),那么就释放streamAllocation资源,抛出异常;如果重定向的body是一个不可重复的httpbody,抛出异常;如果请求不可复用这个连接,那么就释放streamAllocation资源,重新创建一个streamAllocation流。

总结:客户端向服务器请求一个资源,在服务器收到请求之后,发现资源不在请求的位置,而在另一个位置上,于是服务器返回状态码30X,在响应头的location位置添加新的资源的位置。在重试和重定向拦截器中,主要做了以下工作:

  1. 重试:在交付给下一个拦截器之前,重定向拦截器会判断是否需要重新发起请求;
  2. 重定向:在获取response返回值后,会根据返回值判断是否需要重定向,如果需要重定向,就重启所有拦截器。

2.2 BridgeInterceptor

BridgeInterceptor.java

@Override public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();

    RequestBody body = userRequest.body();
    if (body != null) {
      // 获取contentType
      MediaType contentType = body.contentType();
      // 添加头部信息
      if (contentType != null) {
        requestBuilder.header("Content-Type", contentType.toString());
      }
      long contentLength = body.contentLength();
      if (contentLength != -1) {
        requestBuilder.header("Content-Length", Long.toString(contentLength));
        requestBuilder.removeHeader("Transfer-Encoding");
      } else {
        requestBuilder.header("Transfer-Encoding", "chunked");
        requestBuilder.removeHeader("Content-Length");
      }
    }
    // 添加头部的host信息
    if (userRequest.header("Host") == null) {
      requestBuilder.header("Host", hostHeader(userRequest.url(), false));
    }
    // 添加connection是否保活信息
    if (userRequest.header("Connection") == null) {
      requestBuilder.header("Connection", "Keep-Alive");
    }

    // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
    // the transfer stream.
    boolean transparentGzip = false;
    if (userRequest.header("Accept-Encoding") == null) {
      transparentGzip = true;
      requestBuilder.header("Accept-Encoding", "gzip");
    }
    // 添加cookie信息
    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {
      requestBuilder.header("Cookie", cookieHeader(cookies));
    }
    // 在头部添加代理版本
    if (userRequest.header("User-Agent") == null) {
      requestBuilder.header("User-Agent", Version.userAgent());
    }
    // 交付给下一层interceptor处理,返回networkResponse给的返回值
    Response networkResponse = chain.proceed(requestBuilder.build());

    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

    Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);
    // 处理下层返回的response头部信息
    if (transparentGzip
        && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
        && HttpHeaders.hasBody(networkResponse)) {
      GzipSource responseBody = new GzipSource(networkResponse.body().source());
      Headers strippedHeaders = networkResponse.headers().newBuilder()
          .removeAll("Content-Encoding")
          .removeAll("Content-Length")
          .build();
      responseBuilder.headers(strippedHeaders);
      responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
    }

    return responseBuilder.build();
  }

总结:在桥接拦截器中,在发送给下一层拦截器之前,封装了http协议头部的一些信息,例如压缩格式、cookie等;在收到下一层拦截器返回的response之后,处理了一下头部信息和responseBody,解析GZIP,保存一些数据。

2.3 CacheInterceptor


@Override public Response intercept(Chain chain) throws IOException {
  Response cacheCandidate = cache != null
      ? cache.get(chain.request())
      : null;

  long now = System.currentTimeMillis();

  CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
  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.
  }

  // 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(EMPTY_BODY)
        .sentRequestAtMillis(-1L)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();
  }

  // If we don't need the network, we're done.
  if (networkRequest == null) {
    return cacheResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .build();
  }

  Response networkResponse = null;
  try {
    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) {
    if (validate(cacheResponse, networkResponse)) {
      Response response = cacheResponse.newBuilder()
          .headers(combine(cacheResponse.headers(), networkResponse.headers()))
          .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 = networkResponse.newBuilder()
      .cacheResponse(stripBody(cacheResponse))
      .networkResponse(stripBody(networkResponse))
      .build();

  if (HttpHeaders.hasBody(response)) {
    CacheRequest cacheRequest = maybeCache(response, networkResponse.request(), cache);
    response = cacheWritingResponse(cacheRequest, response);
  }

  return response;
}

在缓存拦截器中,一开始创建了了一个CacheStrategy对象strategy,然后实例化strategy的两个属性:strategy.networkRequeststrategy.cacheResponse。这两个属性中networkRequest表示使用网络发送的request对象,networkRequest表示缓存的response。这两个属性的意义是:如果有网络的情况下,从网络获取response;如果没有网络,则从cacheResponse中获取已经缓存过的reponse。 接下来看看在以上代码中最先调用的CacheStrategy.get()

CacheStrategy.java

public CacheStrategy get() {
  CacheStrategy candidate = getCandidate();
  if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
    // We're forbidden from using the network and the cache is insufficient.
    return new CacheStrategy(null, null);
  }

  return candidate;
}

先看下面的方法:如果策略的不需要网络并且缓存无效,则返回一个缓存和网络需求都为空的策略,否则返回getCandidate()返回的对象candidate。 接着看方法getCandidate()

CacheStrategy.java

/** Returns a strategy to use assuming the request can use the network. */
private CacheStrategy getCandidate() {
  // No cached response.
  if (cacheResponse == null) {
    return new CacheStrategy(request, null);
  }

  // Drop the cached response if it's missing a required handshake.
  if (request.isHttps() && cacheResponse.handshake() == null) {
    return new CacheStrategy(request, null);
  }

  // If this response shouldn't have been stored, it should never be used
  // as a response source. This check should be redundant as long as the
  // persistence store is well-behaved and the rules are constant.
  if (!isCacheable(cacheResponse, request)) {
    return new CacheStrategy(request, null);
  }

  CacheControl requestCaching = request.cacheControl();
  if (requestCaching.noCache() || hasConditions(request)) {
    return new CacheStrategy(request, null);
  }

  long ageMillis = cacheResponseAge();
  long freshMillis = computeFreshnessLifetime();

  if (requestCaching.maxAgeSeconds() != -1) {
    freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
  }

  long minFreshMillis = 0;
  if (requestCaching.minFreshSeconds() != -1) {
    minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
  }

  long maxStaleMillis = 0;
  CacheControl responseCaching = cacheResponse.cacheControl();
  if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
    maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
  }

  if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
    Response.Builder builder = cacheResponse.newBuilder();
    if (ageMillis + minFreshMillis >= freshMillis) {
      builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
    }
    long oneDayMillis = 24 * 60 * 60 * 1000L;
    if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
      builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
    }
    return new CacheStrategy(null, builder.build());
  }

  // Find a condition to add to the request. If the condition is satisfied, the response body
  // will not be transmitted.
  String conditionName;
  String conditionValue;
  if (etag != null) {
    conditionName = "If-None-Match";
    conditionValue = etag;
  } else if (lastModified != null) {
    conditionName = "If-Modified-Since";
    conditionValue = lastModifiedString;
  } else if (servedDate != null) {
    conditionName = "If-Modified-Since";
    conditionValue = servedDateString;
  } else {
    return new CacheStrategy(request, null); // No condition! Make a regular request.
  }

  Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
  Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);

  Request conditionalRequest = request.newBuilder()
      .headers(conditionalRequestHeaders.build())
      .build();
  return new CacheStrategy(conditionalRequest, cacheResponse);
}

结合代码和注释不难看出,这是方法返回满足假定request需要网络时的策略。 详细看看这部分代码表达的策略:

  • 如果有缓存过的response(cacheResponse),则返回一个新的、需要网络缓存策略;
  • 如果request是一个http请求并且缓存对象的数据握手为空,则返回一个新的、需要网络的缓存策略;如果response未被缓存过,则不应该作为缓存的response资源,所以当无法缓存时,应当重新创建一个不缓存的策略;
  • 如果缓存被清空或者服务器无法返回request请求的response,则重新创建一个不需要缓存的缓存策略;
  • 如果response有缓存,并且response当前age+最小刷新时间<刷新时间+最大稳定时间,则创建一个不需要网络的缓存策略。
  • 如果不满足condition的各种条件,就创建一个常规的缓存策略。 接着回到intercept()中接着看缓存拦截器的代码:
CacheInterceptor.java
 
 if (cache != null) {
    cache.trackResponse(strategy);
  }

缓存不为空的时候,调用trackResponse()方法,进入Cache中看看这个方法。

cache.java

private synchronized void trackResponse(CacheStrategy cacheStrategy) {
  requestCount++;

  if (cacheStrategy.networkRequest != null) {
    // If this is a conditional request, we'll increment hitCount if/when it hits.
    networkCount++;
  } else if (cacheStrategy.cacheResponse != null) {
    // This response uses the cache and not the network. That's a cache hit.
    hitCount++;
  }
}

原来这个方法是为了计算请求命中次数。 回到CacheInterceptor.intercept()接着往下看:

CacheInterceptor.java

if (cacheCandidate != null && cacheResponse == null) {
  closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}

如果从缓存中得到的数据为空并且缓存中的response为空,则说明这个缓存不可用,那么就关闭这个缓存。

接着往下看:

CacheInterceptor.java

// 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(EMPTY_BODY)
      .sentRequestAtMillis(-1L)
      .receivedResponseAtMillis(System.currentTimeMillis())
      .build();
}

如果缓存策略中不适用网络并且缓存为空,说明请求失败,则返回一个code=504,body为空的response。

CacheInterceptor.java
// If we don't need the network, we're done.
if (networkRequest == null) {
  return cacheResponse.newBuilder()
      .cacheResponse(stripBody(cacheResponse))
      .build();
}

禁止使用网络,返回缓存策略中的response。

CacheInterceptor.java

Response networkResponse = null;
try {
  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());
  }
}

调用拦截器链处理,获取处理结果。如果处理结果为空并且缓存数据不为空,则表示出现IO异常,此时关闭缓存数据的body部分,防止泄露。

CacheInterceptor.java

// If we have a cache response too, then we're doing a conditional get.
if (cacheResponse != null) {
  if (validate(cacheResponse, networkResponse)) {
    Response response = cacheResponse.newBuilder()
        .headers(combine(cacheResponse.headers(), networkResponse.headers()))
        .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不为空时,如果需要使用缓存(HTTP为304等情况),则从缓存的response中获取并返回,否则关闭缓存的body防止泄露。

CacheInterceptor.java

Response response = networkResponse.newBuilder()
    .cacheResponse(stripBody(cacheResponse))
    .networkResponse(stripBody(networkResponse))
    .build();

if (HttpHeaders.hasBody(response)) {
  CacheRequest cacheRequest = maybeCache(response, networkResponse.request(), cache);
  response = cacheWritingResponse(cacheRequest, response);
}

最后,从networkResponse中获得数据,判断是否有body,如果有body,接着判断是否需要缓存,然后将结果写入缓存。

总结: 在交给下一层处理request之前,读取并判断是够使用缓存;在交付response给上一层的时候,判断结果是否使用缓存。

2.4 ConnectInterceptor

ConnectInterceptor.java

@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。对response编码和对request解码之后,使用realchain.processed()处理,返回处理之后的response。 这个方法看起来很简单,但是实际上方法比看上去复杂。这里主要逻辑在streamAllocation.newStream(client, doExtensiveHealthChecks)

StreamAllocation.java

public HttpStream newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
  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 {
      resultConnection.socket().setSoTimeout(readTimeout);
      resultConnection.source.timeout().timeout(readTimeout, MILLISECONDS);
      resultConnection.sink.timeout().timeout(writeTimeout, MILLISECONDS);
      resultStream = new Http1xStream(
          client, this, resultConnection.source, resultConnection.sink);
    }

    synchronized (connectionPool) {
      stream = resultStream;
      return resultStream;
    }
  } catch (IOException e) {
    throw new RouteException(e);
  }
}

在这个方法内部,先使用OkHttpClient对象调用retryOnConnectionFailure(),在连接失败的时候重新请求。然后使用一开始获取的connectTimeout等参数和connectionRetryEnabled,调用函数findHealthyConnection()去寻找一个healthy(健康的?)连接。

StreamAllocation.java

/**
 * 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, 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()用于查找连接。

StreamAllocation.java

/**
 * Returns a connection to host a new stream. This prefers the existing connection if it exists,
 * then the pool, finally building a new connection.
 */findHe
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");
  }

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

  return newConnection;
}

可以看到这个方法中是先创建了一个Route对象,线程不被阻塞,就尝试去线程池中获取RealConnection对象,然后返回这个不为空的对象,如果线程被阻塞,就使用一开始创建的route对象建立一个新的RealConnection对象,然后使用这个对象和一开始传进来的参数调RealConnection.connect(),然后把这个连接的route添加到route数据库中,最后返回这个新的RealConnection对象。

接着看一下RealConnection.connect()方法,执行正真的连接。

RealConnection.java

public void connect(int connectTimeout, int readTimeout, int writeTimeout,
    List<ConnectionSpec> connectionSpecs, boolean connectionRetryEnabled) {
  if (protocol != null) throw new IllegalStateException("already connected");

  RouteException routeException = null;
  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 (protocol == null) {
    try {
      if (route.requiresTunnel()) {
        buildTunneledConnection(connectTimeout, readTimeout, writeTimeout,
            connectionSpecSelector);
      } else {
        buildConnection(connectTimeout, readTimeout, writeTimeout, connectionSpecSelector);
      }
    } catch (IOException e) {
      closeQuietly(socket);
      closeQuietly(rawSocket);
      socket = null;
      rawSocket = null;
      source = null;
      sink = null;
      handshake = null;
      protocol = null;

      if (routeException == null) {
        routeException = new RouteException(e);
      } else {
        routeException.addConnectException(e);
      }

      if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
        throw routeException;
      }
    }
  }
}

在try{}catch{}中,如果支持隧道连接,就建立一个隧道连接,否则就创建一个其他的连接。这个其他的连接其实就是socket连接。

RealConnecttion.java

/** Does all the work necessary to build a full HTTP or HTTPS connection on a raw socket. */
private void buildConnection(int connectTimeout, int readTimeout, int writeTimeout,
    ConnectionSpecSelector connectionSpecSelector) throws IOException {
  connectSocket(connectTimeout, readTimeout);
  establishProtocol(readTimeout, writeTimeout, connectionSpecSelector);
}

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

private void establishProtocol(int readTimeout, int writeTimeout,
    ConnectionSpecSelector connectionSpecSelector) throws IOException {
  if (route.address().sslSocketFactory() != null) {
    connectTls(readTimeout, writeTimeout, connectionSpecSelector);
  } else {
    protocol = Protocol.HTTP_1_1;
    socket = rawSocket;
  }

  if (protocol == Protocol.SPDY_3 || protocol == Protocol.HTTP_2) {
    socket.setSoTimeout(0); // Framed connection timeouts are set per-stream.

    FramedConnection framedConnection = new FramedConnection.Builder(true)
        .socket(socket, route.address().url().host(), source, sink)
        .protocol(protocol)
        .listener(this)
        .build();
    framedConnection.start();

    // Only assign the framed connection once the preface has been sent successfully.
    this.allocationLimit = framedConnection.maxConcurrentStreams();
    this.framedConnection = framedConnection;
  } else {
    this.allocationLimit = 1;
  }
}

回到StreamAllocation.java中的newStream()中,在查找到健康的Connection之后,使用这个返回的RealConnection创建一个HttpStream,然后返回新的HttpStream。 回到Connection.java中,使用返回的新建HttpStream进行连接,并且返回一个RealConnection对象(之前不是已经拿到RealStream对象了么?为什么需要经过这样复杂的调用重新获得一个RealConnection对象?)。

总结: 连接拦截器主要在交给下一层之前,负责找到或者创建一个新的连接,并且获得socket流;在获得结果后并不进行特别的处理,直接交给上一层。

2.5 CallServerInterceptor

CallServerInterceptor.java

@Override public Response intercept(Chain chain) throws IOException {
  HttpStream httpStream = ((RealInterceptorChain) chain).httpStream();
  StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
  Request request = chain.request();

  long sentRequestMillis = System.currentTimeMillis();
  httpStream.writeRequestHeaders(request);

  if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
    Sink requestBodyOut = httpStream.createRequestBody(request, request.body().contentLength());
    BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
    request.body().writeTo(bufferedRequestBody);
    bufferedRequestBody.close();
  }

  httpStream.finishRequest();

  Response response = httpStream.readResponseHeaders()
      .request(request)
      .handshake(streamAllocation.connection().handshake())
      .sentRequestAtMillis(sentRequestMillis)
      .receivedResponseAtMillis(System.currentTimeMillis())
      .build();

  if (!forWebSocket || response.code() != 101) {
    response = response.newBuilder()
        .body(httpStream.openResponseBody(response))
        .build();
  }

  if ("close".equalsIgnoreCase(response.request().header("Connection"))
      || "close".equalsIgnoreCase(response.header("Connection"))) {
    streamAllocation.noNewStreams();
  }

  int code = response.code();
  if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
    throw new ProtocolException(
        "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
  }

  return response;
}
  • 将从interceorChain获得的Request写入httpStream中的header,接着使用这个httpStream创建requestbody,并且把requestbody写入缓存,最后把缓存中的requestbody写入request.body,然后关闭请求。
  • 接下来从httpStream中通过readResponseHeader获取response对象,接着通过response对象从httpStream中获取responsebody,并且写response的body部分
  • 最后处理responseCode。

总结: 请求服务器拦截器是是拦截器链的最下层,负责与服务器的通信,想服务器发送数据;在最终服务器返回数据之后最先处理和解读数据,返回给上一层。