ОбработчикиWebSocketКлиентСоединения.По умолчанию

Конструкторы

Создает объект ОбработчикиWebSocketКлиентСоединения и связывает его с вызовом указанных процедур модуля.

Синтаксис

Новый ОбработчикиWebSocketКлиентСоединения(<ОбработчикОткрытияСоединения>, <ОбработчикПолученияСообщения>, <ОбработчикОшибки>, <ОбработчикЗакрытияСоединения>, <Модуль>)
Полное описание, параметры и ограничения

ОбработчикиWebSocketКлиентСоединения.По умолчанию

ОбработчикиWebSocketКлиентСоединения (WebSocketClientConnectionHandlers)

По умолчанию

Доступен, начиная с версии 8.3.27.

Синтаксис:
Новый ОбработчикиWebSocketКлиентСоединения(<ОбработчикОткрытияСоединения>, <ОбработчикПолученияСообщения>, <ОбработчикОшибки>, <ОбработчикЗакрытияСоединения>, <Модуль>)
Параметры:

<ОбработчикОткрытияСоединения> (необязательный)
Тип: Строка.
Имя экспортируемой процедуры обработчика события открытия соединения.

<ОбработчикПолученияСообщения> (необязательный)
Тип: Строка.
Имя экспортируемой процедуры-обработчика события получения сообщения.

<ОбработчикОшибки> (необязательный)
Тип: Строка.
Имя экспортируемой процедуры обработчика события возникновения ошибки.

<ОбработчикЗакрытияСоединения> (необязательный)
Тип: Строка.
Имя экспортируемой процедуры обработчика события закрытия соединения.

<Модуль> (необязательный)
Тип: ФормаКлиентскогоПриложения, КомандаКомандногоИнтерфейса, ОбщийМодуль, Строка.
Модуль процедур-обработчиков событий.
Описание:

Создает объект ОбработчикиWebSocketКлиентСоединения и связывает его с вызовом указанных процедур модуля.

Использование в версии:

Доступен, начиная с версии 8.3.27.

Методическая информация

Практические примеры

Прямой WebSocket: локальный echo, два способа связывания событий, текст и двоичные данные

Полный толстоклиентский пульт получает параметры из настоящих собственных метаданных и создает прямое асинхронное соединение. Собственный JavaScript-сервер возвращает исходные сообщения; успех подтверждают события с точным payload, не факт вызова открытия.

Где и как запускать

Полный модуль своей управляемой формы ПультПрямогоWebSocket85. Толстый клиент Windows, Управляемое приложение,8.5.1.1343, пользователь УчебныйWS85. Серверная защита отдельна от толстоклиентских callback. Код в1С не запускался.

