使用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次。