okhttp篇4:RetryAndFollowUpInterceptor

10 篇文章 0 订阅
6 篇文章 0 订阅

在上一篇  okhttp篇3:RealCall_yolan6824的博客-CSDN博客 中讲到RealCall无论是在execute还是enqueue方法中,都是通过getResponseWithInterceptorChain方法获取Request对应的Response的。而getResponseWithInterceptorChain这个方法,又是通过RealInterceptorChain这个类串起一系列拦截器顺序执行得到Response的。所以这一篇,先将RealInterceptorChain及第一个拦截器 RetryAndFollowUpInterceptor。

RealInterceptorChain

// RealInterceptorChain
public final class RealInterceptorChain implements Interceptor.Chain {
    public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
        HttpCodec httpCodec, RealConnection connection, int index, Request request,
         Call call,EventListener eventListener, 
         int connectTimeout, int readTimeout, int writeTimeout) {
      this.interceptors = interceptors;
      this.connection = connection;
      this.streamAllocation = streamAllocation;
      this.httpCodec = httpCodec;
      this.index = index;
      this.request = request;
      this.call = call;
      this.eventListener = eventListener;
      this.connectTimeout = connectTimeout;
      this.readTimeout = readTimeout;
      this.writeTimeout = writeTimeout;
    }
    
    @Override public Response proceed(Request request) throws IOException {
      return proceed(request, streamAllocation, httpCodec, connection);
    }
    
    public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
        RealConnection connection) throws IOException {
      if (index >= interceptors.size()) throw new AssertionError();
    
      calls++;
    
      // If we already have a stream, confirm that the incoming request will use it.
      // httpCodec != null,代表已经有一个stream了
      if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
        throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
            + " must retain the same host and port");
      }
    
      // If we already have a stream, confirm that this is the only call to chain.proceed().
      if (this.httpCodec != null && calls > 1) {
        throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
            + " must call proceed() exactly once");
      }
    
      // Call the next interceptor in the chain.
      // 每一个proceed方法都会新创建一个RealInterceptorChain,对应一个Interceptor
      // 简单的说,RealInterceptorChain就是负责携带下一个Interceptor要用的参数的
      // index指向拦截器列表中当前拦截器的索引。
      // 通过index+1更新调用interceptor.intercept方法
      RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
          connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
          writeTimeout);
      Interceptor interceptor = interceptors.get(index);
      // 将next这个chain传给下一个interceptor
      Response response = interceptor.intercept(next);
    
      // Confirm that the next interceptor made its required call to chain.proceed().
      if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
        throw new IllegalStateException("network interceptor " + interceptor
            + " must call proceed() exactly once");
      }
    
      // Confirm that the intercepted response isn't null.
      if (response == null) {
        throw new NullPointerException("interceptor " + interceptor + " returned null");
      }
    
      if (response.body() == null) {
        throw new IllegalStateException(
            "interceptor " + interceptor + " returned a response with no body");
      }
    
      return response;
    }
}

RealInterceptorChain最重要的就是proceed方法。如上面的注释所说,每调用一次chain.proceed方法,都会新创建一个RealInterceptorChain。

这些RealInterceptorChain共享一开始由RealCall传入的拦截器列表,但是index(对应拦截器的索引)会在每一次proceed方法中更新,实现顺序调用interceptor.proceed方法。

因此,客户端自己实现的拦截器,依然需要调用chain.proceed方法(否则剩下的拦截器不会执行),并且可以通过proceed方法,改变Request, StreamAllocation, HttpCodec , RealConnection这四个参数的值。

RetryAndFollowUpInterceptor

 从RealCall的getResponseWithInterceptorChain,可以看出,如果用户没有自定义拦截器,那么RetryAndFollowUpInterceptor就是实际上第一个执行的拦截器。

intercept

// 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
  streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()),
      call, eventListener, callStackTrace);

  int followUpCount = 0;
  Response priorResponse = null;
  while (true) {// while循环进行request重试
    if (canceled) {
      streamAllocation.release();
      throw new IOException("Canceled");
    }

    Response response;
    boolean releaseConnection = true;
    try {
        // 通过proceed方法,传入新的参数给后面的拦截器使用。
      response = realChain.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(), false, request)) {// 路由失败
        throw e.getLastConnectException();
      }
      releaseConnection = false;
      continue;
    } catch (IOException e) {
      // An attempt to communicate with a server failed. The request may have been sent.
      boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
      if (!recover(e, requestSendStarted, 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);// 根据response构建下一个要请求的Request,一般用于重定向

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

    closeQuietly(response.body());

    if (++followUpCount > MAX_FOLLOW_UPS) {// 控制最多只能再次请求20次
      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);
    } 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;
  }
}