Подготовка

  1. НОВАЯ отдельная пустая файловая база точно8.5.1.1343. Конфигурация УчебныйПрямойWebSocket85, язык Русский, совместимость Не использовать. Нет прикладных объектов, общих реквизитов, расширений, подписок, заданий или рабочих данных; модули конфигурации вручную пустые. Не использовать рабочие БП/КА2/УТ/УНФ.

  2. Общие → WebSocket-клиенты: единственный объект Эхо85. URLСервера=ws://127.0.0.1:18485/own-ws85, Предопределенный=Нет, ПодключатьАвтоматически=Нет. Модуль метаобъекта вручную пустой: прямое соединение использует процедуры своей формы. Метаданные и readonly ПараметрыСоединения дают настоящий объект настроек; отдельный Новый ПараметрыWebSocketКлиентСоединения не придумывается.

  3. Только в этой пустой базе подготовьте собственного пользователя УчебныйWS85 и собственную учебную роль с правами использования своей конфигурации и открытия внешней обработки. Вручную разрешите этой роли соответствующие объекты. Запускайте толстый клиент Windows, режим Управляемое приложение, именно под этим пользователем. Имя пользователя ИБ не является HTTP-логином echo-сервера.

  4. Своя внешняя обработка ПультПрямогоWebSocket85, управляемая основная форма. Реквизиты Согласие, ЯвныеОбработчики, ОтветТекста, ОтветДвоичного, ДвоичноеОтправлено — Булево; КлючСтенда — Строка100; ОжидаемыйТекст — Строка200; Журнал — Строка неограниченной длины. Покажите редактируемые флажки согласия/профиля и многострочный Журнал только просмотра. Служебные ключ/ожидание/ответы не делайте редактируемыми.

  5. Четыре команды и кнопки ОткрытьСвойWebSocket85, ОтправитьСвоеДвоичное85, СостояниеСвоегоWebSocket85, ЗакрытьСвойWebSocket85: действия одноименные, ИзменяетСохраняемыеДанные=Ложь. Весь основной модуль вставить в модуль формы. Назначить ПриОткрытии и ПриЗакрытии. Четыре экспортные процедуры сокета вручную к событиям формы не назначать: их связывает настоящий объект обработчиков.

  6. Будущая ручная подготовка: сохранить свою обработку, сохранить и обновить ТОЛЬКО свой стенд F7. Проверить пустые модули и отсутствие автоподключения Эхо85. Толстоклиентский код огражден #Если ТолстыйКлиентУправляемоеПриложение: серверные процедуры не читают соединение.Ключ; тонкий/веб-клиент получает отказ.

  7. Для будущего сервера нужны 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-релиза не объявляется проверенной.

  8. Перед будущим запуском убедиться, что свободный 127.0.0.1:18485 принадлежит только своему стенду. Вручную node own-ws85.cjs. Сервер слушает только IPv4 loopback, принимает /own-ws85, X-Own-Lab и публичные учебные Basic-данные own-ws85/own-local-only. Это НЕ секреты и НЕ защита production. Сеть и сервер здесь не запускались.

  9. runtimeVerifiedScenarios=0. После отдельного разрешения: отметить Согласие, открыть свое соединение, дождаться точного текстового echo, отправить четыре байта PING, дождаться двоичного echo, запросить закрытие и прочитать реальное Закрыто. После подтвержденного закрытия повторить с другим флажком ЯвныеОбработчики для второго конструктора. Каждое открытие получает новый UUID, старый ключ не переиспользуется.

  10. Завершение будущего стенда: сначала кнопка закрытия и ручное подтверждение состояния/события, потом закрытие формы. ПриЗакрытии дополнительно запрашивает только свое закрытие без серверных вызовов/открытия окон. В консоли своего 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;}});
});

Разбор по шагам

  1. Проверяем пустую базу и единственный Эхо85 с локальным URL, без предопределенности/автоподключения. НайтиПредопределенный получает настоящий объект метаданных, не строку. СоздатьКлиента получает его и свой новый ключ; паспорт, readonly Метаданные/Ключ/ПараметрыСоединения и отсутствие текущего соединения проверяются до прямого открытия.

  2. Настоящие параметры получают собственные Basic-данные, заголовок, таймаут установления10секунд и паспорт Метка/Ключ событий. ОС-аутентификация и ОС-прокси отключены. Прокси читается как исходный неинициализированный ИнтернетПрокси; чужая сеть не перенастраивается. Проверки читают значения, но пароль не печатается.

  3. ЯвныеОбработчики=Истина создает пустой объект и заполняет пять свойств. Ложь использует конструктор с четырьмя точными экспортными процедурами и своей формой. Оба пути используются в отдельных циклах, не создают неиспользуемую копию.

  4. Перед ОткрытьСоединение проверяем отсутствие UUID-ключа. Менеджер возвращает существующее соединение для занятого ключа, поэтому он отклоняется. После вызова сверяются URL, ключ, пользователь ИБ, параметры событий и собственный модуль. Ключ читается только внутри толстоклиентской ветки.

  5. Текст ставится на отправку сразу после асинхронного вызова: справка допускает очередь до установки соединения. Событие открытия диагностирует состояние, только точное текстовое сообщение устанавливает ОтветТекста. Двоичная команда требует этот ответ и фактическое Открыто; четыре байта PING проверяются в callback. Тип сообщения — Строка или ДвоичныеДанные, не выдуманный СообщениеWebSocket.

  6. Команда состояния читает свое соединение и реальные массивы обоих менеджеров. Общие размеры не объявляются числом открытых своих сокетов: возможны другие/закрытые объекты. Четыре состояния различаются без бесконечного ожидания или требования обязательного наблюдения каждого перехода.

  7. Закрыть запрашивает закрытие, не обещает мгновенное Закрыто. Событие принимает реальный числовой код;1005 означает отсутствие полученного кода. Ошибка принимает две строки, выводит ограниченный текст и запрашивает свое закрытие. Ошибки открытия и безопасного завершения сохраняются раздельно.

  8. JavaScript использует ws для разбора протокола, проверяет loopback/путь/заголовок/Basic до Upgrade, ограничивает клиентов, сообщения и буфер. Echo сохраняет текстовый/двоичный вид. JS-синтаксис и чистые ветки проверяются отдельно от BSL, не считаются проверкой 1С или настоящей сети.

