Okhttp -RetryAndFollowUpInterceptor拦截器源码分析

320 阅读3分钟

RealCall.java中获取response的时候需要经过一系列的拦截器,我今天大致看一下里面的代码

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 (!forWebSocket) {
    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());

  Response response = chain.proceed(originalRequest);
  if (retryAndFollowUpInterceptor.isCanceled()) {
    closeQuietly(response);
    throw new IOException("Canceled");
  }
  return response;
}
client.interceptors() 这个拦截器是用户自己定义的,略过
retryAndFollowUpInterceptor 重试与重定向拦截器
@Override public Response intercept(Chain chain) throws IOException {
  Request request = chain.request();
  RealInterceptorChain realChain = (RealInterceptorChain) chain;
  Call call = realChain.call();
  EventListener eventListener = realChain.eventListener();
  //创建StreamAllocation对象(包含http请求组件)
  StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),createAddress(request.url()), call, eventListener, callStackTrace);
  this.streamAllocation = streamAllocation;

  int followUpCount = 0;
  Response priorResponse = null;
  while (true) {
    if (canceled) {
      streamAllocation.release();
      throw new IOException("Canceled");
    }

    Response response;
    boolean releaseConnection = true;
    try {
      response = realChain.proceed(request, streamAllocation, null, null);
      releaseConnection = false;
    } catch (RouteException e) {
      //路由异常,连接未成功,请求还没发出去
      if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
        throw e.getFirstConnectException();
      }
      releaseConnection = false;
      continue;
    } catch (IOException e) {
   // 请求发出去了,但是和服务器通信失败了(socket流正在读写数据的时候断开连接)
      boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
      if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
      releaseConnection = false;
      continue;
    } finally {
      // 不是前两种失败,直接关闭和清理所有资源
      if (releaseConnection) {
        streamAllocation.streamFailed(null);
        streamAllocation.release();
      }
    }
    if (priorResponse != null) {
      response = response.newBuilder()
          .priorResponse(priorResponse.newBuilder()
                  .body(null)
                  .build())
          .build();
    }
    //重定向
    Request followUp;
    try {
      followUp = followUpRequest(response, streamAllocation.route());
    } catch (IOException e) {
      streamAllocation.release();
      throw e;
    }

    if (followUp == null) {
      streamAllocation.release();
      //不需要重定向直接返回response终止循环
      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) {
      streamAllocation.release();
      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()), call, eventListener, callStackTrace);
      this.streamAllocation = streamAllocation;
    } else if (streamAllocation.codec() != null) {
      throw new IllegalStateException("Closing the body of " + response
          + " didn't close its backing stream. Bad interceptor?");
    }

    request = followUp;
    priorResponse = response;
  }
}

重试与重定向拦截器主要处理Response,可以看到RouteException和IOException都是调用了recover,返回true表示允许重试。

private boolean recover(IOException e, StreamAllocation streamAllocation,
    boolean requestSendStarted, Request userRequest) {
  streamAllocation.streamFailed(e);
  //是否设置了不允许重试(默认允许),则一旦请求失败就不再重试——>全局配置
  if (!client.retryOnConnectionFailure()) return false;

  //针对某个请求配置是否需要重试 用户自己实现UnrepeatableRequestBody请求体——>单个请求配置
  if (requestSendStarted && requestIsUnrepeatable(e, userRequest)) return false;

  // 是否属于重试的异常(协议异常、超时异常、证书异常、SSL握手未授权异常)
  if (!isRecoverable(e, requestSendStarted)) return false;

  // 是否存在更多的路由
  if (!streamAllocation.hasMoreRoutes()) return false;

  return true;
}

大致流程

image.png

重定向
private Request followUpRequest(Response userResponse, Route route) throws IOException {
  if (userResponse == null) throw new IllegalStateException();
  int responseCode = userResponse.code();

  final String method = userResponse.request().method();
  switch (responseCode) {

//407 客户端使用了HTTP代理服务器,在请求头中添加"Proxy-Authorization"让代理服务器授权
    case HTTP_PROXY_AUTH:
      Proxy selectedProxy = route.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);
//401 服务器接口需要验证使用者身份,在请求头中添加"Authorization"
    case HTTP_UNAUTHORIZED:
      return client.authenticator().authenticate(route, userResponse);
    case HTTP_PERM_REDIRECT://308 永久重定向
    case HTTP_TEMP_REDIRECT://307临时重定向
     //如果请求方式不是GET或HEAD,框架不会自动重定向请求
      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:
      //客服端不允许重定向,返回null
      if (!client.followRedirects()) return null;
      //从响应头取出loaction
      String location = userResponse.header("Location");
      if (location == null) return null;
      //根据location配置新的请求url
      HttpUrl url = userResponse.request().url().resolve(location);
      // 取不出,返回null 不进行重定向
      if (url == null) return null;
      //如果重定向在http到https之间切换,检查用户是否允许(默认允许)
      boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
      if (!sameScheme && !client.followSslRedirects()) return null;
Request.Builder requestBuilder = userResponse.request().newBuilder();
      //请求不是get与head
      if (HttpMethod.permitsRequestBody(method)) {
        final boolean maintainBody = HttpMethod.redirectsWithBody(method);
        //除了propfind请求之外都改成GET请求
        if (HttpMethod.redirectsToGet(method)) {
          requestBuilder.method("GET", null);
        } else {
          RequestBody requestBody = maintainBody ? userResponse.request().body() : null;
          requestBuilder.method(method, requestBody);
        }
        // 不是propfind的请求,把请求头中关于请求体的数据删除
        if (!maintainBody) {
          requestBuilder.removeHeader("Transfer-Encoding");
          requestBuilder.removeHeader("Content-Length");
          requestBuilder.removeHeader("Content-Type");
        }
      }
      // 在跨主机重定向时,删除身份验证请求头
      if (!sameConnection(userResponse, url)) {
        requestBuilder.removeHeader("Authorization");
      }
      //构建request
      return requestBuilder.url(url).build();
    case HTTP_CLIENT_TIMEOUT://408 客户端请求超时
       // 判断用户是否允许重试
      if (!client.retryOnConnectionFailure()) {
        return null;
      }
      if (userResponse.request().body() instanceof UnrepeatableRequestBody) {
        return null;
      }
      //本次重试结果还是408,就放弃,不再重复请求。
      if (userResponse.priorResponse() != null
          && userResponse.priorResponse().code() == HTTP_CLIENT_TIMEOUT) {
        return null;
      }
      //如果服务器告诉我们了 Retry-After 多久后重试,那框架不管了。
      if (retryAfter(userResponse, 0) > 0) {
        return null;
      }
      return userResponse.request();
    case HTTP_UNAVAILABLE://再次请求还是503,就放弃,不再重复请求。
      if (userResponse.priorResponse() != null
          && userResponse.priorResponse().code() == HTTP_UNAVAILABLE) {
        return null;
      }
      if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
        return userResponse.request();
      }
      return null;
    default:
      return null;
  }
}

RetryAndFollowUpInterceptor是整个责任链中的第一个,首次接触到Request和最后接收Response的角色,它的主要功能是判断是否需要重试与重定向。

重试的前提是出现了RouteException或IOException,会通过recover方法进行判断是否进行重试。

重定向是发生在重试判定后,不满足重试的条件,会进一步调用followUpRequest根据Response的响应码进行重定向操作。