修改代理 IP 获取设置,新增 GeoLite 本地归属地查询,修正 devtools 配置导致 ByteBuddy 重定义方法类
ClassLoader 不一致,进一步导致重定义方法内无法获取到自定义规则的问题
This commit is contained in:
@@ -32,8 +32,8 @@ import okhttp3.OkHttpClient;
|
||||
public class EmoneyClient implements Cloneable {
|
||||
|
||||
private static final String MBS_URL = "https://mbs.emoney.cn/";
|
||||
private static final String LOGIN_URL = "https://emapp.emoney.cn/user/auth/login";
|
||||
// private static final String LOGIN_URL = "http://localhost:7790/user/auth/login";
|
||||
//private static final String LOGIN_URL = "https://emapp.emoney.cn/user/auth/login";
|
||||
private static final String LOGIN_URL = "http://localhost:7790/user/auth/login";
|
||||
private static final String LOGIN_X_PROTOCOL_ID = "user%2Fauth%2Flogin";
|
||||
|
||||
private static volatile EmoneyRequestConfig emoneyRequestConfig;
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
package quant.rich.emoney.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Proxy;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
@@ -16,6 +21,7 @@ import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import okhttp3.Dns;
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
@@ -30,6 +36,7 @@ import quant.rich.emoney.util.SpringContextHolder;
|
||||
/**
|
||||
* OkHttpClient 提供器
|
||||
* @see quant.rich.emoney.entity.config.ProxyConfig
|
||||
* @see okhttp3.internal.http.BridgeInterceptor
|
||||
*/
|
||||
public class OkHttpClientProvider {
|
||||
|
||||
@@ -61,7 +68,10 @@ public class OkHttpClientProvider {
|
||||
*/
|
||||
public static OkHttpClient getInstance(Consumer<OkHttpClient.Builder> builderConsumer) {
|
||||
ProxyConfig proxyConfig = getProxyConfig();
|
||||
return getInstance(proxyConfig.getProxy(), proxyConfig.getIgnoreHttpsVerification(), builderConsumer);
|
||||
return getInstance(
|
||||
proxyConfig.getProxy(),
|
||||
proxyConfig.getIgnoreHttpsVerification(),
|
||||
builderConsumer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,7 +91,10 @@ public class OkHttpClientProvider {
|
||||
* @param builderConsumer 可根据该 consumer 自定义 builder 其他参数,注意 proxy、https 校验等最终仍会根据其他参数覆盖
|
||||
* @return
|
||||
*/
|
||||
public static OkHttpClient getInstance(Proxy proxy, boolean ignoreHttpsVerification, Consumer<OkHttpClient.Builder> builderConsumer) {
|
||||
public static OkHttpClient getInstance(
|
||||
Proxy proxy,
|
||||
boolean ignoreHttpsVerification,
|
||||
Consumer<OkHttpClient.Builder> builderConsumer) {
|
||||
OkHttpClient.Builder builder = new OkHttpClient.Builder();
|
||||
if (builderConsumer != null) {
|
||||
builderConsumer.accept(builder);
|
||||
@@ -184,5 +197,5 @@ public class OkHttpClientProvider {
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import quant.rich.emoney.entity.config.DeviceInfoConfig.DeviceInfo;
|
||||
import quant.rich.emoney.interfaces.ConfigInfo;
|
||||
import quant.rich.emoney.interfaces.IConfig;
|
||||
import quant.rich.emoney.patch.okhttp.PatchOkHttp;
|
||||
import quant.rich.emoney.patch.okhttp.PatchOkHttpRule;
|
||||
import quant.rich.emoney.util.EncryptUtils;
|
||||
import quant.rich.emoney.util.SpringContextHolder;
|
||||
import quant.rich.emoney.util.TextUtils;
|
||||
@@ -248,11 +249,11 @@ public class EmoneyRequestConfig implements IConfig<EmoneyRequestConfig> {
|
||||
|
||||
// 注入 OkHttp
|
||||
PatchOkHttp.apply(
|
||||
r -> r
|
||||
PatchOkHttpRule.when()
|
||||
.hostEndsWith("emoney.cn")
|
||||
.or(a -> a.hostContains("emapp"))
|
||||
.or(b -> b.hasHeaderName("X-Protocol-Id"))
|
||||
.overrideIf("User-Agent", getOkHttpUserAgent()));
|
||||
.overrideIf("User-Agent", getOkHttpUserAgent()).build());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,21 +1,8 @@
|
||||
package quant.rich.emoney.entity.config;
|
||||
|
||||
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.concurrent.TimeUnit;
|
||||
|
||||
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 org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
||||
@@ -30,14 +17,10 @@ import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.experimental.Accessors;
|
||||
import okhttp3.ConnectionPool;
|
||||
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.client.OkHttpClientProvider;
|
||||
import quant.rich.emoney.interfaces.ConfigInfo;
|
||||
import quant.rich.emoney.interfaces.IConfig;
|
||||
|
||||
@@ -77,145 +60,25 @@ public class IndexInfoConfig implements IConfig<IndexInfoConfig> {
|
||||
}
|
||||
|
||||
public String getOnlineConfigByUrl(String url) throws IOException {
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.header("User-Agent", emoneyRequestConfig.getOkHttpUserAgent())
|
||||
.header("Accept-Encoding", "gzip")
|
||||
.header("Connection", "Keep-Alive")
|
||||
.header("Cache-Control", "no-cache")
|
||||
.get()
|
||||
// Host 不能在 OkHttp 中直接设置(由 URL 控制)
|
||||
.build();
|
||||
|
||||
// 发出请求
|
||||
Response backendResponse = getInstance().newCall(request).execute();
|
||||
if (backendResponse.body() != null) {
|
||||
// 将内容复制给前端
|
||||
return
|
||||
new String(backendResponse.body().bytes(), "UTF-8");
|
||||
} else {
|
||||
return "";
|
||||
synchronized (this) {
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.header("Cache-Control", "no-cache")
|
||||
.get()
|
||||
.build();
|
||||
|
||||
// 发出请求
|
||||
Response backendResponse = OkHttpClientProvider.getInstance().newCall(request).execute();
|
||||
if (backendResponse.body() != null) {
|
||||
// 将内容复制给前端
|
||||
return
|
||||
new String(backendResponse.body().bytes(), "UTF-8");
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static OkHttpClient okHttpClient;
|
||||
|
||||
public static ConnectionPool connectionPool = new ConnectionPool(10, 5, TimeUnit.MINUTES);
|
||||
|
||||
public static OkHttpClient getInstance() {
|
||||
if (okHttpClient == null) { //加同步安全
|
||||
synchronized (OkHttpClient.class) {
|
||||
if (okHttpClient == null) { //okhttp可以缓存数据....指定缓存路径
|
||||
okHttpClient = new OkHttpClient.Builder()//构建器
|
||||
.proxy(Proxy.NO_PROXY) //来屏蔽系统代理
|
||||
.connectionPool(connectionPool)
|
||||
.sslSocketFactory(getSSLSocketFactory(), getX509TrustManager())
|
||||
.hostnameVerifier(getHostnameVerifier())
|
||||
.connectTimeout(600, TimeUnit.SECONDS)//连接超时
|
||||
.writeTimeout(600, TimeUnit.SECONDS)//写入超时
|
||||
.readTimeout(600, TimeUnit.SECONDS)//读取超时
|
||||
.addNetworkInterceptor(new GzipResponseInterceptor())
|
||||
.build();
|
||||
okHttpClient.dispatcher().setMaxRequestsPerHost(200);
|
||||
okHttpClient.dispatcher().setMaxRequests(200);
|
||||
}
|
||||
}
|
||||
}
|
||||
return okHttpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* description 忽略https证书验证
|
||||
*/
|
||||
private static HostnameVerifier getHostnameVerifier() {
|
||||
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
|
||||
@Override
|
||||
public boolean verify(String s, SSLSession sslSession) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
return hostnameVerifier;
|
||||
}
|
||||
/**
|
||||
* description 忽略https证书验证
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,11 @@ import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
@@ -17,6 +20,9 @@ import quant.rich.emoney.client.OkHttpClientProvider;
|
||||
import quant.rich.emoney.interceptor.EnumOptionsInterceptor.EnumOptions;
|
||||
import quant.rich.emoney.interfaces.ConfigInfo;
|
||||
import quant.rich.emoney.interfaces.IConfig;
|
||||
import quant.rich.emoney.pojo.dto.IpInfo;
|
||||
import quant.rich.emoney.util.CallerLockUtil;
|
||||
import quant.rich.emoney.util.GeoIPUtil;
|
||||
import quant.rich.emoney.validator.ProxyConfigValid;
|
||||
|
||||
/**
|
||||
@@ -33,25 +39,39 @@ public class ProxyConfig implements IConfig<ProxyConfig> {
|
||||
* 代理类型
|
||||
*/
|
||||
@EnumOptions("ProxyTypeEnum")
|
||||
@JsonView(IConfig.Views.Persistence.class)
|
||||
private Proxy.Type proxyType = Proxy.Type.DIRECT;
|
||||
|
||||
/**
|
||||
* 代理主机
|
||||
*/
|
||||
@JsonView(IConfig.Views.Persistence.class)
|
||||
private String proxyHost = "";
|
||||
|
||||
/**
|
||||
* 代理端口
|
||||
*/
|
||||
@JsonView(IConfig.Views.Persistence.class)
|
||||
private Integer proxyPort = 1;
|
||||
|
||||
/**
|
||||
* 是否忽略 HTTPS 证书校验
|
||||
*/
|
||||
@JsonView(IConfig.Views.Persistence.class)
|
||||
private Boolean ignoreHttpsVerification = false;
|
||||
|
||||
/**
|
||||
* 通过代理后的 IP,不做存储,只做呈现
|
||||
*/
|
||||
private IpInfo ipInfo;
|
||||
|
||||
public void afterBeanInit() {
|
||||
|
||||
//refreshIpThroughProxy();
|
||||
}
|
||||
|
||||
public synchronized IpInfo refreshIpThroughProxy() {
|
||||
ipInfo = GeoIPUtil.getIpInfoThroughProxy(this);
|
||||
return ipInfo;
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +81,6 @@ public class ProxyConfig implements IConfig<ProxyConfig> {
|
||||
* 根据配置获取代理
|
||||
* @return
|
||||
*/
|
||||
@JsonIgnore
|
||||
public Proxy getProxy() {
|
||||
if (getProxyType() != null && getProxyType() != Proxy.Type.DIRECT) {
|
||||
return new Proxy(getProxyType(),
|
||||
@@ -70,53 +89,18 @@ public class ProxyConfig implements IConfig<ProxyConfig> {
|
||||
return Proxy.NO_PROXY;
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试给定的 HTTP 代理是否有效,并验证是否真的走了代理。
|
||||
* @param proxy 代理
|
||||
* @param ignoreHttpsVerification 是否忽略 https 证书验证
|
||||
* @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;
|
||||
|
||||
ProxyConfig proxyConfig = new ProxyConfig();
|
||||
proxyConfig.setProxyHost(proxyIp).setProxyPort(proxyPort)
|
||||
.setProxyType(Proxy.Type.SOCKS)
|
||||
.setIgnoreHttpsVerification(false);
|
||||
|
||||
boolean result = isProxyEffective(
|
||||
new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(proxyIp, proxyPort)), true);
|
||||
System.out.println("Proxy is usable: " + result);
|
||||
IpInfo result = GeoIPUtil.getIpInfoThroughProxy(proxyConfig);
|
||||
System.out.println("Proxy is usable with through-proxy ip: " + result);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -2,11 +2,16 @@ package quant.rich.emoney.patch.okhttp;
|
||||
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.agent.ByteBuddyAgent;
|
||||
import net.bytebuddy.agent.builder.AgentBuilder;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import okhttp3.Request;
|
||||
import quant.rich.emoney.util.SpringContextHolder;
|
||||
import net.bytebuddy.dynamic.ClassFileLocator;
|
||||
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
|
||||
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.*;
|
||||
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
@@ -14,12 +19,21 @@ import java.util.function.Consumer;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
public class PatchOkHttp {
|
||||
private static final Logger log = LoggerFactory.getLogger(PatchOkHttp.class);
|
||||
private static final List<PatchOkHttpRule> rules = new CopyOnWriteArrayList<>();
|
||||
|
||||
private static boolean isHooked = false;
|
||||
|
||||
public static void apply(PatchOkHttpRule rule) {
|
||||
rules.add(rule);
|
||||
// log.debug("apply() running in classloader {}", PatchOkHttp.class.getClassLoader());
|
||||
if (!isHooked) hook();
|
||||
}
|
||||
|
||||
public static void apply(PatchOkHttpRule.Builder builder) {
|
||||
rules.add(builder.build());
|
||||
if (!isHooked) hook();
|
||||
@@ -33,6 +47,7 @@ public class PatchOkHttp {
|
||||
}
|
||||
|
||||
public static void match(RequestContext ctx, String currentHeader, Consumer<String> consumer) {
|
||||
// log.debug("match() running in classloader {}", PatchOkHttp.class.getClassLoader());
|
||||
for (PatchOkHttpRule rule : PatchOkHttp.rules) {
|
||||
if (rule.matches(ctx)) {
|
||||
rule.apply(ctx, currentHeader, consumer);
|
||||
@@ -40,17 +55,46 @@ public class PatchOkHttp {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>通过指定 Advice 重定义指定类的指定方法<br>
|
||||
* 此处为:<code>okhttp3.Request$Builder.header(String, String)</code></p>
|
||||
* <p>
|
||||
* <b>注意:</b>必须保证欲重定义的类的方法的 ClassLoader 和调用该方法的 ClassLoader
|
||||
* 一致,否则会导致两者的静态类的静态变量不一致,<b>尤其是项目引入了 spring-boot-devtool
|
||||
* 时</b>,一般调用该方法的类由 RestartClassLoader 加载,而 okhttp 包由于是 jar 包,一般交由默认
|
||||
* ClassLoaders 进行加载,这就导致 PatchOkHttp 在两个 ClassLoader 中不一致,实际注入方法内 <code>PatchOkHttp.<i><b>rules</b></i></code>
|
||||
* 为空,无论如何都无法命中设定的规则。</p>
|
||||
* <p>解决方法:<ol>
|
||||
* <li>在 src/main/resources 下建立 META-INF 目录,新建
|
||||
* spring-devtools.properties,并在其内增加一行“restart.include.okhttp3=.*okhttp-.*\.jar”,指定由
|
||||
* RestartClassLoader 加载 okhttp-*.jar 包;</li>
|
||||
* <li>在 application.yml 中,增加需要额外扫描的路径,即设置参数“spring.devtools.restart.additional-pathes:
|
||||
* lib/”,否则即便是进行了第一步,也不会生效;</li>
|
||||
* <li>在方法体内,使用外部 RestartClassLoader 载入欲重定义的方法类,并在调用 ByteBuddy 时指定该方法类和 ClassLoader。</li>
|
||||
* </ol>
|
||||
* </p>
|
||||
* @see org.springframework.boot.devtools.restart.classloader.RestartClassLoader
|
||||
* @see jdk.internal.loader.ClassLoaders
|
||||
*/
|
||||
private static void hook() {
|
||||
try {
|
||||
ByteBuddyAgent.install();
|
||||
|
||||
new ByteBuddy()
|
||||
.redefine(Class.forName("okhttp3.Request$Builder"))
|
||||
.visit(Advice.to(HeaderInterceptor.class)
|
||||
.on(named("header").and(takesArguments(String.class, String.class))))
|
||||
.make()
|
||||
.load(Class.forName("okhttp3.Request$Builder").getClassLoader(),
|
||||
ClassReloadingStrategy.fromInstalledAgent());
|
||||
ByteBuddyAgent.install();
|
||||
|
||||
ClassLoader appLoader = PatchOkHttp.class.getClassLoader();
|
||||
Class<?> builderClass = appLoader.loadClass("okhttp3.Request$Builder");
|
||||
|
||||
|
||||
if (builderClass.getClassLoader() != appLoader) {
|
||||
int exitCode = SpringApplication.exit(
|
||||
SpringContextHolder.getBean(ConfigurableApplicationContext.class), () -> 0);
|
||||
System.exit(exitCode);
|
||||
}
|
||||
|
||||
new ByteBuddy().redefine(builderClass)
|
||||
.visit(Advice.to(HeaderInterceptor.class)
|
||||
.on(named("header").and(takesArguments(String.class, String.class))))
|
||||
.make().load(appLoader, ClassReloadingStrategy.fromInstalledAgent());
|
||||
|
||||
|
||||
isHooked = true;
|
||||
@@ -64,21 +108,17 @@ public class PatchOkHttp {
|
||||
|
||||
|
||||
public static final Logger log = LoggerFactory.getLogger(HeaderInterceptor.class);
|
||||
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
|
||||
@Advice.OnMethodEnter(
|
||||
skipOn = Advice.OnNonDefaultValue.class)
|
||||
public static boolean intercept(
|
||||
@Advice.This Request.Builder builder,
|
||||
@Advice.Argument(0) String name,
|
||||
@Advice.Argument(value = 1, readOnly = false) String value,
|
||||
@Advice.Local("call") Callable<okhttp3.Request.Builder> superCall
|
||||
) throws Exception {
|
||||
|
||||
// print调试
|
||||
log.debug("[intercept] name = {}, value = {}", name, value);
|
||||
|
||||
|
||||
try {
|
||||
// 获取 headers map
|
||||
|
||||
|
||||
// 获取上下文
|
||||
RequestContext ctx = new RequestContext(builder);
|
||||
|
||||
String[] modified = new String[]{value};
|
||||
|
||||
@@ -4,6 +4,11 @@ import java.util.*;
|
||||
import java.util.function.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import quant.rich.emoney.patch.okhttp.PatchOkHttp.HeaderInterceptor;
|
||||
|
||||
public class PatchOkHttpRule {
|
||||
private final Predicate<RequestContext> condition;
|
||||
private final List<HeaderAction> actions;
|
||||
@@ -37,6 +42,7 @@ public class PatchOkHttpRule {
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
public static final Logger log = LoggerFactory.getLogger(HeaderInterceptor.class);
|
||||
private Predicate<RequestContext> condition;
|
||||
private final List<HeaderAction> actions = new ArrayList<>();
|
||||
|
||||
@@ -51,6 +57,10 @@ public class PatchOkHttpRule {
|
||||
public Builder hasHeaderName(String name) {
|
||||
return and(ctx -> ctx.headers.containsKey(name));
|
||||
}
|
||||
|
||||
public Builder hasNotHeaderName(String name) {
|
||||
return not(r -> r.and(ctx -> ctx.headers.containsKey(name)));
|
||||
}
|
||||
|
||||
public Builder hasHeaderValueMatch(String name, String regex) {
|
||||
Pattern pattern = Pattern.compile(regex);
|
||||
@@ -138,6 +148,8 @@ public class PatchOkHttpRule {
|
||||
public Builder overrideIf(String headerName, String value) {
|
||||
actions.add((ctx, curr, setter) -> {
|
||||
if (curr.equalsIgnoreCase(headerName)) {
|
||||
log.debug("matches and applying - host: {}, currHeader {}, targetHeader {}, value: {}, classLoader: {}", ctx.host, curr, headerName,
|
||||
value, this.getClass().getClassLoader());
|
||||
setter.accept(value);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import jakarta.validation.ConstraintValidator;
|
||||
import jakarta.validation.ConstraintValidatorContext;
|
||||
import quant.rich.emoney.entity.config.ProxyConfig;
|
||||
import quant.rich.emoney.interfaces.IConfig;
|
||||
import quant.rich.emoney.pojo.dto.IpInfo;
|
||||
|
||||
public class ProxyConfigValidator implements IValidator, ConstraintValidator<ProxyConfigValid, IConfig<ProxyConfig>> {
|
||||
|
||||
@@ -25,11 +26,8 @@ public class ProxyConfigValidator implements IValidator, ConstraintValidator<Pro
|
||||
return invalid(context, "端口不合法");
|
||||
}
|
||||
// 非匿名须判断用户名密码是否为空
|
||||
boolean valid = ProxyConfig.isProxyEffective(
|
||||
config.getProxy(), config.getIgnoreHttpsVerification());
|
||||
if (!valid) {
|
||||
return invalid(context, "代理连接失败,请检查");
|
||||
}
|
||||
IpInfo ipInfo = config.refreshIpThroughProxy();
|
||||
return !ipInfo.isEmpty();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user