5.8.5. WebSocket консоль¶
WebSocket-консоль позволяет выполнять административные команды и Jexl-скрипты в пользовательской сессии.
5.8.5.1. Подключение и аутентификация¶
Откройте WebSocket-соединение по адресу:
ws[s]://{server[:port]}/app/sys/ws/console
После подключения последовательно отправьте команду login, исполняемые
команды и logout. Аргументы команды передаются после перевода строки:
{command}\\n{arguments}.
login
{user}/{password}@{database}
5.8.5.2. Допустимые команды¶
loginАутентифицирует пользователя и открывает рабочий сеанс. Аргумент передаётся в формате{user}/{password}@{database}.logoutЗакрывает рабочий сеанс и завершает аутентификацию.jexlВыполняет переданный в аргументе Jexl-скрипт в открытом рабочем сеансе. Результат возвращается в полеdataответа.reload sbtВыполняет частичную перезагрузку образа прикладного решения.reload sbt forceВыполняет полную перезагрузку образа прикладного решения.reload persistenceПерезагружает описания JPA-сущностей текущего решения.
5.8.5.3. Выполнение Jexl¶
Передайте текст скрипта аргументом команды jexl. Возвращаемое скриптом
значение преобразуется в строку и передаётся в поле data ответа.
jexl
return 1 + 2;
See also
5.8.5.3.1. Результат выполнения Jexl¶
Результат каждой команды — JSON-объект:
{
"success": true,
"data": "3",
"exception": null,
"exceptionStack": null
}
5.8.5.4. Java-пример взаимодействия с консолью¶
Пример открывает WebSocket-соединение, аутентифицирует пользователя, выполняет Jexl-скрипт, перезагружает прикладной код и закрывает сеанс.
package ru.bitec.webserver.services.ws;
public class WsConsoleClientMain {
public static void main(String[] args) throws Exception {
try (WsConsoleClient client = WsConsoleClient.open("ws://localhost:8080/app/sys/ws/console")) {
client.login("admin", "admin", "gstest");
System.out.println("1 + 2 = " + client.evaluateJexl("return 1 + 2;"));
client.reloadSbt(false);
client.logout();
}
}
}
Класс WsConsoleClient формирует команды, ожидает ответ и преобразует JSON-
результат в строку:
package ru.bitec.webserver.services.ws;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.util.concurrent.Future;
import org.asynchttpclient.AsyncHttpClient;
import org.asynchttpclient.DefaultAsyncHttpClientConfig;
import org.asynchttpclient.Dsl;
import org.asynchttpclient.ws.WebSocket;
import org.asynchttpclient.ws.WebSocketListener;
import org.asynchttpclient.ws.WebSocketUpgradeHandler;
import javax.websocket.CloseReason;
import java.io.Serializable;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
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) {
}
@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(
new DefaultAsyncHttpClientConfig.Builder()
.setWebSocketMaxFrameSize(1024 * 1024)
.build());
try {
webSocketClient_ = syncHttpClient
.prepareGet(url)
.setRequestTimeout(Duration.ofMillis(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 CompletableFuture<String> evaluateJexlAsync(String script) throws Exception {
return sendCommandAsync("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 {
return parseResponse(sendCommandAsync(command, args).get(commandTimeout_, TimeUnit.MILLISECONDS));
}
CompletableFuture<String> sendCommandAsync(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 : "");
rawSendTextFrameAsync(payload).get();
} catch (Exception e) {
webSocketResultFuture_.completeExceptionally(e);
}
return webSocketResultFuture_;
}
Future<Void> rawSendTextFrameAsync(String payload) {
return webSocketClient_.sendTextFrame(payload);
}
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;
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;
}
}
}