Compare commits

...

16 Commits

Author SHA1 Message Date
Mikhail Chechnev
380df706d8 ЦИТК-790: Косметика в комментах 2026-03-11 18:48:17 +03:00
Mim
d432a5d02e ЦИТК-986, ЦИТК-790 - ДИАДОК/СБИС - Параметр "Загружать все документы" и время жизни токена доступа для СБИС
Reviewed-on: CITKParus/P8-ExchangeService#17
2026-03-11 18:44:02 +03:00
Mikhail Chechnev
4e1b1b010b ЦИТК-795: CodeReview - http_client - удален лишний onError для secureSocket, из интерфейса удалён экспорт HttpError (не используется) 2026-03-11 15:42:54 +03:00
Mikhail Chechnev
5af4b9d498 ЦИТК-795: CodeReview - http_client - комментарии по тексту модуля, приведение к стандартному code-style 2026-03-11 15:38:34 +03:00
Mikhail Chechnev
b2b7d01342 ЦИТК-795: CodeReview - deepMerge в стрелочном стиле, дополнительные комментарии по коду 2026-03-11 15:36:20 +03:00
Mikhail Chechnev
72feca2a38 Инициализация Thick-режима для расширения "Импорт ГАР" с учетом типа данных переменной окружения 2026-03-10 16:25:46 +03:00
Mikhail Chechnev
207d2ada07 Флаг Thin-режима и указание пути для клиента Oracle переведены в config.js, контроль доступности пула подключений Oracle при создании соединения 2026-03-07 00:55:15 +03:00
9d8207417a ЦИТК-790 2026-03-06 13:22:30 +03:00
5d93f31b77 ЦИТК-790 2026-03-06 13:20:15 +03:00
f6bdb12463 Обновить modules/diadoc.js 2026-03-06 12:51:46 +03:00
b156ed7556 Merge branch '2.0.0_NodeJS_22' 2026-02-13 12:19:57 +03:00
2a13f5e350 Внесение изменений из master ветки 2026-02-13 12:01:57 +03:00
Mikhail Chechnev
c15c06bf33 Релиз 2026.02.10 2026-02-09 19:54:16 +03:00
Mim
3dcba310f6 ЦИТК-1018 - Фиксация в очереди идентификатора сервера интеграции, обработавшего входящее сообщение
Reviewed-on: CITKParus/P8-ExchangeService#14
2026-02-09 19:29:57 +03:00
19a7023291 Изменена работа с прокси/tls в модуле http_client, теперь используются модули http, https и net вместо undici. Добавлена инициализация thick-режима при работе с базой данных Oracle для расширения "Интеграция с ГАР". Исправлены ошибки модуля обработки входящих сообщений (in_queue). 2026-02-02 17:53:04 +03:00
97cb8516c3 Инициализация ветки обновления node с версии 14 на версию 22, а также используемых сервером библиотек. Добавление модуля работы с запросами, изменение работы при клонировании переменных, отказ от module-alias и lodash 2026-01-15 14:59:20 +03:00
22 changed files with 1953 additions and 2419 deletions

View File

