Okhttp配置 重试 自动切换代理

配置:

import com.**.interceptor.OkHttpInterceptor;
import com.**.interceptor.SwitchProxySelector;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.concurrent.TimeUnit;

/**
 * OKHttp配置
 *
 * @author qhong
 */
@Configuration
public class OkHttpConfig {

    @Bean
    public X509TrustManager x509TrustManager() {
        return new X509TrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        };
    }

    @Bean
    public SSLSocketFactory sslSocketFactory() {
        try {
            //信任任何链接
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, new TrustManager[]{x509TrustManager()}, new SecureRandom());
            return sslContext.getSocketFactory();
        } catch (NoSuchAlgorithmException e) {
            //e.printStackTrace();
        } catch (KeyManagementException e) {
            //e.printStackTrace();
        }
        return null;
    }

    /**
     * Create a new connection pool with tuning parameters appropriate for a single-user application.
     * The tuning parameters in this pool are subject to change in future OkHttp releases. Currently
     */
    @Bean
    public ConnectionPool pool() {
        return new ConnectionPool(200, 5, TimeUnit.MINUTES);
    }

    @Bean
    public OkHttpClient okHttpClient() {
        return new OkHttpClient.Builder()
                .sslSocketFactory(sslSocketFactory(), x509TrustManager())
                //不自动重连
                .retryOnConnectionFailure(false)
                //连接池
                .connectionPool(pool())
                .connectTimeout(60L, TimeUnit.SECONDS)
                .readTimeout(60L, TimeUnit.SECONDS)
                //代理
				.proxySelector(new SwitchProxySelector())
                //增加3次重试次数
                .addInterceptor(new OkHttpInterceptor())
                .build();
    }
}

重试机制:

import lombok.extern.slf4j.Slf4j;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;

import javax.validation.constraints.NotNull;
import java.io.IOException;

/**
 * OKHttp拦截器
 *
 * @author qhong
 */
@Slf4j
public class OkHttpInterceptor implements Interceptor {

    public static final String metaProxy = "meta.proxy";

    /**
     * 最大重试次数
     */
    @Value("${max.retry:3}")
    private Integer maxRetry = 3;


    @Override
    public Response intercept(@NotNull Chain chain) throws IOException {
        /* 递归 2次下发请求,如果仍然失败 则返回 null ,但是 intercept must not return null.
         * 返回 null 会报 IllegalStateException 异常
         * */
        return retry(chain, 0);
    }


    /**
     * okhttp retry
     *
     * @param chain
     * @param retryCent
     * @return
     */
    Response retry(Chain chain, int retryCent) {
        Response response = null;
        try {
            log.info("第" + (retryCent + 1) + "次执行下发请求.");
            response = proxy(chain);
        } catch (Exception e) {
            if (maxRetry > retryCent) {
                return retry(chain, retryCent + 1);
            }
        } finally {
            return response;
        }
    }

    /**
     * okhttp proxy
     *
     * @param chain
     * @return
     * @throws IOException
     */
    private Response proxy(Chain chain) throws IOException {
        if (StringUtils.isNoneBlank(chain.request().header(metaProxy))) {
            String proxyHeader = chain.request().header(metaProxy);
            if (StringUtils.isNoneBlank(proxyHeader)) {
                log.info("url:{}, add proxy header : {}", chain.request().url().toString(), proxyHeader);
                SwitchProxySelector.proxyCache.put(chain.request().url().host(), SwitchProxySelector.getProxy(proxyHeader));
                Request newRequest = chain.request().newBuilder().removeHeader(metaProxy).build();
                return chain.proceed(newRequest);
            }
        }
        return chain.proceed(chain.request());
    }

}

自动切换代理:

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.*;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author qhong
 * @date 2022/5/13 21:34
 **/
@Slf4j
public class SwitchProxySelector extends ProxySelector {

    /**
     * 根据request返回
     */
    @Override
    public List<Proxy> select(URI uri) {
        Proxy proxy = SwitchProxySelector.proxyCache.getIfPresent(uri.getHost());
        if (proxy == null || proxy.address() == null) {
            proxy = Proxy.NO_PROXY;
        }
        log.info("{} use proxy {}:{}", uri.toString(), proxy.type().name(), proxy.address());
        return Collections.singletonList(proxy);
    }


    @Override
    public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
    }

    public static Cache<String, Proxy> proxyCache = CacheBuilder.newBuilder()
            .maximumSize(2000)
            .expireAfterWrite(60, TimeUnit.MINUTES)
            .build();

    /**
     * proxy 模式
     */
    private static final Pattern PROXY_PATTERN = Pattern.compile("(socket|http):(.*):(.*)");

    /**
     * 工厂方法 获取Proxy
     *
     * @param proxyString meta.proxy 中的 proxy 字符串  header("meta.proxy", "socket:192.168.0.63:1080")   header("meta.proxy", "http:192.168.0.63:8118")
     * @return Proxy
     */
    static Proxy getProxy(String proxyString) {
        if (proxyString == null || "".equals(proxyString)) {
            return Proxy.NO_PROXY;
        }
        Matcher matcher = PROXY_PATTERN.matcher(proxyString);
        if (matcher.matches()) {

            switch (matcher.group(1)) {
                case "socket":
                    return new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(matcher.group(2), Integer.parseInt(matcher.group(3))));
                case "http":
                    return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(matcher.group(2), Integer.parseInt(matcher.group(3))));
                default:
                    return Proxy.NO_PROXY;
            }

        }
        return Proxy.NO_PROXY;
    }

}

总结:

后续如果某个请求需要代理转发,那么只要在该请求header中新增meta.proxy代理配置即可。

例如:

header("meta.proxy", "socket:192.168.0.63:1080")  

header("meta.proxy", "http:192.168.0.63:8118")

参考:

OkHttp 自动切换代理

posted @ 2022-10-25 21:12  hongdada  阅读(1424)  评论(0编辑  收藏  举报