okhttp3-源码分析(3) RetryAndFollowUpInterceptor

本文深入解析OkHttp的RetryAndFollowUpInterceptor拦截器,详细阐述其在请求发送前后的职责,包括创建StreamAllocation、处理取消操作、处理请求超时、重定向和重试。通过状态码分析了不同情况下的处理逻辑,如408请求超时、401和407身份验证问题、503服务器错误以及3xx重定向。此外,还介绍了重试和重定向的具体实现细节,如如何判断是否需要重定向、重试条件以及异常处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

如果没有添加自定义拦截器的话,RetryAndFollowUpInterceptor是okhttp拦截器链中的第一个拦截器。
大体总结一下他的职责:
发送请求前:
1.为请求流程创建StreamAllocation(与连接工作有关)
2.提供了cancel()方法来关闭连接
发送请求后:
1.在接收到response后,负责请求请求的重定向和重试工作
相关处理逻辑:

状态码问题原因okhttp处理逻辑
408请求超时只在返回的超时时间为0时(希望立即重试),重试一次
401,407身份验证出现问题需要我们实现相应的接口,不然不会重试
503服务器出错可能返回的信息中有超时时间,在超时时间为0时重试(与408相似)
300 ~ 303,307.308需要重定向okhttp会自动帮我们完成相关逻辑

源码解析:

// RetryAndFollowUpInterceptor里的实现,需要传入一个实现了Chain接口的对象
@Override public Response intercept(Chain chain) throws IOException {
    //根据传入的chain初始化需要的变量
    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);
    this.streamAllocation = streamAllocation;
    //创建streamAllocation

    int followUpCount = 0;//重定向次数
    Response priorResponse = null;//第一次循环的priorResponse为null
    
    while (true) {
      if (canceled) {
        streamAllocation.release();
    //把streamAllocation从连接的分配列表(这个列表还是弱引用列表)中删除此分配,
    //下面还会超多次的调用这个方法。
    //现在可以理解为:调用了这个方法的连接,就不再标记为活跃状态,这部分的坑后面再来填吧
        throw new IOException("Canceled");
      }

      Response response;
      boolean releaseConnection = true;
      try {
        
      //调用realChain的proceed 等于把request交给下一个拦截器,并接收下一个拦截器传回的response
        response = realChain.proceed(request, streamAllocation, null, null);
        releaseConnection = false;
          
      } catch (RouteException e) {//异常处理,处理RouteException
        // The attempt to connect via a route failed. The request will not have been sent.	
        //机翻:尝试通过路由连接失败。 该请求将不会被发送。 
        if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
          throw e.getFirstConnectException();
        }
        releaseConnection = false;
        continue;
      } catch (IOException e) {///异常处理,处理IOExecption
        // An attempt to communicate with a server failed. The request may have been sent.
        //机翻:尝试与服务器通信失败。 请求可能已发送。 
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        if (!recover(e, streamAllocation, 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.
      //第一次循环不会进到这里,这里定义的response的属性priorResponse是没有返回体的
      if (priorResponse != null) {
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
      }

      Request followUp;
      try {
          //在不需要重定向或重试等情况下,才会返回null,这里是判断重定向或者重试的重要方法,看下面分析
        followUp = followUpRequest(response, streamAllocation.route());
      } catch (IOException e) {
        streamAllocation.release();
        throw e;
      }
	
      if (followUp == null) {//返回null,不进行重定向或重试,退出循环
        streamAllocation.release();
        return response;//这里是死循环的唯一出口
      }
		
      //下面都是进行重定向或重试前的准备工作
      closeQuietly(response.body());
		
       //下面都是异常的一些情况,也都要调用streamAllocation.release()
      if (++followUpCount > MAX_FOLLOW_UPS) {//超过了最大重定向数量
        streamAllocation.release();
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }
	  
       //UnrepeatableRequestBody是一个接口,实现了这个接口就不能重复发送请求
      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) {//codec涉及到后面的拦截器,后面再进行分析
        throw new IllegalStateException("Closing the body of " + response
            + " didn't close its backing stream. Bad interceptor?");
      }

      request = followUp;//request变为重定向request
      priorResponse = response;//把下一次循环的priorResponse 置为 response
    }
  }


