From ca37e92e857b8de791f9ddea16743c4139fe9880 Mon Sep 17 00:00:00 2001 From: syeyoung Date: Mon, 1 Mar 2021 21:37:28 +0900 Subject: stomp. --- src/main/java/kr/syeyoung/dungeonsguide/e.java | 36 +++++- .../dungeonsguide/features/FeatureRegistry.java | 1 + .../features/impl/etc/FeatureBlah.java | 38 ++++++ .../dungeonsguide/stomp/CloseListener.java | 5 + .../syeyoung/dungeonsguide/stomp/StompClient.java | 136 +++++++++++++++++++++ .../dungeonsguide/stomp/StompClientStatus.java | 5 + .../syeyoung/dungeonsguide/stomp/StompHeader.java | 9 ++ .../dungeonsguide/stomp/StompInterface.java | 8 ++ .../dungeonsguide/stomp/StompMessageHandler.java | 5 + .../syeyoung/dungeonsguide/stomp/StompPayload.java | 69 +++++++++++ .../dungeonsguide/stomp/StompSubscription.java | 23 ++++ 11 files changed, 333 insertions(+), 2 deletions(-) create mode 100644 src/main/java/kr/syeyoung/dungeonsguide/features/impl/etc/FeatureBlah.java create mode 100644 src/main/java/kr/syeyoung/dungeonsguide/stomp/CloseListener.java create mode 100644 src/main/java/kr/syeyoung/dungeonsguide/stomp/StompClient.java create mode 100644 src/main/java/kr/syeyoung/dungeonsguide/stomp/StompClientStatus.java create mode 100644 src/main/java/kr/syeyoung/dungeonsguide/stomp/StompHeader.java create mode 100644 src/main/java/kr/syeyoung/dungeonsguide/stomp/StompInterface.java create mode 100644 src/main/java/kr/syeyoung/dungeonsguide/stomp/StompMessageHandler.java create mode 100644 src/main/java/kr/syeyoung/dungeonsguide/stomp/StompPayload.java create mode 100644 src/main/java/kr/syeyoung/dungeonsguide/stomp/StompSubscription.java (limited to 'src/main/java/kr') diff --git a/src/main/java/kr/syeyoung/dungeonsguide/e.java b/src/main/java/kr/syeyoung/dungeonsguide/e.java index 323e73e1..684d2fe9 100755 --- a/src/main/java/kr/syeyoung/dungeonsguide/e.java +++ b/src/main/java/kr/syeyoung/dungeonsguide/e.java @@ -7,6 +7,9 @@ import kr.syeyoung.dungeonsguide.eventlistener.DungeonListener; import kr.syeyoung.dungeonsguide.eventlistener.FeatureListener; import kr.syeyoung.dungeonsguide.eventlistener.PacketListener; import kr.syeyoung.dungeonsguide.features.FeatureRegistry; +import kr.syeyoung.dungeonsguide.stomp.CloseListener; +import kr.syeyoung.dungeonsguide.stomp.StompClient; +import kr.syeyoung.dungeonsguide.stomp.StompInterface; import kr.syeyoung.dungeonsguide.utils.AhUtils; import lombok.Getter; import net.minecraft.client.Minecraft; @@ -17,6 +20,7 @@ import net.minecraftforge.client.event.ClientChatReceivedEvent; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.config.Configuration; import net.minecraftforge.fml.common.ObfuscationReflectionHelper; +import net.minecraftforge.fml.common.ProgressManager; import net.minecraftforge.fml.common.event.FMLInitializationEvent; import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; import org.apache.commons.io.IOUtils; @@ -25,13 +29,17 @@ import javax.crypto.BadPaddingException; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import java.io.*; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.Map; import java.util.Set; -public class e implements c { +public class e implements c, CloseListener { private SkyblockStatus skyblockStatus; @@ -41,6 +49,9 @@ public class e implements c { @Getter private b authenticator; + @Getter + private StompInterface stompConnection; + public e(b authenticator) { this.authenticator = authenticator; } @@ -53,6 +64,17 @@ public class e implements c { CommandReparty commandReparty; public void init(FMLInitializationEvent event) { + ProgressManager.ProgressBar progressbar = ProgressManager.push("DungeonsGuide", 4); + + progressbar.step("Opening connection"); + try { + stompConnection = new StompClient(new URI("wss://dungeonsguide.kro.kr/ws"), authenticator.c(), this); + } catch (Exception e) { + throw new RuntimeException(e); + } + + + progressbar.step("Registering Events & Commands"); dungeonsGuide = this; skyblockStatus = new SkyblockStatus(); @@ -72,6 +94,7 @@ public class e implements c { AhUtils.registerTimer(); + progressbar.step("Downloading Roomdatas"); try { DungeonRoomInfoRegistry.loadAll(configDir); } catch (BadPaddingException e) { @@ -89,14 +112,16 @@ public class e implements c { } catch (InvalidKeyException e) { e.printStackTrace(); } - Keybinds.register(); + progressbar.step("Downloading Roomdatas"); try { Config.loadConfig( null ); } catch (IOException e) { e.printStackTrace(); } + + ProgressManager.pop(progressbar); } public void pre(FMLPreInitializationEvent event) { configDir = new File(event.getModConfigurationDirectory(),"dungeonsguide"); @@ -129,4 +154,11 @@ public class e implements c { public static e getDungeonsGuide() { return dungeonsGuide; } + + @Override + public void onClose(int code, String reason, boolean remote) { + System.out.println("Stomp Connection closed, trying to reconnect - "+reason+ " - "+code); +// stompConnection = new StompClient(new URL("wss://dungeonsguide.kro.kr/ws").toURI(), authenticator.c(), this); +// Minecraft.getMinecraft(). + } } diff --git a/src/main/java/kr/syeyoung/dungeonsguide/features/FeatureRegistry.java b/src/main/java/kr/syeyoung/dungeonsguide/features/FeatureRegistry.java index adbf15c4..fa1b3c92 100644 --- a/src/main/java/kr/syeyoung/dungeonsguide/features/FeatureRegistry.java +++ b/src/main/java/kr/syeyoung/dungeonsguide/features/FeatureRegistry.java @@ -66,6 +66,7 @@ public class FeatureRegistry { public static final FeatureRepartyCommand ETC_REPARTY = register(new FeatureRepartyCommand()); public static final FeatureDecreaseExplosionSound ETC_EXPLOSION_SOUND = register(new FeatureDecreaseExplosionSound()); public static final FeatureAutoAcceptReparty ETC_AUTO_ACCEPT_REPARTY = register(new FeatureAutoAcceptReparty()); + public static final FeatureBlah ETC_TEST = register(new FeatureBlah()); public static final SimpleFeature FIX_SPIRIT_BOOTS = register(new SimpleFeature("Fixes", "Spirit Boots Fixer", "Fix Spirit boots messing up with inventory", "fixes.spirit", true)); diff --git a/src/main/java/kr/syeyoung/dungeonsguide/features/impl/etc/FeatureBlah.java b/src/main/java/kr/syeyoung/dungeonsguide/features/impl/etc/FeatureBlah.java new file mode 100644 index 00000000..16c99db2 --- /dev/null +++ b/src/main/java/kr/syeyoung/dungeonsguide/features/impl/etc/FeatureBlah.java @@ -0,0 +1,38 @@ +package kr.syeyoung.dungeonsguide.features.impl.etc; + +import kr.syeyoung.dungeonsguide.e; +import kr.syeyoung.dungeonsguide.features.SimpleFeature; +import kr.syeyoung.dungeonsguide.features.listener.TickListener; +import kr.syeyoung.dungeonsguide.stomp.StompInterface; +import kr.syeyoung.dungeonsguide.stomp.StompMessageHandler; +import kr.syeyoung.dungeonsguide.stomp.StompPayload; +import kr.syeyoung.dungeonsguide.stomp.StompSubscription; +import net.minecraft.client.Minecraft; +import net.minecraft.util.ChatComponentText; + +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +public class FeatureBlah extends SimpleFeature implements StompMessageHandler, TickListener { + public FeatureBlah() { + super("ETC", "TEST","test.test"); + e.getDungeonsGuide().getStompConnection().subscribe(StompSubscription.builder() + .destination("/topic/updates") + .ackMode(StompSubscription.AckMode.AUTO) + .stompMessageHandler(this).build()); + } + + Queue stompPayloadQueue = new ConcurrentLinkedQueue(); + @Override + public void handle(StompInterface stompInterface, StompPayload stompPayload) { + stompPayloadQueue.add(stompPayload); + } + + + @Override + public void onTick() { + while (!stompPayloadQueue.isEmpty()) { + Minecraft.getMinecraft().thePlayer.addChatMessage(new ChatComponentText(stompPayloadQueue.poll().payload())); + } + } +} diff --git a/src/main/java/kr/syeyoung/dungeonsguide/stomp/CloseListener.java b/src/main/java/kr/syeyoung/dungeonsguide/stomp/CloseListener.java new file mode 100644 index 00000000..a9b21cb7 --- /dev/null +++ b/src/main/java/kr/syeyoung/dungeonsguide/stomp/CloseListener.java @@ -0,0 +1,5 @@ +package kr.syeyoung.dungeonsguide.stomp; + +public interface CloseListener { + void onClose(int code, String reason, boolean remote); +} diff --git a/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompClient.java b/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompClient.java new file mode 100644 index 00000000..248bf04b --- /dev/null +++ b/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompClient.java @@ -0,0 +1,136 @@ +package kr.syeyoung.dungeonsguide.stomp; + +import lombok.Getter; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.server.DefaultSSLWebSocketServerFactory; +import sun.security.ssl.SSLSocketFactoryImpl; + +import javax.net.ssl.SSLContext; +import java.net.URI; +import java.util.HashMap; +import java.util.Map; + +public class StompClient extends WebSocketClient implements StompInterface { + public StompClient(URI serverUri, final String token, CloseListener closeListener) throws Exception { + super(serverUri); + this.closeListener = closeListener; + addHeader("Authorization", token); + + connectBlocking(); + while(this.stompClientStatus == StompClientStatus.CONNECTING); + } + private CloseListener closeListener; + + @Getter + private volatile StompClientStatus stompClientStatus = StompClientStatus.CONNECTING; + + @Getter + private StompPayload errorPayload; + + @Override + public void onOpen(ServerHandshake handshakedata) { + send(new StompPayload().method(StompHeader.CONNECT) + .header("accept-version","1.2") + .header("host",uri.getHost()).getBuilt() + ); + } + + @Override + public void onMessage(String message) { + try { + StompPayload payload = StompPayload.parse(message); + if (payload.method() == StompHeader.CONNECTED) { + stompClientStatus = StompClientStatus.CONNECTED; + } else if (payload.method() == StompHeader.ERROR) { + errorPayload = payload; + stompClientStatus = StompClientStatus.ERROR; + this.close(); + } else if (payload.method() == StompHeader.MESSAGE) { + // mesage + StompSubscription stompSubscription = stompSubscriptionMap.get(Integer.parseInt(payload.headers().get("subscription"))); + try { + stompSubscription.getStompMessageHandler().handle(this, payload); + if (stompSubscription.getAckMode() != StompSubscription.AckMode.AUTO) { + send(new StompPayload().method(StompHeader.ACK) + .header("id",payload.headers().get("ack")).getBuilt() + ); + } + } catch (Exception e) { + send(new StompPayload().method(StompHeader.NACK) + .header("id",payload.headers().get("ack")).getBuilt() + ); + e.printStackTrace(); + } + } else if (payload.method() == StompHeader.RECEIPT) { + String receipt_id = payload.headers().get("receipt-id"); + StompPayload payload1 = receiptMap.remove(Integer.parseInt(receipt_id)); + if (payload1.method() == StompHeader.DISCONNECT) { + stompClientStatus = StompClientStatus.DISCONNECTED; + close(); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onClose(int code, String reason, boolean remote) { + closeListener.onClose(code, reason, remote); + } + + @Override + public void onError(Exception ex) { + + } + + private Map stompSubscriptionMap = new HashMap(); + private Map receiptMap = new HashMap(); + + private int idIncrement = 0; + + @Override + public void send(StompPayload payload) { + if (stompClientStatus != StompClientStatus.CONNECTED) throw new IllegalStateException("not connected"); + payload.method(StompHeader.SEND); + if (payload.headers().get("receipt") != null) + receiptMap.put(Integer.parseInt(payload.headers().get("receipt")), payload); + send(payload); + } + + @Override + public void subscribe(StompSubscription stompSubscription) { + if (stompClientStatus != StompClientStatus.CONNECTED) throw new IllegalStateException("not connected"); + stompSubscription.setId(++idIncrement); + + send(new StompPayload().method(StompHeader.SUBSCRIBE) + .header("id",String.valueOf(stompSubscription.getId())) + .header("destination", stompSubscription.getDestination()) + .header("ack", stompSubscription.getAckMode().getValue()).getBuilt() + ); + + stompSubscriptionMap.put(stompSubscription.getId(), stompSubscription); + } + + @Override + public void unsubscribe(StompSubscription stompSubscription) { + if (stompClientStatus != StompClientStatus.CONNECTED) throw new IllegalStateException("not connected"); + send(new StompPayload().method(StompHeader.UNSUBSCRIBE) + .header("id",String.valueOf(stompSubscription.getId())).getBuilt() + ); + stompSubscriptionMap.remove(stompSubscription.getId()); + } + + @Override + public void disconnect() { + if (stompClientStatus != StompClientStatus.CONNECTED) throw new IllegalStateException("not connected"); + StompPayload stompPayload; + stompClientStatus =StompClientStatus.DISCONNECTING; + send((stompPayload = new StompPayload().method(StompHeader.DISCONNECT) + .header("receipt", String.valueOf(++idIncrement))) + .getBuilt() + ); + receiptMap.put(idIncrement, stompPayload); + } +} diff --git a/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompClientStatus.java b/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompClientStatus.java new file mode 100644 index 00000000..b3f5cbc2 --- /dev/null +++ b/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompClientStatus.java @@ -0,0 +1,5 @@ +package kr.syeyoung.dungeonsguide.stomp; + +public enum StompClientStatus { + CONNECTING, CONNECTED, ERROR, DISCONNECTING, DISCONNECTED; +} diff --git a/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompHeader.java b/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompHeader.java new file mode 100644 index 00000000..0e9c4311 --- /dev/null +++ b/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompHeader.java @@ -0,0 +1,9 @@ +package kr.syeyoung.dungeonsguide.stomp; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; + +public enum StompHeader { + SEND, SUBSCRIBE, UNSUBSCRIBE, BEGIN, COMMIT, ABORT, ACK, NACK, DISCONNECT, CONNECT, STOMP, CONNECTED, MESSAGE, RECEIPT, ERROR +} diff --git a/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompInterface.java b/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompInterface.java new file mode 100644 index 00000000..e78f8ceb --- /dev/null +++ b/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompInterface.java @@ -0,0 +1,8 @@ +package kr.syeyoung.dungeonsguide.stomp; + +public interface StompInterface { + void send(StompPayload payload); + void subscribe(StompSubscription stompSubscription); + void unsubscribe(StompSubscription stompSubscription); + void disconnect(); +} diff --git a/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompMessageHandler.java b/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompMessageHandler.java new file mode 100644 index 00000000..7b13c1fd --- /dev/null +++ b/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompMessageHandler.java @@ -0,0 +1,5 @@ +package kr.syeyoung.dungeonsguide.stomp; + +public interface StompMessageHandler { + void handle(StompInterface stompInterface, StompPayload stompPayload); +} diff --git a/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompPayload.java b/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompPayload.java new file mode 100644 index 00000000..9476241c --- /dev/null +++ b/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompPayload.java @@ -0,0 +1,69 @@ +package kr.syeyoung.dungeonsguide.stomp; + +import lombok.Data; +import lombok.Singular; +import lombok.experimental.Accessors; + +import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; + +@Data +@Accessors(chain = true, fluent = true) +public class StompPayload { + private StompHeader method; + private Map headers = new HashMap(); + private String payload; + + public StompPayload header(String key, String value) { + headers.put(key, value); + return this; + } + + public String getBuilt() { + StringBuilder sb = new StringBuilder(); + sb.append(method.name()); + sb.append("\n"); + for (Map.Entry stringStringEntry : headers.entrySet()) { + sb.append(stringStringEntry.getKey()); + sb.append(":"); + sb.append(stringStringEntry.getValue()); + sb.append("\n"); + if (stringStringEntry.getKey().contains(":")) throw new IllegalStateException("Illegal Character : inside headers"); + if (stringStringEntry.getValue().contains(":")) throw new IllegalStateException("Illegal Character : inside headers"); + } + sb.append("\n"); + if (payload != null) + sb.append(payload); + sb.append((char) 0); + System.out.println("Probably sending "+sb.toString()); + return sb.toString(); + } + + public static StompPayload parse(String payload) { + System.out.println("Parsing "+payload); + Scanner scanner = new Scanner(payload); + StompPayload stompPayload = new StompPayload(); + stompPayload.method = StompHeader.valueOf(scanner.nextLine()); + String line = ""; + while (!(line = scanner.nextLine()).isEmpty()) { + int index = line.indexOf(":"); + if (index == -1) throw new IllegalArgumentException("No : found in headers section"); + String name = line.substring(0, index); + String value; + if (index == line.length() - 1) + value = ""; + else + value = line.substring(index+1); + stompPayload.headers.put(name, value); + } + + StringBuilder payloadBuilder = new StringBuilder(); + while (scanner.hasNextLine() && !(line = scanner.nextLine()).equals("\0")) { + payloadBuilder.append(line); + payloadBuilder.append("\n"); + } + stompPayload.payload = payloadBuilder.toString(); + return stompPayload; + } +} diff --git a/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompSubscription.java b/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompSubscription.java new file mode 100644 index 00000000..5ecbeb06 --- /dev/null +++ b/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompSubscription.java @@ -0,0 +1,23 @@ +package kr.syeyoung.dungeonsguide.stomp; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.Getter; + +@Data +@Builder +public class StompSubscription { + private int id; + private String destination; + private StompMessageHandler stompMessageHandler; + private AckMode ackMode; + + @AllArgsConstructor + public static enum AckMode { + AUTO("auto"), CLIENT("client"), CLIENT_INDIVIDUAL("client-individual"); + + @Getter + private String value; + } +} -- cgit