使用Apache HttpClient 4.x进行异常重试

在进行http请求时,难免会遇到请求失败的情况,失败后需要重新请求,尝试再次获取数据。

Apache的HttpClient提供了异常重试机制,在该机制中,我们可以很灵活的定义在哪些异常情况下进行重试。

重试前提

被请求的方法必须是幂等的:就是多次请求服务端结果应该是准确且一致的。

适合的方法:比如根据ID,修改人员姓名,无论请求多次结果都是一样,这就是幂等。

不适合的方法:比如减少账号50元,多次请求将多次扣减。

实现方式

想要实现异常重试,需要实现它提供的一个接口 HttpRequestRetryHandler ,并实现 retryRequest 方法。然后set到HttpClient中。该httpClient就具备了重试机制。

HttpClient自身提供了 StandardHttpRequestRetryHandler 和 DefaultHttpRequestRetryHandler 两个实现类。 DefaultHttpRequestRetryHandler 继承自 DefaultHttpRequestRetryHandler , StandardHttpRequestRetryHandler 默认几个方法为幂等,如PUT、GET、HEAD等除POST外的方法,如果自定义可以参考它的实现方式。

代码如下:

  1 import java.io.IOException;
  2 import java.io.InterruptedIOException;
  3 import java.net.ConnectException;
  4 import java.net.UnknownHostException;
  5 
  6 import javax.net.ssl.SSLException;
  7 
  8 import org.apache.commons.lang3.ObjectUtils;
  9 import org.apache.commons.lang3.StringUtils;
 10 import org.apache.http.Consts;
 11 import org.apache.http.HttpEntityEnclosingRequest;
 12 import org.apache.http.HttpRequest;
 13 import org.apache.http.HttpStatus;
 14 import org.apache.http.ParseException;
 15 import org.apache.http.client.HttpRequestRetryHandler;
 16 import org.apache.http.client.config.RequestConfig;
 17 import org.apache.http.client.methods.CloseableHttpResponse;
 18 import org.apache.http.client.methods.HttpPost;
 19 import org.apache.http.client.protocol.HttpClientContext;
 20 import org.apache.http.entity.ContentType;
 21 import org.apache.http.entity.StringEntity;
 22 import org.apache.http.impl.client.CloseableHttpClient;
 23 import org.apache.http.impl.client.HttpClients;
 24 import org.apache.http.protocol.HttpContext;
 25 import org.apache.http.util.EntityUtils;
 26 
 27 /**
 28  * @author 29  *
 30  * @date 2017年5月18日 上午9:17:30
 31  *
 32  * @Description
 33  */
 34 public class HttpPostUtils {
 35     /**
 36      * 
 37      * @param uri
 38      *            the request address
 39      * @param json
 40      *            the request data that must be a JSON string
 41      * @param retryCount
 42      *            the number of times this method has been unsuccessfully
 43      *            executed
 44      * @param connectTimeout
 45      *            the timeout in milliseconds until a connection is established
 46      * @param connectionRequestTimeout
 47      *            the timeout in milliseconds used when requesting a connection
 48      *            from the connection manager
 49      * @param socketTimeout
 50      *            the socket timeout in milliseconds, which is the timeout for
 51      *            waiting for data or, put differently, a maximum period
 52      *            inactivity between two consecutive data packets
 53      * @return null when method parameter is null, "", " "
 54      * @throws IOException
 55      *             if HTTP connection can not opened or closed successfully
 56      * @throws ParseException
 57      *             if response data can not be parsed successfully
 58      */
 59     public String retryPostJson(String uri, String json, int retryCount, int connectTimeout,
 60             int connectionRequestTimeout, int socketTimeout) throws IOException, ParseException {
 61         if (StringUtils.isAnyBlank(uri, json)) {
 62             return null;
 63         }
 64 
 65         HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() {
 66 
 67             @Override
 68             public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
 69                 if (executionCount > retryCount) {
 70                     // Do not retry if over max retry count
 71                     return false;
 72                 }
 73                 if (exception instanceof InterruptedIOException) {
 74                     // An input or output transfer has been terminated
 75                     return false;
 76                 }
 77                 if (exception instanceof UnknownHostException) {
 78                     // Unknown host 修改代码让不识别主机时重试,实际业务当不识别的时候不应该重试,再次为了演示重试过程,执行会显示retryCount次下面的输出
 79                     System.out.println("不识别主机重试"); return true;
 80                 }
 81                 if (exception instanceof ConnectException) {
 82                     // Connection refused
 83                     return false;
 84                 }
 85                 if (exception instanceof SSLException) {
 86                     // SSL handshake exception
 87                     return false;
 88                 }
 89                 HttpClientContext clientContext = HttpClientContext.adapt(context);
 90                 HttpRequest request = clientContext.getRequest();
 91                 boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
 92                 if (idempotent) {
 93                     // Retry if the request is considered idempotent
 94                     return true;
 95                 }
 96                 return false;
 97             }
 98         };
 99 
100         CloseableHttpClient client = HttpClients.custom().setRetryHandler(httpRequestRetryHandler).build();
101         HttpPost post = new HttpPost(uri);
102         // Create request data
103         StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
104         // Set request body
105         post.setEntity(entity);
106 
107         RequestConfig config = RequestConfig.custom().setConnectTimeout(connectTimeout)
108                 .setConnectionRequestTimeout(connectionRequestTimeout).setSocketTimeout(socketTimeout).build();
109         post.setConfig(config);
110         // Response content
111         String responseContent = null;
112         CloseableHttpResponse response = null;
113         try {
114             response = client.execute(post, HttpClientContext.create());
115             if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
116                 responseContent = EntityUtils.toString(response.getEntity(), Consts.UTF_8.name());
117             }
118         } finally {
119             if (ObjectUtils.anyNotNull(response)) {
120                 response.close();
121             }
122             if (ObjectUtils.anyNotNull(client)) {
123                 client.close();
124             }
125         }
126         return responseContent;
127     }

在实现的 retryRequest 方法中,遇到不识别主机异常,返回 true ,请求将重试。最多重试请求retryCount次。