关于httpClient的重发策略
HttpClient默认是有重试机制的,其重试策略是:
1.只有发生IOExecetion时才会发生重试
2.InterruptedIOException、UnknownHostException、ConnectException、SSLException,发生这4中异常不重试
3.get方法可以重试3次,post方法在socket对应的输出流没有被write并flush成功时可以重试3次。
4.读/写超时不进行重试
5.socket传输中被重置或关闭会进行重试
一、HttpClient的创建方式:
//设置超时时间
httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(5000);
httpClient.getHttpConnectionManager().getParams().setSoTimeout(5000);
//构造PostMethod的实例 url:请求地址
PostMethod postMethod = new PostMethod(url);
//关闭重发策略
postMethod .getParams().setParameter(HttpMethodParams.RETRY_HANDLER,new DefaultHttpMethodRetryHandler(0,false));
//设置编码格式
postMethod .getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET,"UTF-8");
//设置超时时间
postMethod .getParams().setParameter(HttpMethodParams.SO_TIMEOUT,5000);
try{
httpClient.executeMethod(postMethod);
}catch (Exception e){
//TODO 省略异常处理部分
}finally {
postMethod.releaseConnection();
}
重试方法调用:
在 org.apache.commons.httpclient.HttpMethodDirector类中,如果发生IOException,会判断是否需要重试
具体源码如下:
private void executeWithRetry(HttpMethod method) throws IOException, HttpException {
int execCount = 0;
try {
while(true) {
++execCount;
try {
if (LOG.isTraceEnabled()) {
LOG.trace("Attempt number " + execCount + " to process request");
}
if (this.conn.getParams().isStaleCheckingEnabled()) {
this.conn.closeIfStale();
}
if (!this.conn.isOpen()) {
this.conn.open();
if (this.conn.isProxied() && this.conn.isSecure() && !(method instanceof ConnectMethod) && !this.executeConnect()) {
return;
}
}
this.applyConnectionParams(method);
method.execute(this.state, this.conn);
return;
} catch (HttpException var5) {
throw var5;
} catch (IOException var6) {
LOG.debug("Closing the connection.");
this.conn.close();
if (method instanceof HttpMethodBase) {
MethodRetryHandler handler = ((HttpMethodBase)method).getMethodRetryHandler();
if (handler != null && !handler.retryMethod(method, this.conn, new HttpRecoverableException(var6.getMessage()), execCount, method.isRequestSent())) {
LOG.debug("Method retry handler returned false. Automatic recovery will not be attempted");
throw var6;
}
}
HttpMethodRetryHandler handler = (HttpMethodRetryHandler)method.getParams().getParameter("http.method.retry-handler");
if (handler == null) {
handler = new DefaultHttpMethodRetryHandler();
}
if (!((HttpMethodRetryHandler)handler).retryMethod(method, var6, execCount)) {
LOG.debug("Method retry handler returned false. Automatic recovery will not be attempted");
throw var6;
}
if (LOG.isInfoEnabled()) {
LOG.info("I/O exception (" + var6.getClass().getName() + ") caught when processing request: " + var6.getMessage());
}
if (LOG.isDebugEnabled()) {
LOG.debug(var6.getMessage(), var6);
}
LOG.info("Retrying request");
}
}
} catch (IOException var7) {
if (this.conn.isOpen()) {
LOG.debug("Closing the connection.");
this.conn.close();
}
this.releaseConnection = true;
throw var7;
} catch (RuntimeException var8) {
if (this.conn.isOpen()) {
LOG.debug("Closing the connection.");
this.conn.close();
}
this.releaseConnection = true;
throw var8;
}
}
HttpMethodRetryHandler:
new DefaultHttpMethodRetryHandler(0,false)解读:
DefaultHttpMethodRetryHandler(0,false),
允许重试次数为0
调用如下方法:
public DefaultHttpMethodRetryHandler(int retryCount, boolean requestSentRetryEnabled) {
this.retryCount = retryCount;
this.requestSentRetryEnabled = requestSentRetryEnabled;
}
默认的重试方法:DefaultHttpMethodRetryHandler()
允许重试次数为3
具体处理如下“retryMethod”方法
若实际发送次数大于等于3次,不进行重试;
若实际发送次数小于3次,请求未成功发送,进行重试;
若实际发送次数小于3次,请求成功发送,不进行重试;
public class DefaultHttpMethodRetryHandler implements HttpMethodRetryHandler {
private static Class SSL_HANDSHAKE_EXCEPTION = null;
private int retryCount;
private boolean requestSentRetryEnabled;
public DefaultHttpMethodRetryHandler(int retryCount, boolean requestSentRetryEnabled) {
this.retryCount = retryCount;
this.requestSentRetryEnabled = requestSentRetryEnabled;
}
public DefaultHttpMethodRetryHandler() {
this(3, false);
}
public boolean retryMethod(HttpMethod method, IOException exception, int executionCount) {
if (method == null) {
throw new IllegalArgumentException("HTTP method may not be null");
} else if (exception == null) {
throw new IllegalArgumentException("Exception parameter may not be null");
} else if (method instanceof HttpMethodBase && ((HttpMethodBase)method).isAborted()) {
return false;
} else if (executionCount > this.retryCount) {
return false;
} else if (exception instanceof NoHttpResponseException) {
return true;
} else if (exception instanceof InterruptedIOException) {
return false;
} else if (exception instanceof UnknownHostException) {
return false;
} else if (exception instanceof NoRouteToHostException) {
return false;
} else if (SSL_HANDSHAKE_EXCEPTION != null && SSL_HANDSHAKE_EXCEPTION.isInstance(exception)) {
return false;
} else {
return !method.isRequestSent() || this.requestSentRetryEnabled;
}
}
}
MethodRetryHandler目前在commons-httpclient-3.1.jar中不推荐使用
public class DefaultMethodRetryHandler implements MethodRetryHandler {
private int retryCount = 3;
private boolean requestSentRetryEnabled = false;
public DefaultMethodRetryHandler() {
}
public boolean retryMethod(HttpMethod method, HttpConnection connection, HttpRecoverableException recoverableException, int executionCount, boolean requestSent) {
return (!requestSent || this.requestSentRetryEnabled) && executionCount <= this.retryCount;
}
}
二、CloseableHttpClient的创建方式:
1.HttpClients.custom().setXXX().build();
2.HttpClients.createDefault();
第一种方法用来定制一些HttpClient的属性,比如https证书,代理服务器,http过滤器,连接池管理器等自定义的用法。
第二种方法用来获得一个默认的HttpClient实例,获取到的CloseableHttpClient是默认重试策略的。
这两种方法获得都是CloseableHttpClient实例,且都是通过HttpClientBuilder的build()构建的。
HttpClients.custom();源码
public static HttpClientBuilder custom() {
return HttpClientBuilder.create();
}
HttpClients.createDefault();源码
public static CloseableHttpClient createDefault() {
return HttpClientBuilder.create().build();
}
如果要禁止CloseableHttpClient访问超时时进行重试,则需要用第一种方式创建CloseableHttpClient,设置重试参数automaticRetriesDisabled为true;
CloseableHttpClient build1 = HttpClients.custom().disableAutomaticRetries().build()
其中disableAutomaticRetries()的源码为:
public final HttpClientBuilder disableAutomaticRetries() {
this.automaticRetriesDisabled = true;
return this;
}
这里将参数设置为true,在执行build()方法的时候会进行判断:
if (!this.automaticRetriesDisabled) {
routePlannerCopy = this.retryHandler;
if (routePlannerCopy == null) {
routePlannerCopy = DefaultHttpRequestRetryHandler.INSTANCE;
}
execChain = new RetryExec((ClientExecChain)execChain, (HttpRequestRetryHandler)routePlannerCopy);
}
如果automaticRetriesDisabled为false就会进入if分支,使用RetryExec执行器创建执行链使用用默认的重发策略DefaultHttpRequestRetryHandler。因此automaticRetriesDisabled设置为true时就可以禁止重发。
为HttpClient设置连接超时时间和获取数据超时时间
使用HttpClient,一般都需要设置连接超时时间和获取数据超时时间。这两个参数很重要,目的是为了防止访问其他http时,由于超时导致自己的应用受影响。
4.5版本中,这两个参数的设置都抽象到了RequestConfig中,由相应的Builder构建。
setConnectTimeout:设置连接超时时间,单位毫秒。
setConnectionRequestTimeout:设置从connect Manager获取Connection 超时时间,单位毫秒。这个属性是新加的属性,因为目前版本是可以共享连接池的。
setSocketTimeout:请求获取数据的超时时间,单位毫秒。 如果访问一个接口,多少时间内无法返回数据,就直接放弃此次调用。
HttpPost httpPost = new HttpPost();
RequestConfig requestConfig = RequestConfig.custom().
setConnectTimeout(5000).
setConnectionRequestTimeout(5000).
setSocketTimeout(5000).
build();
httpPost.setConfig(requestConfig);
整体例子
/**
* 创建CloseableHttpClient,禁止超时重试,并设置超时时间
*/
CloseableHttpClient closeableHttpClient = HttpClients.custom().disableAutomaticRetries().build();
HttpPost httpPost = new HttpPost();
RequestConfig requestConfig = RequestConfig.custom().
setConnectTimeout(5000).
setConnectionRequestTimeout(5000).
setSocketTimeout(5000).
build();
httpPost.setConfig(requestConfig);
try {
closeableHttpClient.execute(httpPost);
} catch (IOException e) {
//异常处理部分省略.....
e.printStackTrace();
}