代理验证器、OkHttpClient 提供器(结合 ProxyConfig)
This commit is contained in:
188
src/main/java/quant/rich/emoney/client/OkHttpClientProvider.java
Normal file
188
src/main/java/quant/rich/emoney/client/OkHttpClientProvider.java
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
package quant.rich.emoney.client;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.Proxy;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import javax.net.ssl.HostnameVerifier;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLSession;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
|
||||||
|
import okhttp3.Interceptor;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
import okio.BufferedSource;
|
||||||
|
import okio.GzipSource;
|
||||||
|
import okio.Okio;
|
||||||
|
import quant.rich.emoney.entity.config.ProxyConfig;
|
||||||
|
import quant.rich.emoney.util.SpringContextHolder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OkHttpClient 提供器
|
||||||
|
* @see quant.rich.emoney.entity.config.ProxyConfig
|
||||||
|
*/
|
||||||
|
public class OkHttpClientProvider {
|
||||||
|
|
||||||
|
private static volatile ProxyConfig proxyConfig;
|
||||||
|
|
||||||
|
private static ProxyConfig getProxyConfig() {
|
||||||
|
if (proxyConfig == null) {
|
||||||
|
synchronized (EmoneyClient.class) {
|
||||||
|
if (proxyConfig == null) {
|
||||||
|
proxyConfig = SpringContextHolder.getBean(ProxyConfig.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return proxyConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 ProxyConfig 获取一个 OkHttpClient 实例
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static OkHttpClient getInstance() {
|
||||||
|
return getInstance(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 ProxyConfig 获取一个 OkHttpClient 实例
|
||||||
|
* @param builderConsumer 可根据该 consumer 自定义 builder 其他参数,注意 proxy、https 校验等最终仍会根据 proxyConfig 情况覆盖
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static OkHttpClient getInstance(Consumer<OkHttpClient.Builder> builderConsumer) {
|
||||||
|
ProxyConfig proxyConfig = getProxyConfig();
|
||||||
|
return getInstance(proxyConfig.getProxy(), proxyConfig.getIgnoreHttpsVerification(), builderConsumer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据指定代理和是否忽略 https 证书获取一个 OkHttpClient 实例
|
||||||
|
* @param proxy 指定代理
|
||||||
|
* @param ignoreHttpsVerification 是否忽略 https 证书
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static OkHttpClient getInstance(Proxy proxy, boolean ignoreHttpsVerification) {
|
||||||
|
return getInstance(proxy, ignoreHttpsVerification, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据指定代理、是否忽略 https 证书和额外 builder 设置获取一个 OkHttpClient 实例
|
||||||
|
* @param proxy 指定代理
|
||||||
|
* @param ignoreHttpsVerification 是否忽略 https 证书
|
||||||
|
* @param builderConsumer 可根据该 consumer 自定义 builder 其他参数,注意 proxy、https 校验等最终仍会根据其他参数覆盖
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static OkHttpClient getInstance(Proxy proxy, boolean ignoreHttpsVerification, Consumer<OkHttpClient.Builder> builderConsumer) {
|
||||||
|
OkHttpClient.Builder builder = new OkHttpClient.Builder();
|
||||||
|
if (builderConsumer != null) {
|
||||||
|
builderConsumer.accept(builder);
|
||||||
|
}
|
||||||
|
builder.proxy(proxy);
|
||||||
|
if (ignoreHttpsVerification) {
|
||||||
|
builder
|
||||||
|
.sslSocketFactory(getSSLSocketFactory(), getX509TrustManager())
|
||||||
|
.hostnameVerifier(getHostnameVerifier());
|
||||||
|
}
|
||||||
|
builder.addNetworkInterceptor(new GzipResponseInterceptor());
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HostnameVerifier getHostnameVerifier() {
|
||||||
|
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
|
||||||
|
@Override
|
||||||
|
public boolean verify(String s, SSLSession sslSession) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return hostnameVerifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SSLSocketFactory getSSLSocketFactory() {
|
||||||
|
try {
|
||||||
|
SSLContext sslContext = SSLContext.getInstance("SSL");
|
||||||
|
sslContext.init(null, getTrustManager(), new SecureRandom());
|
||||||
|
return sslContext.getSocketFactory();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static X509TrustManager getX509TrustManager() {
|
||||||
|
X509TrustManager trustManager = null;
|
||||||
|
try {
|
||||||
|
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||||
|
trustManagerFactory.init((KeyStore) null);
|
||||||
|
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
|
||||||
|
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
|
||||||
|
throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
|
||||||
|
}
|
||||||
|
trustManager = (X509TrustManager) trustManagers[0];
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return trustManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TrustManager[] getTrustManager() {
|
||||||
|
TrustManager[] trustAllCerts = new TrustManager[]{
|
||||||
|
new X509TrustManager() {
|
||||||
|
@Override
|
||||||
|
public void checkClientTrusted(X509Certificate[] chain, String authType) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkServerTrusted(X509Certificate[] chain, String authType) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public X509Certificate[] getAcceptedIssuers() {
|
||||||
|
return new X509Certificate[]{};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return trustAllCerts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class GzipResponseInterceptor implements Interceptor {
|
||||||
|
@Override
|
||||||
|
public Response intercept(Chain chain) throws IOException {
|
||||||
|
Request request = chain.request();
|
||||||
|
Response response = chain.proceed(request);
|
||||||
|
|
||||||
|
// 只有服务器返回了 gzip 编码才处理
|
||||||
|
if ("gzip".equalsIgnoreCase(response.header("Content-Encoding"))) {
|
||||||
|
// 原始响应体
|
||||||
|
ResponseBody body = response.body();
|
||||||
|
if (body == null) return response;
|
||||||
|
|
||||||
|
// 用 GzipSource 包装原始流,并缓冲
|
||||||
|
GzipSource gzippedResponseBody = new GzipSource(body.source());
|
||||||
|
BufferedSource unzippedSource = Okio.buffer(gzippedResponseBody);
|
||||||
|
|
||||||
|
// 构造一个新的 ResponseBody,不再带 Content-Encoding/Length
|
||||||
|
ResponseBody newBody = ResponseBody.create(
|
||||||
|
unzippedSource,
|
||||||
|
body.contentType(),
|
||||||
|
-1L
|
||||||
|
);
|
||||||
|
|
||||||
|
// 去掉 Content-Encoding/Length,让后续调用 body().string() 时拿到解压后的内容
|
||||||
|
return response.newBuilder()
|
||||||
|
.removeHeader("Content-Encoding")
|
||||||
|
.removeHeader("Content-Length")
|
||||||
|
.body(newBody)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,37 +1,23 @@
|
|||||||
package quant.rich.emoney.entity.config;
|
package quant.rich.emoney.entity.config;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Proxy;
|
import java.net.Proxy;
|
||||||
import java.time.LocalDateTime;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.apache.commons.lang3.Validate;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
|
||||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
|
||||||
import lombok.AccessLevel;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import quant.rich.emoney.entity.config.DeviceInfoConfig.DeviceInfo;
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import quant.rich.emoney.client.OkHttpClientProvider;
|
||||||
import quant.rich.emoney.interceptor.EnumOptionsInterceptor.EnumOptions;
|
import quant.rich.emoney.interceptor.EnumOptionsInterceptor.EnumOptions;
|
||||||
import quant.rich.emoney.interfaces.ConfigInfo;
|
import quant.rich.emoney.interfaces.ConfigInfo;
|
||||||
import quant.rich.emoney.interfaces.IConfig;
|
import quant.rich.emoney.interfaces.IConfig;
|
||||||
import quant.rich.emoney.interfaces.ValidEmoneyRequestConfig;
|
import quant.rich.emoney.validator.ProxyConfigValid;
|
||||||
import quant.rich.emoney.patch.okhttp.PatchOkHttp;
|
|
||||||
import quant.rich.emoney.util.EncryptUtils;
|
|
||||||
import quant.rich.emoney.util.SpringContextHolder;
|
|
||||||
import quant.rich.emoney.util.TextUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 独立出来一个代理设置的原因是后续可能需要做一个代理池,这样的话独立配置比较适合后续扩展
|
* 独立出来一个代理设置的原因是后续可能需要做一个代理池,这样的话独立配置比较适合后续扩展
|
||||||
@@ -39,7 +25,7 @@ import quant.rich.emoney.util.TextUtils;
|
|||||||
@Data
|
@Data
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@ValidEmoneyRequestConfig
|
@ProxyConfigValid
|
||||||
@ConfigInfo(field = "proxy", name = "代理设置", initDefault = true)
|
@ConfigInfo(field = "proxy", name = "代理设置", initDefault = true)
|
||||||
public class ProxyConfig implements IConfig<ProxyConfig> {
|
public class ProxyConfig implements IConfig<ProxyConfig> {
|
||||||
|
|
||||||
@@ -57,7 +43,7 @@ public class ProxyConfig implements IConfig<ProxyConfig> {
|
|||||||
/**
|
/**
|
||||||
* 代理端口
|
* 代理端口
|
||||||
*/
|
*/
|
||||||
private Integer proxyPort = 0;
|
private Integer proxyPort = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否忽略 HTTPS 证书校验
|
* 是否忽略 HTTPS 证书校验
|
||||||
@@ -71,5 +57,66 @@ public class ProxyConfig implements IConfig<ProxyConfig> {
|
|||||||
|
|
||||||
public ProxyConfig() {}
|
public ProxyConfig() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据配置获取代理
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@JsonIgnore
|
||||||
|
public Proxy getProxy() {
|
||||||
|
if (getProxyType() != null && getProxyType() != Proxy.Type.DIRECT) {
|
||||||
|
return new Proxy(getProxyType(),
|
||||||
|
new InetSocketAddress(getProxyHost(), getProxyPort()));
|
||||||
|
}
|
||||||
|
return Proxy.NO_PROXY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试给定的 HTTP 代理是否有效,并验证是否真的走了代理。
|
||||||
|
* @param proxyHost 代理 IP,如 "127.0.0.1"
|
||||||
|
* @param proxyPort 代理端口,如 8888
|
||||||
|
* @return 是否有效
|
||||||
|
*/
|
||||||
|
public static boolean isProxyEffective(Proxy proxy, boolean ignoreHttpsVerification) {
|
||||||
|
synchronized (ProxyConfig.class) {
|
||||||
|
|
||||||
|
// OkHttp 客户端配置
|
||||||
|
OkHttpClient client = OkHttpClientProvider.getInstance(
|
||||||
|
proxy, ignoreHttpsVerification,
|
||||||
|
builder -> builder
|
||||||
|
.connectTimeout(10, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(10, TimeUnit.SECONDS));
|
||||||
|
|
||||||
|
// 使用 httpbin.org/ip 获取当前请求的公网 IP
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url("https://httpbin.org/ip")
|
||||||
|
.header("User-Agent", "ProxyVerifier")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try (Response response = client.newCall(request).execute()) {
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
System.out.println("Request failed with code: " + response.code());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String responseBody = response.body().string();
|
||||||
|
System.out.println("Response from proxy: " + responseBody);
|
||||||
|
|
||||||
|
// 可在此根据 IP 做进一步验证,例如是否与本地 IP 不同
|
||||||
|
return true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.println("Proxy error: " + e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
String proxyIp = "127.0.0.1";
|
||||||
|
int proxyPort = 7897;
|
||||||
|
|
||||||
|
boolean result = isProxyEffective(
|
||||||
|
new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(proxyIp, proxyPort)), true);
|
||||||
|
System.out.println("Proxy is usable: " + result);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user