java:接口请求重试

java:接口请求重试

1 前言

接口请求重试的思路,采用循环 + try \ catch的方式,若接口请求失败或超时,则根据设置的重试次数再次发起请求重试。

2 使用

具体实现如下,若执行中抛出超时异常,则接口请求发起重试,最多执行次数为5次:

public String buildGetRequestWithRetry(String urlPath,
                              Map<String,Object> paramsMap,
                              Map<String,String> headerMap){
    /* 请求重试次数(包含原本do执行的1次) */
    int retryTimes = 5;
    final int finalRetryTimes = retryTimes;
    String result = null;

    label:{
        do{
            try{

                result = this.buildGetRequest(urlPath, paramsMap, headerMap);

                break label;

            }catch (CrawlerForJException crawlerForJException){

                if(isConnectTimeOut(crawlerForJException) ||
                        isReadTimeOut(crawlerForJException)){

                    retryTimes --;

                    if(retryTimes > 0){
                        LogUtils.warn(requestLogger,
                                "接口请求第{}次请求重试...",
                                (finalRetryTimes - retryTimes));
                    }

                    ThreadUtils.sleepByMilli(1000);

                }else{
                    throw crawlerForJException;
                }

            }
        }while(retryTimes > 0);

        ExcpUtils.throwExp("总执行" + finalRetryTimes + "次请求,请求依然失败.");
    }

    return result;
}

RequestHelper:

@Component
public class RequestHelper {

    @Autowired
    RequestUtils requestUtils;

    public String doRequestDispatch(String method,
                                  String urlPath,
                                  Map<String, Object> params,
                                  Map<String, String> headers){
        /* reqE不会为null,找不到只会抛出异常,避免switch(null)空指针异常 */
        RequestMethodEnum reqE = this.getRequestMEnumByMethod(method);

        switch (reqE){
            case GET:
                return requestUtils.buildGetRequestWithRetry(urlPath, params, headers);
            case POST:
                ExcpUtils.throwExp("post方法暂时不支持");
            default:
                ExcpUtils.throwExp("其他方法暂时不支持");
        }

        return null;
    }

    /* 暂时只支持使用RequestMethodEnum枚举中配置的 */
    public RequestMethodEnum getRequestMEnumByMethod(String method){
        RequestMethodEnum[] var1 = RequestMethodEnum.values();
        int var2 = var1.length;
        int var3 = 0;

        RequestMethodEnum reqM = null;
        label : {
            if(var2 > 0){
                do{
                    RequestMethodEnum requestM = var1[var3];
                    if(requestM.getName().equalsIgnoreCase(method)){
                        reqM = requestM;
                        break label;
                    }
                    var3++;
                }while(var3 < var2);
            }
            ExcpUtils.throwExp("没有找到匹配request请求方法:"+method);
        }
        return reqM;
    }

}

RequestMethodEnum:

public enum  RequestMethodEnum {
    /*避免java.net.ProtocolException: Invalid HTTP method: get 以及
    java.net.ProtocolException: Invalid HTTP method: post异常,应该是GET和POST*/

    GET("GET","get请求","01"),
    POST("POST","post请求","02");

    private String name;
    private String description;
    private String code;

    RequestMethodEnum(String name,String description,String code){
        this.name = name;
        this.description = description;
        this.code = code;
    }

    /*根据code获取请求方式*/
    public static RequestMethodEnum getMethodByCode(String code){
        for (RequestMethodEnum value : RequestMethodEnum.values()) {
            if(value.getCode().equals(code)){
                return value;
            }
        }
        return null;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }
}

RequestUtils:

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import com.xiaoxu.crawler.enums.request.RequestMethodEnum;
import com.xiaoxu.crawler.excp.CrawlerForJException;
import org.apache.commons.collections4.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.*;

/**
 * @author xiaoxu
 * @date 2022-11-06 15:29
 * crawlerJ:com.xiaoxu.crawler.utils.RequestUtils
 */
@Component
public class RequestUtils {

    private static final String UTF8 = "utf-8";

    private static final String AndSet = "&";

    private static final String CommaSet = ",";

    private static final String EqualSet = "=";

    private static final String QuestionMark = "?";

    private static final String ConnectTimeOutMsg = "connect timed out";

    private static final String ReadTimeOutMsg = "read timed out";

    private static final Logger requestLogger =
            LoggerFactory.getLogger(RequestUtils.class);

