okhttp源码阅读---重试机制

1,251 阅读5分钟

1.RealInterceptorChain初始化

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    // 1.重试机制拦截器放在请求责任链中的第一位
    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());
	// 2.开始进行请求
    return chain.proceed(originalRequest);
}

1、重试机制拦截器处于责任链中的第一位, 负责请求结束后的收尾工作, 包括根据请求中出现的异常采取是否启动重试机制

2.RealInterceptorChain.proceed

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();
    calls++;
    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    // 从责任链中遍历获取拦截器对request进行处理
    Response response = interceptor.intercept(next);
    return response;
}

3.RetryAndFollowUpInterceptor.intercept

@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);
    this.streamAllocation = streamAllocation;

    int followUpCount = 0;
    Response priorResponse = null;
    // 1.进入while循环
    while (true) {
      	if (canceled) {
        	// 2.如果请求已经被取消, 释放连接池的资源
        	streamAllocation.release();
        	throw new IOException("Canceled");
      	}
      	Response response;
      	boolean releaseConnection = true;
      	try {
        	// 3.将request交给责任链上的下一个节点处理
        	response = realChain.proceed(request, streamAllocation, null, null);
            // 4.是否需要释放连接池的标识, 正常情况下, 先不释放连接池
        	releaseConnection = false;
            // 5.路由异常, 连接地址失败的异常
      	} catch (RouteException e) {
        	// 6.判断是否需要重试
        	if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
          		throw e.getFirstConnectException();
        	}
        	releaseConnection = false;
            // 7.需要重试
        	continue;
            // 8.IO异常
      	} catch (IOException e) {
			// 9.除去连接异常的其他的IO异常
        	boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        	if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
        		releaseConnection = false;
            // 10.需要重试
        	continue;
      	} finally {
        	// We're throwing an unchecked exception. Release any resources.
            // 11.释放连接
        	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;
      	try {
        	followUp = followUpRequest(response, streamAllocation.route());
      	} catch (IOException e) {
        	streamAllocation.release();
        	throw e;
      	}
      	if (followUp == null) {
        	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) {
        	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;
    }
}

1、责任链上的其他节点在处理request时, 如果抛出异常, 进入**catch(RouteException)或者catch(IOException)**中, 然后根据这两个异常分别进行处理. 然后判断是否需要进行重试机制

4.RouteException

  这个异常发生在Request请求还没有发出去前, 就是打开Socket连接失败. 这个异常是okhttp自定义的异常, 是一个包裹类, 包裹住了建立连接失败中发生的各种Exception, 主要发生在ConnectInterceptor建立连接环节, 比如连接超时抛出的SocketTimeoutException, 包裹在RouteException中.   RouteException在RealConnection和StreamAllocation中被调用, 对应到具体的方法是RealConnection.connect方法和StreamAllocation.newStream

  RealConnection.connect和StreamAllocation.newStream的调用链如下图:

异常在RealConnection.connect中抛出

4.1 RealConnection.connect
public void connect(int connectTimeout, int readTimeout, int writeTimeout,
      int pingIntervalMillis, boolean connectionRetryEnabled, Call call,
      EventListener eventListener) {
    // 1.IllegalStateException
    if (protocol != null) throw new IllegalStateException("already connected");
    RouteException routeException = null;
    List<ConnectionSpec> connectionSpecs = route.address().connectionSpecs();
    ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);
    // 2.UnknownServiceException
	throw new RouteException(new UnknownServiceException("..."));
    while (true) {
      	try {
        	if (route.requiresTunnel()) {
          		connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
        	} else {
          		connectSocket(connectTimeout, readTimeout, call, eventListener);
        	}
        	establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);
        	break;
      	} catch (IOException e) {
        	// 3.IOException
        	if (routeException == null) {
          		routeException = new RouteException(e);
        	} else {
          		routeException.addConnectException(e);
        	}
        	if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
          		throw routeException;
        	}
      	}
    }
    if (route.requiresTunnel() && rawSocket == null) {
      	ProtocolException exception = new ProtocolException("Too many tunnel connections attempted: "
          + MAX_TUNNEL_ATTEMPTS);
    	// 4.ProtocolException
      	throw new RouteException(exception);
    }
}

  会抛出四种异常:

1、IllegalStateException

2、UnknownServiceException

3、IOException

4、ProtocolException

RouteException由RealConnection.connect和StreamAllocation.newStream两个方法抛出, 而newStream方法又是由ConnectInterceptor的intercept方法调用, RealConnection.connect方法是与服务器建立连接, newStream是获取流, 所以是在连接拦截器中抛出.

**注意: **这个拦截器的作用是建立TCP连接, 抛出异常, 也就说明了真正的网络请求还没有发出去, 也就是打开Socket失败了, 比如连接超时抛出的SocketTimeoutException, 包裹在RouteException中

4.2 再次分析recover中的RouteException
@Override 
public Response intercept(Chain chain) throws IOException {
	try {
        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(), streamAllocation, false, request)) {
          		throw e.getFirstConnectException();
        	}
        	releaseConnection = false;
        	continue;
      } 
}

如果是RouteException, 调用recover方法时, 传入的第三个参数**请求是否已经发出的标识**为false, 表示如果发生RouteException时, 真正的请求并没有发送出去, 结合RouteException也可以看出, RouteException发生在RealConnection.connect和StreamAllocation.newStream中, 而这两个方法的调用源头都是在ConnectInterceptor中, 即TCP连接的阶段, 在这个阶段如果发生了异常, 将异常包装进RouteException中, 然后抛出, 接着看recover对RouteException的处理

4.3 recover
/**
 * @param e: 当前发生的异常
 * @param requestSendStarted: true:请求已经发送出去, false: 请求还未发送出去.
 *		  如果是RouteException, 则该值为false, 即请求还未发送出去
 * @return true: 允许重试, false: 不允许重试
 */
private boolean recover(IOException e, StreamAllocation streamAllocation,
      boolean requestSendStarted, Request userRequest) {
    streamAllocation.streamFailed(e);
    // 1.如果设置了不需要重试, 直接返回false
    if (!client.retryOnConnectionFailure()) return false;
	// 2.请求已经发出并且body内容只可以发送一次
    //   如果当前异常是RouteException, 则requestSendStarted = false
    if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;
	// 3.根据异常类型判断是否可以重试
    if (!isRecoverable(e, requestSendStarted)) return false;
	// 4.判断是否还有可用路由, 如果没有, 即路由遍历完也没有连接成功
    if (!streamAllocation.hasMoreRoutes()) return false;
	// 5.可以重试
    return true;
  }

  这里只考虑请求过程中的异常情况, 即第三个判断条件, 根据请求过程中出现的异常来判断是否可重试.

4.4 isRecoverable
/**
 * 根据异常类型判断是否重试
 * @param requestSendStarted: true: 请求已经发送, false: 请求未发送
 * @return true: 允许重试, false: 不允许重试
 */
private boolean isRecoverable(IOException e, boolean requestSendStarted) {
    // 1.如果是协议问题, 不允许重试
    if (e instanceof ProtocolException) {
      	return false;
    }
    // 2.对于Socket超时异常, 如果请求未发送, 即当前异常是在建立连接阶段, 则允许重试
    if (e instanceof InterruptedIOException) {
      	return e instanceof SocketTimeoutException && !requestSendStarted;
    }
    // 3.如果该异常是SSL握手异常
    if (e instanceof SSLHandshakeException) {
		// 4.如果证书出现问题, 则不能进行重试
      	if (e.getCause() instanceof CertificateException) {
        	return false;
      	}
    }
    // 4.如果是SSL握手未授权异常, 不能进行恢复
    if (e instanceof SSLPeerUnverifiedException) {
      	return false;
    }
    return true;
}

1、如果发生协议、证书校验的异常, 则不允许重试

2、对于SocketTimeoutException异常

2.1 如果该异常是发生在TCP连接阶段, 即建立TCP连接超时, 此时允许重试, 返回true。

2.2 如果连接已经建立, 读取响应发生该异常, 此时requestSendStarted为true, 返回false, 即不允许重试