HttpClient重试机制 --- 自定义HttpRequestRetryHandler(自定义 重试次数以及重试的时候业务处理)

时间:2025-02-18 16:29:48

            通过http做接口调用请求的时候,常常会因为网络抖动或者其他未知原因,造成接口在某个瞬间访问不了。此时需要增加重试机制。刚出来的时候掉接口需要三次重试,由于对httpclient不是很了解。只能在for循环里面对异常经常处理并重新调接口。后来做http服务端的时候,有次debug偶然发现客户端调一次请求,服务端会跳多次debug,后来查阅资料发现httpclient有重试机制。

            今天做了个通天塔接口重试的需求,便想起来了httpclient的重试机制。

   查了很久资料,也测试了很多次。后来终于成功了。是通过设置httpclient 的retryHandler来实现。
不多说废话,直接贴代码,如下:

/**
     * @param isPooled 是否使用连接池
     */
    public static CloseableHttpClient getClient(boolean isPooled) {
        HttpRequestRetryHandler handler = new HttpRequestRetryHandler() {
            @Override
            public boolean retryRequest(IOException arg0, int retryTimes, HttpContext arg2) {
                if (retryTimes > 2) {
                    return false;
                }
                if (arg0 instanceof UnknownHostException || arg0 instanceof ConnectTimeoutException
                        || !(arg0 instanceof SSLException) || arg0 instanceof NoHttpResponseException) {
                    return true;
                }

                HttpClientContext clientContext = (arg2);
                HttpRequest request = ();
                boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
                if (idempotent) {
                    // 如果请求被认为是幂等的,那么就重试。即重复执行不影响程序其他效果的
                    return true;
                }
                return false;
            }
        };


        if (isPooled) {
            return ().setConnectionManager(cm).setRetryHandler(handler).build();
        }
        return ();
    }

上面的代码是封装了一个HttpClientUtil的工具类中的获取线程池并设置retry机制的方法。整个HttpClientUtil文件如下:

package ;

import ;
import ;
import ;
import ;
import ;

import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import org.;
import org.;
import ;

import ;
import ;
import ;
import ;
import ;
import ;

import ;
import ;
import ;

@Component
public class HttpClientUtil {

    private static Logger logger = ();

    private static final RequestConfig REQUEST_CONFIG_TIME_OUT = ()
            .setSocketTimeout(5000)
            .setConnectTimeout(5000)
            .setConnectionRequestTimeout(5000)
            .build();

    private static PoolingHttpClientConnectionManager cm = null;

    @PostConstruct
    public void init() {
        cm = new PoolingHttpClientConnectionManager();
        (1000);
        (1000);
    }

    /**
     * @param isPooled 是否使用连接池
     */
    public static CloseableHttpClient getClient(boolean isPooled) {
        HttpRequestRetryHandler handler = new HttpRequestRetryHandler() {
            @Override
            public boolean retryRequest(IOException arg0, int retryTimes, HttpContext arg2) {
                if (retryTimes > 2) {
                    return false;
                }
                if (arg0 instanceof UnknownHostException || arg0 instanceof ConnectTimeoutException
                        || !(arg0 instanceof SSLException) || arg0 instanceof NoHttpResponseException) {
                    return true;
                }

                HttpClientContext clientContext = (arg2);
                HttpRequest request = ();
                boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
                if (idempotent) {
                    // 如果请求被认为是幂等的,那么就重试。即重复执行不影响程序其他效果的
                    return true;
                }
                return false;
            }
        };


        if (isPooled) {
            return ().setConnectionManager(cm).setRetryHandler(handler).build();
        }
        return ();
    }

    public static final String doPostWithRequest(String path, HttpServletRequest request) {
        Enumeration params = ();
        List<NameValuePair> nameValuePairs = ();
        while (()) {
            String paramName = (String) ();
            (new BasicNameValuePair(paramName, (paramName)));
        }
        HttpPost httpPost = new HttpPost(path);

        (REQUEST_CONFIG_TIME_OUT);
        try {
            (new UrlEncodedFormEntity(nameValuePairs));
            return execReq(httpPost);
        } catch (UnsupportedEncodingException e) {
            ("do post error: ", e);
        }
        return "";
    }

    public static final String doPost(String path, Map<String, Object> params) {
        ("doPost from " + path, params);
        HttpPost httpPost = new HttpPost(path);

        (REQUEST_CONFIG_TIME_OUT);
        try {
            (new UrlEncodedFormEntity(createParams(params)));

            String bodyAsString = execReq(httpPost);
            if (bodyAsString != null) return bodyAsString;

        } catch (UnsupportedEncodingException e) {
            ((), e);
        }

        return errorResponse("-2", "unknown error");
    }

    private static String execReq(HttpPost httpPost) {
        try {
            CloseableHttpResponse response = getClient(true).execute(httpPost);
            if (response != null) {
                try {
                    if (().getStatusCode() == HttpStatus.SC_OK) {
                        return (());
                    } else {
                        return errorResponse((().getStatusCode()),
                                "http post request error: " + ());
                    }
                } finally {
                    (());
                }
            } else {
                return errorResponse("-1", "response is null");
            }

        } catch (IOException e) {
            ("doPost error: ", e);
        }
        return errorResponse("-3", "unknown error");
    }

    private static String errorResponse(String code, String msg) {
        return (("code", code, "msg", msg));
    }

    private static List<NameValuePair> createParams(Map<String, Object> params) {
        List<NameValuePair> nameValuePairs = ();
        for (<String, Object> entry : ()) {
            (new BasicNameValuePair((), (())));
        }
        return nameValuePairs;
    }
}

httpclient的调用如下:

String  bingoPortalResStr = HttpClientUtil.doPostWithRequest(path, request);
 

参考:/p/481cbfcd3f13