    /**
     * @param urlPath GET请求接口路径
     * @param paramsMap GET请求传参,可为空
     * @param headerMap 请求头,可为空
     * @return GET请求接口返回值(InputStream)
     */
    /* get接口请求 */
    public InputStream buildGetRequestInputStream(String urlPath,
                                    Map<String,Object> paramsMap,
                                    Map<String,String> headerMap){
        /* query String 的请求方式 */
        return buildAndGetResultInputStream(RequestMethodEnum.GET.getName(),
                urlPath,
                buildParamsStr(paramsMap),
                headerMap);
    }

    public String buildGetRequestWithRetry(String urlPath,
                                  Map<String,Object> paramsMap,
                                  Map<String,String> headerMap){
        /* 请求重试次数(包含原本do执行的1次) */
        int retryTimes = 5;
        final int finalRetryTimes = retryTimes;
        String result = null;

        label:{
            do{
                try{

                    result = this.buildGetRequest(urlPath, paramsMap, headerMap);

                    break label;

                }catch (CrawlerForJException crawlerForJException){

                    if(isConnectTimeOut(crawlerForJException) ||
                            isReadTimeOut(crawlerForJException)){

                        retryTimes --;

                        if(retryTimes > 0){
                            LogUtils.warn(requestLogger,
                                    "接口请求第{}次请求重试...",
                                    (finalRetryTimes - retryTimes));
                        }

                        ThreadUtils.sleepByMilli(1000);

                    }else{
                        throw crawlerForJException;
                    }

                }
            }while(retryTimes > 0);

            ExcpUtils.throwExp("总执行" + finalRetryTimes + "次请求,请求依然失败.");
        }

        return result;
    }

    private boolean isConnectTimeOut(Throwable th) {
        return null != th
                && th.getMessage().toLowerCase(Locale.ROOT).contains(ConnectTimeOutMsg);
    }

    private boolean isReadTimeOut(Throwable th) {
        return null != th
                && th.getMessage().toLowerCase(Locale.ROOT).contains(ReadTimeOutMsg);
    }

    /**
     * @param urlPath GET接口请求路径
     * @param paramsMap GET接口请求参数
     * @param headerMap 请求头
     * @return 响应结果(String)
     */
    public String buildGetRequest(String urlPath,
                                        Map<String,Object> paramsMap,
                                        Map<String,String> headerMap){
        return buildAndGetResult(RequestMethodEnum.GET.getName(),
                urlPath,
                buildParamsStr(paramsMap),
                headerMap);
    }

    /**
     * @param method 请求方式:GET、POST
     * @param urlPath 请求路径
     * @param data 请求数据
     * @param headerMap 请求头(根据接口情况传入)
     * @return
     */
    @SuppressWarnings(value = "all")
    protected static InputStream buildAndGetResultInputStream(@NonNull String method,
                                                @NonNull String urlPath,
                                                String data,
                                                Map<String,String> headerMap){

        StringBuilder str = new StringBuilder();
        InputStream in = null;
        FileOutputStream out = null;
        HttpURLConnection connection = null;

        try {

            if(!StringUtils.hasLength(method)){
                ExcpUtils.throwExp(
                        MessageFormat.format("buildRequest方法的method不能为空:{0}",method));
            }

            if(!StringUtils.hasLength(urlPath)){
                ExcpUtils.throwExp(
                        MessageFormat.format("buildRequest方法的urlPath不能为空:{0}",urlPath));
            }

            if(!method.equals(RequestMethodEnum.GET.getName())&&!method.equals(RequestMethodEnum.POST.getName())){
                ExcpUtils.throwExp(
                        MessageFormat.format("buildRequest方法的method只能为GET或POST:{0}",method));
            }

            /*处理data,如果data为null,转换成"",否则data.getBytes会抛出空指针异常*/
            data = Optional.ofNullable(data).orElse("");
            data = data.replaceAll(" ","");

            if("get".equals(method.toLowerCase(Locale.ROOT))){
                /*get请求需要拼接路径(无参数,data应该是"",有参数则拼接)*/
                urlPath += data;
            }

            LogUtils.info(requestLogger, method+"请求获取stream的url:{}", urlPath);

            URL url;

            url = new URL(urlPath);
            connection = (HttpURLConnection) url.openConnection();

            if(!MapUtils.isEmpty(headerMap)){
                /*设置请求头的属性,比如:Cookie,Token等等
                System.out.println("key:"+k+";"+"value:"+v);*/
                headerMap.forEach(connection::setRequestProperty);
            }

            /*设置请求方式*/
            connection.setRequestMethod(method);
            connection.setRequestProperty("User-Agent", "Mozilla/4.76");

            /*设置超时时间*/
            connection.setConnectTimeout(5000);
            connection.setReadTimeout(2000);

            /*post不同于get,get只需要将请求参数拼接到url中,而post请求的是正文*/
            if("post".equals(method.toLowerCase(Locale.ROOT))){
                /*post请求不能使用缓存*/
                connection.setUseCaches(false);

                /*post参数不是放在URL字符串里,而是http请求的正文
                post:http正文内,因此需要设为true*/
                connection.setDoOutput(true);

                /*connection获取输出的流,write写入post请求的参数:格式:key=value&key1=value1
                (当post请求是Content-Type:x-www-form-urlencoded(表单提交时使用))
                post请求参数为JSON格式:当请求头设置了"Content-Type","application/json;charset=UTF-8"时使用*/
                OutputStream outputStream = connection.getOutputStream();

                /*String的getBytes,字符串转字节*/
                byte[] postBytes = data.getBytes(StandardCharsets.UTF_8);
                outputStream.write(postBytes);
            }

            /*获取URLConnection对象对应的输入流*/
            in = connection.getInputStream();

        } catch (IOException e) {

            throw new CrawlerForJException("buildRequest获取stream发生IO异常:"+e.getMessage(),e.getCause());

        } catch (Throwable th){

            throw new CrawlerForJException("buildRequest获取stream发生未知异常:"+th.getClass().getName()+th.getMessage(),
                th.getCause());

        } finally {

            /*关闭连接*/
            Optional.ofNullable(connection).ifPresent(HttpURLConnection::disconnect);

        }
        return in;
    }

