From 70b38db8c57f903a1acb78a96184ce9e58e98f7f Mon Sep 17 00:00:00 2001 From: QAIU <736226400@qq.com> Date: Tue, 12 Nov 2024 19:05:43 +0800 Subject: [PATCH] =?UTF-8?q?IP=E4=BA=92=E5=8A=A9=E8=AE=A1=E5=88=92,=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=AD=A3=E5=90=91=E4=BB=A3=E7=90=86=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1(TODO)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/pom.xml | 13 ++ .../src/main/java/cn/qaiu/vx/core/Deploy.java | 7 +- .../java/cn/qaiu/vx/core/package-info.java | 7 + .../java/cn/qaiu/vx/core/util/CommonUtil.java | 18 ++ .../vx/core/verticle/HttpProxyVerticle.java | 184 ++++++++++++++++++ .../vx/core/verticle/conf/HttpProxyConf.java | 89 +++++++++ core/src/main/resources/app.properties | 1 + web-service/src/main/resources/app.yml | 6 +- web-service/src/main/resources/http-proxy.yml | 5 + .../src/main/resources/server-proxy.yml | 8 - 10 files changed, 324 insertions(+), 14 deletions(-) create mode 100644 core/src/main/java/cn/qaiu/vx/core/package-info.java create mode 100644 core/src/main/java/cn/qaiu/vx/core/verticle/HttpProxyVerticle.java create mode 100644 core/src/main/java/cn/qaiu/vx/core/verticle/conf/HttpProxyConf.java create mode 100644 core/src/main/resources/app.properties create mode 100644 web-service/src/main/resources/http-proxy.yml diff --git a/core/pom.xml b/core/pom.xml index 82d72ce..ba4a6bb 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -76,6 +76,12 @@ + + + src/main/resources + true + + org.apache.maven.plugins @@ -84,6 +90,13 @@ ${java.version} ${java.version} + + + io.vertx.codegen.CodeGenProcessor + + + ${project.basedir}/src/main/java + diff --git a/core/src/main/java/cn/qaiu/vx/core/Deploy.java b/core/src/main/java/cn/qaiu/vx/core/Deploy.java index 237661c..3611aba 100644 --- a/core/src/main/java/cn/qaiu/vx/core/Deploy.java +++ b/core/src/main/java/cn/qaiu/vx/core/Deploy.java @@ -1,5 +1,6 @@ package cn.qaiu.vx.core; +import cn.qaiu.vx.core.util.CommonUtil; import cn.qaiu.vx.core.util.ConfigUtil; import cn.qaiu.vx.core.util.VertxHolder; import cn.qaiu.vx.core.verticle.ReverseProxyVerticle; @@ -102,7 +103,7 @@ private void outLogo(JsonObject conf) { """; System.out.printf(logoTemplate, - conf.getString("version_app"), + CommonUtil.getAppVersion(), VersionCommand.getVersion(), conf.getString("copyright"), year @@ -125,7 +126,9 @@ private void deployVerticle() { vertxOptions.setAddressResolverOptions( new AddressResolverOptions(). addServer("114.114.114.114"). - addServer("114.114.115.115")); + addServer("114.114.115.115"). + addServer("8.8.8.8"). + addServer("8.8.4.4")); LOGGER.info("vertxConfigEventLoopPoolSize: {}, eventLoopPoolSize: {}, workerPoolSize: {}", vertxConfigELPS, vertxOptions.getEventLoopPoolSize(), vertxOptions.getWorkerPoolSize()); diff --git a/core/src/main/java/cn/qaiu/vx/core/package-info.java b/core/src/main/java/cn/qaiu/vx/core/package-info.java new file mode 100644 index 0000000..1a13278 --- /dev/null +++ b/core/src/main/java/cn/qaiu/vx/core/package-info.java @@ -0,0 +1,7 @@ +/** + * ModuleGen cn.qaiu.vx.core + */ +@ModuleGen(name = "vertx-http-proxy", groupPackage = "cn.qaiu.vx.core", useFutures = true) +package cn.qaiu.vx.core; + +import io.vertx.codegen.annotations.ModuleGen; diff --git a/core/src/main/java/cn/qaiu/vx/core/util/CommonUtil.java b/core/src/main/java/cn/qaiu/vx/core/util/CommonUtil.java index 27dff06..dbb9b99 100644 --- a/core/src/main/java/cn/qaiu/vx/core/util/CommonUtil.java +++ b/core/src/main/java/cn/qaiu/vx/core/util/CommonUtil.java @@ -14,6 +14,7 @@ import java.net.UnknownHostException; import java.util.List; import java.util.Map; +import java.util.Properties; import java.util.Set; import java.util.stream.Collectors; @@ -139,4 +140,21 @@ public static Set sortClassSet(Set> set) { } }).collect(Collectors.toSet()); } + + private static String appVersion; + + public static String getAppVersion() { + if (null == appVersion) { + Properties properties = new Properties(); + try { + properties.load(CommonUtil.class.getClassLoader().getResourceAsStream("app.properties")); + if (!properties.isEmpty()) { + appVersion = properties.getProperty("app.version"); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return appVersion; + } } diff --git a/core/src/main/java/cn/qaiu/vx/core/verticle/HttpProxyVerticle.java b/core/src/main/java/cn/qaiu/vx/core/verticle/HttpProxyVerticle.java new file mode 100644 index 0000000..3c4b4f3 --- /dev/null +++ b/core/src/main/java/cn/qaiu/vx/core/verticle/HttpProxyVerticle.java @@ -0,0 +1,184 @@ +package cn.qaiu.vx.core.verticle; + +import io.vertx.core.AbstractVerticle; +import io.vertx.core.Vertx; +import io.vertx.core.VertxOptions; +import io.vertx.core.dns.AddressResolverOptions; +import io.vertx.core.http.*; +import io.vertx.core.net.NetClient; +import io.vertx.core.net.NetClientOptions; +import io.vertx.core.net.NetSocket; +import io.vertx.core.net.ProxyOptions; + +import java.util.Base64; + +/** + * + */ +public class HttpProxyVerticle extends AbstractVerticle { + + private HttpClient httpClient; + private NetClient netClient; + + @Override + public void start() { + ProxyOptions proxyOptions = new ProxyOptions().setHost("127.0.0.1").setPort(7890); + // 初始化 HTTP 客户端,用于向目标服务器发送 HTTP 请求 + HttpClientOptions httpClientOptions = new HttpClientOptions(); + httpClient = vertx.createHttpClient(httpClientOptions.setProxyOptions(proxyOptions)); + + // 创建并启动 HTTP 代理服务器,监听指定端口 + HttpServer server = vertx.createHttpServer(new HttpServerOptions().setClientAuth(ClientAuth.REQUIRED)); + server.requestHandler(this::handleClientRequest); + + // 初始化 NetClient,用于在 CONNECT 请求中建立 TCP 连接隧道 + netClient = vertx.createNetClient(new NetClientOptions() + .setProxyOptions(proxyOptions) + .setConnectTimeout(15000) + .setTrustAll(true)); + + // 启动 HTTP 代理服务器 + server.listen(7891, ar -> { + if (ar.succeeded()) { + System.out.println("HTTP Proxy server started on port 7891"); + } else { + System.err.println("Failed to start HTTP Proxy server: " + ar.cause()); + } + }); + } + + // 处理 HTTP CONNECT 请求,用于代理 HTTPS 流量 + private void handleConnectRequest(HttpServerRequest clientRequest) { + String[] uriParts = clientRequest.uri().split(":"); + if (uriParts.length != 2) { + clientRequest.response().setStatusCode(400).end("Bad Request: Invalid URI format"); + return; + } + + // 解析目标主机和端口 + String targetHost = uriParts[0]; + int targetPort; + try { + targetPort = Integer.parseInt(uriParts[1]); + } catch (NumberFormatException e) { + clientRequest.response().setStatusCode(400).end("Bad Request: Invalid port"); + return; + } + clientRequest.pause(); + // 通过 NetClient 连接目标服务器并创建隧道 + netClient.connect(targetPort, targetHost, connectionAttempt -> { + if (connectionAttempt.succeeded()) { + NetSocket targetSocket = connectionAttempt.result(); + + // 升级客户端连接到 NetSocket 并实现双向数据流 + clientRequest.toNetSocket().onComplete(clientSocketAttempt -> { + if (clientSocketAttempt.succeeded()) { + NetSocket clientSocket = clientSocketAttempt.result(); + + // 设置双向数据流转发 + clientSocket.handler(targetSocket::write); + targetSocket.handler(clientSocket::write); + + // 关闭其中一方时关闭另一方 + clientSocket.closeHandler(v -> targetSocket.close()); + targetSocket.closeHandler(v -> clientSocket.close()); + } else { + System.err.println("Failed to upgrade client connection to socket: " + clientSocketAttempt.cause().getMessage()); + targetSocket.close(); + clientRequest.response().setStatusCode(500).end("Internal Server Error"); + } + }); + } else { + System.err.println("Failed to connect to target: " + connectionAttempt.cause().getMessage()); + clientRequest.response().setStatusCode(502).end("Bad Gateway: Unable to connect to target"); + } + }); + } + + // 处理客户端的 HTTP 请求 + private void handleClientRequest(HttpServerRequest clientRequest) { + String s = clientRequest.headers().get("Proxy-Authorization"); + if (s == null) { + clientRequest.response().setStatusCode(403).end(); + return; + } + String[] split = new String(Base64.getDecoder().decode(s.replace("Basic ", ""))).split(":"); + if (split.length > 1) { + System.out.println(split[0]); + System.out.println(split[1]); + // TODO + } + + + if (clientRequest.method() == HttpMethod.CONNECT) { + // 处理 CONNECT 请求 + handleConnectRequest(clientRequest); + } else { + // 处理普通的 HTTP 请求 + handleHttpRequest(clientRequest); + } + } + + // 处理 HTTP 请求,转发至目标服务器并返回响应 + private void handleHttpRequest(HttpServerRequest clientRequest) { + // 获取目标主机 + String hostHeader = clientRequest.getHeader("Host"); + if (hostHeader == null) { + clientRequest.response().setStatusCode(400).end("Host header is missing"); + return; + } + + String targetHost = hostHeader.split(":")[0]; + int targetPort = 80; // 默认为 HTTP 的端口 + clientRequest.pause(); // 暂停客户端请求的读取,避免数据丢失 + + httpClient.request(clientRequest.method(), targetPort, targetHost, clientRequest.uri()) + .onSuccess(request -> { + clientRequest.resume(); // 恢复客户端请求的读取 + + // 逐个设置请求头 + clientRequest.headers().forEach(header -> request.putHeader(header.getKey(), header.getValue())); + + // 将客户端请求的 body 转发给目标服务器 + clientRequest.bodyHandler(body -> request.send(body, ar -> { + if (ar.succeeded()) { + var response = ar.result(); + clientRequest.response().setStatusCode(response.statusCode()); + clientRequest.response().headers().setAll(response.headers()); + response.body().onSuccess(b-> clientRequest.response().end(b)); + } else { + clientRequest.response().setStatusCode(502).end("Bad Gateway: Unable to reach target"); + } + })); + }) + .onFailure(err -> { + err.printStackTrace(); + clientRequest.response().setStatusCode(502).end("Bad Gateway: Request failed"); + }); + } + + @Override + public void stop() { + // 停止 HTTP 客户端以释放资源 + if (httpClient != null) { + httpClient.close(); + } + } + + /** + * TODO add Deploy + * @param args + */ + public static void main(String[] args) { + // 配置 DNS 解析器,使用多个 DNS 服务器来提升解析速度 + Vertx vertx = Vertx.vertx(new VertxOptions() + .setAddressResolverOptions(new AddressResolverOptions() + .addServer("114.114.114.114") + .addServer("114.114.115.115") + .addServer("8.8.8.8") + .addServer("8.8.4.4"))); + + // 部署 Verticle 并启动动态 HTTP 代理服务器 + vertx.deployVerticle(new HttpProxyVerticle()); + } +} diff --git a/core/src/main/java/cn/qaiu/vx/core/verticle/conf/HttpProxyConf.java b/core/src/main/java/cn/qaiu/vx/core/verticle/conf/HttpProxyConf.java new file mode 100644 index 0000000..5131fb6 --- /dev/null +++ b/core/src/main/java/cn/qaiu/vx/core/verticle/conf/HttpProxyConf.java @@ -0,0 +1,89 @@ +package cn.qaiu.vx.core.verticle.conf; + +import io.vertx.codegen.annotations.DataObject; +import io.vertx.codegen.json.annotations.JsonGen; +import io.vertx.core.json.JsonObject; +import io.vertx.core.net.ProxyOptions; + +import java.util.UUID; + +@DataObject +@JsonGen(publicConverter = false) +public class HttpProxyConf { + + public static final String DEFAULT_USERNAME = UUID.randomUUID().toString(); + + public static final String DEFAULT_PASSWORD = UUID.randomUUID().toString(); + + public static final Integer DEFAULT_PORT = 6402; + + public static final Integer DEFAULT_TIMEOUT = 15000; + + Integer timeout; + + String username; + + String password; + + Integer port; + + ProxyOptions preProxyOptions; + + public HttpProxyConf() { + this.username = DEFAULT_USERNAME; + this.password = DEFAULT_PASSWORD; + this.timeout = DEFAULT_PORT; + this.timeout = DEFAULT_TIMEOUT; + this.preProxyOptions = new ProxyOptions(); + } + + public HttpProxyConf(JsonObject json) { + this(); + } + + + public Integer getTimeout() { + return timeout; + } + + public HttpProxyConf setTimeout(Integer timeout) { + this.timeout = timeout; + return this; + } + + public String getUsername() { + return username; + } + + public HttpProxyConf setUsername(String username) { + this.username = username; + return this; + } + + public String getPassword() { + return password; + } + + public HttpProxyConf setPassword(String password) { + this.password = password; + return this; + } + + public Integer getPort() { + return port; + } + + public HttpProxyConf setPort(Integer port) { + this.port = port; + return this; + } + + public ProxyOptions getPreProxyOptions() { + return preProxyOptions; + } + + public HttpProxyConf setPreProxyOptions(ProxyOptions preProxyOptions) { + this.preProxyOptions = preProxyOptions; + return this; + } +} diff --git a/core/src/main/resources/app.properties b/core/src/main/resources/app.properties new file mode 100644 index 0000000..001cdbc --- /dev/null +++ b/core/src/main/resources/app.properties @@ -0,0 +1 @@ +app.version=${project.version} diff --git a/web-service/src/main/resources/app.yml b/web-service/src/main/resources/app.yml index f20f511..3947d90 100644 --- a/web-service/src/main/resources/app.yml +++ b/web-service/src/main/resources/app.yml @@ -1,6 +1,4 @@ -# 要激活的配置: dev--连接本地数据库; prod连接线上数据库 +# 要激活的配置: app-配置名称.yml active: dev -# 版本号 -version_app: 0.1.8 -# 公司名称 -> LOGO版权文字 +# 控制台输出的版权文字 copyright: QAIU diff --git a/web-service/src/main/resources/http-proxy.yml b/web-service/src/main/resources/http-proxy.yml new file mode 100644 index 0000000..e42b70f --- /dev/null +++ b/web-service/src/main/resources/http-proxy.yml @@ -0,0 +1,5 @@ +# 正向代理 +server-name: Vert.x-proxy-http(v4.1.2) + +proxy: + - listen: 6402 diff --git a/web-service/src/main/resources/server-proxy.yml b/web-service/src/main/resources/server-proxy.yml index 3525794..d1d80b9 100644 --- a/web-service/src/main/resources/server-proxy.yml +++ b/web-service/src/main/resources/server-proxy.yml @@ -51,12 +51,4 @@ proxy: # origin: 127.0.0.1:8088 -#proxy: -# - listen: 8085 -# static: -# path: /mu-down -# # add-headers: -# # x-token: ABC -# root: webroot/mu-down/ -# index: index.html