# Сервисы сервера приложений Сервисы реализованы в опциональном модуле sys, который входит в дистрибутив Global Server Scala Edition. Так же модуль может быть подключен в конфигурационном файле сервера `global3.config.xml`: ~~~xml ~~~ Ленивая загрузка должна быть отключена, для того чтобы сервисы могли всегда принимать запросы. Более подробную информацию смотрите в руководстве по администрированию сервера. ## SSH консоль сервера ### Введение Global 3 SE Server включает в себя SSH (Secure Shell) сервер, к которому возможно подключиться с помощью любого SSH-терминала. С помощью команд командной строки возможно: - Смотреть статистику, логи, и управлять клиентскими сессиями - Перезагружать код прикладных приложений - Перезапускать Web-приложения - Выполнять SQL и Jexl-скрипты ### Конфигурирование Конфигурационный файл global3.config.xml может содержать секцию ``: ```xml ``` - `defaultDb` - Алиас базы данных по умолчанию \ Данное значение используется в качестве значения параметра по умолчанию для ssh-команды `login`. По имени базы будет определены имя и пароль пользователя, для подключения к базе. - `port` – Порт ssh-сервера \ По умолчанию: 22. \ Данное значение может быть jvm опцией ``` -Dglobal.ssh.port=xxxx ``` или параметрами командной строки при запуске Global3 сервера ``` -global.ssh.port xxxx ``` Учётные данные пользователей, имеющих права на ssh-сединения, указываются в секции `` конфигурационного файл global3.config.xml: ```xml ``` - `user` – Пользователь - `password` | `encryptedPassword` – Пароль \ Пароль может быть указан как в явном, так и в зашифрованном виде. - `roles` – Роли \ Список ролей, через запятую, доступных пользователю. #### Шифрование пароля Для шифрования пароля необходимо запустить Global 3 Server c парамерами ``` Start.bat -encryptPassword password -masterPassword masterpassword ``` Где: - `-encryptPassword` \ Пароль который необходимо зашифровать - `-masterPassword` \ Ключ шифрования, если не указан используется секретный ключ по умолчанию. Результатом выполнения будет вывод в консоль строки, полученной в результате шифрования пароля переданного параметром `-encryptPassword`. ### Подключение Подключиться к SSH-серверу возможно любым SSH-терминалом. Рекомендуемым клиентом для подключения является [PuTTY](https://www.putty.org). [Документация по putty](https://putty.org.ru/htmldoc/index.html) #### Основные параметры Для подключения необходимо указать: - *Host Name* \ host или user@host - *Port* \ 22 или пользовательский - *Connection type* \ SSH Что бы не вводить значения полей при повторном запуске PuTTY, можно сохранить параметры подключения по умолчанию, нажав кнопку "Save". Пример запуска из командной строки: ``` "C:\Program Files\PuTTY\putty.exe" -ssh localhost -P 22 -l admin -pw admin ``` #### Логирование Для сохранения всего текстового вывода в файл, на вкладке [Logging](https://putty.org.ru/htmldoc/chapter4.html#config-logging) диалога подключения необходимо указать: - *Session Logging* = `All session output` - *Log file name* = `полный путь к файлу` ```{attention} В адресах пути: - Символ `\` необходимо экранировать удвоением \ Пример: `\\` - Символ `/` экранировать не надо ``` #### Подключение с использованием SSH-RSA ключа При подключении к SSH из `*.bat` иил `*.sh` можно воспользоваться RSA-ключами вместо пароля. ``` "C:\Program Files\PuTTY\putty.exe" -ssh localhost -P 22 -l admin -i ssh_admin_private.ppk ``` Необходимые файлы размещены в подкаталоге `.\ssh`. Ключи для подключения к пользователю `admin` выдаются по запросу. #### Запуск терминала При первом подключении к серверу будет выдано сообщение с запрашивающие разрешение на подключение к серверу, нажмите: Да. ### Команды Для получения актуальной справки по доступным командам, выполните команду `help`. Список команд: - `alter server mode service|normal` \ Переключает сервер в сервисный режим и обратно. В сервисном режиме возможно подключение пользовательских сессий только от имени системного пользователя сервера приложений, указанного в конфигурационном файле. ```xml ``` - `attach session {sid}` \ Подключение к существующей пользовательской сессии. - `attach db {dbAlias} [as sys]` \ Подключение к базе данных для выполнения сервисных операций и/или Jexl-скриптов. - `сlear` \ Очищает экран терминала. - `clear persistence cache [{dbAlias}]` \ Очищает Shared-кэш объектов. - `compare applib {path}` \ Сравнивает jar-файлы в каталоге (или zip-архиве) `{path}` с jar-файлами в каталоге `SBT.jarFolder`, соответствующему базе данных, к которой выполнено подключение командой `attach db`. Выполняется сравнение версий модулей. \ Выполняется сравнение *.odm.xml файлов на предмет существования новых атрибутов - `copy applib [force] {path}` \ Выполняет копирование jar-файлов из каталога `{path}` в каталог `SBT.jarFolder`, соответствующий базе данных, к которой выполнено подключение командой `attach db`. При указании ключевого слова `force` перед копированием не выполняется сравнения `*.odm.xml` файлов. Сравнение версий модулей выполняется в любом случаи. - `Init schema` \ Выполняет создание/обновление объектов схемы БД - `execute {expression}` \ Выполняет однострочное Jexl-выражение - `exit` \ Закрывает SSH-подключение - `jexl [{file}]` \ Переключает терминал в режим ввода и выполнения ### Выполнение SQL Для выполнения sql-скрипта: 1. Подключитесь к сессии командами `login`, `attach` или `set sid`. 2. Перейдите в режим ввода скрипта командой `sql`. 3. Введите текст скрипта. 4. Выполните скрипт \ Для этого введите символ `/` с новой строки. 5. Выйдите из режима ввода скрипта \ Для этого введите символ `/` с новой строки. Пример: ``` login admin/admin@postgres sql INSERT INTO gs3_roottest ( id, idClass ) VALUES ( (select nextval('GS3_ROOTTEST_SEQ')) , 12351 ) ON CONFLICT DO NOTHING; / / ``` #### Выполнение SQL скрипта из файла Для выполнения sql-скрипта из файла: 1. Подключитесь к сессии. 2. Выполните команду `sql {file}`. \ Где `{file}` – путь к файлу к файлу на сервере. ```{attention} Файл должен находится на локальном диске сервера. ``` Пример: ``` login admin/admin@postgres sql D:\\svn\\depot\\ASSource\\sysapplication\\ssh\\src\\test\\java\\ru\\bitec\\app\\ssh\\shh_sql_exams.txt ``` Содержимое файла: ```sql INSERT INTO gs3_roottest (id, idClass) VALUES ((select nextval('GS3_ROOTTEST_SEQ')), 12351) ON CONFLICT DO NOTHING; INSERT INTO gs3_roottest (id, idClass) VALUES ((select nextval('GS3_ROOTTEST_SEQ')), 12351) ON CONFLICT DO NOTHING; / INSERT INTO gs3_roottest (id, idClass) VALUES ((select nextval('GS3_ROOTTEST_SEQ')), 12351) ON CONFLICT DO NOTHING; INSERT INTO gs3_roottest (id, idClass) VALUES ((select nextval('GS3_ROOTTEST_SEQ')), 12351) ON CONFLICT DO NOTHING; / / ``` ### Jexl скрипты #### Выполнение Jexl скрипта Для выполнения Jexl-скрипта необходимо: 1. Подключитесь к сессии 2. Переключитесь в режим ввода скрипта командой `jexl`. 3. Введите текст скрипта. 4. Выполните скрипт \ Для этого введите символ `/` с новой строки. 5. Выйдите из режима ввода \ Для этого введите символ `/` с новой строки. Пример 1: ``` login admin/admin@postgres jexl var name = Btk_ClassApi.getCanonicalClassName("Btk_Object"); Btk_ClassApi.getApiByCanonicalClassName(name); / / ``` Пример 2 (для dataInstall): ``` login admin/admin@postgres jexl Bbb_DBTypeApi.dataInstall(); Btk_Pkg.commit(); / / ``` Пример 3: ``` login admin/admin@postgres jexl Btk_Pkg.setRWSharedUOWEditType(); Prs_EntTransApi.dataInstall(); Btk_Pkg.commit(); / / ``` #### Выполнение Jexl скрипта из файла Для выполнения Jexl-скрипта из файла необходимо: 1. Подключитесь к сессии 2. Выполнить команду jexl `{file}` \ Где `{file}` – путь к файлу к файлу на сервере. ```{attention} Файл должен находится на локальном диске сервера. ``` Пример: ``` login jexl D:\\svn\\depot\\ASSource\\sysapplication\\ssh\\src\\test\\java\\ru\\bitec\\app\\ssh\\shh_jexl_exams.txt ``` Содержимое файла: ``` var name = Btk_ClassApi.getCanonicalClassName("Btk_Object"); Btk_ClassApi.getApiByCanonicalClassName(name); / / ``` #### Контекст выполнения Jexl скрипта Возможны следующие контексты выполнения Jexl-скрипта. ##### Пользовательская сессия ``` >login user/password@alias >jexl jexl> / / > ``` В данном контексте доступны все Api-классы, присутствующие в SBT, соответсвующего пользовательской сессии. ##### База данных ``` >attach db alias >jexl jexldb> / / > ``` Доступны методы управления инстансом базы данных: - `initschema()` \ Выполняет инициализацию/обновление схемы БД в соответствии с текущей прикладной кодовой базой. ##### Системный контекст ``` >attach db alias as sys >jexl jexlsys> / / > ``` Данный контекст является административным и предназначен для управления сервером приложений. Доступны следующие методы: - `upgrade({release_path})` \ Выполняет инициализацию/обновление схемы БД в соответствии с текущей прикладной кодовой базой.\ Где: \ `{release_path}` – путь к каталогу или zip-архиву с релизом прикладных модулей ### Выполнение командного файла ssh Для выполнения командного файла из командной строки можно выполнить: ``` "C:\Program Files\PuTTY\putty.exe" -ssh localhost -P 22 -l admin -pw admin -m shh_logger_test_script.txt ``` ```{attention} В данном примере файл shh_logger_test_script.txt размещён на диске клиентской машины, он считывается в момент подключения к SSH-серверу. Пути к файлам в скрипте, являются локальными для сервера. ``` Содержимое файла: ``` login log-info app el execute Btk_ClassApi.getCanonicalClassName("Btk_Object") jexl var name = Btk_ClassApi.getCanonicalClassName("Btk_Object"); Btk_ClassApi.getApiByCanonicalClassName(name); / / jexl D:/svn/depot/ASSource/sysapplication/ssh/src/test/java/ru/bitec/app/ssh/shh_jexl_exams.txt jexl D:\\svn\\depot\\ASSource\\sysapplication\\ssh\\src\\test\\java\\ru\\bitec\\app\\ssh\\shh_jexl_exams.txt sql INSERT INTO gs3_roottest (id, idClass) VALUES ((select nextval('GS3_ROOTTEST_SEQ')), 12351) ON CONFLICT DO NOTHING; / / sql D:\\svn\\depot\\ASSource\\sysapplication\\ssh\\src\\test\\java\\ru\\bitec\\app\\ssh\\shh_sql_exams.txt log-off all logout ``` #### Логирование При выполнении скрипта, по умолчанию, в ssh-консоль выводится результат выполнения команды или сообщение об ошибке со стеком вызова. Для вывода в ssh-консоль дополнительных логов, необходимо их включить. В консоль можно вывести логи следующих типов: - `oper` \ Логи из классов с namespace `ru.bitec.engine.model.operation`. - `sql` \ Логи sql-вызовов с уровня jdbc-соединения с базой. - `script` \ Логи из скриптового языка в режиме совместимости с Global 1. При работе со Scala не имеют смысла. - `app` \ Логи из классов прикладной логики с namespace `ru.bitec.app.*`.\ Пример отправки сообщения: ```scala Logger.Factory.get(Xxx_XxxApi.class).info("Текст сообщения") ``` - `el` \ Логи из инфраструктуры EclipseLink. В основном, это sql-вызовы, в более компактном виде, чем логи jdbc. - `all` \ Все выше перечисленные типы логов. Для переключения уровней логирования используются команды: - `log-off` - `log-error` - `log-warn` - `log-info` - `log-debug` - `log-trace` Команды указаны в порядке уменьшения уровня логов. В результате выполнения этой команды: ``` log-info app el ``` в ssh-лог будут попадать сообщения типов `app` и `el` для уровней логирования: `error`, `warn`, `info`. Для отключения вывода сообщений в ssh-лог необходимо вызвать команду: ``` log-off all ``` ##### Перенаправление ssh-лога Putty в файл При выполнении скриптов из командной строки, часто необходимо перенаправить вывод в файл. Для ssh-консоли Putty, это выполняется передачей параметра `-sessionlog {имя_файла.txt}` ``` "C:\Program Files\PuTTY\putty.exe" -ssh localhost -P 22 -l admin -pw admin -m shh_logger_test_script.txt -sessionlog sessionlog.txt ``` ### FAQ #### Зависает ssh-терминал на этапе подключения к серверу. При запуске Putty из скрипта при первом подключении к серверу выдается сообщение PuTTY Security Alert, что может приводить к зависанию скрипта. Необходимо дополнительно передать подтверждение при запуске: ``` echo yes | %plink% -ssh localhost -P 2222 -l admin -i ssh_admin_private.ppk ``` ```{note} Plink также входит в состав дистрибутива PuTTY. ``` ## 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: ```json { "success":true, "data": null, "exception":null, "exceptionStack":null } ``` где: `success` - Флаг успешности выполнения команды: `true|false` `data` - Результат выполненения команды: `null|"string"|JSON` `exception` - Сообщение возникшего исключения: `null|"string"` `exceptionStack` - Стек возникшего исключения: `null|"string"` ### Список команд - `login`\n `{user}/{password}@{database}` Выполняет аутентификацию пользователя `user` в базе данных `database` и запускает рабочий сеанс пользователя. *Использование команды `login`, в качестве способа уатентификации, было выбрано по причине невозможности использования http-заголовков некоторыми WebSocket-клиентами. Например: [JMeter WebSocket Load Testing Sampler](https://www.blazemeter.com/blog/jmeter-websocket-samplers).* - `logout` Закрывает рабочий сеанс пользователя. - `reload sbt` Выполняет перезагрузку прикладного кода текущего решения. - `reload sbt force` Выполняет перезагрузку инфраструктуры EclipseLink, прикладного кода и общих библиотек текущего решения. - `jexl`\n `// Произвольный текст Jexl-скрипта` `return 1 + 2;` *Любое значение, возвращённое из Jexl-скрипта, будет преобразовано в строку.* ### Java пример взаимодествия с консолью Библиотеки, используемые в примере клиента: - "org.asynchttpclient" % "async-http-client" % "2.12.3" - "com.fasterxml.jackson.core" % "jackson-databind" % "2.8.9" ``` java 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 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 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; } } } } ``` ## Jexl через SOAP XML ### Введение В Global 3 SE Server реализован SOAP XML сервис, позволяющий выполнять Jexl-скрипт в контексте пользовательской сессии. ### Аутентификация Читайте в разделе [Аутентификация в REST/SOAP-сервисах](#аутентификация-в-restsoap-сервисах). ### Схемы WSDL описание сервиса доступно по адресу: ``` http://{server:port}/app/sys/soap/sys-service-1.0.0?wsdl ``` XSD схемы POJO запроса и ответа доступны по адресу: ``` http://{server:port}/app/sys/soap/sys-service-1.0.0?xsd=1 ``` ### Передача бинарных данных с использованием MTOM Сервис поддерживает оптимизированную передачу бинарных данных в сервис и обратно, через поля attachment объектов `JexlSoapRequest` и `JexlSoapResponse`. ### Доступ к данным SOAP-запроса из прикладного кода В прикладном коде Api-/Pkg-классов, выполняющемся в результате вызова Jexl-скрипта из SOAP-сервиса, доступен объект `SoapContext`. `SoapContext` предоставляет доступ к данным SOAP-запроса и SOAP-ответа. Для получения контекста, выполните: ```scala val soapContextOpt = SoapContext() ``` Методы доступные в `SoapContext`: - `hasInputAttachment: Boolean` \ Указывает на наличие у SOAP-запроса прикреплённых с использованием MTOM бинарных данных - `getInputData: String` \ Возвращает строковые данные SOAP-запроса. - `forInputStream(foo: InputStream => Unit): Unit` \ Предоставляет доступ к прикреплённым бинарным данным - `setOutputData(data: String): Unit` \ Устанавливает строковые данные в SOAP-ответ. - `forOutputStream(foo: OutputStream => Unit): Unit` \ Предоставляет доступ к выходному потоку бинарных данных передаваемых средствами MTOM ### Методы обработки данных SOAP-запроса из Jexl-скрипта В пакете `Gtk_SoapPkg` хранятся методы, использующие `SoapContext`: - `setOutputData(value: AnyRef): Unit` \ Установливает вывод результата выполнения soap-запроса. - `inputStreamToTemp: File` \ Создает временный файл с данными из прикреплённых данных soap-запроса - `attachFile(file: File): Unit` \ Принимает файл, закачаивает его данные в выходной поток бинарных данных soap-запроса Пример ипользования из Jexl-скрипта: ``` var f = new("java.io.File", 'C:\users\userName\data.txt'); Gtk_SoapPkg.setOutputData('Данные в прикрепленном файле'); Gtk_SoapPkg.attachFile(f); ``` Данный скрипт, отправленный через SOAP-запрос, в ответ запишет - в data - строку 'Данные в прикрепленном файле' - в attachment - закодированный base64 файл ## REST сервис c обработкой http-запроса в прикладном пакете ### Введение В Global 3 SE Server реализован REST-сервис, позволяющий выполнять обработку HTTP-запроса в прикладном пакете, в контексте прикладной сессии. Возможны 2 режима обработки запросов: - Exclusive Session - с сохранением состояния между запросами. Rest- и Gtk-сессии создаются при первом обращении клинета к сервису и закрываются по таймауту или при явном указании на необходимость закрытия сессий по завершению запроса. - Shared Session - без сохранения состояния между запросами. Каждый запрос обрабатывается в новых Rest- и Gtk-сессиях. Доступно с AS 1.14 RC7. Пакет должен быть унаследован от одного из трейтов `RestPkg`, `RestESPkg`, `RestSSPkg`. ```scala class Xxx_XxxPkg extends Pkg with RestPkg { } object Xxx_XxxPkg extends PkgFactory[Xxx_XxxPkg]{ } ``` ### Аутентификация Читайте в разделе [Аутентификация в REST/SOAP-сервисах](#аутентификация-в-restsoap-сервисах). ### Методы `RestPkg` / `RestESPkg` / `RestSSPkg` - `get(relativePath: String): AnyRef` \ Вызывается при поступлении GET запроса. \ Допускается возврат: `null`, `None`, `String`, `ResponseBuilder`. - `post(relativePath: String): AnyRef` \ Вызывается при поступлении POST запроса. \ Допускается возврат: `null`, `None`, `String`, `ResponseBuilder`. - `onActivate()` \ Вызывается при подключении новой gtk-сессии - `onDeactivate()` \ Вызывается при закрытии gtk-сессии - `beforeReload(keyBundle: KeyBundle)` \ Вызывается перед перезагрузкой SBT - `afterReload(keyBundle: KeyBundle)` \ Вызывается после перезагрузки SBT и пересоздании gtk-сессии - `isSupportsSharedSession(): Boolean `\ Метод указывает, что пакет может обрабатывать запросы в режиме разделяемой GTK-сессии. - `isSupportsExclusiveSession(): Boolean` \ Метод указывает, что пакет может обрабатывать запросы в режиме эксклюзивной GTK-сессии. #### Допустимые результаты методов get() и post() От типа возвращённого значения будет зависеть способ формирования ответа на Http-запрос. - `null`,`None` \ Ответ будет сформирован через `RestfulContext().get.responseBuilder` - `String` \ В ответ будет записано возвращённая строка - `ResponseBuilder` \ Ответ будет сформирован через возвращенный `ResponseBuilder`. Он может быть равен `RestfulContext().get.responseBuilder`, либо сформирован произвольным образом. - `Response` \ Ответом будет возвращенное значение. ### Адреса Доступно с AS 1.14 RC7: - `http://{server:port}/app/sys/rest/es/pkg/{Xxx_XxxxPkg}/{relativePath}` Доступно с AS 1.14 RC7: - `http://{server:port}/app/sys/rest/ss/pkg/{Xxx_XxxxPkg}/{relativePath}` где: - `{Xxx_XxxxPkg}` \ Имя прикладного пакета, унаследованного от RestfulPkg - `{relativePath}` \ Произвольный путь, который будет передан в методы get/post прикладного пакета - `http://{server:port} ` \ Адрес подключения к серверу - `sys` \ Системный прикладной модуль, в будущем будет app/sys - `rest` \ Шлюз для rest запросов - `es` / `ss`\ Группировка по времени жизни gtk-сессии (Exclusive Session) / (Shared Session) - `pkg` Узел для доступа к пакетам. ### Рабочее пространство `Workspace` - Рабочее пространство. Используется для возможности параллельной работы нескольких gtk сессий в рамках одного пользователя в es режиме. Для предотвращения неконтролируемого разрастания сессий, количество сессий на пользователя в эксклюзивном режиме ограничено. На одного пользователя и один workspace может существовать только одна сессия. Рабочее пространство задается в http заголовке: - `Workspace` \ Имя рабочего пространства пользователя. ### Exclusive Session #### Принцип работы сервиса 1. Получение HTTP-запроса (GET или POST) 2. Проверка авторизационных данных пользователя. 3. Получение, захват существующего рабочего сеанса или создание нового. 4. Вызов метода прикладного пакета `get(…)` или `post(…)`, соответственно \ В данном методе производится формирование тела http-ответа 5. Отправка ответа #### Жизненный цикл http-сессий При обращении к rest - сервису. 1. Если cookie `JSESSIONID` не задан, создается новая http сессия 2. Иначе, используется сессия с переданным идентификатором \ Если переданный id не корректный создается новая http сессия 3. Обработка запроса 4. Возврат результата \ При этом cookie `JSESSIONID` будет содержать идентификатор http сессии Время жизни http-сессии 15 минут. При отсутствии обращений к http-сессии в течении этого интервала, сессия уничтожается, и установленные в её атрибуты значения становятся не доступны. #### Жизненный цикл gtk-сессий Gtk-сессия существует в разрезе 4-х параметров: 1. Алиас база данных \ Получается из http-заголовка `Database`. Если заголовок не передан, используется алиас из конфигурационного файла сервера. 2. Имя пользователя \ Получается из авторизационных данных. Передаётся в http-заголовке `Authorization` 3. Имя прикладного пакета \ Получается из строки адреса 4. Рабочее пространство \ Получается из http-заголовка `Workspace`. Если не задано, используется `Default` Одной http-сессии (одному `JSESSIONID`) может соответствовать только одна gtk-сессия. При обращении к другому пакету из одной http-сессии, предыдущая gtk-сессия будет деактивирована и закрыта. Таймаут gtk-сессии по умолчанию – 15 минут. Не используемая gtk-сессия будет закрыта через 15 минут. Изменить таймаут или закрыть сессию по завершению обработки запроса возможно через установку свойств `RestEXContext()`. ##### Создание новой сессии Происходит в случае, если: - в запрос не передан куки http сессии - или gtk сессия не существует в уникальном разрезе - Имя пользователя - База данных - Workspace - Пакет При этом: - Создается gtk сессия - Вызываются методы прикладного пакета - `onActivate` - в соответствии с типом http запроса вызывается один из методов обработки запроса - `get` - `post` - В ответ добавляется кука http сессии ##### Работа в текущий сессии Происходит в случае, если: - в запрос передан куки http сессии - и gtk сессия существует в уникальном разрезе, и ее куки совпадает с переданным запросом ##### Таймаут сессии Происходит в случае, если: - в запрос передан куки не существующий http сессии - gtk сессии не существует При этом - Происходит создание новой сессии и вызов метода обработки (смотри создание) - Возврат новой куки ##### Конфликт сессии Происходит в случае, если: - в запрос передан куки http сессии - gtk сессия существует в уникальном разрезе, и ее текущий куки не совпадает с куки http сессии При этом - Генерируется ошибка захвата сессии ##### Захват сессии Происходит при условии: - в запрос не передан куки http сессии - или передан устаревший куки, который совпадает с куки в gtk сессии - gtk сессия существует в уникальном разрезе При этом: - Вызываются методы прикладного пакета - `onDeactivate` - `onActivate` - в соответствии с типом http запроса вызывается один из методов обработки - Возвращается куки новой сессии #### Доступ к данным REST-запроса из прикладного кода В прикладном коде Pkg-класса, выполняемом при REST-запросе, доступен объект `RestESContext`, предоставляющий доступ к данным REST-запроса и REST-ответа. Для получения контекста, выполните: ```scala val restContextOpt = RestESContext() ``` Методы контекста: - `sessionTimeout: Long` \ Время жизни gtk-сессии после последнего запроса - `request: HttpServletRequest` \ Объект-запрос. Предоставляет доступ к параметрам, кукам и т.д. - `responseBuilder: Response.ResponseBuilder` \ Билдер ответа. - `markSessionClose(): Unit` \ Устанавливает флаг закрытия gtk-сессии по завершении обработки запроса - `isSessionClose: Boolean` \ Возвращает значение флага закрытия gtk-сессии ### Обработка ошибок #### Коды ошибок - `500` – server error \ Прикладное исключение - `409` - conflict \ Конфликт сессий #### Формат ошибок При возникновении ошибки, по умолчанию, ответ будет возвращён в формате xml. Для изменения формата ответа, передайте http-параметр `format`. Доступные значения: - `xml` - `json` Пример: ``` http://localhost:8080/app/sys/rest/es/pkg/Gs3_RestfulTestPkg/anypath?format=json ``` ##### Xml ответ с ошибкой ```xml \ %s %s %s ``` ##### Json ответ с ошибкой ```json { "response": { "status" : 0, "data":{}, "error": { "type": "" , "message": "", "stacktrace": "" } }} ``` ### Пример обращения к сервису #### Java ```java package ru.bitec.app.sys.rest; import org.junit.Test; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Invocation; import javax.ws.rs.core.Cookie; import javax.ws.rs.core.Response; import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; public class PkgRestServiceTest0 { private static String rootAddress = "http://localhost:8080/sys/rest/es"; private static Client client = ClientBuilder.newClient(); private Path testPath = Paths.get(System.getProperty("user.dir"), "src\\test\\java\\ru\\bitec\\app\\sys\\rest"); private Path cookiePath = testPath.resolve("JSESSIONID.cookie"); @Test public void get_2_Cookie_test() throws Exception { String JSESSION_Cookie = load_JSESSIONID_Cookie(); Invocation.Builder builder = client.target(rootAddress + "/pkg/Gs3_RestfulTestPkg/anypath").request() //.header("Authorization", "Basic " + Base64.encodeToString("admin:admin".getBytes("UTF-8"), false)) .header("Authorization", "Basic " + java.util.Base64.getEncoder().encodeToString("admin:admin".getBytes("UTF-8"))) .cookie(Cookie.valueOf(JSESSION_Cookie)); Response response = builder.get(); try { save_JSESSIONID_Cookie(response); String s = response.readEntity(String.class); System.out.println("response: " + s); } finally { response.close(); } } private void save_JSESSIONID_Cookie(Response response) throws IOException { if (response.getCookies().containsKey("JSESSIONID")) { String cookie = response.getCookies().get("JSESSIONID").toString(); Files.write(cookiePath, cookie.getBytes(Charset.defaultCharset())); } } private String load_JSESSIONID_Cookie() throws IOException { if (Files.exists(cookiePath)) return new String(Files.readAllBytes(cookiePath), Charset.defaultCharset()); else return null; } } ``` ## REST-сервис для взаимодействия с пользовательскими сессиями Сервис доступен только в режиме разработки. В проектных решениях не доступен. Функциональность сервиса: 1. Получение списка сессий, удовлетворяющих условиям поиска. 2. Выполнение Jexl-скриптов в контексте гл.выборки приложения. ### Получение списка сессий При отправке HTTP GET на адрес сервиса, будет возвращён JSON со списком сессий, удовлетворяющих условиям поиска. Http-запрос может содержать следующие параметры: - `sbt` \ Фильтр по имени SBT - `clientId` \ Фильтр по идентификатору клиента (этот идентификатор присваивается каждому экземпляру браузера и содержится в куках) - `user` \ Фильтр по имени пользователя - `app` \ Фильтр по имени главной выборки приложения (поиск осуществляется по вхождению переданного значения в полное имя гл.выбокрки) Пример HTTP GET: ``` http://localhost:8080/app/sys/rest/sessions?sbt=test&user=admin&clientId=123456&app=Xxx_xxxxxx ``` Пример ответа: ```json {"sessions":[{ "sid": "E1", "id": "02b5f602-ac2c-4259-9452-18840a1cd124", "user": "admin", "clientId": "B54E2C1F-04E5-4BB2-9372-A9AB8E9C6676", "database": "PGTEST", "app": "gtk-ru.bitec.app.gs3.Gs3_TestXmlApplication" }]} ``` ### Выполнение Jexl в контексте главной выборки приложения #### Простой HTTP POST При отправке HTTP POST на адрес сервиса, в контексте гл.выборки приложения сессии, с переданным идентификатором, будет выполнен Jexl-скрипт, переданный в теле Http POST. Адреса сервисов: - `http://{server:port}/app/sys/rest/sessions/{id}/jexl/mainsel` \ Где `{id}` - Идентификатор сессии. Значение `{id}` можно получить из результата http get к сервису. - `http://{server:port}/app/sys/rest/sessions/new/jexl/mainsel?appname={имя_гл_выборки}` \ При этом будет запущена новая сессия. Для корректного запуска новой сессии и открытия приложения, необходимо передать имя главной выборки через http-параметр `appname` Пример HTTP POST: ``` http://localhost:8080/app/sys/rest/sessions/949d77e9-80bf-4b93-bd3b-b424f8887783/jexl/mainsel http://localhost:8080/app/sys/rest/sessions/new/jexl/mainsel?appname=Gs3_TestXmlApplication ``` В ответе вернётся JSON с результатом выполнения: ```json { "sessionId":"14849330-3196-4b33-80c5-caa370186720", "result":"[результат выполнения Jexl]" } ``` ##### Пример на Java ```java @Test public void post_test_1() throws Exception { Map rootMap = (Map) Json.parse(doTestGET(rootAddress + "?sbt=test")); List> sessions = (List>) rootMap.get("sessions"); if (sessions.isEmpty()) throw new Exception("No one ESession opened."); Map session = sessions.get(0); String id = (String) session.get("id"); String rootAddress = "http://localhost:8080/app/sys/rest/sessions"; doTestPOST(rootAddress + "/" + id + "/jexl/mainsel", "Some Jexl Script"); } public String doTestGET(String uri, String database) throws Exception { System.out.println("HTTP GET: " + uri); Invocation.Builder builder = client.target(uri).request() //.header("Authorization", "Basic " + Base64.encodeToString("admin:admin".getBytes("UTF-8"), false)) .header("Authorization", "Basic " + java.util.Base64.getEncoder().encodeToString("admin:admin".getBytes("UTF-8"))); if (database != null) { builder.header("Database", database); } String result = ""; Response response = builder.get(); try { System.out.println("response status = " + response.getStatus()); System.out.println("response MediaType = " + response.getMediaType()); if (response.getStatus() == 200) { ByteArrayOutputStream out = new ByteArrayOutputStream(); try (InputStream inputStream = response.readEntity(InputStream.class)) { StreamHelper.copyStream(inputStream, out); } byte[] bytes = out.toByteArray(); System.out.println("response: " + bytes.length + " bytes"); result = new String(bytes); //System.out.println("filename: " + response.getHeaderString("Content-Disposition")); } else { result = response.readEntity(String.class); } } finally { response.close(); } System.out.println(result); return result; } public String doTestPOST(String uri, String database, String body) throws Exception { System.out.println("HTTP POST: " + uri); Invocation.Builder builder = client.target(uri).request() //.header("Authorization", "Basic " + Base64.encodeToString("admin:admin".getBytes("UTF-8"), false)) .header("Authorization", "Basic " + java.util.Base64.getEncoder().encodeToString("admin:admin".getBytes("UTF-8"))); if (database != null) { builder.header("Database", database); } String result = ""; Entity entity = Entity.entity(body, MediaType.TEXT_PLAIN_TYPE); Response response = builder.post(entity); try { System.out.println("response status = " + response.getStatus()); System.out.println("response MediaType = " + response.getMediaType()); if (response.getStatus() == 200) { ByteArrayOutputStream out = new ByteArrayOutputStream(); try (InputStream inputStream = response.readEntity(InputStream.class)) { StreamHelper.copyStream(inputStream, out); } byte[] bytes = out.toByteArray(); System.out.println("response: " + bytes.length + " bytes"); result = new String(bytes); //System.out.println("filename: " + response.getHeaderString("Content-Disposition")); } else { result = response.readEntity(String.class); } } finally { response.close(); } System.out.println(result); return result; } ``` #### Form HTTP POST Альтернативным вариантом передачи Jexl скрипта, является отправка на адрес сервиса http формы, поле которой содержит Jexl-скрипт. Адреса сервисов: - `http://{server:port}/app/sys/rest/form/sessions/{id}/jexl/mainsel` - `http://{server:port}/app/sys/rest/form/sessions/new/jexl/mainsel?appname={имя_гл_выборки}` В ответе вернётся команда перенаправления на страницу входа с идентификатором сессии в качестве http-параметра. ##### Пример HTML-страницы. ```html ``` ## Сервис отчётов Сервис предназначен для формирования печатных документов, средствами сервера Global 3, по запросу из внешних систем. Для получения файла со сформированным отчётом, необходимо отправить HTTP GET по адресу сервиса. Адреса сервисов: - `http://{server:port}/app/sys/rest/report/{name}` - `http://{server:port}/app/sys/rest/report/{name}/{date}` где: - `{name}` \ Системное имя печатной формы - `{date}` \ Дата версии отчёта Пример: ``` http://localhost:8080/app/sys/rest/report/Rpt_JasperSimpleQuery/15.02.2019 ``` ### Аутентификация Читайте в разделе [Аутентификация в REST/SOAP-сервисах](#аутентификация-в-restsoap-сервисах). ### Параметризация отчёта Для передачи параметров в формируемый отчёт, передавайте значения через http-параметры. Служебные символы и пробелы должны бить заменены Escape-символами. Пример: ``` http://localhost:8080/app/sys/rest/report/Rpt_JasperSimpleQuery?param1=value1¶m2=value2 ``` ## Аутентификация в REST/SOAP сервисах Сервисы используют HTTP-аутентификацию двух типов: Basic и Bearer. ### Basic - c использованием логина, пароля и имени базы При Basic-аутентификации, клиент должен передать в запросе: - Имя пользователя - Пароль - Имя базы Имя пользователя и пароль передаются через HTTP-заголовок: - `Authorization` \ Значение: `Basic {Base64Cred}` \ где: \ `{Base64Cred}` – строка `user:password`, кодированная в Base64 Имя базы может быть передано через (в порядке приоритета): - Сегмент строки адреса. Пример: `http://server/{DATABASE}/` - HTTP-загловок `Database` со значением `{DATABASE}`. - HTTP-параметр `Database` со значением `{DATABASE}`. Пример: `http://server/?Database={DATABASE}` где: \ `{DATABASE}` - имя базы данных. Если имя базы не передано ни одним из описанных способов, будет произведена попытка аутентификации с использованием имени базы по-умолчанию. База по умолчанию определяется из конфигурационного файла `global3.config.xml` (в порядке приоритета). - Значение атрибута `` - Значение атрибута `` первой базы в списке конфигураций `` ### Bearer - c использованием токена аутентификации При Bearer-аутентификации, клиент должен передать в запросе: - Токен аутентификации ``` До версии AS 1.20 rc 15 включительно, доступны только токены сформрованные сервером приложений на основе: имени базы, имени пользователя и пароля. ``` - Имя базы ``` До версии AS 1.20 rc 15 включительно, указания имени базы при Bearer-аутентификации не требуется. ``` Запрос должен содержать HTTP-заголовок: - `Authorization` \ Значение: `Bearer {Token}` \ Где: \ `{Token}` – ключ авторизации, присвоенный пользователю. Можно получить в прикладном коде от сущности ```scala session.user.token: String ``` Имя базы может быть передано через (в порядке приоритета): - Сегмент строки адреса. Пример: `http://server/{DATABASE}/` - HTTP-загловок `Database` со значением `{DATABASE}`. - HTTP-параметр `Database` со значением `{DATABASE}`. Пример: `http://server/?Database={DATABASE}` где: \ `{DATABASE}` - имя базы данных. Если имя базы не передано ни одним из описанных способов, будет произведена попытка аутентификации с использованием имени базы по-умолчанию. База по умолчанию определяется из конфигурационного файла `global3.config.xml` (в порядке приоритета). - Значение атрибута `` - Значение атрибута `` первой базы в списке конфигураций `` ### Типы токенов аутентификации - `ast` - [Application Server Token](#application-server-token) - `gjwt` - [Gtk Json Web Token](#gtk-json-web-token) #### Application Server Token Токен формируется сервером приложений после входа в приложение Глобал с использованием имени пользователя и пароля. Токен возвращется клиенту в Cookie access_token, привязанной к подмножеству адресов `http[s]://server/{DATABASE}/`. Эта Cookie автоматически присоединяется ко всем запросам по адресам `http[s]://server/{DATABASE}/`, выполняемым из браузера, аутентифицированного в системе Глобал. Токен устаревает после перезапуска сервера приложений. #### Gtk Json Web Token Токен, по стандарту JWT, формируется в модуле GTK (или на внешнем серсисе) и валидируется в модуле GTK. GJWT разделяются на подтипы - `UserHash` - [Долгоживущий токен пользователя](#долгоживущий-токен-пользователя) - `UserCrt` - [Подписанный пользователем токен](#подписанный-пользователем-токен) - `ProxyCrt` - [Подписанный прокси-пользователем токен](#подписанный-прокси-пользователем-токен) ##### Долгоживущий токен пользователя Формируется администратором системы Глобал и сообщается пользователю любым удобным способом, исключающим утечку токена. Токен, сопоставленный с учётной записью пользователя, хранится в БД решения. Срок годности - определяется администратором. Тело JWT (payload) должно содержать следующие поля: - ``typ`` - `"UserHash"` - ``sub`` - имя пользователя, под которым необходимо аутентифицироваться - ``exp`` - дата устаревания При проверке валидности токена, проверяется существование токена в списке сопоставленных с учётной записью токенов и дата устаревания. ##### Подписанный пользователем токен Формируется пользователем и подписывается закрытым RSA-ключом пользователя. Открытый RSA-ключ, сопоставленный с учётной записью пользователя, хранится в БД решения. Срок годности - определяется пользователем. Тело JWT (payload) должно содержать следующие поля: - ``typ`` - `"UserCrt"` - ``sub`` - имя пользователя, под которым необходимо аутентифицироваться - ``cid`` - идентификатор открытого ключа в системе Глобал, сопоставленного с пользователем - ``exp`` - дата устаревания При проверке валидности токена, на основе полей ``sub`` и ``cid`` из БД решения получается открытый RSA-ключ и проверяется валидность подписи JWT. ##### Подписанный прокси-пользователем токен ``` Прокси-аутентификация - это аутентификация под именем пользователя ``№2`` от имени пользователя ``№1``, именуемого ``прокси-пользователем``. Данный вид аутентификации может быть использован внешними планировщиками задач, которые необходимо выполнять под разными пользователями. При этом, планировщику нет необходимости хранить и передавать секретные учётные данные пользователей. ``` Формируется прокси-пользователем и подписывается закрытым RSA-ключом прокси-пользователя. Открытый RSA-ключ, сопоставленный с учётной записью прокси-пользователя, хранится в БД решения. Срок годности - определяется прокси-пользователем. Тело JWT (payload) должно содержать следующие поля: - ``typ`` - `"ProxyCrt"` - ``sub`` - имя пользователя, под которым необходимо аутентифицироваться - ``psub`` - имя прокси-пользователя, которому принадлежитам закрытый и открыты ключи - ``cid`` - идентификатор открытого ключа в системе Глобал, сопоставленного с прокси-пользователем - ``exp`` - дата устаревания При проверке валидности токена, на основе полей ``psub`` и ``cid``, из БД решения получается открытый RSA-ключ прокси-пользователя и проверяется валидность подписи JWT. Если токен валиден и пользователь ``psub`` имеет права на выполнение кода от имени других пользователей, запрос аутентифицируется под именем пользователя ``sub``. #### Пример запроса с аутентификацией по токену В примере используются библиотеки: - "io.jsonwebtoken" % "jjwt-api" % "0.10.8", - "io.jsonwebtoken" % "jjwt-impl" % "0.10.8", - "io.jsonwebtoken" % "jjwt-jackson" % "0.10.8", - "org.apache.httpcomponents" % "httpclient" % "4.5.8" ```java import io.jsonwebtoken.Jwts; import org.apache.http.HttpHeaders; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.junit.Assert; import org.junit.Test; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.KeyPair; import java.security.PrivateKey; import java.util.Calendar; import java.util.Date; import java.util.stream.Collectors; public class AuthProviderTest { private PrivateKey getPrivateKey(String proxyUser) throws NoSuchAlgorithmException { // Формируется рандомный закрытый ключ. // В рабочем коде необходимо использовать реальный закрытый ключ, принадлежащий пользователю. return KeyPairGenerator.getInstance("RSA").generateKeyPair().getPrivate(); } @Test public void authenticate_with_gjwt_proxy_cert() throws Exception { Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.DAY_OF_MONTH, 7); String proxyUser = "proxy_user"; String gjwt = "gjwt_" + Jwts.builder() .setAudience("GS") // Кому предназначен токен. Необязательный параметр. .setIssuer("Scheduler") // Кем сформирован токен. Необязательный параметр. .setSubject("real_user") // Имя пользователя, под которым необходимо аутентифицироваться. .claim("typ", "ProxyCrt") // Подтип токена. В данном случае, токен для прокси-аутентификации. .claim("psub", proxyUser) // Имя прокси-пользователя. .claim("cid", "123456789") // Идентификатор открытого RSA-ключа прокси-пользователя в системе Глобал. .setExpiration(calendar.getTime()) // Дата устаревания токена. .signWith(getPrivateKey(proxyUser)) // Закрытый ключ прокси-пользователя, которым будет подписан токен. .compact(); HttpGet httpGet = new HttpGet("http://localhost:8080/PGTEST/app/sys/rest/ss/pkg/Gs3_RestfulTestPkg/anypath"); httpGet.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + gjwt); try (CloseableHttpClient client = HttpClients.createDefault()) { try (CloseableHttpResponse response = client.execute(httpGet)) { try (InputStream in = response.getEntity().getContent()) { String s = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)) .lines() .collect(Collectors.joining(System.lineSeparator())); System.out.println(s); } } } } } ``` ## Администрирование Rest-сервисов Класс Btk_AcPackage содержит информацию об администрируемых пакетах: * Имя пакета * id модуля, которому он принадлежит * является ли он rest-пакетом (`bIsRest`) * администрируются ли серверные полномочия (`bControlServerPriv`) Пакет считается Rest-пакетом, если он наследуется от трейта RestPkg, в том числе от его потомков. Rest-пакеты регистрируются при обновлении администрируемых объектов по модулю. Если при обновлении окажется, что пакет больше не наследуется от RestPkg, то признак `bIsRest` в таблице будет снят. ```{attention} После изменения статуса администрируемости пакета, необходимо сбросить shared-кэш по классу Btk_AcPackage. ``` ### Выдача прав на вызов Rest-пакетов В карточке роли на закладке "`Права на Rest-пакеты`" отображен список пакетов из класса Btk_AcPackage и имеет ли роль доступ к ним. Информация по доступу для роли хранится в таблице Btk_AcRolePackagePriv. Для выбора\снятия доступа роли к пакету используйте чекбокс `Имеет доступ`. Во время индексации прав пользователей по роли для всех Rest-пакетов, у которых стоит галка "Контролировать серверные полномочия", будут выданы объектные привилегии пользователям, которые имеют данный профиль. Привилегии регистрируются на адм. объект Btk_AcPackage. ```{note} Супер-пользователь имеет полный доступ ``` ## Администрирование SOAP-вызовов Администрирование SOAP-вызовов включается в ``Администратор \ Настройки > Настройки администрирования`` соответствующим чекбоксом. Для выдачи права роли необходимо дать доступ к объектной привилегии `UseSoap` адм. объекта Btk_ManagementPkg. ```{note} Супер-пользователь имеет полный доступ ```