followup在翻译中,代表的是跟进的意思,在这里可以理解成一个请求的后续请求,比如说,如果一个请求,因为url改变了,返回了302,那么这时候,可以通过再发送一个新的请求,带上新的url去请求Response。okhttp为我们把这一步给做了。

在上面的intercept方法中,新创建了一个StreamAllocation。这个StreamAllocation是负责协调Connection,Stream,Call三者的关系的。这个后面会详细讲。在这里可以理解成,它可以负责资源的释放。

总结一下intercept方法干的事情:

  1. 如果已经cancel,直接调用streamAllocation.release(释放相关资源)
  2. 调用chain.proceed获取response
    1. 如果获取到RouteException:路由异常 / IOException
      1. client.retryOnConnectionFailure() == false / streamAllocation.hasMoreRoutes() == false / ProtocolException
        1. 不重试
      2. socket超时会重试,SSLHandshakeException,只要不是证书错误,会重试
  3. 根据response获取request(下面的followupRequest方法)
    1. 如果获取到的request为空-->除了认证错误,超时,重定向,其他情况返回的request都是空,这种情况会直接调用streamAllocation.release() 释放资源。
    2. 如果获取到的request不为空,证明需要重试。
    3. 重试的次数最多为20次。

也就是说,路由异常,认证错误,socket超时,重定向,RetryAndFollowUpInterceptor都会按一定的规则,为我们重新构造Request,重新请求。重试的次数最多是20次。

followUpRequest

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:// 407,代理验证,报告客户端需要使用代理服务器进行身份验证。
      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:// 401,未授权,需要重新进行身份验证
      return client.authenticator().authenticate(route, userResponse);

    case HTTP_PERM_REDIRECT:// 307,308临时重定向,确保请求方法(get/post)和消息主体不会发生变化
    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:// 永久重定向,method(get/post)有可能会变化
    case HTTP_MOVED_TEMP:
    case HTTP_SEE_OTHER:
      // Does the client allow redirects?
      if (!client.followRedirects()) return null;

      String location = userResponse.header("Location");// 获取response中的新地址
      if (location == null) return null;
      HttpUrl url = userResponse.request().url().resolve(location);// 根据新地址,获取一个新的HttpUrl

      // 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;

      // Most redirects don't include a request body.
      Request.Builder requestBuilder = userResponse.request().newBuilder();
      if (HttpMethod.permitsRequestBody(method)) {
        final boolean maintainBody = HttpMethod.redirectsWithBody(method);
        if (HttpMethod.redirectsToGet(method)) {
          requestBuilder.method("GET", null);
        } else {
          RequestBody requestBody = maintainBody ? userResponse.request().body() : null;
          requestBuilder.method(method, requestBody);
        }
        if (!maintainBody) {
          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 (!client.retryOnConnectionFailure()) {
        // The application layer has directed us not to retry the request.
        return null;
      }

      if (userResponse.request().body() instanceof UnrepeatableRequestBody) {
        return null;
      }

      if (userResponse.priorResponse() != null
          && userResponse.priorResponse().code() == HTTP_CLIENT_TIMEOUT) {
        // We attempted to retry and got another timeout. Give up.
        return null;
      }

      return userResponse.request();

    default:
      return null;
  }
}

followRequests方法,主要是根据第一次request返回的response,看看需不需要再次请求,主要分为以下情形:

  1. 407 --> 需要使用代理服务器重新进行身份认证
  2. 401-> 直接进行身份认证
  3. 307/308(method == "get"/"head"), 300/301/302/304 依赖client.followRedirects()
    1. 获取Response中的url,重新构建Request
  4. 408(请求超时) -- 依赖client.retryOnConnectionFailure()
    1. 原request重新请求
  5. 其他情况,都返回空

总结

RetryAndFollowUpInterceptor,跟名字一样,主要负责重试。

跟重试相关联的api(默认都是true,允许重试):

  1. client.followRedirects()
  2. client.retryOnConnectionFailure()

需要重试的情况一般包括:

  1. 路由异常
  2. 身份认证失败
  3. 重定向
  4. 请求超时

最多重试20次。重试的时候,如果发现已经cancel了,取消重试。

下一篇讲下一个拦截器。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值