    protected static String buildAndGetResult(@NonNull String method,
                                              @NonNull String urlPath,
                                              String data,
                                              Map<String,String> headerMap){
        StringBuilder str = new StringBuilder();
        InputStream in = null;
        HttpURLConnection connection = null;

        try {
            if(!StringUtils.hasLength(method)){
                ExcpUtils.throwExp(
                        MessageFormat.format("buildRequest方法的method不能为空:{0}",method));
            }

            if(!StringUtils.hasLength(urlPath)){
                ExcpUtils.throwExp(
                        MessageFormat.format("buildRequest方法的urlPath不能为空:{0}",urlPath));
            }

            if(!method.equals(RequestMethodEnum.GET.getName())&&!method.equals(RequestMethodEnum.POST.getName())){
                ExcpUtils.throwExp(
                        MessageFormat.format("buildRequest方法的method只能为GET或POST:{0}",method));
            }

            /*处理data,如果data为null,转换成"",否则data.getBytes会抛出空指针异常*/
            data = Optional.ofNullable(data).orElse("");
            data = data.replaceAll(" ","");

            if("get".equals(method.toLowerCase(Locale.ROOT))){
                /* get请求需要拼接路径(无参数,data应该是"",有参数则拼接) */
                urlPath += data;
            }

            LogUtils.info(requestLogger, method+"请求的url:{}", urlPath);

            URL url;

            url = new URL(urlPath);
            connection = (HttpURLConnection) url.openConnection();

            if(!MapUtils.isEmpty(headerMap)){
                /*
                * 设置请求头的属性,比如:Cookie,Token等等;
                * System.out.println("key:"+k+";"+"value:"+v);
                * */
                headerMap.forEach(connection::setRequestProperty);
            }

            /* 设置请求方式 */
            connection.setRequestMethod(method);
            connection.setRequestProperty("User-Agent", "Mozilla/4.76");

            /* 设置超时时间 */
            connection.setConnectTimeout(5000);
            connection.setReadTimeout(2000);

            /* post不同于get,get只需要将请求参数拼接到url中,而post请求的是正文 */
            if("post".equals(method.toLowerCase(Locale.ROOT))){
                /* post请求不能使用缓存 */
                connection.setUseCaches(false);

                /*
                * post参数不是放在URL字符串里,而是http请求的正文
                * post:http正文内,因此需要设为true
                *  */
                connection.setDoOutput(true);

                /*
                * connection获取输出的流,write写入post请求的参数:格式:key=value&key1=value1
                * (当post请求是Content-Type:x-www-form-urlencoded(表单提交时使用))
                * post请求参数为JSON格式:当请求头设置了"Content-Type","application/json;charset=UTF-8"时使用
                * */
                OutputStream outputStream = connection.getOutputStream();

                /* String的getBytes,字符串转字节 */
                byte[] postBytes = data.getBytes(StandardCharsets.UTF_8);
                outputStream.write(postBytes);
            }

            /* 获取URLConnection对象对应的输入流 */
            in = connection.getInputStream();

            /* 将接口请求的接口打印出来 */
            BufferedReader b = new BufferedReader(new InputStreamReader(in));
            String s;

            while((s = b.readLine()) != null){
                str.append(s);
            }

        } catch (IOException e) {

            throw new CrawlerForJException("buildRequest发生IO异常:"+e.getMessage(), e.getCause());

        } catch (Throwable th){

            throw new CrawlerForJException("buildRequest发生未知异常:"+th.getClass().getName() + ":[" + th.getMessage() + "]",
                    th.getCause());

        } finally {

            /*关闭连接*/
            Optional.ofNullable(connection).ifPresent(HttpURLConnection::disconnect);

            /*关闭流*/
            closeIn(in, "请求接口, 关闭输入流失败:{}");

        }

        return str.toString();
    }