//重定向及重试方法
  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) {//处理状态码
    //上面的401和407都是身份验证失败的情形,如果要实现重试,
   //我们需要在okhttpclient中设置,okhttp不会自动帮我们做
      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");
        }
        //需要传入实现了Authenticator接口的对象,不然就会返回null
        return client.proxyAuthenticator().authenticate(route, userResponse);

      case HTTP_UNAUTHORIZED://401重试
        //同样需要传入实现了Authenticator接口的对象,不然就会返回null
        return client.authenticator().authenticate(route, userResponse);

      case HTTP_PERM_REDIRECT://308
      case HTTP_TEMP_REDIRECT://307
        // "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"、
        //307 或 308 只支持 GET 和 HEAD 请求     
        if (!method.equals("GET") && !method.equals("HEAD")) {
          return null;
        }
        // fall-through
      case HTTP_MULT_CHOICE://依次是300,301,302,303
      case HTTP_MOVED_PERM:
      case HTTP_MOVED_TEMP:
      case HTTP_SEE_OTHER:
        // Does the client allow redirects?
        //查询okhttp client 是否支持重定向?,client.followRedirects()默认值
        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.
        //机翻:如果已经进行配置,请勿遵循 SSL 和非 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不允许有requestbody  
        Request.Builder requestBuilder = userResponse.request().newBuilder();
            
          //如果request是 除get和head之外的request,才会进入下面这个if体里面  
        if (HttpMethod.permitsRequestBody(method)) {
          final boolean maintainBody = HttpMethod.redirectsWithBody(method);
//上面这部分是判断是否属于PROPFIND类型的request,这种类型的request重定向比较特殊,是有requestbody的
            
          if (HttpMethod.redirectsToGet(method)) {
            requestBuilder.method("GET", null);
              //除了PROPFIND类型的request,其他request都会重定向成get请求
          } else {
              //这部分是PROPFIND类型的request的重定向过程
            RequestBody requestBody = maintainBody ? userResponse.request().body() : null;
            requestBuilder.method(method, requestBody);
          }
            
          if (!maintainBody) {//下面是除了PROPFIND类型的request,在重定向时,还要干的事
            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,请求超时。客户端没有在服务器预备等待的时间内完成一个请求的发送
        // 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.)
     //机翻:408 在实践中很少见,但一些服务器(如 HAProxy)使用此响应代码。 
      //规范说我们可以不加修改地重复请求。 现代浏览器也会重复请求(即使是非幂等的。)
            
        if (!client.retryOnConnectionFailure()) {
          // The application layer has directed us not to retry the request.
          //应用层叫我们别重试(我们自己在okhttpclient里面设定了不重试)  
          return null;
        }
		
        //UnrepeatableRequestBody 是一个接口,里面没有任何属性或方法
        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.
          //重试过一次了,返回还是408,那就不再重试了  
          return null;
        }

        if (retryAfter(userResponse, 0) > 0) {
         //给出的延迟重试时间大于0,那也放弃(下面503的处理,也用到了这个方法)   
          return null;
        }

        return userResponse.request();

      case HTTP_UNAVAILABLE:
  //状态码503。服务器出现问题。如果状态是临时的,并且将在一段时间以后恢复,那么会给出延迟时间,并在响应中
  //用Retry-After 头用以标明这个延迟时间
        if (userResponse.priorResponse() != null
            && userResponse.priorResponse().code() == HTTP_UNAVAILABLE) {
          // We attempted to retry and got another timeout. Give up.
          return null;
        }
		
        //计算延迟时间,如果没有包含Retry-After 头,那么默认返回传进去的Integer.MAX_VALUE
        //okhttp只会自动重试 延迟时间为0,正好需要立即重试的503请求    
        if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
          // specifically received an instruction to retry without delay
          return userResponse.request();
        }

        return null;

      default:
        return null;
    }
  }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值