@ -14,7 +14,7 @@ let common = {
//Версия сервера приложений
sVersion: "8.5.6.1",
//Релиз сервера приложений
sRelease: "2025.12.24",
sRelease: "2026.02.12",
//Таймаут останова сервера (мс)
nTerminateTimeout: 60000,
//Контролировать версию Системы
@ -34,7 +34,11 @@ let dbConnect = {
//Наименование сервера приложений в сессии БД
sSessionAppName: "PARUS$ExchangeServer",
//Подключаемый модуль обслуживания БД (низкоуровневые функции работы с СУБД)
sConnectorModule: "parus_oracle_db.js"
sConnectorModule: "parus_oracle_db.js",
//Применение "тонкого" (Thin) или "толстого" (Thick) режима подключения (false - Thick-режим - работа через установленного клиента СУБД Oracle, true - Thin-режим - не требует установки клиента СУБД Oracle, допустим только для Oracle >= 12.1)
bOraUseThinMode: false,
//Путь к домашней директории Oracle Client (только для Thick-режима подключения к СУБД Oracle, если значение не указано, то будет использоваться значение из переменной окружения "PATH" ОС)
sOraClient: ""
};
//Параметры обработки очереди исходящих сообщений

View File

@ -118,7 +118,6 @@ class ParusAppServer {
await this.logger.info(
`Обработчик очереди входящих сообщений запущен (порт - ${nPort}, доступные IP - ${sHost === "0.0.0.0" ? getIPs().join("; ") : sHost})`
);
//Запускаем
//Запускаем модуль отправки уведомлений
await this.logger.info("Запуск модуля отправки уведомлений...");
try {

View File

@ -8,9 +8,9 @@
//-----------------
//Путь к модулям
exports.SMODULES_PATH_CORE = "@core"; //Модули ядра
exports.SMODULES_PATH_MODULES = "@modules"; //Дополнительные пользовательские модули
exports.SMODULES_PATH_MODELS = "@models"; //Модели данных и схемы валидации
exports.SMODULES_PATH_CORE = "."; //Модули ядра
exports.SMODULES_PATH_MODULES = "../modules"; //Дополнительные пользовательские модули
exports.SMODULES_PATH_MODELS = "../models"; //Модели данных и схемы валидации
//Типовые коды ошибок
exports.SERR_COMMON = "ERR_COMMON"; //Общая ошибка

View File

@ -7,10 +7,9 @@
// Подключение библиотек
//----------------------
const _ = require("lodash"); //Работа с массивами и объектами
const EventEmitter = require("events"); //Обработчик пользовательских событий
const { ServerError } = require("./server_errors"); //Типовая ошибка
const { makeModuleFullPath, validateObject } = require("./utils"); //Вспомогательные функции
const { makeModuleFullPath, validateObject, deepClone } = require("./utils"); //Вспомогательные функции
const prmsDBConnectorSchema = require("../models/prms_db_connector"); //Схемы валидации параметров функций модуля
const intfDBConnectorModuleSchema = require("../models/intf_db_connector_module"); //Схема валидации интерфейса модуля взаимодействия с БД
const objServiceSchema = require("../models/obj_service"); //Схема валидации сервиса
@ -69,7 +68,7 @@ class DBConnector extends EventEmitter {
throw new ServerError(SERR_MODULES_BAD_INTERFACE, sCheckResult);
}
//Всё успешно - сохраним настройки подключения
this.connectSettings = _.cloneDeep(prms.connectSettings);
this.connectSettings = deepClone(prms.connectSettings);
//Инициализируем остальные свойства
this.connection = null;
this.bConnected = false;
@ -176,7 +175,7 @@ class DBConnector extends EventEmitter {
//Забираем для каждого из сервисов список его функций
let srvsFuncs = srvs.map(async srv => {
const response = await this.getServiceFunctions({ nServiceId: srv.nId });
let tmp = _.cloneDeep(srv);
let tmp = deepClone(srv);
response.forEach(f => {
tmp.functions.push(f);
});
@ -211,7 +210,7 @@ class DBConnector extends EventEmitter {
if (!sCheckResult) {
try {
//Подготовим параметры для передачи в БД
let getServiceFunctionsData = _.cloneDeep(prms);
let getServiceFunctionsData = deepClone(prms);
getServiceFunctionsData.connection = this.connection;
//И выполним считывание функций сервиса
let res = await this.connector.getServiceFunctions(getServiceFunctionsData);
@ -240,7 +239,7 @@ class DBConnector extends EventEmitter {
if (!sCheckResult) {
try {
//Подготовим параметры для передачи в БД
let getServiceContextData = _.cloneDeep(prms);
let getServiceContextData = deepClone(prms);
getServiceContextData.connection = this.connection;
//И выполним считывание контекста сервиса
let res = await this.connector.getServiceContext(getServiceContextData);
@ -269,7 +268,7 @@ class DBConnector extends EventEmitter {
if (!sCheckResult) {
try {
//Подготовим параметры для передачи в БД
let setServiceContextData = _.cloneDeep(prms);
let setServiceContextData = deepClone(prms);
setServiceContextData.connection = this.connection;
//И выполним установку контекста сервиса
await this.connector.setServiceContext(setServiceContextData);
@ -295,7 +294,7 @@ class DBConnector extends EventEmitter {
if (!sCheckResult) {
try {
//Подготовим параметры для передачи в БД
let clearServiceContextData = _.cloneDeep(prms);
let clearServiceContextData = deepClone(prms);
clearServiceContextData.connection = this.connection;
//И выполним очистку контекста сервиса
await this.connector.clearServiceContext(clearServiceContextData);
@ -321,7 +320,7 @@ class DBConnector extends EventEmitter {
if (!sCheckResult) {
try {
//Подготовим параметры для передачи в БД
let isServiceAuthData = _.cloneDeep(prms);
let isServiceAuthData = deepClone(prms);
isServiceAuthData.connection = this.connection;
//И выполним проверку атентифицированности сервиса
let res = await this.connector.isServiceAuth(isServiceAuthData);
@ -354,7 +353,7 @@ class DBConnector extends EventEmitter {
if (!sCheckResult) {
try {
//Подготовим параметры для передачи в БД
let putServiceAuthInQueueData = _.cloneDeep(prms);
let putServiceAuthInQueueData = deepClone(prms);
putServiceAuthInQueueData.connection = this.connection;
//И выполним постановку в очередь задания на аутентификацию сервиса
await this.connector.putServiceAuthInQueue(putServiceAuthInQueueData);
@ -384,7 +383,7 @@ class DBConnector extends EventEmitter {
if (!sCheckResult) {
try {
//Подготовим параметры для передачи в БД
let getServiceExpiredQueueInfoData = _.cloneDeep(prms);
let getServiceExpiredQueueInfoData = deepClone(prms);
getServiceExpiredQueueInfoData.connection = this.connection;
//И выполним получение информации о просроченных сообщениях
let res = await this.connector.getServiceExpiredQueueInfo(getServiceExpiredQueueInfoData);
@ -413,7 +412,7 @@ class DBConnector extends EventEmitter {
if (!sCheckResult) {
try {
//Подготовим параметры для передачи в БД
let logData = _.cloneDeep(prms);
let logData = deepClone(prms);
logData.connection = this.connection;
logData.nExsSrv = this.nExsSrv;
//И выполним запись в журнал
@ -436,7 +435,7 @@ class DBConnector extends EventEmitter {
//Запись информации в журнал работы
async putLogInf(sMsg, prms) {
//Подготовим параметры для передачи в БД
let logData = _.cloneDeep(prms);
let logData = deepClone(prms);
//Выставим сообщение и тип записи журнала
logData.nLogState = objLogSchema.NLOG_STATE_INF;
logData.sMsg = sMsg;
@ -452,7 +451,7 @@ class DBConnector extends EventEmitter {
//Запись предупреждения в журнал работы
async putLogWrn(sMsg, prms) {
//Подготовим параметры для передачи в БД
let logData = _.cloneDeep(prms);
let logData = deepClone(prms);
//Выставим сообщение и тип записи журнала
logData.nLogState = objLogSchema.NLOG_STATE_WRN;
logData.sMsg = sMsg;
@ -468,7 +467,7 @@ class DBConnector extends EventEmitter {
//Запись ошибки в журнал работы
async putLogErr(sMsg, prms) {
//Подготовим параметры для передачи в БД
let logData = _.cloneDeep(prms);
let logData = deepClone(prms);
//Выставим сообщение и тип записи журнала
logData.nLogState = objLogSchema.NLOG_STATE_ERR;
logData.sMsg = sMsg;
@ -489,7 +488,7 @@ class DBConnector extends EventEmitter {
//Если структура объекта в норме
if (!sCheckResult) {
//Подготовим параметры
let getQueueData = _.cloneDeep(prms);
let getQueueData = deepClone(prms);
getQueueData.connection = this.connection;
try {
//Исполняем действие в БД
@ -518,7 +517,7 @@ class DBConnector extends EventEmitter {
//Если структура объекта в норме
if (!sCheckResult) {
//Подготовим параметры
let putQueueData = _.cloneDeep(prms);
let putQueueData = deepClone(prms);
putQueueData.blMsg = prms.blMsg ? prms.blMsg : Buffer.from("");
putQueueData.connection = this.connection;
putQueueData.nExsSrv = this.nExsSrv;
@ -550,7 +549,7 @@ class DBConnector extends EventEmitter {
if (!sCheckResult) {
try {
//Подготовим параметры для передачи в БД
let getQueueOutgoingData = _.cloneDeep(prms);
let getQueueOutgoingData = deepClone(prms);
getQueueOutgoingData.connection = this.connection;
getQueueOutgoingData.nExsSrv = this.nExsSrv;
//Выполняем считывание из БД
@ -578,7 +577,7 @@ class DBConnector extends EventEmitter {
//Если структура объекта в норме
if (!sCheckResult) {
//Подготовим параметры
let setStateData = _.cloneDeep(prms);
let setStateData = deepClone(prms);
setStateData.connection = this.connection;
try {
//Исполняем действие в БД
@ -611,7 +610,7 @@ class DBConnector extends EventEmitter {
//Если структура объекта в норме
if (!sCheckResult) {
//Подготовим параметры
let getQueueMsgData = _.cloneDeep(prms);
let getQueueMsgData = deepClone(prms);
getQueueMsgData.connection = this.connection;
//Исполняем действие в БД
try {
@ -644,7 +643,7 @@ class DBConnector extends EventEmitter {
//Если структура объекта в норме
if (!sCheckResult) {
//Подготовим параметры
let setQueueMsgData = _.cloneDeep(prms);
let setQueueMsgData = deepClone(prms);
if (!setQueueMsgData.blMsg) setQueueMsgData.blMsg = Buffer.from("");
setQueueMsgData.connection = this.connection;
//Исполняем действие в БД
@ -678,7 +677,7 @@ class DBConnector extends EventEmitter {
//Если структура объекта в норме
if (!sCheckResult) {
//Подготовим параметры
let setQueueOptionsData = _.cloneDeep(prms);
let setQueueOptionsData = deepClone(prms);
setQueueOptionsData.connection = this.connection;
//Исполняем действие в БД
try {
@ -711,7 +710,7 @@ class DBConnector extends EventEmitter {
//Если структура объекта в норме
if (!sCheckResult) {
//Подготовим параметры
let getQueueRespData = _.cloneDeep(prms);
let getQueueRespData = deepClone(prms);
getQueueRespData.connection = this.connection;
//Исполняем действие в БД
try {
@ -744,7 +743,7 @@ class DBConnector extends EventEmitter {
//Если структура объекта в норме
if (!sCheckResult) {
//Подготовим параметры
let setQueueRespData = _.cloneDeep(prms);
let setQueueRespData = deepClone(prms);
if (!setQueueRespData.blResp) setQueueRespData.blResp = Buffer.from("");
setQueueRespData.connection = this.connection;
//Исполняем действие в БД
@ -778,7 +777,7 @@ class DBConnector extends EventEmitter {
//Если структура объекта в норме
if (!sCheckResult) {
//Подготовим параметры
let setQueueOptionsRespData = _.cloneDeep(prms);
let setQueueOptionsRespData = deepClone(prms);
setQueueOptionsRespData.connection = this.connection;
//Исполняем действие в БД
try {
@ -836,7 +835,7 @@ class DBConnector extends EventEmitter {
//Исполняем действие в БД
try {
//Подготовим параметры для передачи в БД
let execQueuePrcData = _.cloneDeep(prms);
let execQueuePrcData = deepClone(prms);
execQueuePrcData.connection = this.connection;
//И выполним обработчик со стороны БД
let res = await this.connector.execQueuePrc(execQueuePrcData);

634
core/http_client.js Normal file
View File

@ -0,0 +1,634 @@
/*
Сервис интеграции ПП Парус 8 с WEB API
Модуль ядра: работа с HTTP/HTTPS запросами
*/
//------------------------------
// Подключение внешних библиотек
//------------------------------
const http = require("http"); //Встроенная поддержка HTTP
const https = require("https"); //Встроенная поддержка HTTPS
const { URL } = require("url"); //Обслуживание типовых URL
const { Socket } = require("net"); //Встроенная поддержка сокетов
//-------------------------
// Локальные идентификаторы
//-------------------------
//Таймаут по умолчанию
const DEFAULT_TIMEOUT = 30000;
//Ошибка HTTP-запроса
class HttpError extends Error {
constructor(message, response) {
super(message);
this.name = "HttpError";
this.response = response;
}
}
//------------------------
// Вспомогательные функции
//------------------------
//Нормализация параметров запроса (для совместимости с ранее применяемыми библиотеками "Request" и "Request-Promice")
const normalizeOptions = options => {
//Проверим, что нам предлагают обработать объект
if (!options || typeof options !== "object") {
throw new TypeError("Параметры HTTP-запроса должны быть объектом");
}
//Поддержка параметра uri (как в request-promise)
const url = options.uri || options.url;
if (!url) throw new Error("options.url или options.uri обязателен");
//Обработаем параметр "JSON"
const hasJsonOption = Object.prototype.hasOwnProperty.call(options, "json");
const jsonOption = options.json;
let body = options.body;
let jsonRequest = false;
let json = false;
if (hasJsonOption) {
if (jsonOption === true) {
json = true;
} else if (jsonOption && typeof jsonOption === "object") {
if (body === undefined) {
body = jsonOption;
jsonRequest = true;
}
json = true;
}
}
//Определяем HTTP-метод
let method = options.method;
if (!method) {
const hasRequestBody = body != null;
method = hasRequestBody ? "POST" : "GET";
}
//Собираем все уже отконвертированные параметры вместе + конвертируем то, что не требует выноса логики
return {
method: String(method).toUpperCase(),
url,
headers: options.headers || {},
query: options.query || options.qs || {},
body,
json,
jsonRequest,
timeout: options.timeout ?? DEFAULT_TIMEOUT,
followRedirects: options.followRedirects ?? true,
proxy: options.proxy || null,
ca: options.ca,
cert: options.cert,
key: options.key,
passphrase: options.passphrase,
rejectUnauthorized: options.rejectUnauthorized !== undefined ? options.rejectUnauthorized : true,
throwOnErrorStatus: options.throwOnErrorStatus ?? options.simple !== false,
signal: options.signal || null,
resolveWithFullResponse: options.resolveWithFullResponse || false,
encoding: options.encoding || null
};
};
//Формирование ссылки
const buildURL = (base, query = {}) => {
const url = new URL(base);
Object.entries(query).forEach(([key, value]) => {
if (value === undefined || value === null) return;
url.searchParams.append(key, String(value));
});
return url;
};
//Подготовка заголовков запроса
const prepareHeaders = (inputHeaders = {}) => {
const headers = new Map();
Object.entries(inputHeaders).forEach(([key, value]) => {
if (value === undefined || value === null) return;
headers.set(String(key).toLowerCase(), String(value));
});
return headers;
};
//Подготовка тела запроса
const prepareBody = ({ body, jsonRequest, headers }) => {
//Тела нет
if (body === undefined || body === null) {
return { body: undefined, contentLength: undefined, isStream: false };
}
//Явно указано, что тело нужно сериализовать как JSON
if (jsonRequest) {
const payload = Buffer.from(JSON.stringify(body));
if (!headers.has("content-type")) headers.set("content-type", "application/json; charset=utf-8");
return { body: payload, contentLength: payload.length, isStream: false };
}
//Тело - бинарные данные
if (Buffer.isBuffer(body) || body instanceof Uint8Array) {
return { body, contentLength: body.length, isStream: false };
}
//Тело - строка
if (typeof body === "string") {
const payload = Buffer.from(body);
return { body: payload, contentLength: payload.length, isStream: false };
}
//Тело - поток
if (typeof body.pipe === "function") {
return { body, contentLength: undefined, isStream: true };
}
//По умолчанию объектное тело сериализуем в JSON
if (typeof body === "object") {
const payload = Buffer.from(JSON.stringify(body));
if (!headers.has("content-type")) headers.set("content-type", "application/json; charset=utf-8");
return { body: payload, contentLength: payload.length, isStream: false };
}
//Фолбэк для прочих типов
const payload = Buffer.from(String(body));
return { body: payload, contentLength: payload.length, isStream: false };
};
//Обработка ответа (парсинг JSON, форматирование для совместимости с "Request" и "Request-Promice")
const processResponse = (result, options) => {
//Буфер для обработки тела
let processedBody = result.body;
//Если запрошен автоматический парсинг JSON
if (options.json === true && processedBody) {
try {
const text = processedBody.toString(options.encoding || "utf8");
processedBody = text ? JSON.parse(text) : null;
} catch (e) {
//Если не удалось распарсить JSON, возвращаем как есть
processedBody = processedBody.toString(options.encoding || "utf8");
}
} else if (options.encoding && processedBody) {
//Если указана кодировка, конвертируем в строку
processedBody = processedBody.toString(options.encoding);
}
//Формируем результат в зависимости от "resolveWithFullResponse" (это параметр из "Request": true - отвечать с заголовком, false - отвечать сразу телом)
if (options.resolveWithFullResponse) {
//Просили ответить полным ответом
return {
statusCode: result.statusCode,
statusMessage: result.statusMessage || "",
headers: result.headers,
body: processedBody,
url: result.url
};
} else {
//Просили только тело
return processedBody;
}
};
//Выполнение HTTP/HTTPS запроса через встроенные модули NodeJS (для прокси и TLS)
const httpRequestNative = (options, url, headers, body) => {
//Возвращаем промис - запрос исполняется асинхронно
return new Promise((resolve, reject) => {
//Определяемся с адресом
const urlObj = new URL(url);
//И модулем, обслуживающим протокол
const isHttps = urlObj.protocol === "https:";
const httpModule = isHttps ? https : http;
//Настройки TLS
const tlsOptions = {};
if (options.ca) {
tlsOptions.ca = Array.isArray(options.ca) ? options.ca : [options.ca];
}
if (options.cert) {
tlsOptions.cert = options.cert;
}
if (options.key) {
tlsOptions.key = options.key;
}
if (options.passphrase) {
tlsOptions.passphrase = options.passphrase;
}
if (options.rejectUnauthorized !== undefined) {
tlsOptions.rejectUnauthorized = options.rejectUnauthorized;
}
//Настройки прокси
let proxyUrl = null;
let proxyAuth = null;
if (options.proxy) {
try {
proxyUrl = new URL(options.proxy);
if (proxyUrl.username || proxyUrl.password) {
proxyAuth = `Basic ${Buffer.from(`${decodeURIComponent(proxyUrl.username || "")}:${decodeURIComponent(proxyUrl.password || "")}`).toString("base64")}`;
}
} catch (e) {
return reject(new Error(`Некорректный URL прокси: ${e.message}`));
}
}
//Параметры запроса
const requestOptions = {
hostname: proxyUrl ? proxyUrl.hostname : urlObj.hostname,
port: proxyUrl ? proxyUrl.port || (proxyUrl.protocol === "https:" ? 443 : 80) : urlObj.port || (isHttps ? 443 : 80),
path: proxyUrl ? url : urlObj.pathname + urlObj.search,
method: options.method,
headers: Object.fromEntries(headers),
timeout: options.timeout || DEFAULT_TIMEOUT
};
//Добавляем авторизацию прокси в HTTP-заголовки
if (proxyAuth) {
requestOptions.headers["Proxy-Authorization"] = proxyAuth;
}
//Для HTTPS добавляем TLS опции
if (isHttps && Object.keys(tlsOptions).length > 0) {
Object.assign(requestOptions, tlsOptions);
}
//Если используется прокси, нужно использовать CONNECT метод для HTTPS
if (proxyUrl && isHttps) {
//Для HTTPS через прокси используем туннелирование
return httpRequestThroughProxy(requestOptions, urlObj, headers, body, proxyUrl, proxyAuth, tlsOptions, options)
.then(result => resolve(processResponse(result, options)))
.catch(reject);
}
//Обычный запрос (с прокси для HTTP или без прокси)
const req = httpModule.request(requestOptions, res => {
//Буфер для данных ответа
const chunks = [];
//При получении ответа
res.on("data", chunk => {
//Наполняем буфер данными от удаленного сервера
chunks.push(chunk);
});
//При получении признака завершения обмена
res.on("end", () => {
//Собираем тело как бинарный буфер
const responseBody = Buffer.concat(chunks);
//Формируем объект ответа в стиле "Request" для совметимости
const result = {
statusCode: res.statusCode,
statusMessage: res.statusMessage || "",
headers: res.headers,
body: responseBody,
ok: res.statusCode >= 200 && res.statusCode < 300,
url: url
};
//Если установлена опция "Выдавать ошибку для HTTP-статуса != 200" - будем отклонять промис с ней
if (options.throwOnErrorStatus && !result.ok) {
const error = new HttpError(`Запрос не выполнен со статусом ${result.statusCode}`, result);
const httpError = new Error(error.message);
httpError.response = result;
httpError.statusCode = result.statusCode;
return reject(httpError);
}
//Разрешаем промис с данными ответа
resolve(processResponse(result, options));
});
});
//При получении сетевых ошибок
req.on("error", err => {
const error = new Error(err.message);
error.code = err.code;
error.error = { code: err.code };
reject(error);
});
//При нарушении времени, выделенного на выполнения запроса
req.on("timeout", () => {
req.destroy();
const timeoutError = new Error(`Время ожидания выполнения запроса истекло после ${options.timeout || DEFAULT_TIMEOUT} мс`);
timeoutError.code = "ETIMEDOUT";
timeoutError.error = { code: "ETIMEDOUT" };
reject(timeoutError);
});
//Отправляем тело запроса
if (body) {
if (Buffer.isBuffer(body)) {
req.write(body);
} else if (typeof body === "string") {
req.write(Buffer.from(body));
} else {
req.write(Buffer.from(JSON.stringify(body)));
}
}
//Закрываем запрос и начинаем ждать ответ
req.end();
});
};
//Выполнение HTTPS запроса через HTTP прокси (туннелирование)
const httpRequestThroughProxy = (requestOptions, targetUrl, headers, body, proxyUrl, proxyAuth, tlsOptions, options) => {
//Выставим предельное время исполнения запроса
const timeout = options.timeout || DEFAULT_TIMEOUT;
//Запрос всё ещё асинхронный - возвращаем промис
return new Promise((resolve, reject) => {
//Начальная точка туннеля
const proxyHost = proxyUrl.hostname;
const proxyPort = parseInt(proxyUrl.port || (proxyUrl.protocol === "https:" ? 443 : 80), 10);
//Конечная точка туннеля
const targetHost = targetUrl.hostname;
const targetPort = parseInt(targetUrl.port || 443, 10);
//Создаем соединение с прокси и флаги для отслеживания его состояния (подключено, запрос отправлен)
const proxySocket = new Socket();
let connected = false;
let requestSent = false;
//Взводим таймер ожидания подключения для подчистки сокета, чтобы не было утечек памяти
const connectTimeout = setTimeout(() => {
if (!connected || !requestSent) {
proxySocket.destroy();
reject(new Error(`Время ожидания подключения к прокси истекло после ${timeout} мс`));
}
}, timeout);
//Настраиваем встроенный таймаут сокета
proxySocket.setTimeout(timeout);
proxySocket.on("timeout", () => {
proxySocket.destroy();
reject(new Error(`Время ожидания выполнения запроса истекло после ${timeout} мс`));
});
//При подключении
proxySocket.connect(proxyPort, proxyHost, () => {
//Поднимаем флаг подключенности
connected = true;
//Настраиваем CONNECT-запрос
const connectRequest = `CONNECT ${targetHost}:${targetPort} HTTP/1.1\r\nHost: ${targetHost}:${targetPort}\r\n`;
const authHeader = proxyAuth ? `Proxy-Authorization: ${proxyAuth}\r\n` : "";
const connectHeaders = connectRequest + authHeader + "\r\n";
//Отправляем CONNECT-запрос
proxySocket.write(connectHeaders);
//Буфер для данных ответа на CONNECT-запрос
let connectResponse = "";
//Функция для наполнения буфера ответа на CONNECT-запрос и его обработки
const onConnectData = chunk => {
//Добавляем в буфер очередную порцию данных, полученных от удаленного сервера
connectResponse += chunk.toString();
//Вычислим значение флага завершения заголовка и начала тела ответа (в HTTP они отделяются последовательностью "\r\n\r\n")
const headerEnd = connectResponse.indexOf("\r\n\r\n");
//Если был достигнут конец заголовка
if (headerEnd !== -1) {
//Прекращаем прием данных ответа на CONNECT-запрос
proxySocket.removeListener("data", onConnectData);
clearTimeout(connectTimeout);
//Проверяем ответ на CONNECT-запрос
const statusLine = connectResponse.substring(0, connectResponse.indexOf("\r\n"));
if (statusLine.includes("200")) {
//Успешное подключение, создаем TLS соединение
const tls = require("tls");
const secureSocket = tls.connect(
{
socket: proxySocket,
host: targetHost,
servername: targetHost,
...tlsOptions
},
() => {
//Поднимаем флаг отправки запроса
requestSent = true;
clearTimeout(connectTimeout);
//Подготавливаем HTTP-запрос
const path = targetUrl.pathname + targetUrl.search;
const requestLine = `${requestOptions.method} ${path} HTTP/1.1\r\n`;
const hostHeader = `Host: ${targetHost}${targetPort !== 443 ? `:${targetPort}` : ""}\r\n`;
const requestHeaders = Array.from(headers.entries())
.map(([key, value]) => `${key}: ${value}\r\n`)
.join("");
const httpRequest = requestLine + hostHeader + requestHeaders + "\r\n";
//Отправляем HTTP-запрос через TLS
secureSocket.write(httpRequest);
if (body) {
const bodyBuffer = Buffer.isBuffer(body)
? body
: typeof body === "string"
? Buffer.from(body)
: Buffer.from(JSON.stringify(body));
secureSocket.write(bodyBuffer);
}
//Готовим буфер для чтения ответа
let responseBuffer = Buffer.alloc(0);
let responseHeaders = {};
let statusCode = 200;
let headerParsed = false;
let contentLength = -1;
//Функция для наполнения и обработки буфера ответа
const onSecureData = chunk => {
//Добавляем очередную порцию к данным буфера ответа
responseBuffer = Buffer.concat([responseBuffer, chunk]);
//Если ещё не поднят флаг того, что разобрали заголовок
if (!headerParsed) {
//Найдем в буфере метку конца заголовка
const headerEnd = responseBuffer.indexOf("\r\n\r\n");
//Метка конца заголовка найдена
if (headerEnd !== -1) {
//Фиксируем текст заголовка от начала буфера до отметки конца заголовка
const headerText = responseBuffer.subarray(0, headerEnd).toString();
//Остальное - будет телом
const bodyStart = headerEnd + 4;
responseBuffer = responseBuffer.subarray(bodyStart);
//Разбираем полученный текст заголовка
const lines = headerText.split("\r\n");
const statusLine = lines[0];
const statusMatch = statusLine.match(/HTTP\/[\d.]+\s+(\d+)/);
if (statusMatch) {
statusCode = parseInt(statusMatch[1], 10);
}
for (let i = 1; i < lines.length; i++) {
const line = lines[i];
const colonIndex = line.indexOf(":");
if (colonIndex !== -1) {
const key = line.substring(0, colonIndex).trim().toLowerCase();
const value = line.substring(colonIndex + 1).trim();
responseHeaders[key] = value;
if (key === "content-length") {
contentLength = parseInt(value, 10);
}
}
}
//И поднимаем флаг его разобранности
headerParsed = true;
//Если есть Content-Length и мы уже получили все данные
if (contentLength >= 0 && responseBuffer.length >= contentLength) {
//Прекращаем прием данных в буфер
secureSocket.removeListener("data", onSecureData);
//Закрываем TCP-соединение
secureSocket.end();
}
}
} else {
//Заголовок уже прочитан, но данные ещё могут приходить - проверяем, получили ли мы все данные
if (contentLength >= 0 && responseBuffer.length >= contentLength) {
//Прекращаем прием данных в буфер
secureSocket.removeListener("data", onSecureData);
//Закрываем TCP-соединение
secureSocket.end();
} else if (contentLength < 0) {
//Нет Content-Length, читаем до конца
//Будем ждать закрытия соединения со стороны хоста
}
}
};
//Подключаем определенную выше функцию обработки данных к событию их получения
secureSocket.on("data", onSecureData);
//Слушаем завершение соединения и реагируем на него
secureSocket.on("end", () => {
//Формируем объект с результатами обработки соединения, совместимый с ожиданиями сервера приложений
const result = {
statusCode: statusCode,
statusMessage: "",
headers: responseHeaders,
body: responseBuffer,
ok: statusCode >= 200 && statusCode < 300,
url: targetUrl.toString()
};
//Если установлена опция "Выдавать ошибку для HTTP-статуса != 200" - будем отклонять промис с ней
if (options.throwOnErrorStatus && !result.ok) {
const error = new HttpError(`Запрос не выполнен со статусом ${result.statusCode}`, result);
const httpError = new Error(error.message);
httpError.response = result;
httpError.statusCode = result.statusCode;
return reject(httpError);
}
//Разрешаем промис предварительно сформировав ответ, совместимый со старыми библиотекаим "Request"
resolve(processResponse(result, options));
});
}
);
//Слушаем сетевые ошибки на защищенном соединении
secureSocket.on("error", err => reject(err));
} else {
//Ответ на CONNECT-запрос содержит статус, отличный от 200 - отключаемся, не прошли прокси
proxySocket.destroy();
reject(new Error(`Прокси вернул ошибку: ${statusLine}`));
}
}
};
//Подключаем функцию обработки данных CONNECT-запроса и слушаем их
proxySocket.on("data", onConnectData);
});
//Слушаем сетевые ошибки при выполнении CONNECT-запроса
proxySocket.on("error", err => {
clearTimeout(connectTimeout);
reject(err);
});
});
};
//------------
// Тело модуля
//------------
//Выполнение HTTP/HTTPS запроса
const httpRequest = async (rawOptions = {}) => {
try {
//Нормализуем параметры запроса
const options = normalizeOptions(rawOptions);
//Сформируем ссылку
const url = buildURL(options.url, options.query);
//Подготовим заголовки и тело запроса
const headers = prepareHeaders(options.headers);
const { body, contentLength, isStream } = prepareBody({
body: options.body,
jsonRequest: options.jsonRequest,
headers
});
//Если не указан размер тела
if (contentLength !== undefined && !headers.has("content-length")) {
//Установим размер тела в заголовок
headers.set("content-length", String(contentLength));
}
//Если есть поток, читаем его
let requestBody = body;
if (isStream && body && typeof body.pipe === "function") {
const chunks = [];
try {
for await (const chunk of body) {
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
}
requestBody = chunks.length > 0 ? Buffer.concat(chunks) : undefined;
} catch (streamError) {
throw new Error(`Ошибка чтения потока: ${streamError.message}`);
}
}
//Если нужен прокси или специальные TLS настройки, используем встроенные модули
if (options.proxy || options.ca || options.cert || options.key || options.passphrase || options.rejectUnauthorized === false) {
return await httpRequestNative(options, url.toString(), headers, requestBody);
}
//Для простых запросов используем встроенный fetch
const fetchImpl = globalThis.fetch;
if (typeof fetchImpl !== "function") {
throw new Error('Среда исполнения не поддерживает "fetch", используйте NodeJS >= 22.21.1.');
}
//Добавим к нему аборт-контроллер для управления отменой запроса по таймауту
const timeoutController = new AbortController();
const signals = [];
//Один - на базе параметра "nTimeoutAsynch", получаемого из "Функции сервиса обмена", атрибут "Таймаут асинхронной отправки" (им управляет таймер, взводимый в обёртке "wrapPromiseTimeout" и передаваемый сюда в качестве сигнала)
if (options.signal && options.signal instanceof AbortSignal) {
signals.push(options.signal);
}
//Второй - на базе параметра "nTimeoutConn", получаемого из "Функции сервиса обмена", атрибут "Таймаут сетевого подключения"
signals.push(timeoutController.signal);
const combinedSignal = AbortSignal.any(signals);
//Взводим таймер для "второго" сигнала (по атрибуту "Таймаут сетевого подключения")
const timeoutId =
options.timeout > 0
? setTimeout(
() => timeoutController.abort(new Error(`Время ожидания выполнения запроса истекло после ${options.timeout} мс`)),
options.timeout
)
: null;
//Выполняем запрос
try {
//Соберем параметры запроса в формате сервиса интеграции в формат, ожидаемый "fetch"
const fetchOptions = {
method: options.method,
headers: Object.fromEntries(headers),
body: requestBody,
signal: combinedSignal,
redirect: options.followRedirects ? "follow" : "manual"
};
//Непосредственно запрос и чтение ответа
const response = await fetchImpl(url.toString(), fetchOptions);
//Читаем тело ответа
const responseBody = Buffer.from(await response.arrayBuffer());
//Собираем ответ в формате, совместимом с сервисом интеграции
const result = {
statusCode: response.status,
statusMessage: response.statusText || "",
headers: Object.fromEntries(response.headers.entries()),
body: responseBody,
ok: response.ok,
url: response.url
};
//Если установлена опция "Выдавать ошибку для HTTP-статуса != 200" - будем отклонять промис с ней
if (options.throwOnErrorStatus && !result.ok) {
const error = new HttpError(`Запрос не выполнен со статусом ${result.statusCode}`, result);
const httpError = new Error(error.message);
httpError.response = result;
httpError.statusCode = result.statusCode;
throw httpError;
}
//Возвращаем ответ, предварительно обеспечив совместимомть со старыми библиотекаим "Request"
return processResponse(result, options);
} finally {
//Подчистка таймера - успели всё сделать до его истечения
if (timeoutId) clearTimeout(timeoutId);
}
} catch (e) {
//Преобразуем ошибки для совместимости с request-promise
if (e.name === "AbortError" || e.message.includes("время ожидания")) {
const timeoutError = new Error(e.message);
timeoutError.code = "ETIMEDOUT";
timeoutError.error = { code: "ETIMEDOUT" };
throw timeoutError;
}
//Если ошибка уже имеет response (из httpRequestNative или fetch), пробрасываем как есть
if (e.response) {
throw e;
}
//Полученную от нативных методов (httpRequestNative) "HttpError" преобразуем в обычный объект ошибки с дополнительными полями для совместимости
if (e instanceof HttpError) {
const httpError = new Error(e.message);
httpError.response = e.response;
httpError.statusCode = e.response.statusCode;
throw httpError;
}
//Для других ошибок добавляем поле error для совместимости
if (e.code) {
e.error = { code: e.code };
}
//Наконец-то её можно отдать в вызывающее окружение
throw e;
}
};
//-----------------
// Интерфейс модуля
//-----------------
exports.httpRequest = httpRequest;

View File

@ -7,11 +7,9 @@
// Подключение внешних библиотек
//------------------------------
const _ = require("lodash"); //Работа с массивами и коллекциями
const EventEmitter = require("events"); //Обработчик пользовательских событий
const express = require("express"); //WEB-сервер Express
const cors = require("cors"); //Управление заголовками безопасности для WEB-сервера Express
const bodyParser = require("body-parser"); //Модуль для Express (разбор тела входящего запроса)
const { ServerError } = require("./server_errors"); //Типовая ошибка
const {
makeErrorText,
@ -23,6 +21,7 @@ const {
deepMerge,
deepCopyObject,
isUndefined,
deepClone,
getKafkaConnectionSettings,
getMQTTConnectionSettings,
getURLProtocol
@ -67,13 +66,13 @@ class InQueue extends EventEmitter {
//Если структура объекта в норме
if (!sCheckResult) {
//Общие параметры сервера приложений
this.common = _.cloneDeep(prms.common);
this.common = deepClone(prms.common);
//Список обслуживаемых сервисов
this.services = null;
//Признак функционирования обработчика
this.bWorking = false;
//Параметры очереди
this.inComing = _.cloneDeep(prms.inComing);
this.inComing = deepClone(prms.inComing);
//Запомним подключение к БД
this.dbConn = prms.dbConn;
//Запомним логгер
@ -82,8 +81,8 @@ class InQueue extends EventEmitter {
this.notifier = prms.notifier;
//WEB-приложение
this.webApp = express();
// Глобально разрешаем CORS для всех маршрутов и методов
this.webApp.use(cors());
this.webApp.options("*", cors());
//WEB-сервер
this.srv = null;
//Параметры подключения к Kafka
@ -124,22 +123,34 @@ class InQueue extends EventEmitter {
let optionsResp = {};
//Флаг прекращения обработки сообщения
let bStopPropagation = false;
//Нормализация сообщения
const toMessageBuffer = body => {
if (body === undefined || body === null) return null;
if (Buffer.isBuffer(body)) return body.length ? body : null;
if (body instanceof Uint8Array) return body.length ? Buffer.from(body) : null;
if (typeof body === "string") return body.length ? Buffer.from(body, "utf8") : null;
if (typeof body === "object") {
if (Object.keys(body).length === 0) return null;
return Buffer.from(JSON.stringify(body));
}
return Buffer.from(String(body));
};
//Определимся с телом сообщения - для POST, PATCH и PUT сообщений - это тело запроса
if (
[objServiceFnSchema.NFN_PRMS_TYPE_POST, objServiceFnSchema.NFN_PRMS_TYPE_PATCH, objServiceFnSchema.NFN_PRMS_TYPE_PUT].includes(
prms.function.nFnPrmsType
)
) {
blMsg = prms.req.body && !_.isEmpty(prms.req.body) ? prms.req.body : null;
blMsg = toMessageBuffer(prms.req.body);
} else {
//Для GET, HEAD, DELETE, CONNECT, OPTIONS и TRACE - параметры запроса
if (!_.isEmpty(prms.req.query)) blMsg = Buffer.from(JSON.stringify(prms.req.query));
if (!(Object.keys(prms.req.query ?? {}).length === 0)) blMsg = Buffer.from(JSON.stringify(prms.req.query));
}
//Определимся с параметрами сообщения полученными от внешней системы
options = {
method: prms.req.method,
qs: _.cloneDeep(prms.req.query),
headers: _.cloneDeep(prms.req.headers),
qs: deepClone(prms.req.query),
headers: deepClone(prms.req.headers),
ip: prms.req.ip,
hostName: prms.req.hostname,
protocol: prms.req.protocol,
@ -173,11 +184,11 @@ class InQueue extends EventEmitter {
const fnBefore = getAppSrvFunction(prms.function.sAppSrvBefore);
let resBefore = null;
try {
let resBeforePrms = _.cloneDeep(prms);
resBeforePrms.queue = _.cloneDeep(q);
let resBeforePrms = deepClone(prms);
resBeforePrms.queue = deepClone(q);
resBeforePrms.queue.blMsg = blMsg;
resBeforePrms.queue.blResp = blResp;
resBeforePrms.options = _.cloneDeep(options);
resBeforePrms.options = deepClone(options);
resBeforePrms.dbConn = this.dbConn;
resBeforePrms.notifier = this.notifier;
resBeforePrms.res = prms.res;
@ -200,7 +211,7 @@ class InQueue extends EventEmitter {
nExecState: objQueueSchema.NQUEUE_EXEC_STATE_APP_OK
});
//Фиксируем результат исполнения "До" - обработанный запрос внешней системы
if (!_.isUndefined(resBefore.blMsg)) {
if (resBefore.blMsg !== undefined) {
blMsg = resBefore.blMsg;
q = await this.dbConn.setQueueMsg({
nQueueId: q.nId,
@ -208,7 +219,7 @@ class InQueue extends EventEmitter {
});
}
//Фиксируем результат исполнения "До" - ответ на запрос
if (!_.isUndefined(resBefore.blResp)) {
if (resBefore.blResp !== undefined) {
blResp = resBefore.blResp;
q = await this.dbConn.setQueueResp({
nQueueId: q.nId,
@ -217,16 +228,16 @@ class InQueue extends EventEmitter {
});
}
//Фиксируем результат исполнения "До" - параметры ответа на запрос
if (!_.isUndefined(resBefore.optionsResp)) {
if (resBefore.optionsResp !== undefined) {
optionsResp = deepMerge(optionsResp, resBefore.optionsResp);
let sOptionsResp = buildOptionsXML({ options: optionsResp });
q = await this.dbConn.setQueueOptionsResp({ nQueueId: q.nId, sOptionsResp });
}
//Фиксируем результат исполнения "До" - флаг ошибочной аутентификации - если он поднят, то это ошибка, дальше ничего не делаем
if (!_.isUndefined(resBefore.bUnAuth) && resBefore.bUnAuth === true)
if (resBefore.bUnAuth !== undefined && resBefore.bUnAuth === true)
throw new ServerError(SERR_UNAUTH, "Нет аутентификации");
//Фиксируем результат исполнения "До" - флаг прекращения дальнейшей обработки сообщения - если он поднят, то дальше обработку сообщения прекращаем
if (!_.isUndefined(resBefore.bStopPropagation) && resBefore.bStopPropagation === true) bStopPropagation = true;
if (resBefore.bStopPropagation !== undefined && resBefore.bStopPropagation === true) bStopPropagation = true;
} else {
//Или расскажем об ошибке
throw new ServerError(SERR_OBJECT_BAD_INTERFACE, sCheckResult);
@ -281,12 +292,12 @@ class InQueue extends EventEmitter {
const fnAfter = getAppSrvFunction(prms.function.sAppSrvAfter);
let resAfter = null;
try {
let resAfterPrms = _.cloneDeep(prms);
resAfterPrms.queue = _.cloneDeep(q);
let resAfterPrms = deepClone(prms);
resAfterPrms.queue = deepClone(q);
resAfterPrms.queue.blMsg = blMsg;
resAfterPrms.queue.blResp = blResp;
resAfterPrms.options = _.cloneDeep(options);
resAfterPrms.optionsResp = _.cloneDeep(optionsResp);
resAfterPrms.options = deepClone(options);
resAfterPrms.optionsResp = deepClone(optionsResp);
resAfterPrms.dbConn = this.dbConn;
resAfterPrms.notifier = this.notifier;
resAfter = await fnAfter(resAfterPrms);
@ -308,7 +319,7 @@ class InQueue extends EventEmitter {
nExecState: objQueueSchema.NQUEUE_EXEC_STATE_APP_OK
});
//Фиксируем результат исполнения "После" - ответ системы
if (!_.isUndefined(resAfter.blResp)) {
if (resAfter.blResp !== undefined) {
blResp = resAfter.blResp;
q = await this.dbConn.setQueueResp({
nQueueId: q.nId,
@ -317,13 +328,13 @@ class InQueue extends EventEmitter {
});
}
//Фиксируем результат исполнения "После" - параметры ответа на запрос
if (!_.isUndefined(resAfter.optionsResp)) {
if (resAfter.optionsResp !== undefined) {
optionsResp = deepMerge(optionsResp, resAfter.optionsResp);
let sOptionsResp = buildOptionsXML({ options: optionsResp });
q = await this.dbConn.setQueueOptionsResp({ nQueueId: q.nId, sOptionsResp });
}
//Если пришел флаг ошибочной аутентификации и он положительный - то это ошибка, дальше ничего не делаем
if (!_.isUndefined(resAfter.bUnAuth))
if (resAfter.bUnAuth !== undefined)
if (resAfter.bUnAuth === true) throw new ServerError(SERR_UNAUTH, "Нет аутентификации");
} else {
//Или расскажем об ошибке
@ -576,16 +587,18 @@ class InQueue extends EventEmitter {
next();
});
//Конфигурируем сервер - обработка тела сообщения
this.webApp.use(bodyParser.raw({ limit: `${this.inComing.nMsgMaxSize}mb`, type: "*/*" }));
this.webApp.use(express.json());
this.webApp.use(express.urlencoded({ extended: true }));
this.webApp.use(express.raw({ limit: `${this.inComing.nMsgMaxSize}mb`, type: "*/*" }));
//Конфигурируем сервер - обходим все сервисы, работающие на приём сообщений
_.forEach(
_.filter(this.services, srv => {
this.services
.filter(srv => {
return (
srv.nSrvType === objServiceSchema.NSRV_TYPE_RECIVE &&
[objServiceSchema.SPROTOCOL_HTTP, objServiceSchema.SPROTOCOL_HTTPS].includes(getURLProtocol(srv.sSrvRoot))
);
}),
srvs => {
})
.forEach(srvs => {
//Для любых запросов к корневому адресу сервиса - ответ о том, что это за сервис, и что он работает
this.webApp.all(srvs.sSrvRoot, (req, res) => {
res.status(200).send(
@ -593,19 +606,18 @@ class InQueue extends EventEmitter {
);
});
//Для всех статических функций сервиса...
_.forEach(
_.filter(srvs.functions, fn => fn.sFnURL.startsWith("@")),
fn => {
srvs.functions
.filter(fn => fn.sFnURL.startsWith("@"))
.forEach(fn => {
this.webApp.use(
buildURL({ sSrvRoot: srvs.sSrvRoot, sFnURL: fn.sFnURL.substr(1) }),
express.static(`${this.inComing.sStaticDir}/${fn.sFnURL.substr(1)}`)
);
}
);
});
//Для всех функций сервиса (кроме статических)...
_.forEach(
_.filter(srvs.functions, fn => !fn.sFnURL.startsWith("@")),
fn => {
srvs.functions
.filter(fn => !fn.sFnURL.startsWith("@"))
.forEach(fn => {
//...собственный обработчик, в зависимости от указанного способа передачи параметров
this.webApp[fn.sFnPrmsType.toLowerCase()](buildURL({ sSrvRoot: srvs.sSrvRoot, sFnURL: fn.sFnURL }), async (req, res) => {
try {
@ -635,10 +647,8 @@ class InQueue extends EventEmitter {
//Отправим ошибку клиенту
res.status(err.status || 500).send(makeErrorText(new ServerError(SERR_WEB_SERVER, err.message)));
});
}
);
}
);
});
});
//Инициализируем настройки подключения
let connectionSettings = null;
//Считываем прием сообщений по Kafka
@ -713,7 +723,7 @@ class InQueue extends EventEmitter {
}
}
//Запросы на адреса, не входящие в состав объявленных сервисов - 404 NOT FOUND
this.webApp.use("*", (req, res) => {
this.webApp.use((req, res) => {
res.status(404).send(
`<html><body><center><br><h1>Сервер приложений ПП Парус 8<br>(${this.common.sVersion} релиз ${this.common.sRelease})</h1><h3>Запрошенный адрес не найден</h3></center></body></html>`
);

View File

@ -7,14 +7,9 @@
// Подключение библиотек
//----------------------
const _ = require("lodash"); //Работа с массивами и объектами
const { validateObject, getNowString } = require("./utils"); //Вспомогательные функции
const { validateObject, getNowString, deepClone } = require("./utils"); //Вспомогательные функции
const db = require("./db_connector"); //Модуль взаимодействия с БД
const {
SCONSOLE_LOG_COLOR_PATTERN_ERR,
SCONSOLE_LOG_COLOR_PATTERN_WRN,
SCONSOLE_LOG_COLOR_PATTERN_INF
} = require("./constants"); //Общие константы
const { SCONSOLE_LOG_COLOR_PATTERN_ERR, SCONSOLE_LOG_COLOR_PATTERN_WRN, SCONSOLE_LOG_COLOR_PATTERN_INF } = require("./constants"); //Общие константы
const { NLOG_STATE_INF, NLOG_STATE_WRN, NLOG_STATE_ERR } = require("../models/obj_log"); //Схемы валидации записи журнала работы сервиса обмена
const prmsLoggerSchema = require("../models/prms_logger"); //Схемы валидации параметров функций модуля
@ -97,8 +92,7 @@ class Logger {
//Протоколирование ошибки
async error(sMsg, prms) {
//Подготовим параметры для протоколирования
let logData = {};
if (prms) logData = _.cloneDeep(prms);
let logData = prms ? deepClone(prms) : {};
//Выставим сообщение и тип записи журнала
logData.nLogState = NLOG_STATE_ERR;
logData.sMsg = sMsg;
@ -108,8 +102,7 @@ class Logger {
//Протоколирование предупреждения
async warn(sMsg, prms) {
//Подготовим параметры для протоколирования
let logData = {};
if (prms) logData = _.cloneDeep(prms);
let logData = prms ? deepClone(prms) : {};
//Выставим сообщение и тип записи журнала
logData.nLogState = NLOG_STATE_WRN;
logData.sMsg = sMsg;
@ -119,8 +112,7 @@ class Logger {
//Протоколирование информации
async info(sMsg, prms) {
//Подготовим параметры для протоколирования
let logData = {};
if (prms) logData = _.cloneDeep(prms);
let logData = prms ? deepClone(prms) : {};
//Выставим сообщение и тип записи журнала
logData.nLogState = NLOG_STATE_INF;
logData.sMsg = sMsg;

View File

@ -7,11 +7,10 @@
// Подключение внешних библиотек
//------------------------------
const _ = require("lodash"); //Работа с массивами и коллекциями
const EventEmitter = require("events"); //Обработчик пользовательских событий
const { ServerError } = require("./server_errors"); //Типовая ошибка
const { SERR_OBJECT_BAD_INTERFACE, SERR_MAIL_FAILED } = require("./constants"); //Общесистемные константы
const { makeErrorText, validateObject, sendMail } = require("./utils"); //Вспомогательные функции
const { makeErrorText, validateObject, sendMail, deepClone } = require("./utils"); //Вспомогательные функции
const prmsNotifierSchema = require("../models/prms_notifier"); //Схемы валидации параметров функций класса
//--------------------------
@ -65,21 +64,15 @@ class Notifier extends EventEmitter {
//Добавление уведомления в очередь отправки
async addMessage(prms) {
//Проверяем структуру переданного объекта для старта
let sCheckResult = validateObject(
prms,
prmsNotifierSchema.addMessage,
"Параметры функции добавления уведомления в очередь отправки"
);
let sCheckResult = validateObject(prms, prmsNotifierSchema.addMessage, "Параметры функции добавления уведомления в очередь отправки");
//Если структура объекта в норме
if (!sCheckResult) {
let tmp = _.cloneDeep(prms);
let tmp = deepClone(prms);
tmp.bSent = false;
this.messages.push(tmp);
} else {
await this.logger.error(
`Ошибка добавления уведомления в очередь: ${makeErrorText(
new ServerError(SERR_OBJECT_BAD_INTERFACE, sCheckResult)
)}`
`Ошибка добавления уведомления в очередь: ${makeErrorText(new ServerError(SERR_OBJECT_BAD_INTERFACE, sCheckResult))}`
);
}
}
@ -113,7 +106,13 @@ class Notifier extends EventEmitter {
if (!message.bSent) {
try {
//Если всё в порядке с настройками
if (this.mail.sHost && this.mail.nPort && this.mail.sFrom && this.mail.hasOwnProperty('bSecure') && this.mail.hasOwnProperty('bRejectUnauthorized')) {
if (
this.mail.sHost &&
this.mail.nPort &&
this.mail.sFrom &&
this.mail.hasOwnProperty("bSecure") &&
this.mail.hasOwnProperty("bRejectUnauthorized")
) {
//Отправляем
await sendMail({
mail: this.mail,
@ -135,16 +134,12 @@ class Notifier extends EventEmitter {
);
}
} catch (e) {
await this.logger.error(
`Ошибка отправки сообщения с темой "${message.sSubject}" для ${message.sTo}: ${makeErrorText(
e
)}`
);
await this.logger.error(`Ошибка отправки сообщения с темой "${message.sSubject}" для ${message.sTo}: ${makeErrorText(e)}`);
}
}
}
//Подчищаем очередь - удалим уже отправленные
_.remove(this.messages, { bSent: true });
this.messages = this.messages.filter(msg => !msg.bSent);
//Выставим флаг - цикл опроса неактивен
this.bInSendLoop = false;
//Перезапускаем опрос

View File

@ -7,12 +7,11 @@
// Подключение внешних библиотек
//------------------------------
const _ = require("lodash"); //Работа с массивами и коллекциями
const EventEmitter = require("events"); //Обработчик пользовательских событий
const ChildProcess = require("child_process"); //Работа с дочерними процессами
const { ServerError } = require("./server_errors"); //Типовая ошибка
const { SERR_OBJECT_BAD_INTERFACE } = require("./constants"); //Общесистемные константы
const { makeErrorText, validateObject } = require("./utils"); //Вспомогательные функции
const { makeErrorText, validateObject, deepClone } = require("./utils"); //Вспомогательные функции
const { NINC_EXEC_CNT_YES, NINC_EXEC_CNT_NO } = require("../models/prms_db_connector"); //Схемы валидации параметров функций модуля взаимодействия с БД
const objOutQueueProcessorSchema = require("../models/obj_out_queue_processor"); //Схемы валидации сообщений обмена с обработчиком сообщения очереди
const { NFORCE_YES } = require("../models/common"); //Общие константы и схемы валидации
@ -53,7 +52,7 @@ class OutQueue extends EventEmitter {
//Признак функционирования обработчика
this.bWorking = false;
//Параметры очереди
this.outGoing = _.cloneDeep(prms.outGoing);
this.outGoing = deepClone(prms.outGoing);
//Количество доступных обработчиков
this.nWorkersLeft = this.outGoing.nMaxWorkers;
//Идентификатор таймера проверки очереди
@ -150,6 +149,8 @@ class OutQueue extends EventEmitter {
if (!sCheckResult) {
//Добавляем идентификатор позиции очереди в список обрабатываемых
this.addInProgress({ nQueueId: prms.queue.nId });
//Найдем сервис
const service = this.services.find(s => s.nId === prms.queue.nServiceId);
//Отдаём команду дочернему процессу обработчика на старт исполнения
prms.proc.send({
nQueueId: prms.queue.nId,
@ -159,10 +160,8 @@ class OutQueue extends EventEmitter {
nPoolMax: this.outGoing.nPoolMax,
nPoolIncrement: this.outGoing.nPoolIncrement
},
service: _.find(this.services, { nId: prms.queue.nServiceId }),
function: _.find(_.find(this.services, { nId: prms.queue.nServiceId }).functions, {
nId: prms.queue.nServiceFnId
}),
service: service,
function: service?.functions.find(f => f.nId === prms.queue.nServiceFnId),
sProxy: this.sProxy,
kafka: this.kafka,
mqtt: this.mqtt
@ -201,10 +200,8 @@ class OutQueue extends EventEmitter {
//Если структура объекта в норме
if (!sCheckResult) {
//Найдем сервис и функцию, исполнявшие данное сообщение
let service = _.find(this.services, { nId: prms.queue.nServiceId });
let func = _.find(_.find(this.services, { nId: prms.queue.nServiceId }).functions, {
nId: prms.queue.nServiceFnId
});
let service = this.services.find(s => s.nId === prms.queue.nServiceId);
let func = service?.functions.find(f => f.nId === prms.queue.nServiceFnId);
//Если нашли и для функции-обработчика указан признак необходимости оповещения об ошибках
if (service && func && func.nErrNtfSign == objServiceFnSchema.NERR_NTF_SIGN_YES)
//Отправим уведомление об ошибке отработки в почту

View File

@ -7,9 +7,7 @@
// Подключение библиотек
//----------------------
require("module-alias/register"); //Поддержка псевонимов при подключении модулей
const _ = require("lodash"); //Работа с массивами и объектами
const rqp = require("request-promise"); //Работа с HTTP/HTTPS запросами
const { httpRequest } = require("./http_client"); //Работа с HTTP/HTTPS запросами
const lg = require("./logger"); //Протоколирование работы
const db = require("./db_connector"); //Взаимодействие с БД
const {
@ -20,6 +18,7 @@ const {
parseOptionsXML,
buildOptionsXML,
deepMerge,
deepClone,
getKafkaConnectionSettings,
getMQTTConnectionSettings,
getKafkaBroker,
@ -137,9 +136,9 @@ const appProcess = async prms => {
//Флаг установленности контекста для функции начала сеанса
let bCtxIsSet = false;
//Кладём данные тела в объект сообщения и инициализируем поле для ответа
_.extend(prms.queue, { blMsg: qData.blMsg, blResp: null });
Object.assign(prms.queue, { blMsg: qData.blMsg, blResp: null });
//Кладём данные контекста в сервис
_.extend(prms.service, serviceCtx);
Object.assign(prms.service, serviceCtx);
//Собираем параметры для передачи серверу
let options = { method: prms.function.sFnPrmsType, encoding: null };
//Инициализируем параметры ответа сервера
@ -238,8 +237,8 @@ const appProcess = async prms => {
const fnBefore = getAppSrvFunction(prms.function.sAppSrvBefore);
let resBefore = null;
try {
let resBeforePrms = _.cloneDeep(prms);
resBeforePrms.options = _.cloneDeep(options);
let resBeforePrms = deepClone(prms);
resBeforePrms.options = deepClone(options);
resBeforePrms.dbConn = dbConn;
resBefore = await fnBefore(resBeforePrms);
} catch (e) {
@ -255,7 +254,7 @@ const appProcess = async prms => {
//Если структура ответа в норме
if (!sCheckResult) {
//Применим ответ "До" - обработанное сообщение очереди
if (!_.isUndefined(resBefore.blMsg)) {
if (resBefore.blMsg !== undefined) {
prms.queue.blMsg = resBefore.blMsg;
await dbConn.setQueueMsg({
nQueueId: prms.queue.nId,
@ -281,14 +280,14 @@ const appProcess = async prms => {
}
}
//Применим ответ "До" - параметры отправки сообщения удаленному серверу
if (!_.isUndefined(resBefore.options)) options = deepMerge(options, resBefore.options);
if (resBefore.options !== undefined) options = deepMerge(options, resBefore.options);
//Применим ответ "До" - флаг отсуствия аутентификации
if (!_.isUndefined(resBefore.bUnAuth))
if (resBefore.bUnAuth !== undefined)
if (resBefore.bUnAuth === true) {
throw new ServerError(SERR_UNAUTH, "Нет аутентификации");
}
//Применим ответ "До" - контекст работы сервиса
if (!_.isUndefined(resBefore.sCtx))
if (resBefore.sCtx !== undefined)
if (prms.function.nFnType == objServiceFnSchema.NFN_TYPE_LOGIN) {
prms.service.sCtx = resBefore.sCtx;
prms.service.dCtxExp = resBefore.dCtxExp;
@ -300,7 +299,7 @@ const appProcess = async prms => {
bCtxIsSet = true;
}
//Применим ответ "До" - флаг прекращения дальнейшей обработки сообщения - если он поднят, то дальше обработку сообщения прекращаем
if (!_.isUndefined(resBefore.bStopPropagation) && resBefore.bStopPropagation === true) bStopPropagation = true;
if (resBefore.bStopPropagation !== undefined && resBefore.bStopPropagation === true) bStopPropagation = true;
} else {
//Или расскажем об ошибке в структуре ответа
throw new ServerError(SERR_OBJECT_BAD_INTERFACE, sCheckResult);
@ -317,7 +316,7 @@ const appProcess = async prms => {
try {
//Сохраняем параметры с которыми уходило сообщение
try {
let tmpOptions = _.cloneDeep(options);
let tmpOptions = deepClone(options);
//Исключим из параметров заведомо бинарные поля (их сохранение не предусмотрено)
delete tmpOptions.body;
delete tmpOptions.cert;
@ -366,8 +365,13 @@ const appProcess = async prms => {
if (prms.function.nTimeoutConn && !options.timeout) options.timeout = prms.function.nTimeoutConn;
//Отправляем запрос
serverResp = prms.function.nTimeoutAsynch
? await wrapPromiseTimeout(prms.function.nTimeoutAsynch, rqp(options))
: await rqp(options);
? await wrapPromiseTimeout(prms.function.nTimeoutAsynch, signal =>
httpRequest({
...options,
signal
})
)
: await httpRequest(options);
break;
}
//Сохраняем полученный ответ
@ -378,7 +382,7 @@ const appProcess = async prms => {
nIsOriginal: NIS_ORIGINAL_YES
});
//Сохраняем заголовки ответа и HTTP-статус
optionsResp.headers = _.cloneDeep(serverResp.headers);
optionsResp.headers = deepClone(serverResp.headers);
optionsResp.statusCode = serverResp.statusCode;
try {
let sOptionsResp = buildOptionsXML({ options: optionsResp });
@ -403,9 +407,9 @@ const appProcess = async prms => {
const fnAfter = getAppSrvFunction(prms.function.sAppSrvAfter);
let resAfter = null;
try {
let resAfterPrms = _.cloneDeep(prms);
resAfterPrms.options = _.cloneDeep(options);
resAfterPrms.optionsResp = _.cloneDeep(optionsResp);
let resAfterPrms = deepClone(prms);
resAfterPrms.options = deepClone(options);
resAfterPrms.optionsResp = deepClone(optionsResp);
resAfter = await fnAfter(resAfterPrms);
} catch (e) {
throw new ServerError(SERR_APP_SERVER_AFTER, e.message);
@ -420,7 +424,7 @@ const appProcess = async prms => {
//Если структура ответа в норме
if (!sCheckResult) {
//Применим ответ "После" - обработанный ответ удаленного сервиса
if (!_.isUndefined(resAfter.blResp)) {
if (resAfter.blResp !== undefined) {
prms.queue.blResp = resAfter.blResp;
await dbConn.setQueueResp({
nQueueId: prms.queue.nId,
@ -429,10 +433,10 @@ const appProcess = async prms => {
});
}
//Применим ответ "После" - флаг утентификации сервиса
if (!_.isUndefined(resAfter.bUnAuth))
if (resAfter.bUnAuth !== undefined)
if (resAfter.bUnAuth === true) throw new ServerError(SERR_UNAUTH, "Нет аутентификации");
//Применим ответ "После" - контекст работы сервиса
if (!_.isUndefined(resAfter.sCtx))
if (resAfter.sCtx !== undefined)
if (prms.function.nFnType == objServiceFnSchema.NFN_TYPE_LOGIN) {
prms.service.sCtx = resAfter.sCtx;
prms.service.dCtxExp = resAfter.dCtxExp;

View File

@ -7,12 +7,11 @@
// Подключение внешних библиотек
//------------------------------
const _ = require("lodash"); //Работа с массивами и коллекциями
const rqp = require("request-promise"); //Работа с HTTP/HTTPS запросами
const { httpRequest } = require("./http_client"); //Работа с HTTP/HTTPS запросами
const EventEmitter = require("events"); //Обработчик пользовательских событий
const { ServerError } = require("./server_errors"); //Типовая ошибка
const { SERR_SERVICE_UNAVAILABLE, SERR_OBJECT_BAD_INTERFACE } = require("./constants"); //Общесистемные константы
const { makeErrorText, validateObject } = require("./utils"); //Вспомогательные функции
const { makeErrorText, validateObject, deepClone } = require("./utils"); //Вспомогательные функции
const prmsServiceAvailableControllerSchema = require("../models/prms_service_available_controller"); //Схемы валидации параметров функций класса
const objServiceSchema = require("../models/obj_service"); //Схемы валидации сервисов
@ -107,10 +106,7 @@ class ServiceAvailableController extends EventEmitter {
//Обходим список сервисов для проверки
for (let service of this.services) {
//Если сервис надо проверять на доступность и это сервис для отправки исходящих сообщений
if (
service.nUnavlblNtfSign == objServiceSchema.NUNAVLBL_NTF_SIGN_YES &&
service.nSrvType == objServiceSchema.NSRV_TYPE_SEND
) {
if (service.nUnavlblNtfSign == objServiceSchema.NUNAVLBL_NTF_SIGN_YES && service.nSrvType == objServiceSchema.NSRV_TYPE_SEND) {
try {
// Инициализируем параметры запроса
let options = {};
@ -123,7 +119,7 @@ class ServiceAvailableController extends EventEmitter {
options.proxy = service.sProxyURL ?? this.sProxy;
}
//Отправляем проверочный запрос
await rqp(options);
await httpRequest(options);
//Запрос прошел - фиксируем дату доступности и сбрасываем дату недоступности
service.dAvailable = new Date();
service.dUnAvailable = null;
@ -193,10 +189,9 @@ class ServiceAvailableController extends EventEmitter {
});
}
} catch (e) {
await this.logger.error(
`При проверке просроченных сообщений сервиса ${service.sCode}: ${makeErrorText(e)}`,
{ nServiceId: service.nId }
);
await this.logger.error(`При проверке просроченных сообщений сервиса ${service.sCode}: ${makeErrorText(e)}`, {
nServiceId: service.nId
});
}
}
}
@ -232,7 +227,7 @@ class ServiceAvailableController extends EventEmitter {
//Выставляем флаг неактивности (пока) цикла опроса
this.bInDetectingLoop = false;
//запоминаем список обслуживаемых сервисов и инициализируем даты доступности и недоступности
this.services = _.cloneDeep(prms.services);
this.services = deepClone(prms.services);
this.services.forEach(s => {
s.dUnAvailable = null;
s.dAvailable = new Date();

View File

@ -8,7 +8,6 @@
//----------------------
const fs = require("fs"); //Работа с файлами
const _ = require("lodash"); //Работа с массивами и объектами
const os = require("os"); //Средства операционной системы
const xml2js = require("xml2js"); //Конвертация XML в JSON
const Schema = require("validate"); //Схемы валидации
@ -29,6 +28,95 @@ const { SPROTOCOL_HTTP, SPROTOCOL_KAFKA } = require("../models/obj_service"); //
// Тело модуля
//------------
//Глубокое клонирование
const deepClone = value => {
//Буфер для фиксации "клонированных" элементов исходного объекта
const seen = new WeakMap();
//Функция рекурсивного клонирования (разбирает тип полученного значения и в зависимости от него выполняет клонирование, сохраняя прототипы)
const clone = val => {
//Примитивы и функции возвращаем как есть
if (val === null || typeof val !== "object") return val;
if (typeof val === "function") return val;
//Защита от циклических ссылок
if (seen.has(val)) return seen.get(val);
//Специализированные типы
if (val instanceof Date) return new Date(val);
if (Buffer.isBuffer(val)) return Buffer.from(val);
if (ArrayBuffer.isView(val)) return new val.constructor(val);
if (val instanceof RegExp) return new RegExp(val);
//Ошибки: сохраняем тип, сообщение, стек и пользовательские поля
if (val instanceof Error) {
const clonedError = new val.constructor(val.message);
seen.set(val, clonedError);
clonedError.name = val.name;
clonedError.stack = val.stack;
Reflect.ownKeys(val).forEach(key => {
if (key === "name" || key === "message" || key === "stack") return;
const desc = Object.getOwnPropertyDescriptor(val, key);
if (!desc) return;
if ("value" in desc) desc.value = clone(desc.value);
try {
Object.defineProperty(clonedError, key, desc);
} catch (e) {}
});
return clonedError;
}
//Коллекции
if (val instanceof Map) {
const m = new Map();
seen.set(val, m);
val.forEach((v, k) => m.set(clone(k), clone(v)));
return m;
}
if (val instanceof Set) {
const s = new Set();
seen.set(val, s);
val.forEach(v => s.add(clone(v)));
return s;
}
//Коллекции, которые невозможно корректно клонировать - возвращаем по ссылке
if (val instanceof WeakMap || val instanceof WeakSet || val instanceof Promise) {
return val;
}
//Массивы
if (Array.isArray(val)) {
const arr = new Array(val.length);
seen.set(val, arr);
for (let i = 0; i < val.length; i++) {
if (Object.prototype.hasOwnProperty.call(val, i)) {
arr[i] = clone(val[i]);
}
}
return arr;
}
//Общие объекты и пользовательские классы: сохраняем прототип и дескрипторы свойств
const proto = Object.getPrototypeOf(val);
const obj = Object.create(proto);
seen.set(val, obj);
Reflect.ownKeys(val).forEach(key => {
const desc = Object.getOwnPropertyDescriptor(val, key);
if (!desc) return;
if ("value" in desc) {
desc.value = clone(desc.value);
}
try {
Object.defineProperty(obj, key, desc);
} catch (e) {}
});
return obj;
};
//Выполняем копирование рекурсивно с верхнего уровня
try {
//Возвращаем что получилось
return clone(value);
} catch (e) {
//В случае непредвиденной ошибки формируем исключение
const err = new Error("Ошибка глубокого копирования объекта");
err.originalError = e;
throw err;
}
};
//Валидация объекта
const validateObject = (obj, schema, sObjName) => {
//Объявим результат
@ -38,7 +126,7 @@ const validateObject = (obj, schema, sObjName) => {
//И есть что проверять
if (obj) {
//Сделаем это
const objTmp = _.cloneDeep(obj);
const objTmp = deepClone(obj);
const errors = schema.validate(objTmp, { strip: false });
//Если есть ошибки
if (errors && Array.isArray(errors)) {
@ -47,7 +135,7 @@ const validateObject = (obj, schema, sObjName) => {
let a = errors.map(e => {
return e.message;
});
sRes = `Объект${sObjName ? ` "${sObjName}" ` : " "}имеет некорректный формат: ${_.uniq(a).join("; ")}`;
sRes = `Объект${sObjName ? ` "${sObjName}" ` : " "}имеет некорректный формат: ${Array.from(new Set(a)).join("; ")}`;
}
} else {
//Валидатор вернул не то, что мы ожидали
@ -322,10 +410,32 @@ const getNowString = () => {
};
//Глубокое слияние объектов
const deepMerge = (...args) => {
let res = {};
for (let i = 0; i < args.length; i++) _.merge(res, args[i]);
return res;
const deepMerge = (...sources) => {
//Проверка на простой объект (не имеет специального поведения или прототипа)
const isPlainObject = value => Object.prototype.toString.call(value) === "[object Object]";
//Клоникарование значения
const cloneValue = value => deepClone(value);
//Буфер для результата
const target = {};
//Обходим входные параметры по очереди
for (const source of sources) {
//Пропускаем непростые объекты - сливать можем только простые
if (!isPlainObject(source)) continue;
//Обходим пары "ключ-значение"
for (const [key, value] of Object.entries(source)) {
//Если значение ключа - объект
if (isPlainObject(value)) {
//Сливаем рекурсивно
if (!isPlainObject(target[key])) target[key] = {};
target[key] = deepMerge(target[key], value);
} else {
//Примитивы - просто клонируем
target[key] = cloneValue(value);
}
}
}
//Возвращаем собранный буфер, содержащий все слитые воедино объекты-параметры
return target;
};
//Глубокое копирование объекта
@ -404,17 +514,32 @@ const getURLProtocol = sURL => {
};
//Обёртывание промиса в таймаут исполнения
const wrapPromiseTimeout = (timeout, promise, promiseCancellable = true) => {
if (!timeout) return promise;
const wrapPromiseTimeout = (timeout, executor) => {
//Проверяем входные параметры - должен быть указан ненулевой таймаут и функция-исполнитель промиса
if (!timeout || typeof executor !== "function") {
//Разрешаем сразу, не ожидая
return executor ? executor() : Promise.resolve();
}
//Параметры прошли проверку, создаем экхемпляр контроллера прерывания асинхронного процесса
const controller = new AbortController();
//Подготовим объект для выбрасывания ошибки
const sMessage = `Истёк интервал ожидания (${timeout} мс) завершения асинхронного процесса.`;
const timeoutError = new Error(sMessage);
timeoutError.error = sMessage;
//Буфер для идентификатора таймера
let timeoutPid;
const timeoutPromise = new Promise((resolve, reject) => {
const sMessage = `Истёк интервал ожидания (${timeout} мс) завершения асинхронного процесса.`;
let e = new Error(sMessage);
e.error = sMessage;
timeoutPid = setTimeout(() => reject(e), timeout);
//Создаём промис с взведенным таумером на у казанный таймаут, промис будет "отклонён" по истечении таймера, но перед этим контроллер прерывания выдаст "сигнал" на отмену осинхронного процесса
const timeoutPromise = new Promise((_, reject) => {
timeoutPid = setTimeout(() => {
controller.abort(timeoutError);
reject(timeoutError);
}, timeout);
});
return Promise.race([promise, timeoutPromise]).finally(() => {
if (promiseCancellable && promise.promise().isPending()) promise.cancel();
//Создаём промис с функцией-исполнителем асинхронного процесса
const taskPromise = Promise.resolve(executor(controller.signal));
//Запускаем оба промиса в режиме "гонка" - кто "быстрее" исполнится
return Promise.race([taskPromise, timeoutPromise]).finally(() => {
//В любом случае не забываем зачистить взведённый таймер
if (timeoutPid) clearTimeout(timeoutPid);
});
};
@ -437,6 +562,7 @@ exports.parseOptionsXML = parseOptionsXML;
exports.buildOptionsXML = buildOptionsXML;
exports.getNowString = getNowString;
exports.deepMerge = deepMerge;
exports.deepClone = deepClone;
exports.deepCopyObject = deepCopyObject;
exports.isUndefined = isUndefined;
exports.getKafkaConnectionSettings = getKafkaConnectionSettings;

View File

@ -7,7 +7,6 @@
// Подключение библиотек
//----------------------
require("module-alias/register"); //Поддержка псевонимов при подключении модулей
const cfg = require("./config"); //Настройки сервера приложений
const app = require("./core/app"); //Сервер приложений
const { makeErrorText, getNowString } = require("./core/utils"); //Вспомогательные функции
@ -24,16 +23,18 @@ let appSrv = new app.ParusAppServer(); //Экземпляр сервера пр
//----------------------------------------
//Разрешение на TLS (Transport Layer Security) без авторизации
process.env.NODE_TLS_REJECT_UNAUTHORIZED = cfg.outGoing.bValidateSSL === false ? "0" : "1";
process.env.NODE_TLS_REJECT_UNAUTHORIZED = cfg?.outGoing?.bValidateSSL === false ? "0" : "1";
//Включение "Токого режима" для Oracle
process.env.NODE_ORACLE_DB_THIN_MODE = cfg?.dbConnect?.bOraUseThinMode === true ? "1" : "0";
//Путь к клиентским библиотекам для Oracle
process.env.ORACLE_CLIENT_LIB_DIR = cfg?.dbConnect?.sOraClient || "";
//Обработка события "выход" жизненного цикла процесса
process.on("exit", code => {
//Сообщим о завершении процесса
console.log(
SCONSOLE_LOG_COLOR_PATTERN_WRN,
`${getNowString()} ПРЕДУПРЕЖДЕНИЕ: `,
`Сервер приложений остановлен (код: ${code})`
);
console.log(SCONSOLE_LOG_COLOR_PATTERN_WRN, `${getNowString()} ПРЕДУПРЕЖДЕНИЕ: `, `Сервер приложений остановлен (код: ${code})`);
});
//Обработка событий мягкого останова процесса

View File

@ -189,6 +189,24 @@ const dbConnect = new Schema({
type: path => `Наименование подключаемого модуля обслуживания БД (${path}) имеет некорректный тип данных (ожидалось - String)`,
required: path => `Не указано наименование подключаемого модуля обслуживания БД (${path})`
}
},
//Признак применения "тонкого" (Thin) или "толстого" (Thick) режима подключения к БД
bOraUseThinMode: {
type: Boolean,
required: true,
message: {
type: path => `Признак режима подключения (Thin/Thick) к БД (${path}) имеет некорректный тип данных (ожидалось - Boolean)`,
required: path => `Не указан признак режима подключения (Thin/Thick) к БД (${path})`
}
},
//Путь к домашней директории Oracle Client
sOraClient: {
type: String,
required: false,
message: {
type: path => `Путь к домашней директории Oracle Client (${path}) имеет некорректный тип данных (ожидалось - String)`,
required: path => `Не указан путь к домашней директории Oracle Client (${path})`
}
}
});

View File

@ -8,8 +8,7 @@
//------------------------------
const xml2js = require("xml2js"); //Конвертация XML в JSON и JSON в XML
const _ = require("lodash"); //Работа с коллекциями и объектами
const rqp = require("request-promise"); //Работа с HTTP/HTTPS запросами
const { httpRequest } = require("../core/http_client"); //Работа с HTTP/HTTPS запросами
const config = require("../config"); //Параметры сервера
const { SDDAUTH_API_CLIENT_ID, SDEPARTMENT_NAME, SDEPARTMENT_ID } = require("./diadoc_config"); //Ключ разработчика
@ -62,7 +61,7 @@ const toArray = (obj, tags) => {
if (typeof value === "object") {
obj[prop] = toArray(value, tag);
}
if (tags.indexOf(prop) != -1 && !_.isArray(obj[prop])) {
if (tags.indexOf(prop) != -1 && !Array.isArray(obj[prop])) {
obj[prop] = JSON.parse("[" + JSON.stringify(value) + "]");
}
}
@ -145,18 +144,18 @@ const getOrganizations = (organizations, sNumEdo = null) => {
//Задан код участника ЭДО
if (sNumEdo) {
//Найдем активную организацию не в роуминге
organization.Organizations[0] = organizations.Organizations.find(org => (org.FnsParticipantId === sNumEdo));
};
organization.Organizations[0] = organizations.Organizations.find(org => org.FnsParticipantId === sNumEdo);
}
//Не удалось получить организацию по коду участника ЭДО
if (!organization.Organizations[0]) {
//Найдем активную организацию не в роуминге
organization.Organizations[0] = organizations.Organizations.find(org => (org.IsRoaming === isRoaming) && (org.IsActive === isActive));
};
organization.Organizations[0] = organizations.Organizations.find(org => org.IsRoaming === isRoaming && org.IsActive === isActive);
}
//Не удалось получить организацию не в роуминге
if (!organization.Organizations[0]) {
//Найдем активную организацию
organization.Organizations[0] = organizations.Organizations.find(org => (org.IsActive === isActive));
};
organization.Organizations[0] = organizations.Organizations.find(org => org.IsActive === isActive);
}
//Если не удалось получить активную организацию
if (!organization.Organizations[0]) {
//Если нет организации не в роуминге и найдено более одной организации
@ -175,10 +174,10 @@ const getOrganizations = (organizations, sNumEdo = null) => {
//Получение организации по ИНН/КПП контрагента
const getOrganization = async (sSrvRoot, headers, nInn, nKpp) => {
//Параметры запроса
let rqpOptions;
let httpRequestOptions;
let serverResp;
//Формируем запрос для получения BoxId
rqpOptions = {
httpRequestOptions = {
uri: buildOrganizationURL(sSrvRoot),
qs: {
inn: nInn,
@ -188,18 +187,18 @@ const getOrganization = async (sSrvRoot, headers, nInn, nKpp) => {
json: true
};
//Выполним запрос
serverResp = { Organizations: [await rqp(rqpOptions)] };
serverResp = { Organizations: [await httpRequest(httpRequestOptions)] };
return serverResp;
};
//Получение ящика организации по ИНН/КПП контрагента
const getOrganizationBoxId = async (sSrvRoot, headers, nInn, nKpp, sNumEdo) => {
//Параметры запроса
let rqpOptions;
let httpRequestOptions;
let serverResp;
let organization = {};
//Формируем запрос для получения BoxId по ИНН/КПП
rqpOptions = {
httpRequestOptions = {
uri: buildOrganizationsByInnKppURL(sSrvRoot),
qs: {
inn: nInn,
@ -210,19 +209,21 @@ const getOrganizationBoxId = async (sSrvRoot, headers, nInn, nKpp, sNumEdo) => {
};
try {
//Выполним запрос
serverResp = await rqp(rqpOptions);
serverResp = await httpRequest(httpRequestOptions);
try {
//Получим организацию не в роуминге (или единственную организацию в роуминге)
serverResp = getOrganizations(serverResp, sNumEdo);
if (!serverResp?.Organizations[0]) {
throw Error(`Не удалось получить ящик получателя для контрагента с ${sNumEdo ? `кодом участника ЭДО: ${sNumEdo}, ` : ""}ИНН: ${nInn} и КПП: ${nKpp}`);
throw Error(
`Не удалось получить ящик получателя для контрагента с ${sNumEdo ? `кодом участника ЭДО: ${sNumEdo}, ` : ""}ИНН: ${nInn} и КПП: ${nKpp}`
);
}
} catch (e) {
//Получим головную организацию по ИНН/КПП
serverResp = await getOrganization(sSrvRoot, headers, nInn, nKpp);
};
}
//Проверим соответствие КПП организации
if ((serverResp?.Organizations[0]?.Kpp != nKpp) && (serverResp?.Organizations[0])) {
if (serverResp?.Organizations[0]?.Kpp != nKpp && serverResp?.Organizations[0]) {
//Если КПП не соответстует заданному - проверим, что в полученной организации есть департамент с заданным КПП
for (let i in serverResp.Organizations[0].Departments) {
//Если найден подходящий департамент - запомним идентификатор и выходим из цикла
@ -230,23 +231,25 @@ const getOrganizationBoxId = async (sSrvRoot, headers, nInn, nKpp, sNumEdo) => {
//Сохраняем полученный ответ
organization.DepartmentId = serverResp.Organizations[0].Departments[i].DepartmentId;
break;
};
};
};
}
}
}
//Не удалось получить ящик получателя или полученая организация не соответствует заданному ИНН
if ((!serverResp?.Organizations[0]?.Boxes[0]?.BoxId) || (serverResp?.Organizations[0]?.Inn != nInn)) {
if (!serverResp?.Organizations[0]?.Boxes[0]?.BoxId || serverResp?.Organizations[0]?.Inn != nInn) {
throw new Error(`Не удалось получить ящик получателя для контрагента с ИНН: ${nInn} и КПП: ${nKpp}`);
}
//Не удалось получить департаментом с соответствующим КПП
if ((serverResp?.Organizations[0]?.Kpp != nKpp) && (!organization?.DepartmentId)) {
throw new Error(`Не удалось получить ящик получателя для контрагента с ИНН: ${nInn} и КПП: ${nKpp}, у головной организации отсутствует подразделение с КПП: ${nKpp}`);
if (serverResp?.Organizations[0]?.Kpp != nKpp && !organization?.DepartmentId) {
throw new Error(
`Не удалось получить ящик получателя для контрагента с ИНН: ${nInn} и КПП: ${nKpp}, у головной организации отсутствует подразделение с КПП: ${nKpp}`
);
}
//Сохраняем полученный ответ
organization.BoxId = serverResp.Organizations[0].Boxes[0].BoxId;
return organization;
} catch (e) {
throw Error(`Ошибка при получении ящика получателя: ${e.message}`);
};
}
};
//Обработчик "До" подключения к сервису
@ -309,7 +312,7 @@ const beforeMessagePost = async prms => {
//Конвертируем XML из "Парус 8" в JSON
let obj = await toJSON(prms.queue.blMsg.toString());
//Формируем запрос для получения FromBoxId
let rqpOptions = {
let httpRequestOptions = {
uri: buildMyOrganizationURL(prms.service.sSrvRoot),
headers: buildHeaders(sAPIClientId, sToken),
json: true
@ -319,7 +322,7 @@ const beforeMessagePost = async prms => {
let organization;
try {
//Выполним запрос
serverResp = await rqp(rqpOptions);
serverResp = await httpRequest(httpRequestOptions);
//Получим идентификатор организации по ИНН/КПП поставщика документа
for (let i in serverResp.Organizations) {
//Если найдена подходящая организация - запомним идентификатор и выходим из цикла
@ -337,12 +340,18 @@ const beforeMessagePost = async prms => {
throw Error(`Ошибка при получении ящика текущей организации: ${e.message}`);
}
//Получим ящик получателя
organization = await getOrganizationBoxId(prms.service.sSrvRoot, buildHeaders(sAPIClientId, sToken), optionsData.inn_cs, optionsData.kpp_cs, optionsData.num_cs);
organization = await getOrganizationBoxId(
prms.service.sSrvRoot,
buildHeaders(sAPIClientId, sToken),
optionsData.inn_cs,
optionsData.kpp_cs,
optionsData.num_cs
);
obj.ToBoxId = organization.BoxId;
//Если не заполнен идентификатор подразделения и при получении ящика удалось его подобрать
if ((!obj.ToDepartmentId) && (organization.DepartmentId)) {
if (!obj.ToDepartmentId && organization.DepartmentId) {
obj.ToDepartmentId = organization.DepartmentId;
};
}
//Если пришел ответ
if (prms.queue.blResp && serverResp.statusCode == 200) {
//Вернем загруженный документ
@ -467,14 +476,14 @@ const beforeEvent = async prms => {
//Получим параметры запроса
const optionsData = await toJSON(prms.queue.sOptions);
//Формируем запрос для получения BoxId
let rqpOptions = {
let httpRequestOptions = {
uri: buildMyOrganizationURL(prms.service.sSrvRoot),
headers: buildHeaders(sAPIClientId, sToken),
json: true
};
try {
//Выполним запрос
serverResp = await rqp(rqpOptions);
serverResp = await httpRequest(httpRequestOptions);
//Получим идентификатор организации по ИНН/КПП контрагента организации
for (let i in serverResp.Organizations) {
//Если найдена подходящая организация - запомним идентификатор и выходим из цикла
@ -524,6 +533,10 @@ const beforeEvent = async prms => {
if (prms.options.sdepartment_name && sDepartmentId) {
surl = `${surl}&departmentId=${sDepartmentId}`;
}
//Заполним максимальное количество возвращаемых элементов
if (prms.options.limit) {
surl = `${surl}&limit=${prms.options.limit}`;
}
} else {
if (prms.queue.blMsg) {
//Конвертируем XML из "Парус 8" в понятный "ДИАДОК" JSON
@ -551,7 +564,7 @@ const afterEvent = async prms => {
let sAPIClientId = null; //Ключ разработчика
let sToken = null; //Токен доступа
let resu = null; //Ответ сервера
let rqpOptions = null; //Параметры для запроса информации по ящику
let httpRequestOptions = null; //Параметры для запроса информации по ящику
let serverResp; //Результат запроса информации по организации
let resp = null; //Ответ сервера
let box = null; //Информация ящика
@ -567,10 +580,10 @@ const afterEvent = async prms => {
//Получим список уникальных ящиков
for (let i in resp.Events) {
if (resp.Events[i]?.Message) {
if ((!boxIds.boxIds.find(box => box.boxId === resp.Events[i]?.Message.FromBoxId)) && (resp.Events[i]?.Message.FromBoxId)) {
if (!boxIds.boxIds.find(box => box.boxId === resp.Events[i]?.Message.FromBoxId) && resp.Events[i]?.Message.FromBoxId) {
boxIds.boxIds.push({ boxId: resp.Events[i]?.Message.FromBoxId });
}
if ((!boxIds.boxIds.find(box => box.boxId === resp.Events[i]?.Message.ToBoxId)) && (resp.Events[i]?.Message.ToBoxId)) {
if (!boxIds.boxIds.find(box => box.boxId === resp.Events[i]?.Message.ToBoxId) && resp.Events[i]?.Message.ToBoxId) {
boxIds.boxIds.push({ boxId: resp.Events[i]?.Message.ToBoxId });
}
}
@ -584,9 +597,9 @@ const afterEvent = async prms => {
//Если не достали из контекста токен доступа - значит нет аутентификации на сервере
if (!sToken) return { bUnAuth: true };
for (let i in boxIds.boxIds) {
rqpOptions = null;
httpRequestOptions = null;
//Формируем запрос для получения BoxId
rqpOptions = {
httpRequestOptions = {
uri: buildOrganizationBoxIdURL(prms.service.sSrvRoot),
headers: buildHeaders(sAPIClientId, sToken),
qs: {
@ -596,7 +609,7 @@ const afterEvent = async prms => {
};
try {
//Выполним запрос
serverResp = await rqp(rqpOptions);
serverResp = await httpRequest(httpRequestOptions);
if (serverResp?.Organization) {
//Запишем полученную информацию о контрагенте
boxIds.boxIds[i].Inn = serverResp?.Organization?.Inn;
@ -710,7 +723,7 @@ const beforeDocLoad = async prms => {
surl = `${surl}?${msgId}${prms.options.smsgid}&${entId}${prms.options.sentid}`;
let obj;
let rblMsg;
if (prms.queue.blMsg && (prms.options.type != 5) && (prms.options.type != 6)) {
if (prms.queue.blMsg && prms.options.type != 5 && prms.options.type != 6) {
//Конвертируем XML из "Парус 8" в понятный "ДИАДОК" JSON
obj = await toJSON(prms.queue.blMsg.toString());
rblMsg = Buffer.from(JSON.stringify(obj));
@ -759,7 +772,7 @@ const afterDocLoad = async prms => {
await new Promise(resolve => setTimeout(resolve, 2000));
}
//Выполним повторный запрос
serverResp = await rqp(prms.options);
serverResp = await httpRequest(prms.options);
//Сохраняем полученный ответ
prms.queue.blResp = Buffer.from(serverResp.body || "");
prms.optionsResp.statusCode = serverResp.statusCode;
@ -854,7 +867,9 @@ const afterDepartmentIdGet = async prms => {
//Получим организацию не в роуминге (или единственную организацию в роуминге)
organization = getOrganizations(JSON.parse(prms.queue.blResp.toString()), optionsData.sNumEdo);
if (!organization) {
throw Error(`Не удалось получить ящик для контрагента с ${optionsData.sNumEdo ? `кодом участника ЭДО: ${optionsData.sNumEdo}, ` : ""}ИНН: ${optionsData.nINN} и КПП: ${optionsData.nKPP}`);
throw Error(
`Не удалось получить ящик для контрагента с ${optionsData.sNumEdo ? `кодом участника ЭДО: ${optionsData.sNumEdo}, ` : ""}ИНН: ${optionsData.nINN} и КПП: ${optionsData.nKPP}`
);
}
} catch (e) {
//Получим ключ разработчика
@ -863,7 +878,7 @@ const afterDepartmentIdGet = async prms => {
let sToken = prms.service.sCtx;
//Получим головную организацию по ИНН/КПП
organization = await getOrganization(prms.service.sSrvRoot, buildHeaders(sAPIClientId, sToken), optionsData.nINN, optionsData.nKPP);
};
}
//Преобразуем JSON ответ сервиса "ДИАДОК" в XML, понятный "Парус 8"
resu = toXML({ root: organization });
} catch (e) {

View File

@ -14,6 +14,24 @@ const { WRK_MSG_TYPE, logInf, logErr, makeTaskOKResult, makeTaskErrResult, makeS
const { PARSERS, findModelByFileName } = require("./parsers"); //Модели и парсеры
const sax = require("./node_modules/sax"); //Событийный XML-парсер
//---------------------------
// Инициализация Thick-режима
//---------------------------
//Инициализируем Thick-режим до любых подключений к БД
try {
if (typeof oracledb.initOracleClient === "function" && !(process.env.NODE_ORACLE_DB_THIN_MODE === "1")) {
const libDir = process.env.ORACLE_CLIENT_LIB_DIR;
if (libDir) {
oracledb.initOracleClient({ libDir });
} else {
oracledb.initOracleClient();
}
}
} catch (e) {
throw new Error(`Не удалось инициализировать Oracle Client (Thick-режим): ${makeErrorText(e)}`);
}
//--------------------------
// Глобальные идентификаторы
//--------------------------

View File

@ -8,6 +8,25 @@
//----------------------
const oracledb = require("oracledb"); //Работа с СУБД Oracle
const { makeErrorText } = require("../core/utils"); //Вспомогательные функции
//---------------------------
// Инициализация Thick-режима
//---------------------------
//Инициализируем Thick-режим до любых подключений к БД
try {
if (typeof oracledb.initOracleClient === "function" && !(process.env.NODE_ORACLE_DB_THIN_MODE === "1")) {
const libDir = process.env.ORACLE_CLIENT_LIB_DIR;
if (libDir) {
oracledb.initOracleClient({ libDir });
} else {
oracledb.initOracleClient();
}
}
} catch (e) {
throw new Error(`Не удалось инициализировать Oracle Client (Thick-режим): ${makeErrorText(e)}`);
}
//--------------------------
// Глобальные идентификаторы
@ -184,9 +203,10 @@ const clearServer = async prms => {
//Подключение к БД
const connect = async prms => {
let pool = null;
try {
//Создаем пул подключения
let pool = await oracledb.createPool({
pool = await oracledb.createPool({
user: prms.sUser,
password: prms.sPassword,
connectString: prms.sConnectString,
@ -210,8 +230,15 @@ const connect = async prms => {
});
}
});
//Проверяем доступность пула
let tstConn = await pool.getConnection();
await tstConn.close();
//Всё ок - возвращяем его
return pool;
} catch (e) {
//Закрываем пул, если успели открыть
if (pool) pool.close(NPOOL_DRAIN_TIME);
//Возвращаем ошибку
throw new Error(e.message);
}
};

View File

@ -8,7 +8,7 @@
//------------------------------
const xml2js = require("xml2js"); //Конвертация XML в JSON и JSON в XML
const _ = require("lodash"); //Работа с коллекциями и объектами
const { deepClone } = require("../core/utils"); //Вспомогательные функции
//---------------------
// Глобальные константы
@ -48,24 +48,23 @@ const converXMLArraysToJSON = (obj, arrayKey) => {
let tmp = [];
let itemKey = obj[key][arrayKey];
if (obj[key][itemKey]) {
if (_.isArray(obj[key][itemKey])) {
if (Array.isArray(obj[key][itemKey])) {
for (let i = 0; i < obj[key][itemKey].length; i++) {
let buf = {};
buf[itemKey] = _.cloneDeep(obj[key][itemKey][i]);
buf[itemKey] = deepClone(obj[key][itemKey][i]);
tmp.push(buf);
}
} else {
let buf = {};
buf[itemKey] = _.cloneDeep(obj[key][itemKey]);
buf[itemKey] = deepClone(obj[key][itemKey]);
tmp.push(buf);
}
}
obj[key] = tmp;
converXMLArraysToJSON(obj[key], arrayKey);
} else {
if (_.isObject(obj[key])) converXMLArraysToJSON(obj[key], arrayKey);
if (_.isArray(obj[key]))
for (let i = 0; i < obj[key].length; i++) converXMLArraysToJSON(obj[key][i], arrayKey);
if (typeof obj[key] === "object" && obj[key] !== null && !Array.isArray(obj[key])) converXMLArraysToJSON(obj[key], arrayKey);
if (Array.isArray(obj[key])) for (let i = 0; i < obj[key].length; i++) converXMLArraysToJSON(obj[key][i], arrayKey);
}
}
};
@ -73,10 +72,7 @@ const converXMLArraysToJSON = (obj, arrayKey) => {
//Обработчик "До" для полученного сообщения
const before = async prms => {
//Если пришел запрос в JSON
if (
prms.options.headers["content-type"] &&
prms.options.headers["content-type"].startsWith(SHEADER_REQ_CONTENT_TYPE_JSON)
) {
if (prms.options.headers["content-type"] && prms.options.headers["content-type"].startsWith(SHEADER_REQ_CONTENT_TYPE_JSON)) {
//Конвертируем полученный в JSON-запрос в XML, понятный серверной части
let requestXML = "";
try {
@ -102,11 +98,8 @@ const after = async prms => {
prms.options.qs[SQUERY_RESP_CT] &&
prms.options.qs[SQUERY_RESP_CT].startsWith(SHEADER_REQ_CONTENT_TYPE_JSON)) ||
(prms.function.sCode != SFNC_UPLOAD &&
((prms.options.headers["content-type"] &&
prms.options.headers["content-type"].startsWith(SHEADER_REQ_CONTENT_TYPE_JSON)) ||
(prms.options.qs &&
prms.options.qs[SQUERY_RESP_CT] &&
prms.options.qs[SQUERY_RESP_CT].startsWith(SHEADER_REQ_CONTENT_TYPE_JSON))))
((prms.options.headers["content-type"] && prms.options.headers["content-type"].startsWith(SHEADER_REQ_CONTENT_TYPE_JSON)) ||
(prms.options.qs && prms.options.qs[SQUERY_RESP_CT] && prms.options.qs[SQUERY_RESP_CT].startsWith(SHEADER_REQ_CONTENT_TYPE_JSON))))
) {
//Буфер для конвертации
let parseRes = "";

View File

@ -8,9 +8,8 @@
//------------------------------
const xml2js = require("xml2js"); //Конвертация XML в JSON и JSON в XML
const _ = require("lodash"); //Работа с коллекциями и объектами
const rqp = require("request-promise"); //Работа с HTTP/HTTPS запросами
const { SMCHD_STORAGE_SYSTEM } = require("./sbis_config"); //Система хранения МЧД
const { httpRequest } = require("../core/http_client"); //Работа с HTTP/HTTPS запросами
const { SMCHD_STORAGE_SYSTEM, NCTX_EXP } = require("./sbis_config"); //Параметры работы расширения
//---------------------
// Глобальные константы
@ -33,7 +32,7 @@ const tag = [
// Список имен тегов для замены ([Старое значение], [Новое значение])
const replaceTags = [
['"Иные получатели":', '"ИныеПолучатели":'],
['"Создатель документа":', '"СоздательДокумента":'],
['"Создатель документа":', '"СоздательДокумента":']
];
//------------
@ -41,9 +40,9 @@ const replaceTags = [
//------------
//Замена наименований тегов (для корректной работы toXML)
const replaceTag = (obj) => {
const replaceTag = obj => {
for (let value of replaceTags) {
obj = obj.replace(new RegExp(value[0], 'g'), value[1]);
obj = obj.replace(new RegExp(value[0], "g"), value[1]);
}
return obj;
};
@ -52,7 +51,7 @@ const replaceTag = (obj) => {
const toArray = (obj, tags) => {
for (const prop in obj) {
const value = obj[prop];
if (tags.indexOf(prop) != -1 && !_.isArray(obj[prop])) {
if (tags.indexOf(prop) != -1 && !Array.isArray(obj[prop])) {
obj[prop] = JSON.parse("[" + JSON.stringify(value) + "]");
}
if (typeof value === "object") {
@ -139,7 +138,7 @@ const afterConnect = async prms => {
return {
blResp: Buffer.from(resp.result),
sCtx: resp.result,
dCtxExp: addHours(new Date(), 23)
dCtxExp: addHours(new Date(), NCTX_EXP)
};
} else {
throw new Error(`Сервер ЭДО "СБИС" вернул ошибку: ${resp.error.message ? resp.error.message : "Неожиданная ошибка"}`);
@ -255,7 +254,7 @@ const afterAttParse = async prms => {
if (prms.optionsResp.statusCode != 200) {
//Выполним повторный запрос
await new Promise(resolve => setTimeout(resolve, 2000));
let serverResp = await rqp(prms.options);
let serverResp = await httpRequest(prms.options);
//Сохраняем полученный ответ
prms.queue.blResp = Buffer.from(serverResp.body || "");
prms.optionsResp.statusCode = serverResp.statusCode;

View File

@ -9,3 +9,6 @@
//Система хранения МЧД
exports.SMCHD_STORAGE_SYSTEM = "https://m4d.nalog.gov.ru/EMCHD/check-status";
//Время жизни токена аутентификации (в часах)
exports.NCTX_EXP = 20;

3038
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "parus_exchange_service",
"version": "1.0.0",
"version": "2.0.0",
"description": "Parus 8 and WEB API integration platform",
"main": "index.js",
"scripts": {
@ -18,24 +18,17 @@
},
"homepage": "https://git.citpb.ru/CITKParus/P8-ExchangeService/",
"dependencies": {
"body-parser": "^1.19.0",
"cors": "^2.8.5",
"express": "^4.17.1",
"express": "^5.2.1",
"kafkajs": "^2.2.4",
"lodash": "^4.17.19",
"module-alias": "^2.2.2",
"mqtt": "^5.10.1",
"nodemailer": "^6.4.11",
"oracledb": "^4.2.0",
"nodemailer": "^6.9.16",
"oracledb": "^6.6.0",
"pg": "^8.13.1",
"request": "^2.88.2",
"request-promise": "^4.2.6",
"validate": "^5.1.0",
"xml2js": "^0.4.23"
"xml2js": "^0.6.2"
},
"_moduleAliases": {
"@core": "core",
"@modules": "modules",
"@models": "models"
"engines": {
"node": ">=22"
}
}