    private static void closeIn(InputStream stream, String message) {

        if(stream != null){
            try {
                stream.close();
            } catch (IOException e) {
                LogUtils.info(requestLogger, message, e.getMessage());
            }
        }

    }

    private static void closeOut(OutputStream stream, String message) {

        if(stream != null){
            try {
                stream.close();
            } catch (IOException e) {
                LogUtils.info(requestLogger, message, e.getMessage());
            }
        }

    }


    /* 构建get请求的参数url,类似:?key=value&key1=value1 (query String)*/
    @SuppressWarnings(value = "all")
    public static String buildParamsStr(Map<String,Object> paramsMap){

        paramsMap = Optional.ofNullable(paramsMap).orElse(new HashMap<>());
        StringBuilder s = new StringBuilder();
        StringBuilder temp = new StringBuilder();

        try{

            for(Map.Entry<String,Object> m: paramsMap.entrySet()) {
                StringBuilder rep = new StringBuilder();

                if (m.getValue() instanceof List) {
                    Iterator<?> iter =((List<?>) m.getValue()).iterator();

                    while(iter.hasNext()){
                        String s1 = String.valueOf(iter.next());

                        if(rep.length() == 0){
                            rep.append(getEncode(m.getKey())).append(EqualSet).append(getEncode(s1));
                        }else{
                            rep.append(getEncode(CommaSet)).append(getEncode(s1));
                        }
                    }

                }else if(m.getValue() instanceof String ||
                        m.getValue() instanceof Long ||
                        m.getValue() instanceof Integer){

                    rep.append(getEncode(m.getKey())).append(EqualSet).append(getEncode(String.valueOf(m.getValue())));

                }else{

                    throw new CrawlerForJException("get请求参数存在value为非String,或非List,非Integer,非Long的数据");

                }

                if(temp.length() == 0){
                    temp.append(rep);
                }
                temp.append(AndSet).append(rep);
            }
        }catch (CrawlerForJException | UnsupportedEncodingException c){
            throw new CrawlerForJException(c);
        }catch (Exception e){
            throw new CrawlerForJException("未知异常:" + e.getMessage());
        }

        if (temp.length() != 0) {
            s.append(QuestionMark).append(temp);
        }
        return s.toString();
    }

    /* urlEncoded,get请求 query String使用 */
    private static String getEncode(String val) throws UnsupportedEncodingException {
        return URLEncoder.encode(Optional.ofNullable(val).orElse(""), UTF8);
    }

    /*
    * 构建post请求参数,类似:key = value & key1 = value1
    * 当post请求是Content-Type:x-www-form-urlencoded(表单提交时使用)
    * */
    public static String buildPayloadStr(Map<String,Object> payloadMap){

        StringBuilder s = new StringBuilder();
        payloadMap = Optional.ofNullable(payloadMap).orElse(Maps.newHashMap());

        for(Map.Entry<String,Object> m: payloadMap.entrySet()){
            if(s.length() == 0){
                s.append(m.getKey()).append(EqualSet).append(m.getValue());
            }else{
                s.append(AndSet).append(m.getKey()).append(EqualSet).append(m.getValue());
            }
        }

        return s.toString();
    }

    /*
    * 构建post请求2:json字符串格式,
    * 当请求头设置了"Content-Type","application/json;charset=UTF-8"时使用
    * */
    public static String buildJsonPayloadStr(Map<String,Object> payloadMap){

        String jsonData;
        /*允许为空*/
        payloadMap = Optional.ofNullable(payloadMap).orElse(Maps.newHashMap());

        try {
            jsonData = JSON.toJSONString(payloadMap);
        } catch (Exception e) {
            throw new CrawlerForJException("application-jsonData转换异常: "+e.getClass().getName()+"; "+e.getMessage());
        }

        return jsonData;
    }

}

ThreadUtils:

public class ThreadUtils {

    public static void sleepByMilli(long milliSeconds){

        if(milliSeconds <= 0)
            return;

        try {
            Thread.sleep(milliSeconds);
        } catch (InterruptedException ignored) {
            /* 忽略人为中止异常 */
        }

    }

}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值