WebSocket консоль сервера#
Global 3 SE Server предоставляет консоль управления, доступную через WebSocket-соединение. С помощью команд, передаваемых через WebSocket, возможно:
Перезагружать код прикладных приложений.
Выполнять Jexl-скрипты.
Алгоритм работы с консолью#
Открытие WebSocket-соединения.
Отправка команды аутентификации пользователя в системе.
Отправка исполняемых команд.
Закрытие WebSocket-соединения.
Открытие WebSocket-соединения#
Для открытия WebSocket-соединения с консолью сервера необходимо выполнить HTTP-запрос по адресу:
ws[s]://{server[:port]}/app/sys/ws/console.
Формат команды#
Командой является строка в формате:
{command}[\n{arguments}]
где:
{command}— строка команды. Может состоять из одного или нескольких слов.\n— символ новой строки #10. Является разделителем команды и её аргументов. Не обязателен, если у команды нет аргументов.{arguments}— строка аргументов команды. Может содержать любые символы, включая переносы строк.
Формат результата выполнения команды#
Результатом выполнения команды является строка в формате JSON:
{
"success": true,
"data": null,
"exception": null,
"exceptionStack": null
}
где:
success— флаг успешности выполнения команды:true|false.data— результат выполнения команды:null|"string"|JSON.exception— сообщение возникшего исключения:null|"string".exceptionStack— стек возникшего исключения:null|"string".
Список команд#
login— выполняет аутентификацию пользователя{user}в базе данных{database}и запускает рабочий сеанс пользователя.
Использование командыlogin, в качестве способа аутентификации, было выбрано по причине невозможности использования HTTP-заголовков некоторыми WebSocket-клиентами. Например: JMeter WebSocket Load Testing Sampler.{user}/{password}@{database}
logout— закрывает рабочий сеанс пользователя.reload sbt— выполняет перезагрузку прикладного кода текущего решения.reload sbt force— выполняет перезагрузку инфраструктуры EclipseLink, прикладного кода и общих библиотек текущего решения.jexl— любое значение, возвращённое из Jexl-скрипта, будет преобразовано в строку.// Произвольный текст Jexl-скрипта return 1 + 2;
Java пример взаимодествия с консолью#
Библиотеки, используемые в примере клиента:
«org.asynchttpclient» % «async-http-client» % «2.12.3».
«com.fasterxml.jackson.core» % «jackson-databind» % «2.8.9».
package ru.bitec.app.examples.ws.console;
import org.asynchttpclient.AsyncHttpClient;
import org.asynchttpclient.Dsl;
import org.asynchttpclient.ws.WebSocket;
import org.asynchttpclient.ws.WebSocketListener;
import org.asynchttpclient.ws.WebSocketUpgradeHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.websocket.CloseReason;
import java.io.Serializable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class WsConsoleClientTest {
public static void main(String[] args) throws Exception {
try (WsConsoleClient client = WsConsoleClient.open("ws://localhost:8080/app/sys/ws/console")) {
client.login("admin", "admin", "pgtest");
System.out.println("1 + 2 = " + client.evaluateJexl("return 1 + 2;"));
client.reloadSbt(false);
client.logout();
}
}
public class WsConsoleClient implements AutoCloseable {
public static WsConsoleClient open(String url) throws Exception {
return open(url, 3000);
}
public static WsConsoleClient open(String url, int timeout) throws Exception {
return new WsConsoleClient().connect(url, timeout);
}
private final ObjectMapper objectMapper_ = new ObjectMapper();
private int commandTimeout_ = 60000;
private AsyncHttpClient asyncHttpClient_;
private WebSocket webSocketClient_;
private volatile CompletableFuture<String> webSocketResultFuture_;
private final WebSocketUpgradeHandler wsHandler = new WebSocketUpgradeHandler.Builder().addWebSocketListener(new WebSocketListener() {
@Override
public void onOpen(WebSocket websocket) {
// WebSocket connection opened
}
@Override
public void onClose(WebSocket websocket, int code, String reason) {
if (code != CloseReason.CloseCodes.NORMAL_CLOSURE.getCode()) {
webSocketResultFuture_.completeExceptionally(new Exception(reason));
}
}
@Override
public void onError(Throwable t) {
webSocketResultFuture_.completeExceptionally(t);
}
@Override
public void onTextFrame(String payload, boolean finalFragment, int rsv) {
webSocketResultFuture_.complete(payload);
}
}).build();
public WsConsoleClient connect(String url, int timeout) throws Exception {
AsyncHttpClient syncHttpClient = Dsl.asyncHttpClient();
try {
webSocketClient_ = syncHttpClient
.prepareGet(url)
.setRequestTimeout(timeout)
.execute(wsHandler)
.get();
} catch (Exception e) {
syncHttpClient.close();
throw e;
}
asyncHttpClient_ = syncHttpClient;
return this;
}
public void close() throws Exception {
if (asyncHttpClient_ != null) {
try {
if (webSocketClient_ != null && webSocketClient_.isOpen()) {
try {
webSocketClient_.sendCloseFrame().get();
} finally {
webSocketClient_ = null;
}
}
} finally {
asyncHttpClient_.close();
}
}
}
public int getCommandTimeout() {
return commandTimeout_;
}
public void setCommandTimeout(int commandTimeout) {
this.commandTimeout_ = commandTimeout;
}
public void login(String user, String password, String database) throws Exception {
sendCommand("login", String.format("%s/%s@%s", user, password, database));
}
public void logout() throws Exception {
sendCommand("logout");
}
public String evaluateJexl(String script) throws Exception {
return sendCommand("jexl", script);
}
public void reloadSbt(boolean force) throws Exception {
sendCommand("reload sbt" + (force ? " force" : ""));
}
String sendCommand(String command) throws Exception {
return sendCommand(command, null);
}
String sendCommand(String command, String args) throws Exception {
validateConnection();
if (command == null || command.trim().isEmpty()) {
throw new Exception("Command can not be null or empty.");
}
webSocketResultFuture_ = new CompletableFuture<>();
try {
String payload = command + "\n" + (args != null ? args : "");
webSocketClient_.sendTextFrame(payload).get();
} catch (Exception e) {
webSocketResultFuture_.completeExceptionally(e);
}
return parseResponse(webSocketResultFuture_.get(commandTimeout_, TimeUnit.MILLISECONDS));
}
private String parseResponse(String response) throws Exception {
ConsoleResponse consoleResponse = objectMapper_.readValue(response, ConsoleResponse.class);
if (consoleResponse.success) {
return consoleResponse.data;
} else {
throw new Exception(consoleResponse.exception);
}
}
private void validateConnection() throws Exception {
if (webSocketClient_ == null) {
throw new Exception("WebSocket connection is not connected.");
}
if (!webSocketClient_.isOpen()) {
throw new Exception("WebSocket connection is closed");
}
CompletableFuture<String> webSocketResultFuture = webSocketResultFuture_;
if (webSocketResultFuture != null && !webSocketResultFuture.isDone()) {
throw new Exception("Prior console command is not completed. Wait for the result of the previous command.");
}
}
private final static class ConsoleResponse implements Serializable {
private static final long serialVersionUID = -5577579081118070434L;
/**
* Флаг успешного выполнения запроса. false, если при обработке запроса возникло исключение.
*/
private boolean success = false;
/**
* Строковые данные
*/
private String data;
/**
* Текст возникшего исключения
*/
private String exception;
/**
* Стек возникшего исключения
*/
private String exceptionStack;
public boolean getSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public String getException() {
return exception;
}
public void setException(String exception) {
this.exception = exception;
}
public String getExceptionStack() {
return exceptionStack;
}
public void setExceptionStack(String exceptionStack) {
this.exceptionStack = exceptionStack;
}
}
}
}