[FEATURE] StdWebSocketService

This commit is contained in:
Gravit 2019-12-11 09:15:24 +07:00
parent 0922c18b22
commit 2fc32aa1aa
No known key found for this signature in database
GPG key ID: 061981E1E85D3216
19 changed files with 155 additions and 468 deletions

View file

@ -1,20 +0,0 @@
String mainClassName = "pro.gravit.launchserver.console.ConsoleMain"
repositories {
maven {
url "http://repo.spring.io/plugins-release/"
}
}
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
jar {
from { configurations.runtime.collect { it.isDirectory() ? it : zipTree(it) } }
manifest.attributes("Main-Class": mainClassName)
}
dependencies {
compileOnly project(':ServerWrapper')
}

View file

@ -1,43 +0,0 @@
package pro.gravit.launchserver.console;
import pro.gravit.launcher.server.ServerWrapper;
import pro.gravit.utils.command.CommandHandler;
import pro.gravit.utils.command.JLineCommandHandler;
import pro.gravit.utils.command.StdCommandHandler;
import pro.gravit.utils.command.basic.HelpCommand;
import pro.gravit.utils.helper.LogHelper;
import java.io.IOException;
public class ConsoleMain {
public static CommandHandler commandHandler;
public static void main(String[] args) throws IOException {
if (ServerWrapper.wrapper.config == null) {
LogHelper.warning("ServerWrapper not found");
}
if (!ServerWrapper.wrapper.permissions.canAdmin) {
LogHelper.warning("Permission canAdmin not found");
}
try {
Class.forName("org.jline.terminal.Terminal");
// JLine2 available
commandHandler = new JLineCommandHandler();
LogHelper.info("JLine2 terminal enabled");
} catch (ClassNotFoundException ignored) {
commandHandler = new StdCommandHandler(true);
LogHelper.warning("JLine2 isn't in classpath, using std");
}
registerCommands();
LogHelper.info("CommandHandler started. Use 'exit' to exit this console");
commandHandler.run();
}
public static void registerCommands() {
commandHandler.registerCommand("help", new HelpCommand(commandHandler));
commandHandler.registerCommand("exit", new ExitCommand());
commandHandler.registerCommand("logListener", new LogListenerCommand());
commandHandler.registerCommand("exec", new ExecCommand());
}
}

View file

@ -1,24 +0,0 @@
package pro.gravit.launchserver.console;
import pro.gravit.launcher.events.request.ExecCommandRequestEvent;
import pro.gravit.launcher.request.admin.ExecCommandRequest;
import pro.gravit.utils.command.Command;
import pro.gravit.utils.helper.LogHelper;
public class ExecCommand extends Command {
@Override
public String getArgsDescription() {
return null;
}
@Override
public String getUsageDescription() {
return null;
}
@Override
public void invoke(String... args) throws Exception {
ExecCommandRequestEvent request = new ExecCommandRequest(String.join(" ")).request();
if (!request.success) LogHelper.error("Error executing command");
}
}

View file

@ -1,23 +0,0 @@
package pro.gravit.launchserver.console;
import pro.gravit.utils.command.Command;
public class ExitCommand extends Command {
public ExitCommand() {
}
@Override
public String getArgsDescription() {
return null;
}
@Override
public String getUsageDescription() {
return null;
}
@Override
public void invoke(String... args) {
System.exit(0);
}
}

View file

@ -1,46 +0,0 @@
package pro.gravit.launchserver.console;
import pro.gravit.launcher.LauncherNetworkAPI;
import pro.gravit.launcher.events.request.LogEvent;
import pro.gravit.launcher.request.Request;
import pro.gravit.launcher.request.websockets.WebSocketRequest;
import pro.gravit.utils.command.Command;
import pro.gravit.utils.helper.LogHelper;
public class LogListenerCommand extends Command {
public static class LogListenerRequest implements WebSocketRequest {
@LauncherNetworkAPI
public final LogHelper.OutputTypes outputType;
public LogListenerRequest(LogHelper.OutputTypes outputType) {
this.outputType = outputType;
}
@Override
public String getType() {
return "addLogListener";
}
}
@Override
public String getArgsDescription() {
return null;
}
@Override
public String getUsageDescription() {
return null;
}
@Override
public void invoke(String... args) throws Exception {
LogHelper.info("Send log listener request");
Request.service.sendObject(new LogListenerRequest(LogHelper.JANSI ? LogHelper.OutputTypes.JANSI : LogHelper.OutputTypes.PLAIN));
LogHelper.info("Add log handler");
Request.service.registerHandler((result) -> {
if (result instanceof LogEvent) {
System.out.println(((LogEvent) result).string);
}
});
}
}