Как проверить результат

  1. Будущий положительный опыт: в каждом профиле обработчиков реальные событие открытия, точный UUID-текст и четыре двоичных байта, затем реальное закрытие. До callback ответы Ложь. В журнале нет пароля; вызов отправки не называется подтвержденным получением.

  2. Будущий отрицательный опыт: Согласие=Ложь, занятый ключ, другой URL/метаобъект или второе открытие до закрытия прекращают действия. Остановленный сервер/неверные публичные Basic-данные не дают флаги ответа; фактическая ошибка выводится, свое закрытие запрашивается.

  3. Будущий контроль контекстов: тонкий/веб-клиент получает отказ вместо вызова Ключ. При закрытии формы отсутствуют серверные вызовы/новые окна. Не считать закрытие успешным по отсутствию исключения у Закрыть.

  4. runtimeVerifiedScenarios=0: реальные разрешения пользователя, Basic-handshake/повтор после401, передача формы в обработчики, callback-сеанс, очередь, echo и завершение не запускались. Исходники не заменяют ручной протокол.

Ожидаемый результат

Полный собственный пульт прямого соединения и отдельный локальный echo-сервер с точными контрактами и разделением инициирования, событий, ответов и закрытия.

На что обратить внимание

  1. Этот урок не вызывает клиент.Записать/Подключить/Отключить/Удалить и не учит серверные события Модуль WebSocket клиента. Эти оставшиеся API не объявляются покрытыми прямым опытом. Источник параметров — настоящий новый клиент; не обещаются неописанные правила долговременного хранения черновика.

  2. Ключ соединения доступен только толстому клиенту. Пользователь ИБ — пользователь сеанса обработчиков; Пользователь/Пароль параметров — внешние HTTP-данные. Требуются заранее подготовленный собственный пользователь и будущая проверка callback-контекста.

  3. Таймаут параметров относится к установлению, не ожиданию echo. Пульт не блокирует UI циклом; ответы приходят событиями. Не требуются заранее заданный код ошибки или обязательный статус1000 при закрытии.

  4. Readonly параметры/обработчики соединения не заменяются новыми объектами. Настройки задаются ДО открытия через настоящий источник, не меняются гонкой после начала handshake.

  5. ws:// и публичные Basic-данные допустимы только своему loopback-опыту без чувствительной информации. Production требует отдельного анализа TLS/wss, секретов, сертификатов, прокси, повторов, лимитов и журналов.

  6. Callback-форма может жить меньше соединения: свое закрытие подтверждают до закрытия формы. ПриЗакрытии лишь дополнительная попытка, не подтверждение освобождения. Отсутствие объекта у ПолучитьСоединение не доказывает успешный обмен.

  7. Не запускать JavaScript как модуль1С. Проверяются свой JS-синтаксис/ветки на заглушках и BSL-контракты, не сама библиотека ws, не TCP/сервер/1С. Установка зависит от фактически выбранной пользователем версии и окружения.

Пример не запускался в вашей базе. Перед применением проверьте его в указанном учебном контексте.

Не хотите разбираться сами?

Адаптируем это решение под вашу базу. Оценка бесплатно.

Оставить задачу

1С, 1С:Предприятие — товарные знаки ООО «1С». Сайт не является официальным представительством фирмы «1С» и не аффилирован с ней.