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")