From c1b0988d774a623ce785990282caf3ef48c52a46 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Wed, 24 May 2023 11:54:08 -0400 Subject: Write custom BytesocksClient implementation for Java 8 --- spark-common/build.gradle | 3 +- .../java/me/lucko/spark/common/SparkPlatform.java | 4 +- .../spark/common/legacy/LegacyBytesocksClient.java | 155 +++++++++++++++++++++ .../legacy/LegacyBytesocksClientFactory.java | 9 ++ spark-forge1710/build.gradle | 4 +- 5 files changed, 171 insertions(+), 4 deletions(-) create mode 100644 spark-common/src/main/java/me/lucko/spark/common/legacy/LegacyBytesocksClient.java create mode 100644 spark-common/src/main/java/me/lucko/spark/common/legacy/LegacyBytesocksClientFactory.java diff --git a/spark-common/build.gradle b/spark-common/build.gradle index 1713168..514c5dd 100644 --- a/spark-common/build.gradle +++ b/spark-common/build.gradle @@ -20,7 +20,8 @@ dependencies { implementation 'org.ow2.asm:asm:9.1' implementation 'net.bytebuddy:byte-buddy-agent:1.11.0' implementation 'com.google.protobuf:protobuf-javalite:3.21.11' - implementation 'me.lucko:bytesocks-java-client:1.0-SNAPSHOT' + implementation 'me.lucko:bytesocks-java-client-api:1.0-SNAPSHOT' + implementation 'com.neovisionaries:nv-websocket-client:2.14' api('net.kyori:adventure-api:4.12.0') { exclude(module: 'adventure-bom') diff --git a/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java b/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java index 84f435a..24b879a 100644 --- a/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java +++ b/spark-common/src/main/java/me/lucko/spark/common/SparkPlatform.java @@ -24,7 +24,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import me.lucko.bytesocks.client.BytesocksClient; -import me.lucko.bytesocks.client.BytesocksClientFactory; import me.lucko.spark.common.activitylog.ActivityLog; import me.lucko.spark.common.api.SparkApi; import me.lucko.spark.common.command.Arguments; @@ -40,6 +39,7 @@ import me.lucko.spark.common.command.modules.TickMonitoringModule; import me.lucko.spark.common.command.sender.CommandSender; import me.lucko.spark.common.command.tabcomplete.CompletionSupplier; import me.lucko.spark.common.command.tabcomplete.TabCompleter; +import me.lucko.spark.common.legacy.LegacyBytesocksClientFactory; import me.lucko.spark.common.monitor.cpu.CpuMonitor; import me.lucko.spark.common.monitor.memory.GarbageCollectorStatistics; import me.lucko.spark.common.monitor.net.NetworkMonitor; @@ -128,7 +128,7 @@ public class SparkPlatform { String bytesocksHost = this.configuration.getString("bytesocksHost", "spark-usersockets.lucko.me"); this.bytebinClient = new BytebinClient(bytebinUrl, "spark-plugin"); - this.bytesocksClient = BytesocksClientFactory.newClient(bytesocksHost, "spark-plugin"); + this.bytesocksClient = LegacyBytesocksClientFactory.newClient(bytesocksHost, "spark-plugin"); this.trustedKeyStore = new TrustedKeyStore(this.configuration); this.disableResponseBroadcast = this.configuration.getBoolean("disableResponseBroadcast", false); diff --git a/spark-common/src/main/java/me/lucko/spark/common/legacy/LegacyBytesocksClient.java b/spark-common/src/main/java/me/lucko/spark/common/legacy/LegacyBytesocksClient.java new file mode 100644 index 0000000..e552b0e --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/legacy/LegacyBytesocksClient.java @@ -0,0 +1,155 @@ +package me.lucko.spark.common.legacy; + +import com.neovisionaries.ws.client.*; +import me.lucko.bytesocks.client.BytesocksClient; + +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.concurrent.CompletableFuture; + +/** + * Implementation of BytesocksClient that works on Java 8. + */ +public class LegacyBytesocksClient implements BytesocksClient { + /* The bytesocks urls */ + private final String httpUrl; + private final String wsUrl; + + /** The client user agent */ + private final String userAgent; + + LegacyBytesocksClient(String host, String userAgent) { + + this.httpUrl = "https://" + host + "/"; + this.wsUrl = "wss://" + host + "/"; + this.userAgent = userAgent; + } + + @Override + public BytesocksClient.Socket createAndConnect(BytesocksClient.Listener listener) throws Exception { + HttpURLConnection con = (HttpURLConnection) new URL(this.httpUrl + "create").openConnection(); + con.setRequestMethod("GET"); + con.setRequestProperty("User-Agent", this.userAgent); + if (con.getResponseCode() != 201) { + throw new RuntimeException("Request failed"); + } + + String channelId = null; + + for(Map.Entry> entry : con.getHeaderFields().entrySet()) { + String key = entry.getKey(); + List value = entry.getValue(); + if(key != null && key.equalsIgnoreCase("Location") && value != null && value.size() > 0) { + channelId = value.get(0); + if(channelId != null) + break; + } + } + + if(channelId == null) { + throw new RuntimeException("Location header not returned"); + } + + return connect(channelId, listener); + } + + @Override + public BytesocksClient.Socket connect(String channelId, BytesocksClient.Listener listener) throws Exception { + WebSocketFactory factory = new WebSocketFactory() + .setConnectionTimeout(5000); + WebSocket socket = factory.createSocket(URI.create(this.wsUrl + channelId)) + .addHeader("User-Agent", this.userAgent) + .addListener(new ListenerImpl(listener)) + .connect(); + + return new SocketImpl(channelId, socket); + } + + private static final class SocketImpl implements BytesocksClient.Socket { + private final String id; + private final WebSocket ws; + private final WeakHashMap> frameFutures = new WeakHashMap<>(); + + private SocketImpl(String id, WebSocket ws) { + this.id = id; + this.ws = ws; + this.ws.addListener(new WebSocketAdapter() { + @Override + public void onFrameSent(WebSocket websocket, WebSocketFrame frame) throws Exception { + synchronized (frameFutures) { + CompletableFuture future = frameFutures.remove(frame); + if(future != null) + future.complete(null); + } + } + + @Override + public void onFrameUnsent(WebSocket websocket, WebSocketFrame frame) throws Exception { + synchronized (frameFutures) { + CompletableFuture future = frameFutures.remove(frame); + if(future != null) + future.completeExceptionally(new Exception("Failed to send frame")); + } + } + }); + } + + @Override + public String getChannelId() { + return this.id; + } + + @Override + public boolean isOpen() { + return this.ws.isOpen(); + } + + @Override + public CompletableFuture send(CharSequence msg) { + WebSocketFrame targetFrame = WebSocketFrame.createTextFrame(msg.toString()); + CompletableFuture future = new CompletableFuture<>(); + synchronized (frameFutures) { + frameFutures.put(targetFrame, future); + } + this.ws.sendText(msg.toString()); + return future; + } + + @Override + public void close(int statusCode, String reason) { + this.ws.sendClose(statusCode, reason); + } + } + + private static final class ListenerImpl extends WebSocketAdapter { + private final Listener listener; + + private ListenerImpl(Listener listener) { + this.listener = listener; + } + + @Override + public void onConnected(WebSocket websocket, Map> headers) throws Exception { + this.listener.onOpen(); + } + + @Override + public void onDisconnected(WebSocket websocket, WebSocketFrame serverCloseFrame, WebSocketFrame clientCloseFrame, boolean closedByServer) throws Exception { + this.listener.onClose(serverCloseFrame.getCloseCode(), serverCloseFrame.getCloseReason()); + } + + @Override + public void onError(WebSocket websocket, WebSocketException cause) throws Exception { + this.listener.onError(cause); + } + + @Override + public void onTextMessage(WebSocket websocket, String text) throws Exception { + this.listener.onText(text); + } + } +} diff --git a/spark-common/src/main/java/me/lucko/spark/common/legacy/LegacyBytesocksClientFactory.java b/spark-common/src/main/java/me/lucko/spark/common/legacy/LegacyBytesocksClientFactory.java new file mode 100644 index 0000000..91f2628 --- /dev/null +++ b/spark-common/src/main/java/me/lucko/spark/common/legacy/LegacyBytesocksClientFactory.java @@ -0,0 +1,9 @@ +package me.lucko.spark.common.legacy; + +import me.lucko.bytesocks.client.BytesocksClient; + +public class LegacyBytesocksClientFactory { + public static BytesocksClient newClient(String host, String userAgent) { + return new LegacyBytesocksClient(host, userAgent); + } +} diff --git a/spark-forge1710/build.gradle b/spark-forge1710/build.gradle index 8fd166d..f7c5724 100644 --- a/spark-forge1710/build.gradle +++ b/spark-forge1710/build.gradle @@ -74,6 +74,8 @@ shadowJar { relocate 'com.google.protobuf', 'me.lucko.spark.lib.protobuf' relocate 'org.objectweb.asm', 'me.lucko.spark.lib.asm' relocate 'one.profiler', 'me.lucko.spark.lib.asyncprofiler' + relocate 'me.lucko.bytesocks.client', 'me.lucko.spark.lib.bytesocks' + relocate 'com.neovisionaries.ws', 'me.lucko.spark.lib.websockets' exclude 'module-info.class' exclude 'META-INF/maven/**' @@ -89,4 +91,4 @@ artifacts { shadow shadowJar } -build.dependsOn(shadowJar) \ No newline at end of file +build.dependsOn(shadowJar) -- cgit