Предоставляет доступ к функциям для работы с WebSocket-клиентами.
Полное описание, параметры и ограничения
МенеджерWebSocketКлиентов (WebSocketClientsManager)
МенеджерWebSocketКлиентов (WebSocketClientsManager)
Доступен, начиная с версии 8.3.27.
Методы:
НайтиПоКлючу (FindByKey)
НайтиПредопределенный (FindPredefined)
ПолучитьКлиентов (GetClients)
СоздатьКлиента (CreateClient)
Описание:
Предоставляет доступ к функциям для работы с WebSocket-клиентами.
Доступность:
Сервер, толстый клиент, внешнее соединение.
Использование в версии:
Доступен, начиная с версии 8.3.27.
Методическая информация
Практические примеры
Прямой WebSocket: локальный echo, два способа связывания событий, текст и двоичные данные
Полный толстоклиентский пульт получает параметры из настоящих собственных метаданных и создает прямое асинхронное соединение. Собственный JavaScript-сервер возвращает исходные сообщения; успех подтверждают события с точным payload, не факт вызова открытия.
Где и как запускать
Полный модуль своей управляемой формы ПультПрямогоWebSocket85. Толстый клиент Windows, Управляемое приложение,8.5.1.1343, пользователь УчебныйWS85. Серверная защита отдельна от толстоклиентских callback. Код в1С не запускался.
Подготовка
НОВАЯ отдельная пустая файловая база точно8.5.1.1343. Конфигурация УчебныйПрямойWebSocket85, язык Русский, совместимость Не использовать. Нет прикладных объектов, общих реквизитов, расширений, подписок, заданий или рабочих данных; модули конфигурации вручную пустые. Не использовать рабочие БП/КА2/УТ/УНФ.
Общие → WebSocket-клиенты: единственный объект Эхо85. URLСервера=ws://127.0.0.1:18485/own-ws85, Предопределенный=Нет, ПодключатьАвтоматически=Нет. Модуль метаобъекта вручную пустой: прямое соединение использует процедуры своей формы. Метаданные и readonly ПараметрыСоединения дают настоящий объект настроек; отдельный Новый ПараметрыWebSocketКлиентСоединения не придумывается.
Только в этой пустой базе подготовьте собственного пользователя УчебныйWS85 и собственную учебную роль с правами использования своей конфигурации и открытия внешней обработки. Вручную разрешите этой роли соответствующие объекты. Запускайте толстый клиент Windows, режим Управляемое приложение, именно под этим пользователем. Имя пользователя ИБ не является HTTP-логином echo-сервера.
Своя внешняя обработка ПультПрямогоWebSocket85, управляемая основная форма. Реквизиты Согласие, ЯвныеОбработчики, ОтветТекста, ОтветДвоичного, ДвоичноеОтправлено — Булево; КлючСтенда — Строка100; ОжидаемыйТекст — Строка200; Журнал — Строка неограниченной длины. Покажите редактируемые флажки согласия/профиля и многострочный Журнал только просмотра. Служебные ключ/ожидание/ответы не делайте редактируемыми.
Четыре команды и кнопки ОткрытьСвойWebSocket85, ОтправитьСвоеДвоичное85, СостояниеСвоегоWebSocket85, ЗакрытьСвойWebSocket85: действия одноименные, ИзменяетСохраняемыеДанные=Ложь. Весь основной модуль вставить в модуль формы. Назначить ПриОткрытии и ПриЗакрытии. Четыре экспортные процедуры сокета вручную к событиям формы не назначать: их связывает настоящий объект обработчиков.
Будущая ручная подготовка: сохранить свою обработку, сохранить и обновить ТОЛЬКО свой стенд F7. Проверить пустые модули и отсутствие автоподключения Эхо85. Толстоклиентский код огражден #Если ТолстыйКлиентУправляемоеПриложение: серверные процедуры не читают соединение.Ключ; тонкий/веб-клиент получает отказ.
Для будущего сервера нужны Node.js и ws. Первичные источники: https://github.com/websockets/ws и https://github.com/websockets/ws/blob/master/doc/ws.md. В НОВОЙ отдельной папке вручную сохранить дополнительный JavaScript как own-ws85.cjs; вручную npm init -y и npm install ws --ignore-scripts --save-exact. Фактическую версию сохранить командой npm ls ws. Здесь ничего не устанавливалось, версия npm-релиза не объявляется проверенной.
Перед будущим запуском убедиться, что свободный 127.0.0.1:18485 принадлежит только своему стенду. Вручную node own-ws85.cjs. Сервер слушает только IPv4 loopback, принимает /own-ws85, X-Own-Lab и публичные учебные Basic-данные own-ws85/own-local-only. Это НЕ секреты и НЕ защита production. Сеть и сервер здесь не запускались.
runtimeVerifiedScenarios=0. После отдельного разрешения: отметить Согласие, открыть свое соединение, дождаться точного текстового echo, отправить четыре байта PING, дождаться двоичного echo, запросить закрытие и прочитать реальное Закрыто. После подтвержденного закрытия повторить с другим флажком ЯвныеОбработчики для второго конструктора. Каждое открытие получает новый UUID, старый ключ не переиспользуется.
Завершение будущего стенда: сначала кнопка закрытия и ручное подтверждение состояния/события, потом закрытие формы. ПриЗакрытии дополнительно запрашивает только свое закрытие без серверных вызовов/открытия окон. В консоли своего Node-сервера Ctrl+C. Потеря ссылки на объект сама по себе соединение НЕ закрывает.
&НаСервереБезКонтекста
Процедура ПроверитьСвойПустойСтенд()
Если Метаданные.Справочники.Количество() <> 0 Или Метаданные.Документы.Количество() <> 0 Или Метаданные.Перечисления.Количество() <> 0 Или Метаданные.РегистрыСведений.Количество() <> 0 Или Метаданные.РегистрыНакопления.Количество() <> 0 Или Метаданные.РегистрыБухгалтерии.Количество() <> 0 Или Метаданные.РегистрыРасчета.Количество() <> 0 Или Метаданные.ПланыВидовХарактеристик.Количество() <> 0 Или Метаданные.ПланыВидовРасчета.Количество() <> 0 Или Метаданные.ПланыСчетов.Количество() <> 0 Или Метаданные.ПланыОбмена.Количество() <> 0 Или Метаданные.Последовательности.Количество() <> 0 Или Метаданные.БизнесПроцессы.Количество() <> 0 Или Метаданные.Задачи.Количество() <> 0 Или Метаданные.ОбщиеРеквизиты.Количество() <> 0 Или Метаданные.ПодпискиНаСобытия.Количество() <> 0 Или Метаданные.РегламентныеЗадания.Количество() <> 0 Или Метаданные.ВнешниеИсточникиДанных.Количество() <> 0 Тогда
ВызватьИсключение "Нужен только свой новый пустой учебный стенд";
КонецЕсли;
Если Метаданные.ЕстьИзмененияРасширениямиКонфигурации() Тогда
ВызватьИсключение "Свой сеанс не должен изменяться расширениями";
КонецЕсли;
Источники = Новый Массив;
Источники.Добавить(ИсточникРасширенийКонфигурации.БазаДанных);
Источники.Добавить(ИсточникРасширенийКонфигурации.СеансАктивные);
Источники.Добавить(ИсточникРасширенийКонфигурации.СеансОтключенные);
Для Каждого Источник Из Источники Цикл
Расширения = РасширенияКонфигурации.Получить(, Источник);
Если ТипЗнч(Расширения) <> Тип("Массив") Тогда
ВызватьИсключение "Native список расширений должен иметь тип Массив";
КонецЕсли;
Если Расширения.Количество() <> 0 Тогда
ВызватьИсключение "В своём новом стенде нет расширений базы или сеанса";
КонецЕсли;
КонецЦикла;
Если ТранзакцияАктивна() Тогда
ВызватьИсключение "Чужая или вложенная активная транзакция не допускается";
КонецЕсли;
КонецПроцедуры
#Если ТолстыйКлиентУправляемоеПриложение Тогда
&НаКлиенте
Процедура ПриОткрытии(Отказ)
Согласие = Ложь;
ЯвныеОбработчики = Истина;
КлючСтенда = "";
ОжидаемыйТекст = "";
ОтветТекста = Ложь;
ОтветДвоичного = Ложь;
ДвоичноеОтправлено = Ложь;
Журнал = "Подготовьте собственный echo-сервер. Ни одно соединение еще не открывалось";
КонецПроцедуры
&НаКлиенте
Процедура ДобавитьСвойОтчет85(Текст)
Если СтрДлина(Журнал) > 16000 Тогда
Журнал = "Предыдущий собственный диагностический текст отсечен";
КонецЕсли;
БезПароля = СтрЗаменить(Текст, "own-local-only", "[учебный пароль скрыт]");
БезПароля = СтрЗаменить(БезПароля, "b3duLXdzODU6b3duLWxvY2FsLW9ubHk=", "[учебный Basic скрыт]");
Журнал = Журнал + Символы.ПС + Лев(БезПароля, 2000);
КонецПроцедуры
&НаКлиенте
Функция ПолучитьСвоиМетаданные85()
ПроверитьСвойПустойСтенд();
Если Метаданные.WebSocketКлиенты.Количество() <> 1 Тогда
ВызватьИсключение "Нужен единственный собственный WebSocket-клиент в метаданных";
КонецЕсли;
Описание = Метаданные.WebSocketКлиенты.Найти("Эхо85");
Если Описание = Неопределено Или Описание.URLСервера <> "ws://127.0.0.1:18485/own-ws85"
Или Описание.Предопределенный Или Описание.ПодключатьАвтоматически Тогда
ВызватьИсключение "Собственные метаданные должны быть непредопределенными и без автоподключения";
КонецЕсли;
Если WebSocketКлиенты.НайтиПредопределенный(Описание) <> Неопределено Тогда
ВызватьИсключение "Не допускается подключение предопределенного клиента";
КонецЕсли;
Возврат Описание;
КонецФункции
&НаКлиенте
Функция СоздатьСвоиПараметры85(Описание, Ключ)
Если WebSocketКлиенты.НайтиПоКлючу(Ключ) <> Неопределено Тогда
ВызватьИсключение "Ключ клиента уже занят; существующий клиент не меняется";
КонецЕсли;
Клиент = WebSocketКлиенты.СоздатьКлиента(Описание, Ключ);
Если ТипЗнч(Клиент) <> Тип("WebSocketКлиент") Или Клиент.Предопределенный
Или Клиент.Ключ <> Ключ Или Клиент.Метаданные <> Описание Или Клиент.URLСервера <> "ws://127.0.0.1:18485/own-ws85" Тогда
ВызватьИсключение "Не совпал паспорт собственного нового клиента параметров";
КонецЕсли;
Клиент.ПодключатьАвтоматически = Ложь;
Если Клиент.ПодключатьАвтоматически Или Клиент.ПолучитьТекущееСоединение() <> Неопределено
Или Клиент.ПолучитьСостояниеСоединения() <> СостояниеWebSocketСоединения.Закрыто Тогда
ВызватьИсключение "Источник параметров должен быть собственным неподключенным клиентом";
КонецЕсли;
Параметры = Клиент.ПараметрыСоединения;
Если ТипЗнч(Параметры) <> Тип("ПараметрыWebSocketКлиентСоединения") Или ТипЗнч(Параметры.Прокси) <> Тип("ИнтернетПрокси") Тогда
ВызватьИсключение "Нет настоящих параметров и исходного неинициализированного прокси";
КонецЕсли;
Параметры.ИспользоватьПроксиОС = Ложь;
Параметры.ИспользоватьАутентификациюОС = Ложь;
Параметры.Пользователь = "own-ws85";
Параметры.Пароль = "own-local-only";
Параметры.Таймаут = 10;
Заголовки = Новый Соответствие;
Заголовки.Вставить("X-Own-Lab", "owned-ws85-direct");
Параметры.Заголовки = Заголовки;
Параметры.ДополнительныеПараметры = Новый Структура("Метка,Ключ", "owned-ws85-direct", Ключ);
Если Параметры.ИспользоватьПроксиОС Или Параметры.ИспользоватьАутентификациюОС
Или Параметры.Пользователь <> "own-ws85" Или Параметры.Пароль <> "own-local-only"
Или Параметры.Таймаут <> 10 Или Параметры.Заголовки.Получить("X-Own-Lab") <> "owned-ws85-direct" Тогда
ВызватьИсключение "Не совпали реальные собственные параметры перед открытием соединения";
КонецЕсли;
Возврат Параметры;
КонецФункции
&НаКлиенте
Функция СоздатьСвоихОбработчиков85()
Если ЯвныеОбработчики Тогда
Обработчики = Новый ОбработчикиWebSocketКлиентСоединения();
Обработчики.Модуль = ЭтотОбъект;
Обработчики.ОбработчикОткрытияСоединения = "СобственноеОткрытие85";
Обработчики.ОбработчикПолученияСообщения = "СобственноеСообщение85";
Обработчики.ОбработчикОшибки = "СобственнаяОшибка85";
Обработчики.ОбработчикЗакрытияСоединения = "СобственноеЗакрытие85";
Иначе
Обработчики = Новый ОбработчикиWebSocketКлиентСоединения("СобственноеОткрытие85", "СобственноеСообщение85",
"СобственнаяОшибка85", "СобственноеЗакрытие85", ЭтотОбъект);
КонецЕсли;
Если Обработчики.Модуль <> ЭтотОбъект Или Обработчики.ОбработчикОткрытияСоединения <> "СобственноеОткрытие85"
Или Обработчики.ОбработчикПолученияСообщения <> "СобственноеСообщение85"
Или Обработчики.ОбработчикОшибки <> "СобственнаяОшибка85"
Или Обработчики.ОбработчикЗакрытияСоединения <> "СобственноеЗакрытие85" Тогда
ВызватьИсключение "Не совпали реальные четыре процедуры и собственный модуль";
КонецЕсли;
Возврат Обработчики;
КонецФункции
&НаКлиенте
Процедура СверитьСвоеСоединение85(Соединение)
Если ТипЗнч(Соединение) <> Тип("WebSocketКлиентСоединение") Или Соединение.URLСервера <> "ws://127.0.0.1:18485/own-ws85"
Или КлючСтенда = "" Или Соединение.Ключ <> КлючСтенда
Или Соединение.ИмяПользователяИнформационнойБазы <> "УчебныйWS85" Тогда
ВызватьИсключение "Получено не собственное соединение этого толстого клиента";
КонецЕсли;
Параметры = Соединение.Параметры;
Дополнительные = Параметры.ДополнительныеПараметры;
Если ТипЗнч(Дополнительные) <> Тип("Структура") Или Дополнительные.Метка <> "owned-ws85-direct"
Или Дополнительные.Ключ <> КлючСтенда Тогда
ВызватьИсключение "Нет собственного паспорта обработки событий";
КонецЕсли;
Обработчики = Соединение.Обработчики;
Если ТипЗнч(Обработчики) <> Тип("ОбработчикиWebSocketКлиентСоединения")
Или Обработчики.Модуль <> ЭтотОбъект Или Обработчики.ОбработчикПолученияСообщения <> "СобственноеСообщение85" Тогда
ВызватьИсключение "Соединение не связано с событиями собственной формы";
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Функция ОписатьСвоеСостояние85(Состояние)
Если Состояние = СостояниеWebSocketСоединения.Закрыто Тогда Возврат "Закрыто"; КонецЕсли;
Если Состояние = СостояниеWebSocketСоединения.Открывается Тогда Возврат "Открывается"; КонецЕсли;
Если Состояние = СостояниеWebSocketСоединения.Открыто Тогда Возврат "Открыто"; КонецЕсли;
Если Состояние = СостояниеWebSocketСоединения.Закрывается Тогда Возврат "Закрывается"; КонецЕсли;
ВызватьИсключение "Недокументированное состояние собственного соединения";
КонецФункции
&НаКлиенте
Процедура ОткрытьСвойWebSocket85(Команда)
Если Не Согласие Тогда
ВызватьИсключение "Нужно явное согласие на собственный локальный сетевой опыт";
КонецЕсли;
Описание = ПолучитьСвоиМетаданные85();
Если КлючСтенда <> "" Тогда
Предыдущее = WebSocketКлиентСоединения.ПолучитьСоединение(КлючСтенда);
Если Предыдущее <> Неопределено Тогда
СверитьСвоеСоединение85(Предыдущее);
Если Предыдущее.ПолучитьСостояние() <> СостояниеWebSocketСоединения.Закрыто Тогда
ВызватьИсключение "Сначала закройте и подтвердите закрытие своего предыдущего соединения";
КонецЕсли;
КонецЕсли;
КонецЕсли;
КлючСтенда = "own-ws85-direct-" + Строка(Новый УникальныйИдентификатор);
Если WebSocketКлиентСоединения.ПолучитьСоединение(КлючСтенда) <> Неопределено Тогда
ВызватьИсключение "Новый собственный ключ уже занят; существующее соединение не переиспользуется";
КонецЕсли;
Параметры = СоздатьСвоиПараметры85(Описание, КлючСтенда);
Обработчики = СоздатьСвоихОбработчиков85();
ОжидаемыйТекст = "own-ws85-text:" + КлючСтенда;
ОтветТекста = Ложь;
ОтветДвоичного = Ложь;
ДвоичноеОтправлено = Ложь;
Соединение = Неопределено;
Попытка
Соединение = WebSocketКлиентСоединения.ОткрытьСоединение(КлючСтенда, "ws://127.0.0.1:18485/own-ws85", Обработчики, Параметры, "УчебныйWS85");
СверитьСвоеСоединение85(Соединение);
Соединение.ОтправитьСообщение(ОжидаемыйТекст);
ДобавитьСвойОтчет85("Открытие только инициировано. Собственный текст поставлен на отправку; успех ожидается по событию получения точного echo");
Исключение
ОшибкаОткрытия = ОписаниеОшибки();
Если Соединение <> Неопределено Тогда
Попытка
СверитьСвоеСоединение85(Соединение);
Соединение.Закрыть();
Исключение
ОшибкаОткрытия = ОшибкаОткрытия + " Собственное безопасное закрытие НЕ подтверждено: " + ОписаниеОшибки();
КонецПопытки;
КонецЕсли;
ДобавитьСвойОтчет85(ОшибкаОткрытия);
ВызватьИсключение ОшибкаОткрытия;
КонецПопытки;
КонецПроцедуры
&НаКлиенте
Процедура ОтправитьСвоеДвоичное85(Команда)
Если Не Согласие Или Не ОтветТекста Тогда
ВызватьИсключение "Сначала подтвердите свой фактический текстовый echo";
КонецЕсли;
Соединение = WebSocketКлиентСоединения.ПолучитьСоединение(КлючСтенда);
СверитьСвоеСоединение85(Соединение);
Если Соединение.ПолучитьСостояние() <> СостояниеWebSocketСоединения.Открыто Тогда
ВызватьИсключение "Для двоичного опыта нужно фактическое Открыто";
КонецЕсли;
Данные = Base64Значение("UElORw==");
Если ТипЗнч(Данные) <> Тип("ДвоичныеДанные") Тогда ВызватьИсключение "Ожидались четыре собственных байта PING"; КонецЕсли;
ОтветДвоичного = Ложь;
ДвоичноеОтправлено = Истина;
Попытка
Соединение.ОтправитьСообщение(Данные);
Исключение
ДвоичноеОтправлено = Ложь;
ОшибкаОтправки = ОписаниеОшибки();
Попытка
Соединение.Закрыть();
Исключение
ОшибкаОтправки = ОшибкаОтправки + " Свое закрытие после ошибки НЕ подтверждено: " + ОписаниеОшибки();
КонецПопытки;
ДобавитьСвойОтчет85(ОшибкаОтправки);
ВызватьИсключение ОшибкаОтправки;
КонецПопытки;
ДобавитьСвойОтчет85("Четыре собственных байта отправлены; двоичный echo еще не подтвержден");
КонецПроцедуры
&НаКлиенте
Процедура СостояниеСвоегоWebSocket85(Команда)
Если КлючСтенда = "" Тогда ВызватьИсключение "Свой опыт еще не создан"; КонецЕсли;
Соединение = WebSocketКлиентСоединения.ПолучитьСоединение(КлючСтенда);
Если Соединение = Неопределено Тогда
ДобавитьСвойОтчет85("Менеджер не предоставил соединение собственного ключа; это не доказательство успешного обмена");
Возврат;
КонецЕсли;
СверитьСвоеСоединение85(Соединение);
ДобавитьСвойОтчет85("Фактическое состояние: " + ОписатьСвоеСостояние85(Соединение.ПолучитьСостояние()));
Соединения = WebSocketКлиентСоединения.ПолучитьСоединения();
Клиенты = WebSocketКлиенты.ПолучитьКлиентов();
Если ТипЗнч(Соединения) <> Тип("Массив") Или ТипЗнч(Клиенты) <> Тип("Массив") Тогда
ВызватьИсключение "Ожидались реальные массивы менеджеров";
КонецЕсли;
ДобавитьСвойОтчет85("Всего объектов соединений: " + Строка(Соединения.Количество()) + "; объектов клиентов: " + Строка(Клиенты.Количество()));
ДобавитьСвойОтчет85("Свой текстовый echo: " + Строка(ОтветТекста) + "; свой двоичный echo: " + Строка(ОтветДвоичного));
КонецПроцедуры
&НаКлиенте
Процедура ЗакрытьСвойWebSocket85(Команда)
Если КлючСтенда = "" Тогда Возврат; КонецЕсли;
Соединение = WebSocketКлиентСоединения.ПолучитьСоединение(КлючСтенда);
Если Соединение = Неопределено Тогда
ДобавитьСвойОтчет85("Собственное соединение не предоставлено менеджером");
Возврат;
КонецЕсли;
СверитьСвоеСоединение85(Соединение);
Соединение.Закрыть();
ДобавитьСвойОтчет85("Закрытие запрошено. Дождитесь события либо отдельно прочитайте фактическое Закрыто; немедленное закрытие не обещается");
КонецПроцедуры
&НаКлиенте
Процедура СобственноеОткрытие85(Соединение) Экспорт
СверитьСвоеСоединение85(Соединение);
ДобавитьСвойОтчет85("Событие открытия своего соединения: " + ОписатьСвоеСостояние85(Соединение.ПолучитьСостояние()));
КонецПроцедуры
&НаКлиенте
Процедура СобственноеСообщение85(Соединение, Сообщение) Экспорт
СверитьСвоеСоединение85(Соединение);
Попытка
Если ТипЗнч(Сообщение) = Тип("Строка") Тогда
Если Сообщение <> ОжидаемыйТекст Тогда ВызватьИсключение "Получен не точный собственный текстовый echo"; КонецЕсли;
ОтветТекста = Истина;
ДобавитьСвойОтчет85("Подтвержден точный собственный текстовый echo");
ИначеЕсли ТипЗнч(Сообщение) = Тип("ДвоичныеДанные") Тогда
Если Не ДвоичноеОтправлено Или Base64Строка(Сообщение) <> "UElORw==" Тогда
ВызватьИсключение "Получены не запрошенные четыре собственных байта PING";
КонецЕсли;
ОтветДвоичного = Истина;
ДобавитьСвойОтчет85("Подтвержден собственный двоичный echo из четырех байтов");
Иначе
ВызватьИсключение "Недокументированный тип сообщения";
КонецЕсли;
Исключение
ОшибкаОтвета = ОписаниеОшибки();
Попытка
Соединение.Закрыть();
Исключение
ОшибкаОтвета = ОшибкаОтвета + " Свое закрытие после неверного ответа НЕ подтверждено: " + ОписаниеОшибки();
КонецПопытки;
ДобавитьСвойОтчет85(ОшибкаОтвета);
ВызватьИсключение ОшибкаОтвета;
КонецПопытки;
КонецПроцедуры
&НаКлиенте
Процедура СобственнаяОшибка85(Соединение, КодОшибки, Описание) Экспорт
СверитьСвоеСоединение85(Соединение);
Если ТипЗнч(КодОшибки) <> Тип("Строка") Или ТипЗнч(Описание) <> Тип("Строка") Тогда
ВызватьИсключение "Не совпал контракт ошибки";
КонецЕсли;
ДобавитьСвойОтчет85("Фактическая ошибка собственного соединения: " + Лев(КодОшибки, 100) + "; " + Лев(Описание, 1000));
Соединение.Закрыть();
КонецПроцедуры
&НаКлиенте
Процедура СобственноеЗакрытие85(Соединение, КодЗакрытия) Экспорт
СверитьСвоеСоединение85(Соединение);
Если ТипЗнч(КодЗакрытия) <> Тип("Число") Тогда ВызватьИсключение "Нужен фактический числовой статус закрытия"; КонецЕсли;
ДобавитьСвойОтчет85("Событие закрытия своего соединения, код " + Строка(КодЗакрытия) + ";1005 означает, что код не получен");
КонецПроцедуры
&НаКлиенте
Процедура ПриЗакрытии(ЗавершениеРаботы)
ЗакрытьСвойWebSocket85(Неопределено);
КонецПроцедуры
#Иначе
&НаКлиенте
Процедура ПриОткрытии(Отказ)
ВызватьИсключение "Этот собственный опыт требует толстого клиента в управляемом приложении; соединение не открывается";
КонецПроцедуры
&НаКлиенте
Процедура ОткрытьСвойWebSocket85(Команда)
ВызватьИсключение "Нужен толстый клиент в управляемом приложении";
КонецПроцедуры
&НаКлиенте
Процедура ОтправитьСвоеДвоичное85(Команда)
ВызватьИсключение "Нужен толстый клиент в управляемом приложении";
КонецПроцедуры
&НаКлиенте
Процедура СостояниеСвоегоWebSocket85(Команда)
ВызватьИсключение "Нужен толстый клиент в управляемом приложении";
КонецПроцедуры
&НаКлиенте
Процедура ЗакрытьСвойWebSocket85(Команда)
ВызватьИсключение "Нужен толстый клиент в управляемом приложении";
КонецПроцедуры
&НаКлиенте
Процедура ПриЗакрытии(ЗавершениеРаботы)
КонецПроцедуры
#КонецЕслиown-ws85.cjs — JavaScript echo-сервер, НЕ модуль1С
Новая отдельная папка своего опыта → own-ws85.cjs
Полный JavaScript для будущего Node.js с ws. Только127.0.0.1:18485; сейчас не устанавливался и не выполнялся на реальной сети.
const http=require('node:http');
const {WebSocketServer,WebSocket}=require('ws');
const host='127.0.0.1';
const port=18485;
const endpoint='/own-ws85';
const credentials='Basic '+Buffer.from('own-ws85:own-local-only','ascii').toString('base64');
const server=http.createServer((request,response)=>{response.writeHead(404);response.end('own-ws85 only');});
const sockets=new Set();
const wss=new WebSocketServer({noServer:true,maxPayload:4096,perMessageDeflate:false});
server.maxConnections=8;
server.headersTimeout=10000;
server.requestTimeout=10000;
function reportError(error) {console.error('own-ws85 failure:',error.code||error.message);}
function rejectUpgrade(socket,status,challenge='') {
socket.end('HTTP/1.1 '+status+'\r\nConnection: close\r\nContent-Length: 0\r\n'+challenge+'\r\n');
}
server.on('connection',socket=>{
sockets.add(socket);
socket.setTimeout(60000,()=>socket.destroy());
socket.on('error',reportError);
socket.on('close',()=>sockets.delete(socket));
});
server.on('upgrade',(request,socket,head)=>{
if(request.socket.remoteAddress!==host||request.method!=='GET'||request.url!==endpoint||request.headers.host!==host+':'+port||request.headers['x-own-lab']!=='owned-ws85-direct') {
rejectUpgrade(socket,'403 Forbidden');return;
}
if(request.headers.authorization!==credentials) {
rejectUpgrade(socket,'401 Unauthorized','WWW-Authenticate: Basic realm="own-ws85"\r\n');return;
}
if(wss.clients.size>=4) {rejectUpgrade(socket,'503 Service Unavailable');return;}
try {wss.handleUpgrade(request,socket,head,ws=>wss.emit('connection',ws,request));}
catch(error) {reportError(error);socket.destroy();}
});
wss.on('error',reportError);
wss.on('connection',ws=>{
let received=0;
ws.on('error',reportError);
ws.on('message',(data,isBinary)=>{
received++;
if(received>8||data.length>512||ws.bufferedAmount>4096) {ws.close(1008,'own-lab limit');return;}
if(ws.readyState!==WebSocket.OPEN) return;
ws.send(data,{binary:isBinary},error=>{if(error){reportError(error);ws.terminate();}});
});
ws.on('close',code=>console.log('own-ws85 connection closed:',code));
});
server.on('error',error=>{reportError(error);process.exitCode=1;});
server.listen(port,host,()=>console.log('own-ws85 listening on ws://'+host+':'+port+endpoint));
process.once('SIGINT',()=>{
for(const client of wss.clients) client.terminate();
for(const socket of sockets) socket.destroy();
wss.close();
server.close(error=>{if(error){reportError(error);process.exitCode=1;}});
});Разбор по шагам
Проверяем пустую базу и единственный Эхо85 с локальным URL, без предопределенности/автоподключения. НайтиПредопределенный получает настоящий объект метаданных, не строку. СоздатьКлиента получает его и свой новый ключ; паспорт, readonly Метаданные/Ключ/ПараметрыСоединения и отсутствие текущего соединения проверяются до прямого открытия.
Настоящие параметры получают собственные Basic-данные, заголовок, таймаут установления10секунд и паспорт Метка/Ключ событий. ОС-аутентификация и ОС-прокси отключены. Прокси читается как исходный неинициализированный ИнтернетПрокси; чужая сеть не перенастраивается. Проверки читают значения, но пароль не печатается.
ЯвныеОбработчики=Истина создает пустой объект и заполняет пять свойств. Ложь использует конструктор с четырьмя точными экспортными процедурами и своей формой. Оба пути используются в отдельных циклах, не создают неиспользуемую копию.
Перед ОткрытьСоединение проверяем отсутствие UUID-ключа. Менеджер возвращает существующее соединение для занятого ключа, поэтому он отклоняется. После вызова сверяются URL, ключ, пользователь ИБ, параметры событий и собственный модуль. Ключ читается только внутри толстоклиентской ветки.
Текст ставится на отправку сразу после асинхронного вызова: справка допускает очередь до установки соединения. Событие открытия диагностирует состояние, только точное текстовое сообщение устанавливает ОтветТекста. Двоичная команда требует этот ответ и фактическое Открыто; четыре байта PING проверяются в callback. Тип сообщения — Строка или ДвоичныеДанные, не выдуманный СообщениеWebSocket.
Команда состояния читает свое соединение и реальные массивы обоих менеджеров. Общие размеры не объявляются числом открытых своих сокетов: возможны другие/закрытые объекты. Четыре состояния различаются без бесконечного ожидания или требования обязательного наблюдения каждого перехода.
Закрыть запрашивает закрытие, не обещает мгновенное Закрыто. Событие принимает реальный числовой код;1005 означает отсутствие полученного кода. Ошибка принимает две строки, выводит ограниченный текст и запрашивает свое закрытие. Ошибки открытия и безопасного завершения сохраняются раздельно.
JavaScript использует ws для разбора протокола, проверяет loopback/путь/заголовок/Basic до Upgrade, ограничивает клиентов, сообщения и буфер. Echo сохраняет текстовый/двоичный вид. JS-синтаксис и чистые ветки проверяются отдельно от BSL, не считаются проверкой 1С или настоящей сети.
Как проверить результат
Будущий положительный опыт: в каждом профиле обработчиков реальные событие открытия, точный UUID-текст и четыре двоичных байта, затем реальное закрытие. До callback ответы Ложь. В журнале нет пароля; вызов отправки не называется подтвержденным получением.
Будущий отрицательный опыт: Согласие=Ложь, занятый ключ, другой URL/метаобъект или второе открытие до закрытия прекращают действия. Остановленный сервер/неверные публичные Basic-данные не дают флаги ответа; фактическая ошибка выводится, свое закрытие запрашивается.
Будущий контроль контекстов: тонкий/веб-клиент получает отказ вместо вызова Ключ. При закрытии формы отсутствуют серверные вызовы/новые окна. Не считать закрытие успешным по отсутствию исключения у Закрыть.
runtimeVerifiedScenarios=0: реальные разрешения пользователя, Basic-handshake/повтор после401, передача формы в обработчики, callback-сеанс, очередь, echo и завершение не запускались. Исходники не заменяют ручной протокол.
Ожидаемый результат
Полный собственный пульт прямого соединения и отдельный локальный echo-сервер с точными контрактами и разделением инициирования, событий, ответов и закрытия.
На что обратить внимание
Этот урок не вызывает клиент.Записать/Подключить/Отключить/Удалить и не учит серверные события Модуль WebSocket клиента. Эти оставшиеся API не объявляются покрытыми прямым опытом. Источник параметров — настоящий новый клиент; не обещаются неописанные правила долговременного хранения черновика.
Ключ соединения доступен только толстому клиенту. Пользователь ИБ — пользователь сеанса обработчиков; Пользователь/Пароль параметров — внешние HTTP-данные. Требуются заранее подготовленный собственный пользователь и будущая проверка callback-контекста.
Таймаут параметров относится к установлению, не ожиданию echo. Пульт не блокирует UI циклом; ответы приходят событиями. Не требуются заранее заданный код ошибки или обязательный статус1000 при закрытии.
Readonly параметры/обработчики соединения не заменяются новыми объектами. Настройки задаются ДО открытия через настоящий источник, не меняются гонкой после начала handshake.
ws:// и публичные Basic-данные допустимы только своему loopback-опыту без чувствительной информации. Production требует отдельного анализа TLS/wss, секретов, сертификатов, прокси, повторов, лимитов и журналов.
Callback-форма может жить меньше соединения: свое закрытие подтверждают до закрытия формы. ПриЗакрытии лишь дополнительная попытка, не подтверждение освобождения. Отсутствие объекта у ПолучитьСоединение не доказывает успешный обмен.
Не запускать JavaScript как модуль1С. Проверяются свой JS-синтаксис/ветки на заглушках и BSL-контракты, не сама библиотека ws, не TCP/сервер/1С. Установка зависит от фактически выбранной пользователем версии и окружения.
Пример не запускался в вашей базе. Перед применением проверьте его в указанном учебном контексте.
Сохранить свой WebSocket-клиент, подключить, отключить и удалить только его
Собственный UUID-клиент имеет реальные сохраненные данные и отдельный журнал событий. Контроллер не изменяет предопределенные/чужие клиенты и не путает сохранение с подключением.
Где и как запускать
Полный серверный общий модуль УчебныйСохраненныйWS85 своей новой конфигурации УчебныйСохраненныйWebSocket85. Вызовы только из подготовленного собственного пульта; сервер/журнал/данные в1С не запускались.
Подготовка
НОВАЯ отдельная пустая файловая база точно8.5.1.1343. Конфигурация УчебныйСохраненныйWebSocket85, Русский, совместимость Не использовать. Нет прикладных объектов, регистров, расширений, подписок и заданий; модули конфигурации вручную пустые. Не использовать рабочую базу или базу прямого WebSocket-урока.
Общие → WebSocket-клиенты: единственный Эхо85, URLСервера=ws://127.0.0.1:18485/own-ws85, Предопределенный=Нет, ПодключатьАвтоматически=Нет, ИмяПользователяИнформационнойБазы=УчебныйWS85. В МОДУЛЬ этого метаобъекта вставить полный блок событий. Это не модуль формы или общего модуля; имена пяти процедур соответствуют нативным событиям и вручную к кнопкам не привязываются.
Общие → Общие модули → УчебныйСохраненныйWS85: Сервер=Да, Вызов сервера=Да, Клиент=Нет, Глобальный=Нет, Привилегированный=Нет. Вставить весь контроллер, включая проверку пустого стенда, ключа, реального клиента, настройки и записи журнала. Все вызываемые экспортные процедуры присутствуют в этом блоке.
Создайте ТОЛЬКО в своей пустой базе собственную роль УчебнаяАдминистрацияWS85. Разрешите использование своей конфигурации/клиента, открытие своей внешней обработки и обязательно право Администрирование данных: установленная справка требует его для Записать и Удалить. Создайте пользователя УчебныйWS85 с этой ролью. Проверить его права вручную; серверные процедуры и сеансы событий должны работать именно под ним, не под администратором рабочей базы.
Свою внешнюю обработку ПультСохраненногоWS85 создать с управляемой основной формой. Реквизиты Согласие — Булево; КлючСтенда и КлючВосстановления — Строка100; Результат — Строка неограниченной длины. Флажок согласия редактируемый, КлючСтенда только просмотр для копирования в фильтр журнала, КлючВосстановления редактируемый только для настоящего ранее записанного своего ключа, Результат многострочный только просмотра.
Создать шесть команд и кнопок Подготовить85, Подключить85, Состояние85, Отключить85, Удалить85, ВосстановитьКлюч85 с одноименными действиями. Основной блок формы вставить полностью, назначить ПриОткрытии. Команды вызывают сервер, клиенту возвращаются только строки, не клиент/соединение/параметры.
Будущая ручная подготовка: сохранить свою обработку, сохранить и обновить ТОЛЬКО собственную конфигурацию F7. Включить в своем журнале регистрации Информация и Ошибка; открыть свой журнал и фильтровать Событие=OwnWS85.*, Метаданные=WebSocketКлиент.Эхо85, Данные=точный собственный ключ. Без включенного уровня и фактических записей событий нельзя объявлять echo подтвержденным.
Дополнительный own-ws85.cjs — JavaScript, НЕ модуль1С. Это тот же полный loopback-сервер из прямого урока. Только после отдельного разрешения в новой собственной папке вручную npm init -y, npm install ws --ignore-scripts --save-exact, npm ls ws и node own-ws85.cjs. Порт127.0.0.1:18485 предварительно вручную проверить как свободный и принадлежащий только своему стенду. Здесь ни сервер, ни установка, ни сеть не запускались.
Будущий ручной опыт: отметить согласие, Подготовить85 и сохранить настоящий показанный UUID-ключ. Клиент сохраняется, но автоматически не подключается. Подключить85 — отдельная команда. Фактические события Открытие, ТекстEcho и ДвоичныйEcho читать в журнале своего ключа, не считать подтверждением строку о запросе подключения.
Будущее завершение: Отключить85, отдельным Состояние85 подтвердить реальное Закрыто и выключенное автоподключение, затем Удалить85. Журнал не удаляется. Сохраненный клиент не исчезает от закрытия формы. Если форма была закрыта до завершения, открыть новую, перенести СОХРАНЕННЫЙ настоящий ключ в КлючВосстановления и нажать ВосстановитьКлюч85: сервер заново проверяет существующего своего клиента до присваивания ключа пульту.
runtimeVerifiedScenarios=0. Записи клиентских данных/журнала, фоновые сеансы, права, Basic-handshake, echo и отключение в1С здесь не выполнялись. Эти разрешенные будущие изменения касаются только своей новой базы. Производственные конфигурации/данные не читались и не изменялись.
Функция ПроверитьСвойКлюч(Ключ) Экспорт
Если ТипЗнч(Ключ) <> Тип("Строка") Или Лев(Ключ, 17) <> "own-ws85-persist-" Тогда
ВызватьИсключение "Нужен только свой ключ сохраненного WebSocket-опыта";
КонецЕсли;
Хвост = Сред(Ключ, 18);
UUID = Новый УникальныйИдентификатор(Хвост);
Если Строка(UUID) <> Хвост Тогда
ВызватьИсключение "Нужен точный собственный UUID, не произвольное имя";
КонецЕсли;
Возврат Ключ;
КонецФункции
Функция ПолучитьСвоиМетаданные() Экспорт
ПроверитьСвойПустойСтенд();
Если ИмяПользователя() <> "УчебныйWS85" Или Метаданные.WebSocketКлиенты.Количество() <> 1 Тогда
ВызватьИсключение "Нужен свой пользователь и единственный собственный метаобъект";
КонецЕсли;
Описание = Метаданные.WebSocketКлиенты.Найти("Эхо85");
Если Описание = Неопределено Или Описание.Предопределенный Или Описание.ПодключатьАвтоматически
Или Описание.URLСервера <> "ws://127.0.0.1:18485/own-ws85" Тогда
ВызватьИсключение "Нужно свое непредопределенное Эхо85 с ручным подключением к loopback";
КонецЕсли;
Возврат Описание;
КонецФункции
Процедура СверитьСвоегоКлиента(Клиент, Ключ) Экспорт
ПроверитьСвойКлюч(Ключ);
Описание = ПолучитьСвоиМетаданные();
Если ТипЗнч(Клиент) <> Тип("WebSocketКлиент") Или Клиент.Предопределенный Или Клиент.Ключ <> Ключ
Или Клиент.Метаданные <> Описание Или Клиент.URLСервера <> "ws://127.0.0.1:18485/own-ws85"
Или Клиент.ИмяПользователяИнформационнойБазы <> "УчебныйWS85" Или Клиент.ПодключатьАвтоматически Тогда
ВызватьИсключение "Не совпал паспорт своего клиента; существующий чужой объект не меняется";
КонецЕсли;
КонецПроцедуры
Функция ПолучитьСвоегоКлиента(Ключ) Экспорт
ПроверитьСвойКлюч(Ключ);
Клиент = WebSocketКлиенты.НайтиПоКлючу(Ключ);
Если Клиент = Неопределено Тогда
ВызватьИсключение "Свой сохраненный клиент не найден";
КонецЕсли;
СверитьСвоегоКлиента(Клиент, Ключ);
Возврат Клиент;
КонецФункции
Процедура НастроитьСвоиПараметры(Клиент, Ключ) Экспорт
СверитьСвоегоКлиента(Клиент, Ключ);
Параметры = Клиент.ПараметрыСоединения;
Если ТипЗнч(Параметры) <> Тип("ПараметрыWebSocketКлиентСоединения") Тогда
ВызватьИсключение "Нужен настоящий readonly-источник изменяемых сетевых параметров";
КонецЕсли;
Параметры.ИспользоватьАутентификациюОС = Ложь;
Параметры.ИспользоватьПроксиОС = Ложь;
Параметры.Пользователь = "own-ws85";
Параметры.Пароль = "own-local-only";
Параметры.Таймаут = 10;
Заголовки = Новый Соответствие;
Заголовки.Вставить("X-Own-Lab", "owned-ws85-direct");
Параметры.Заголовки = Заголовки;
Параметры.ДополнительныеПараметры = Новый Структура("Метка,Ключ", "owned-ws85-persistent", Ключ);
КонецПроцедуры
Процедура ЗаписатьСвоеСобытие(Ключ, Имя, Комментарий, Ошибка = Ложь) Экспорт
ПроверитьСвойКлюч(Ключ);
Описание = ПолучитьСвоиМетаданные();
Разрешенные = СтрРазделить("ПередПодключением,Открытие,ТекстEcho,ДвоичныйEcho,Ошибка,Закрытие,Подготовлен,ПодключениеЗапрошено,ОтключениеЗапрошено,УдалениеЗапрошено", ",");
Если Разрешенные.Найти(Имя) = Неопределено Тогда
ВызватьИсключение "Событие не принадлежит собственному учебному протоколу";
КонецЕсли;
БезПароля = СтрЗаменить(Комментарий, "own-local-only", "[учебный пароль скрыт]");
БезПароля = СтрЗаменить(БезПароля, "b3duLXdzODU6b3duLWxvY2FsLW9ubHk=", "[учебный Basic скрыт]");
Уровень = ?(Ошибка, УровеньЖурналаРегистрации.Ошибка, УровеньЖурналаРегистрации.Информация);
ЗаписьЖурналаРегистрации("OwnWS85." + Имя, Уровень, Описание, Ключ, Лев(БезПароля, 1200));
КонецПроцедуры
Функция ПолучитьКлючСвоегоСобытия(Соединение) Экспорт
ПолучитьСвоиМетаданные();
Если ТипЗнч(Соединение) <> Тип("WebSocketКлиентСоединение") Или Соединение.URLСервера <> "ws://127.0.0.1:18485/own-ws85"
Или Соединение.ИмяПользователяИнформационнойБазы <> "УчебныйWS85" Тогда
ВызватьИсключение "Событие не принадлежит своему локальному соединению";
КонецЕсли;
Параметры = Соединение.Параметры;
Дополнительные = Параметры.ДополнительныеПараметры;
Если ТипЗнч(Дополнительные) <> Тип("Структура") Или Дополнительные.Метка <> "owned-ws85-persistent" Тогда
ВызватьИсключение "Нет собственного паспорта события";
КонецЕсли;
Возврат ПроверитьСвойКлюч(Дополнительные.Ключ);
КонецФункции
Функция ЗавершитьСвоеСоединениеПослеОшибки(Соединение, Ключ, ТекстОшибки) Экспорт
Если ПолучитьКлючСвоегоСобытия(Соединение) <> Ключ Тогда
ВызватьИсключение "Чужое или неподтвержденное соединение не закрывается";
КонецЕсли;
ИтогОшибки = ТекстОшибки;
Попытка
ЗаписатьСвоеСобытие(Ключ, "Ошибка", ТекстОшибки, Истина);
Исключение
ИтогОшибки = ИтогОшибки + " Журнал НЕ записан: " + ОписаниеОшибки();
КонецПопытки;
Попытка
Соединение.Закрыть();
Исключение
ИтогОшибки = ИтогОшибки + " Свое закрытие НЕ подтверждено: " + ОписаниеОшибки();
КонецПопытки;
Возврат ИтогОшибки;
КонецФункции
Функция ПодготовитьСвоегоКлиента(Ключ, Согласие) Экспорт
Если Не Согласие Тогда ВызватьИсключение "Нужно согласие на запись только своего нового клиента"; КонецЕсли;
ПроверитьСвойКлюч(Ключ);
Описание = ПолучитьСвоиМетаданные();
Если WebSocketКлиенты.НайтиПоКлючу(Ключ) <> Неопределено Тогда
ВызватьИсключение "Ключ уже занят; существующий клиент не перезаписывается";
КонецЕсли;
Клиент = WebSocketКлиенты.СоздатьКлиента(Описание, Ключ);
Клиент.URLСервера = "ws://127.0.0.1:18485/own-ws85";
Клиент.ИмяПользователяИнформационнойБазы = "УчебныйWS85";
Клиент.ПодключатьАвтоматически = Ложь;
СверитьСвоегоКлиента(Клиент, Ключ);
Попытка
НастроитьСвоиПараметры(Клиент, Ключ);
Клиент.Записать();
Повторно = ПолучитьСвоегоКлиента(Ключ);
Если Повторно.ПолучитьТекущееСоединение() <> Неопределено Тогда
ВызватьИсключение "Подготовка не должна автоматически открывать соединение";
КонецЕсли;
ЗаписатьСвоеСобытие(Ключ, "Подготовлен", "Свой клиент записан, автоподключение выключено");
Исключение
ОшибкаПодготовки = ОписаниеОшибки();
Попытка
СверитьСвоегоКлиента(Клиент, Ключ);
Если Клиент.ПолучитьТекущееСоединение() <> Неопределено Тогда Клиент.Отключить(); КонецЕсли;
Клиент.Удалить();
Исключение
ОшибкаПодготовки = ОшибкаПодготовки + " Удаление своего нового клиента после ошибки НЕ подтверждено: " + ОписаниеОшибки();
КонецПопытки;
ВызватьИсключение ОшибкаПодготовки;
КонецПопытки;
Возврат "Свой клиент записан без подключения. Сохраните ключ для журнала и завершения: " + Ключ;
КонецФункции
Функция ПодключитьСвоегоКлиента(Ключ, Согласие) Экспорт
Если Не Согласие Тогда ВызватьИсключение "Нужно согласие на свое локальное соединение"; КонецЕсли;
Клиент = ПолучитьСвоегоКлиента(Ключ);
Если Клиент.ПолучитьТекущееСоединение() <> Неопределено Тогда
ВызватьИсключение "Текущее собственное соединение уже предоставлено; повторное подключение не начинается";
КонецЕсли;
Попытка
Соединение = Клиент.Подключить();
Если Соединение <> Неопределено И ТипЗнч(Соединение) <> Тип("WebSocketКлиентСоединение") Тогда
ВызватьИсключение "Не совпал тип результата Подключить";
КонецЕсли;
ЗаписатьСвоеСобытие(Ключ, "ПодключениеЗапрошено", "Подключить выполнен; результат типа " + Строка(ТипЗнч(Соединение)));
Исключение
ОшибкаПодключения = ОписаниеОшибки();
Попытка
Клиент.Отключить();
Исключение
ОшибкаПодключения = ОшибкаПодключения + " Собственное отключение после ошибки НЕ подтверждено: " + ОписаниеОшибки();
КонецПопытки;
ВызватьИсключение ОшибкаПодключения;
КонецПопытки;
Возврат "Подключение запрошено. Echo подтверждают отдельные события OwnWS85.ТекстEcho и OwnWS85.ДвоичныйEcho своего ключа";
КонецФункции
Функция ПрочитатьСвойСтатус(Ключ) Экспорт
Клиент = ПолучитьСвоегоКлиента(Ключ);
Возврат "Фактическое состояние своего клиента: " + Строка(Клиент.ПолучитьСостояниеСоединения())
+ "; автоподключение: " + Строка(Клиент.ПодключатьАвтоматически)
+ ". Этот статус не доказывает получение сообщений; откройте журнал OwnWS85 по своему ключу";
КонецФункции
Функция ОтключитьСвоегоКлиента(Ключ) Экспорт
Клиент = ПолучитьСвоегоКлиента(Ключ);
Клиент.Отключить();
Если Клиент.ПодключатьАвтоматически Тогда
ВызватьИсключение "Отключить не выключил собственное автоподключение";
КонецЕсли;
ЗаписатьСвоеСобытие(Ключ, "ОтключениеЗапрошено", "Прежнее соединение после Отключить больше не используется");
Возврат ПрочитатьСвойСтатус(Ключ) + ". Ранее полученный объект соединения НЕ использовать";
КонецФункции
Функция УдалитьСвоегоКлиента(Ключ, Согласие) Экспорт
Если Не Согласие Тогда ВызватьИсключение "Нужно согласие на удаление только своего сохраненного клиента"; КонецЕсли;
Клиент = ПолучитьСвоегоКлиента(Ключ);
Если Клиент.ПолучитьСостояниеСоединения() <> СостояниеWebSocketСоединения.Закрыто Тогда
ВызватьИсключение "Сначала отключите и отдельно подтвердите фактическое Закрыто";
КонецЕсли;
ЗаписатьСвоеСобытие(Ключ, "УдалениеЗапрошено", "Удаляется только проверенный собственный непредопределенный клиент");
Клиент.Удалить();
После = WebSocketКлиенты.НайтиПоКлючу(Ключ);
Если После = Неопределено Тогда
Возврат "Удалить выполнен; менеджер не нашел клиента своего ключа. Журнал событий не удалялся";
КонецЕсли;
Возврат "Удалить выполнен; менеджер еще предоставил объект типа " + Строка(ТипЗнч(После))
+ ". Не считать это подтверждением исчезновения объекта; исследуйте только свой стенд. Журнал не удалялся";
КонецФункции
Процедура ПроверитьСвойПустойСтенд()
Если Метаданные.Справочники.Количество() <> 0 Или Метаданные.Документы.Количество() <> 0 Или Метаданные.Перечисления.Количество() <> 0 Или Метаданные.РегистрыСведений.Количество() <> 0 Или Метаданные.РегистрыНакопления.Количество() <> 0 Или Метаданные.РегистрыБухгалтерии.Количество() <> 0 Или Метаданные.РегистрыРасчета.Количество() <> 0 Или Метаданные.ПланыВидовХарактеристик.Количество() <> 0 Или Метаданные.ПланыВидовРасчета.Количество() <> 0 Или Метаданные.ПланыСчетов.Количество() <> 0 Или Метаданные.ПланыОбмена.Количество() <> 0 Или Метаданные.Последовательности.Количество() <> 0 Или Метаданные.БизнесПроцессы.Количество() <> 0 Или Метаданные.Задачи.Количество() <> 0 Или Метаданные.ОбщиеРеквизиты.Количество() <> 0 Или Метаданные.ПодпискиНаСобытия.Количество() <> 0 Или Метаданные.РегламентныеЗадания.Количество() <> 0 Или Метаданные.ВнешниеИсточникиДанных.Количество() <> 0 Тогда
ВызватьИсключение "Нужен только свой новый пустой учебный стенд";
КонецЕсли;
Если Метаданные.ЕстьИзмененияРасширениямиКонфигурации() Тогда
ВызватьИсключение "Свой сеанс не должен изменяться расширениями";
КонецЕсли;
Источники = Новый Массив;
Источники.Добавить(ИсточникРасширенийКонфигурации.БазаДанных);
Источники.Добавить(ИсточникРасширенийКонфигурации.СеансАктивные);
Источники.Добавить(ИсточникРасширенийКонфигурации.СеансОтключенные);
Для Каждого Источник Из Источники Цикл
Расширения = РасширенияКонфигурации.Получить(, Источник);
Если ТипЗнч(Расширения) <> Тип("Массив") Тогда
ВызватьИсключение "Native список расширений должен иметь тип Массив";
КонецЕсли;
Если Расширения.Количество() <> 0 Тогда
ВызватьИсключение "В своём новом стенде нет расширений базы или сеанса";
КонецЕсли;
КонецЦикла;
Если ТранзакцияАктивна() Тогда
ВызватьИсключение "Чужая или вложенная активная транзакция не допускается";
КонецЕсли;
КонецПроцедурыЭхо85 — модуль WebSocket-клиента
УчебныйСохраненныйWebSocket85 → Общие → WebSocket-клиенты → Эхо85 → Модуль
Полный модуль метаданных с пятью собственными WebSocket-событиями. НЕ форма и НЕ общий модуль; нет чтения серверного Соединение.Ключ.
Процедура ПередПодключением(Клиент)
Ключ = Клиент.Ключ;
УчебныйСохраненныйWS85.СверитьСвоегоКлиента(Клиент, Ключ);
УчебныйСохраненныйWS85.НастроитьСвоиПараметры(Клиент, Ключ);
УчебныйСохраненныйWS85.ЗаписатьСвоеСобытие(Ключ, "ПередПодключением", "Настроены настоящие Клиент.ПараметрыСоединения перед попыткой соединения");
КонецПроцедуры
Процедура ПриОткрытииСоединения(Соединение)
Ключ = УчебныйСохраненныйWS85.ПолучитьКлючСвоегоСобытия(Соединение);
УчебныйСохраненныйWS85.ЗаписатьСвоеСобытие(Ключ, "Открытие", "Реальное событие открытия; получение echo еще не подтверждено");
Попытка
Соединение.ОтправитьСообщение("own-ws85-persist-text:" + Ключ);
Исключение
ВызватьИсключение УчебныйСохраненныйWS85.ЗавершитьСвоеСоединениеПослеОшибки(Соединение, Ключ, ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
Процедура ПриПолученииСообщения(Соединение, Сообщение)
Ключ = УчебныйСохраненныйWS85.ПолучитьКлючСвоегоСобытия(Соединение);
Попытка
Если ТипЗнч(Сообщение) = Тип("Строка") Тогда
Если Сообщение <> "own-ws85-persist-text:" + Ключ Тогда
ВызватьИсключение "Не совпал точный текстовый echo своего сохраненного клиента";
КонецЕсли;
УчебныйСохраненныйWS85.ЗаписатьСвоеСобытие(Ключ, "ТекстEcho", "Подтвержден точный собственный текст");
Данные = Base64Значение("UElORw==");
Соединение.ОтправитьСообщение(Данные);
ИначеЕсли ТипЗнч(Сообщение) = Тип("ДвоичныеДанные") Тогда
Если Base64Строка(Сообщение) <> "UElORw==" Тогда ВызватьИсключение "Не совпали свои четыре байта PING"; КонецЕсли;
УчебныйСохраненныйWS85.ЗаписатьСвоеСобытие(Ключ, "ДвоичныйEcho", "Подтверждены свои четыре байта PING");
Иначе
ВызватьИсключение "Недокументированный тип входящего сообщения";
КонецЕсли;
Исключение
ВызватьИсключение УчебныйСохраненныйWS85.ЗавершитьСвоеСоединениеПослеОшибки(Соединение, Ключ, ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
Процедура ПриОшибке(Соединение, КодОшибки, Описание)
Ключ = УчебныйСохраненныйWS85.ПолучитьКлючСвоегоСобытия(Соединение);
Если ТипЗнч(КодОшибки) <> Тип("Строка") Или ТипЗнч(Описание) <> Тип("Строка") Тогда
ВызватьИсключение "Не совпали два строковых параметра ошибки";
КонецЕсли;
ВызватьИсключение УчебныйСохраненныйWS85.ЗавершитьСвоеСоединениеПослеОшибки(Соединение, Ключ,
Лев(КодОшибки, 100) + ": " + Лев(Описание, 1000));
КонецПроцедуры
Процедура ПриЗакрытииСоединения(Соединение, КодЗакрытия)
Ключ = УчебныйСохраненныйWS85.ПолучитьКлючСвоегоСобытия(Соединение);
Если ТипЗнч(КодЗакрытия) <> Тип("Число") Тогда ВызватьИсключение "Нужен реальный числовой код закрытия"; КонецЕсли;
УчебныйСохраненныйWS85.ЗаписатьСвоеСобытие(Ключ, "Закрытие", "Реальный статус " + Строка(КодЗакрытия) + ";1005 означает отсутствие полученного кода");
КонецПроцедурыПультСохраненногоWS85
Своя внешняя обработка ПультСохраненногоWS85 → Управляемая основная форма → Модуль
Полный клиент-серверный пульт своих шести команд; клиенту возвращаются строки. Настоящий UUID сохранен для журнала и восстановления.
&НаКлиенте
Процедура ПриОткрытии(Отказ)
Согласие = Ложь;
КлючСтенда = "";
КлючВосстановления = "";
Результат = "Подготовьте свой сервер и права. Сохраненный клиент еще не создан";
КонецПроцедуры
&НаКлиенте
Процедура Подготовить85(Команда)
Если Не Согласие Или КлючСтенда <> "" Тогда
ВызватьИсключение "Нужны согласие и новый опыт; старый ключ не заменяется";
КонецЕсли;
КлючСтенда = "own-ws85-persist-" + Строка(Новый УникальныйИдентификатор);
Результат = ПодготовитьНаСервере(КлючСтенда, Согласие);
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПодготовитьНаСервере(Ключ, Согласие)
Возврат УчебныйСохраненныйWS85.ПодготовитьСвоегоКлиента(Ключ, Согласие);
КонецФункции
&НаКлиенте
Процедура Подключить85(Команда)
Результат = ПодключитьНаСервере(КлючСтенда, Согласие);
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПодключитьНаСервере(Ключ, Согласие)
Возврат УчебныйСохраненныйWS85.ПодключитьСвоегоКлиента(Ключ, Согласие);
КонецФункции
&НаКлиенте
Процедура Состояние85(Команда)
Результат = СостояниеНаСервере(КлючСтенда);
КонецПроцедуры
&НаСервереБезКонтекста
Функция СостояниеНаСервере(Ключ)
Возврат УчебныйСохраненныйWS85.ПрочитатьСвойСтатус(Ключ);
КонецФункции
&НаКлиенте
Процедура Отключить85(Команда)
Результат = ОтключитьНаСервере(КлючСтенда);
КонецПроцедуры
&НаСервереБезКонтекста
Функция ОтключитьНаСервере(Ключ)
Возврат УчебныйСохраненныйWS85.ОтключитьСвоегоКлиента(Ключ);
КонецФункции
&НаКлиенте
Процедура Удалить85(Команда)
Результат = УдалитьНаСервере(КлючСтенда, Согласие);
КонецПроцедуры
&НаСервереБезКонтекста
Функция УдалитьНаСервере(Ключ, Согласие)
Возврат УчебныйСохраненныйWS85.УдалитьСвоегоКлиента(Ключ, Согласие);
КонецФункции
&НаКлиенте
Процедура ВосстановитьКлюч85(Команда)
Если Не Согласие Или КлючСтенда <> "" Тогда
ВызватьИсключение "Для восстановления нужны согласие и пустой текущий ключ; другой опыт не подменяется";
КонецЕсли;
Проверка = СостояниеНаСервере(КлючВосстановления);
КлючСтенда = КлючВосстановления;
Результат = "Восстановлен только реально найденный собственный клиент. " + Проверка;
КонецПроцедурыown-ws85.cjs — JavaScript echo-сервер, НЕ модуль1С
Новая отдельная папка собственного опыта → own-ws85.cjs
Полный ранее статически проверенный loopback-сервер Node.js/ws; не запускался на реальной сети.
const http=require('node:http');
const {WebSocketServer,WebSocket}=require('ws');
const host='127.0.0.1';
const port=18485;
const endpoint='/own-ws85';
const credentials='Basic '+Buffer.from('own-ws85:own-local-only','ascii').toString('base64');
const server=http.createServer((request,response)=>{response.writeHead(404);response.end('own-ws85 only');});
const sockets=new Set();
const wss=new WebSocketServer({noServer:true,maxPayload:4096,perMessageDeflate:false});
server.maxConnections=8;
server.headersTimeout=10000;
server.requestTimeout=10000;
function reportError(error) {console.error('own-ws85 failure:',error.code||error.message);}
function rejectUpgrade(socket,status,challenge='') {
socket.end('HTTP/1.1 '+status+'\r\nConnection: close\r\nContent-Length: 0\r\n'+challenge+'\r\n');
}
server.on('connection',socket=>{
sockets.add(socket);
socket.setTimeout(60000,()=>socket.destroy());
socket.on('error',reportError);
socket.on('close',()=>sockets.delete(socket));
});
server.on('upgrade',(request,socket,head)=>{
if(request.socket.remoteAddress!==host||request.method!=='GET'||request.url!==endpoint||request.headers.host!==host+':'+port||request.headers['x-own-lab']!=='owned-ws85-direct') {
rejectUpgrade(socket,'403 Forbidden');return;
}
if(request.headers.authorization!==credentials) {
rejectUpgrade(socket,'401 Unauthorized','WWW-Authenticate: Basic realm="own-ws85"\r\n');return;
}
if(wss.clients.size>=4) {rejectUpgrade(socket,'503 Service Unavailable');return;}
try {wss.handleUpgrade(request,socket,head,ws=>wss.emit('connection',ws,request));}
catch(error) {reportError(error);socket.destroy();}
});
wss.on('error',reportError);
wss.on('connection',ws=>{
let received=0;
ws.on('error',reportError);
ws.on('message',(data,isBinary)=>{
received++;
if(received>8||data.length>512||ws.bufferedAmount>4096) {ws.close(1008,'own-lab limit');return;}
if(ws.readyState!==WebSocket.OPEN) return;
ws.send(data,{binary:isBinary},error=>{if(error){reportError(error);ws.terminate();}});
});
ws.on('close',code=>console.log('own-ws85 connection closed:',code));
});
server.on('error',error=>{reportError(error);process.exitCode=1;});
server.listen(port,host,()=>console.log('own-ws85 listening on ws://'+host+':'+port+endpoint));
process.once('SIGINT',()=>{
for(const client of wss.clients) client.terminate();
for(const socket of sockets) socket.destroy();
wss.close();
server.close(error=>{if(error){reportError(error);process.exitCode=1;}});
});Разбор по шагам
Новый UUID создается пультом ДО серверного вызова и остается у пользователя даже при ошибке подготовки. Сервер проверяет канонический ключ с own-ws85-persist-префиксом, собственные имя базы/версию/пользователя и единственный непредопределенный Эхо85. Занятый ключ отклоняется; существующий клиент не перезаписывается.
СоздатьКлиента получает настоящий метаобъект и ключ. URL, ИмяПользователяИнформационнойБазы и выключенное ПодключатьАвтоматически задаются у своего нового клиента. Записать — процедура сохранения, не подключение и не возвращаемое значение. Клиент повторно находится и проверяется; после неуспешной подготовки независимо предпринимается удаление только этого собственного нового клиента.
Подключить вызывается отдельно только без текущего соединения. Его результат допускает Неопределено или настоящий WebSocketКлиентСоединение. Ни сам результат, ни строка запроса не выдаются за завершенный handshake/echo. При ошибке соединения вызывается только собственный Отключить, его ошибка сохраняется.
ПередПодключением получает настоящий Клиент и заново задает Клиент.ПараметрыСоединения: публичную Basic-пару, заголовок, таймаут и паспорт событий. Ошибка исходного нативного фрагмента Клиент.Параметры не копируется. У readonly-источника не заменяется объект, меняются свойства реально полученных параметров.
Модуль метаданных содержит пять точных событий. Открытие отправляет свой UUID-текст, подтвержденное текстовое echo отправляет четыре байта PING, двоичный callback проверяет Base64. Ошибка получает две строки, закрытие — числовой код;1005 означает отсутствие кода. Тексты ошибок/закрытия фактические, а не заранее придуманное сообщение.
Серверные callback НЕ используют Соединение.Ключ, доступный только толстому клиенту. Ключ берется из реальных Параметры.ДополнительныеПараметры, заполненных перед подключением; сверяются marker/URL/пользователь и собственный UUID. Клиент.Ключ в ПередПодключением — другой API, допускающий сервер.
Регистрационный журнал связывает событие с настоящим метаобъектом Эхо85, строкой собственного ключа в Данные и ограниченным комментарием. Разрешенные имена проверяются точно; пароль/Basic-текст скрываются. Журнал регистрации не равен сообщению формы: его нужно открыть и реально проверить собственный фильтр и включенные уровни.
Отключить выключает автоподключение и делает прежнее соединение непригодным. Контроллер больше не обращается к прежнему объекту: статус читается заново через клиента. Удалить разрешен только непредопределенному собственному клиенту в фактическом Закрыто; исходные метаданные и регистрационный журнал остаются.
Удалить сбрасывает данные к значениям метаданных. После него диагностируется реальный результат НайтиПоКлючу: если менеджер еще предоставил объект, это явно не объявляется подтверждением его исчезновения. Восстановление пульта допускает только действительно найденный сохраненный клиент своего стенда; новый UUID вместо потерянного ключа не восстанавливает старый опыт.
Как проверить результат
Будущий положительный контроль: Подготовлен без автоматического открытия; после отдельного Подключить фактические ПередПодключением/Открытие/ТекстEcho/ДвоичныйEcho в собственном журнале, затем отдельные отключение, Закрыто и удаление только своего клиента.
Будущий отрицательный контроль: отсутствие Согласие, занятый или иной key-префикс, другой пользователь/метаданные/URL, предопределенность либо автоподключение должны прекращать изменения. Отдельно проверить отказ записи/удаления без права Администрирование данных; подготовка не должна выдавать успешный статус.
Будущий контроль ошибок: остановленный сервер или неверные публичные Basic-данные не дают события ТекстEcho/ДвоичныйEcho. Ошибка/неподтвержденное завершение отражается реально. Отключенный объект соединения не используется для повторной отправки или чтения.
Будущий контроль завершения и восстановления: если пульт закрыт до удаления, восстановить его только сохраненным настоящим ключом; неизменные владение/URL/пользователь снова проверены. Журнал не очищается. При отсутствии фактической записи Закрытие не придумывать ее код: исследовать callback-жизненный цикл своего клиента.
runtimeVerifiedScenarios=0: текущие проверки — source/native/JSON/поиск/копирование, не выполнение этих шагов в1С. Каждый факт обмена и записи журнала требует отдельного разрешенного ручного протокола.
Ожидаемый результат
Полный собственный жизненный цикл сохраненного клиента, явные права, отключение без повторного использования соединения, проверенное собственное удаление и восстановление пульта.
На что обратить внимание
Данные сохраненного клиента и регистрационный журнал — реальные будущие записи собственной учебной базы. Не запускать этот код в production. Право Администрирование данных не обходится привилегированным модулем; оно должно быть явно разрешено только своей учебной роли.
ИмяПользователяИнформационнойБазы — пользователь сеанса обработчиков; Пользователь/Пароль параметров — HTTP-учебная пара. Публичный пароль не является защитой рабочего сервиса. Для production нужны отдельные TLS/wss, секреты и политика доступа.
Подключить может вернуть Неопределено. Нельзя угадать успешный handshake из результата вызова. Статус Открыто не доказывает точный echo; нужны фактические события по собственному UUID.
После Отключить прежнее соединение использовать нельзя. Событие закрытия дает платформенный объект только в контексте своего callback; наличие/доступность его паспорта и фактической записи надо проверить вручную, не обращаться к сохраненной старой ссылке из пульта.
События разных сеансов могут попасть в один момент журнала. Порядок строк/секунд не доказывает порядок исполнения; проверяйте собственный key и смысл событий. Не требуйте статуса1000, фиксированного описания сетевой ошибки или обязательного полного журнала при выключенных уровнях.
Предопределенный клиент удалять нельзя. Контроллер проверяет Предопределенный и всю собственную пару key/метаданные/URL/пользователь до удаления, не перебирает и не удаляет массив чужих клиентов.
Сохраненный клиент живет независимо от формы. Закрытие пульта не является завершением или удалением. Потеря ключа требует исследования только своей базы; новая метка не относится к старому клиенту. Код восстановления не выполняет запись или подключение.
JS-сервер проверяется отдельно как JavaScript, не BSL. Его библиотека/сеть/1С не запускались. Переиспользование полного ранее проверенного сервера не является новым runtime-тестом.
Пример не запускался в вашей базе. Перед применением проверьте его в указанном учебном контексте.