View file

@ -14,7 +14,7 @@
import pro.gravit.launcher.request.Request;
import pro.gravit.launcher.request.RequestException;
import pro.gravit.launcher.request.auth.RestoreSessionRequest;
import pro.gravit.launcher.request.websockets.StandartClientWebSocketService;
import pro.gravit.launcher.request.websockets.StdWebSocketService;
import pro.gravit.utils.helper.*;
import java.io.IOException;
@ -146,7 +146,7 @@ public void start(String... args) throws Throwable {
if (Request.service == null) {
String address = Launcher.getConfig().address;
LogHelper.debug("Start async connection to %s", address);
Request.service = StandartClientWebSocketService.initWebSockets(address, true);
Request.service = StdWebSocketService.initWebSockets(address, true);
Request.service.reconnectCallback = () ->
{
LogHelper.debug("WebSocket connect closed. Try reconnect");

View file

@ -1,24 +0,0 @@
package pro.gravit.launcher.console.admin;
import pro.gravit.launcher.events.request.ExecCommandRequestEvent;
import pro.gravit.launcher.request.admin.ExecCommandRequest;
import pro.gravit.utils.command.Command;
import pro.gravit.utils.helper.LogHelper;
public class ExecCommand extends Command {
@Override
public String getArgsDescription() {
return null;
}
@Override
public String getUsageDescription() {
return null;
}
@Override
public void invoke(String... args) throws Exception {
ExecCommandRequestEvent request = new ExecCommandRequest(String.join(" ", args)).request();
if (!request.success) LogHelper.error("Error executing command");
}
}

View file

@ -1,46 +0,0 @@
package pro.gravit.launcher.console.admin;
import pro.gravit.launcher.LauncherNetworkAPI;
import pro.gravit.launcher.events.request.LogEvent;
import pro.gravit.launcher.request.Request;
import pro.gravit.launcher.request.websockets.WebSocketRequest;
import pro.gravit.utils.command.Command;
import pro.gravit.utils.helper.LogHelper;
public class LogListenerCommand extends Command {
public static class LogListenerRequest implements WebSocketRequest {
@LauncherNetworkAPI
public final LogHelper.OutputTypes outputType;
public LogListenerRequest(LogHelper.OutputTypes outputType) {
this.outputType = outputType;
}
@Override
public String getType() {
return "addLogListener";
}
}
@Override
public String getArgsDescription() {
return null;
}
@Override
public String getUsageDescription() {
return null;
}
@Override
public void invoke(String... args) throws Exception {
LogHelper.info("Send log listener request");
Request.service.sendObject(new LogListenerRequest(LogHelper.JANSI ? LogHelper.OutputTypes.JANSI : LogHelper.OutputTypes.PLAIN));
LogHelper.info("Add log handler");
Request.service.registerHandler((result) -> {
if (result instanceof LogEvent) {
LogHelper.rawLog(() -> ((LogEvent) result).string, () -> ((LogEvent) result).string, () -> ((LogEvent) result).string);
}
});
}
}

View file

@ -2,9 +2,6 @@
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.console.UnlockCommand;
import pro.gravit.launcher.console.admin.ExecCommand;
import pro.gravit.launcher.console.admin.LogListenerCommand;
import pro.gravit.utils.command.BaseCommandCategory;
import pro.gravit.utils.command.CommandHandler;
import pro.gravit.utils.command.JLineCommandHandler;
import pro.gravit.utils.command.StdCommandHandler;
@ -53,10 +50,6 @@ public static boolean checkUnlockKey(String key) {
public static void unlock() {
handler.registerCommand("debug", new DebugCommand());
BaseCommandCategory admin = new BaseCommandCategory();
admin.registerCommand("exec", new ExecCommand());
admin.registerCommand("logListen", new LogListenerCommand());
handler.registerCategory(new CommandHandler.Category(admin, "admin", "Server admin commands"));
isConsoleUnlock = true;
}
}

View file

@ -1,14 +1,7 @@
package pro.gravit.launcher.request;
import pro.gravit.launcher.request.websockets.StandartClientWebSocketService;
public final class PingRequest extends Request<WebSocketEvent> {
@Override
protected WebSocketEvent requestDo(StandartClientWebSocketService service) {
return null;
}
@Override
public String getType() {
return null;

View file

@ -2,7 +2,7 @@
import pro.gravit.launcher.Launcher;
import pro.gravit.launcher.LauncherNetworkAPI;
import pro.gravit.launcher.request.websockets.StandartClientWebSocketService;
import pro.gravit.launcher.request.websockets.StdWebSocketService;
import pro.gravit.launcher.request.websockets.WebSocketRequest;
import pro.gravit.utils.helper.SecurityHelper;
@ -13,7 +13,7 @@ public abstract class Request<R extends WebSocketEvent> implements WebSocketRequ
private static long session = SecurityHelper.secureRandom.nextLong();
@LauncherNetworkAPI
public final UUID requestUUID = UUID.randomUUID();
public static StandartClientWebSocketService service;
public static StdWebSocketService service;
public static void setSession(long session) {
Request.session = session;
@ -35,20 +35,20 @@ public R request() throws Exception {
if (!started.compareAndSet(false, true))
throw new IllegalStateException("Request already started");
if (service == null)
service = StandartClientWebSocketService.initWebSockets(Launcher.getConfig().address, false);
service = StdWebSocketService.initWebSockets(Launcher.getConfig().address, false);
return requestDo(service);
}
public R request(StandartClientWebSocketService service) throws Exception {
public R request(StdWebSocketService service) throws Exception {
if (!started.compareAndSet(false, true))
throw new IllegalStateException("Request already started");
return requestDo(service);
}
@SuppressWarnings("unchecked")
protected R requestDo(StandartClientWebSocketService service) throws Exception {
return (R) service.sendRequest(this);
protected R requestDo(StdWebSocketService service) throws Exception {
return service.requestSync(this);
}
}

View file

@ -4,7 +4,7 @@
import pro.gravit.launcher.LauncherNetworkAPI;
import pro.gravit.launcher.events.request.LauncherRequestEvent;
import pro.gravit.launcher.request.Request;
import pro.gravit.launcher.request.websockets.StandartClientWebSocketService;
import pro.gravit.launcher.request.websockets.StdWebSocketService;
import pro.gravit.launcher.request.websockets.WebSocketRequest;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.JVMHelper;
@ -83,8 +83,8 @@ public static void update(LauncherRequestEvent result) throws IOException {
}
@Override
public LauncherRequestEvent requestDo(StandartClientWebSocketService service) throws Exception {
LauncherRequestEvent result = (LauncherRequestEvent) service.sendRequest(this);
public LauncherRequestEvent requestDo(StdWebSocketService service) throws Exception {
LauncherRequestEvent result = super.request(service);
if (result.needUpdate) update(result);
return result;
}

View file

@ -3,9 +3,7 @@
import pro.gravit.launcher.LauncherNetworkAPI;
import pro.gravit.launcher.events.request.UpdateRequestEvent;
import pro.gravit.launcher.request.Request;
import pro.gravit.launcher.request.websockets.StandartClientWebSocketService;
import pro.gravit.launcher.request.websockets.WebSocketRequest;
import pro.gravit.utils.helper.LogHelper;
public final class UpdateRequest extends Request<UpdateRequestEvent> implements WebSocketRequest {
@ -18,14 +16,6 @@ public String getType() {
return "update";
}
@Override
public UpdateRequestEvent requestDo(StandartClientWebSocketService service) throws Exception {
LogHelper.debug("Start update request");
return (UpdateRequestEvent) service.sendRequest(this);
}
// Instance
@LauncherNetworkAPI
private final String dirName;

View file

@ -76,6 +76,18 @@ public void open() throws Exception {
ch = bootstrap.connect(uri.getHost(), port).sync().channel();
webSocketClientHandler.handshakeFuture().sync();
}
public void openAsync(Runnable onConnect) {
//System.out.println("WebSocket Client connecting");
webSocketClientHandler =
new WebSocketClientHandler(
WebSocketClientHandshakerFactory.newHandshaker(
uri, WebSocketVersion.V13, null, false, EmptyHttpHeaders.INSTANCE, 12800000), this);
ChannelFuture future = bootstrap.connect();
future.addListener((e) -> {
ch = future.channel();
webSocketClientHandler.handshakeFuture().addListener((e1) -> onConnect.run());
});
}
public ChannelFuture send(String text) {
LogHelper.dev("Send: %s", text);

View file

@ -22,18 +22,16 @@
import java.net.URISyntaxException;
import java.util.HashSet;
public class ClientWebSocketService extends ClientJSONPoint {
public abstract class ClientWebSocketService extends ClientJSONPoint {
public final Gson gson;
public OnCloseCallback onCloseCallback;
public final Boolean onConnect;
public ReconnectCallback reconnectCallback;
public static final ProviderMap<WebSocketEvent> results = new ProviderMap<>();
public static final ProviderMap<WebSocketRequest> requests = new ProviderMap<>();
private HashSet<EventHandler> handlers;
public ClientWebSocketService(String address) throws SSLException {
super(createURL(address));
handlers = new HashSet<>();
this.gson = Launcher.gsonManager.gson;
this.onConnect = true;
}
@ -56,10 +54,9 @@ private static URI createURL(String address) {
@Override
void onMessage(String message) {
WebSocketEvent result = gson.fromJson(message, WebSocketEvent.class);
for (EventHandler handler : handlers) {
handler.process(result);
}
eventHandle(result);
}
public abstract<T extends WebSocketEvent> void eventHandle(T event);
@Override
void onDisconnect() {
@ -113,10 +110,6 @@ public void registerResults() {
results.register("signal", SignalEvent.class);
}
public void registerHandler(EventHandler eventHandler) {
handlers.add(eventHandler);
}
public void waitIfNotConnected() {
/*if(!isOpen() && !isClosed() && !isClosing())
{
@ -150,6 +143,11 @@ public void sendObject(Object obj, Type type) throws IOException {
@FunctionalInterface
public interface EventHandler {
void process(WebSocketEvent webSocketEvent);
/**
* @param event processing event
* @param <T> event type
* @return false - continue, true - stop
*/
<T extends WebSocketEvent> boolean eventHandle(T event);
}
}

View file

@ -1,151 +0,0 @@
package pro.gravit.launcher.request.websockets;
import pro.gravit.launcher.events.ExceptionEvent;
import pro.gravit.launcher.events.request.ErrorRequestEvent;
import pro.gravit.launcher.request.Request;
import pro.gravit.launcher.request.RequestException;
import pro.gravit.launcher.request.WebSocketEvent;
import pro.gravit.launcher.request.auth.AuthRequest;
import pro.gravit.utils.helper.JVMHelper;
import pro.gravit.utils.helper.LogHelper;
import javax.net.ssl.SSLException;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
public class StandartClientWebSocketService extends ClientWebSocketService {
public final WaitEventHandler waitEventHandler = new WaitEventHandler();
public StandartClientWebSocketService(String address) throws SSLException {
super(address);
AuthRequest.registerProviders();
}
public class RequestFuture implements Future<WebSocketEvent> {
public final WaitEventHandler.ResultEvent event;
public boolean isCanceled = false;
@SuppressWarnings("rawtypes")
public RequestFuture(WebSocketRequest request) throws IOException {
event = new WaitEventHandler.ResultEvent();
event.type = request.getType();
if (request instanceof Request) {
event.uuid = ((Request) request).requestUUID;
}
waitEventHandler.requests.add(event);
sendObject(request);
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
waitEventHandler.requests.remove(event);
isCanceled = true;
return true;
}
@Override
public boolean isCancelled() {
return isCanceled;
}
@Override
public boolean isDone() {
return event.ready;
}
@Override
public WebSocketEvent get() throws InterruptedException, ExecutionException {
if (isCanceled) return null;
synchronized (event) {
while (!event.ready) {
event.wait();
}
}
WebSocketEvent result = event.result;
waitEventHandler.requests.remove(event);
if (event.result.getType().equals("error")) {
ErrorRequestEvent errorRequestEvent = (ErrorRequestEvent) event.result;
throw new ExecutionException(new RequestException(errorRequestEvent.error));
}
if (event.result.getType().equals("exception")) {
ExceptionEvent error = (ExceptionEvent) event.result;
throw new ExecutionException(new RequestException(String.format("LaunchServer fatal error: %s: %s", error.clazz, error.message)));
}
return result;
}
@Override
public WebSocketEvent get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException {
if (isCanceled) return null;
synchronized (event) {
while (!event.ready) {
event.wait(timeout);
}
}
WebSocketEvent result = event.result;
waitEventHandler.requests.remove(event);
if (event.result.getType().equals("error")) {
ErrorRequestEvent errorRequestEvent = (ErrorRequestEvent) event.result;
throw new ExecutionException(new RequestException(errorRequestEvent.error));
}
if (event.result.getType().equals("exception")) {
ExceptionEvent error = (ExceptionEvent) event.result;
throw new ExecutionException(new RequestException(String.format("LaunchServer fatal error: %s: %s", error.clazz, error.message)));
}
return result;
}
}
public WebSocketEvent sendRequest(WebSocketRequest request) throws IOException, InterruptedException {
RequestFuture future = new RequestFuture(request);
WebSocketEvent result;
try {
result = future.get();
} catch (ExecutionException e) {
throw (RequestException) e.getCause();
}
return result;
}
public RequestFuture asyncSendRequest(WebSocketRequest request) throws IOException {
return new RequestFuture(request);
}
public static StandartClientWebSocketService initWebSockets(String address, boolean async) {
StandartClientWebSocketService service;
try {
service = new StandartClientWebSocketService(address);
} catch (SSLException e) {
throw new SecurityException(e);
}
service.registerResults();
service.registerRequests();
service.registerHandler(service.waitEventHandler);
if (!async) {
try {
service.open();
LogHelper.debug("Connect to %s", address);
} catch (Exception e) {
e.printStackTrace();
}
} else {
try {
service.open();
} catch (Exception e) {
e.printStackTrace();
}
}
JVMHelper.RUNTIME.addShutdownHook(new Thread(() -> {
try {
//if(service.isOpen())
// service.closeBlocking();
service.close();
} catch (InterruptedException e) {
LogHelper.error(e);
}
}));
return service;
}
}

View file

@ -0,0 +1,123 @@
package pro.gravit.launcher.request.websockets;
import pro.gravit.launcher.events.ExceptionEvent;
import pro.gravit.launcher.events.RequestEvent;
import pro.gravit.launcher.events.request.ErrorRequestEvent;
import pro.gravit.launcher.request.Request;
import pro.gravit.launcher.request.RequestException;
import pro.gravit.launcher.request.WebSocketEvent;
import pro.gravit.utils.helper.JVMHelper;
import pro.gravit.utils.helper.LogHelper;
import javax.net.ssl.SSLException;
import java.io.IOException;
import java.util.HashSet;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
public class StdWebSocketService extends ClientWebSocketService {
private final ConcurrentHashMap<UUID, CompletableFuture> futureMap = new ConcurrentHashMap<>();
private final HashSet<EventHandler> eventHandlers = new HashSet<>();
public void registerEventHandler(EventHandler handler)
{
eventHandlers.add(handler);
}
public<T extends WebSocketEvent> void processEventHandlers(T event)
{
for(EventHandler handler : eventHandlers)
{
if(handler.eventHandle(event)) return;
}
}
public StdWebSocketService(String address) throws SSLException {
super(address);
}
@SuppressWarnings("unchecked")
public<T extends WebSocketEvent> void eventHandle(T webSocketEvent) {
if(webSocketEvent instanceof RequestEvent)
{
RequestEvent event = (RequestEvent) webSocketEvent;
if(event.requestUUID == null)
{
LogHelper.warning("Request event type %s.requestUUID is null", event.getType() == null ? "null" : event.getType());
return;
}
if(event.requestUUID.equals(RequestEvent.eventUUID))
{
processEventHandlers(webSocketEvent);
return;
}
CompletableFuture future = futureMap.get(event.requestUUID);
if(future != null) {
if (event instanceof ErrorRequestEvent) {
future.completeExceptionally(new RequestException(((ErrorRequestEvent) event).error));
} else if (event instanceof ExceptionEvent) {
future.completeExceptionally(new RequestException(
String.format("LaunchServer internal error: %s %s", ((ExceptionEvent) event).clazz, ((ExceptionEvent) event).message)));
} else
future.complete(event);
futureMap.remove(event.requestUUID);
}
else
{
processEventHandlers(event);
return;
}
}
//
processEventHandlers(webSocketEvent);
}
public<T extends WebSocketEvent> CompletableFuture<T> request(Request<T> request) throws IOException {
CompletableFuture<T> result = new CompletableFuture<T>();
futureMap.put(request.requestUUID, result);
sendObject(request, WebSocketRequest.class);
return result;
}
public<T extends WebSocketEvent> T requestSync(Request<T> request) throws IOException {
try {
return request(request).get();
} catch (InterruptedException e) {
throw new RequestException("Request interrupted");
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if(cause instanceof IOException)
throw (IOException) e.getCause();
else
{
throw new RequestException(cause);
}
}
}
public static StdWebSocketService initWebSockets(String address, boolean async) {
StdWebSocketService service;
try {
service = new StdWebSocketService(address);
} catch (SSLException e) {
throw new SecurityException(e);
}
service.registerResults();
service.registerRequests();
if (!async) {
try {
service.open();
} catch (Exception e) {
LogHelper.error(e);
}
} else {
service.openAsync(() -> {});
}
JVMHelper.RUNTIME.addShutdownHook(new Thread(() -> {
try {
//if(service.isOpen())
// service.closeBlocking();
service.close();
} catch (InterruptedException e) {
LogHelper.error(e);
}
}));
return service;
}
}

View file

@ -1,44 +0,0 @@
package pro.gravit.launcher.request.websockets;
import pro.gravit.launcher.events.RequestEvent;
import pro.gravit.launcher.request.WebSocketEvent;
import pro.gravit.utils.helper.LogHelper;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class WaitEventHandler implements ClientWebSocketService.EventHandler {
public final Set<ResultEvent> requests = ConcurrentHashMap.newKeySet();
@Override
public void process(WebSocketEvent result) {
LogHelper.debug("Processing event %s type", result.getType());
UUID checkUUID = null;
if (result instanceof RequestEvent) {
RequestEvent event = (RequestEvent) result;
checkUUID = event.requestUUID;
if (checkUUID != null)
LogHelper.debug("Event UUID: %s found", checkUUID.toString());
}
for (ResultEvent r : requests) {
if (r.uuid != null)
LogHelper.debug("Request UUID found: %s", r.uuid.toString());
if ((r.uuid != null && r.uuid.equals(checkUUID)) || (checkUUID == null && (r.type.equals(result.getType()) || result.getType().equals("error")))) {
LogHelper.debug("Event %s type", r.type);
synchronized (r) {
r.result = result;
r.ready = true;
r.notifyAll();
}
}
}
}
public static class ResultEvent {
public WebSocketEvent result;
public UUID uuid;
public String type;
public boolean ready;
}
}

View file

@ -6,7 +6,6 @@
include 'LauncherAuthlib'
include 'ServerWrapper'
include 'LaunchServer'
include 'LaunchServerConsole'
include 'modules'
file('modules').eachDir { sub ->
if (sub.name.endsWith('_module') || sub.name.endsWith('_swmodule') || sub.name.endsWith('_lmodule')) include 'modules:' + sub.name