在进行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次。