Использование WebSockets для работы с RabbitMQ посредством протокола WebSTOMP

07.07.25

Интеграция - WEB-интеграция

Использование новых возможностей платформы 1С:Предприятие для работы с брокером сообщений RabbitMQ посредством WebSockets в связке с WebSTOMP. Реализация основных функций: установка соединения, отправка сообщений в очередь, прием сообщений из очереди и подтверждение приема.

Файлы

ВНИМАНИЕ: Файлы из Базы знаний - это исходный код разработки. Это примеры решения задач, шаблоны, заготовки, "строительные материалы" для учетной системы. Файлы ориентированы на специалистов 1С, которые могут разобраться в коде и оптимизировать программу для запуска в базе данных. Гарантии работоспособности нет. Возврата нет. Технической поддержки нет.

Наименование Скачано Купить файл
(только для физ. лиц)
Использование WebSockets для работы с RabbitMQ посредством протокола WebSTOMP
.cfe 32,32Kb
0 1 850 руб. Купить

Подписка PRO — скачивайте любые файлы со скидкой до 85% из Базы знаний

Оформите подписку на компанию для решения рабочих задач

Оформить подписку и скачать решение со скидкой

Здравствуйте, коллеги.

В своей работе в некоторых задачах использую преимущества обмена данными с использованием очередей сообщений RabbitMQ (например обмен сообщениями между сайтом и РМК, между РМК и ЕРП, между ЕРП и сайтом).

Когда может понадобится RabbitMQ:

  • Когда требуется реализовать асинхронное взаимодействие между составляющими в распределительной системе. Одно сообщение может быть обработано сразу несколькими сервисами.
  • Когда необходимо обрабатывать большое количество сообщений. Это снимает нагрузку и гарантирует, что данные будут доставлены до адресата.
  • Когда должна быть возможность масштабирования и обработки больших потоков информации.
  • Когда требуется обеспечение отказоустойчивости и надёжности системы. В случае сбоя одного из компонентов, RabbitMQ сохраняет сообщения и передаёт их, как только проблемный он вновь сможет нормально работать.
  • В микросервисных архитектурах для обеспечения независимой работы каждого сервиса и обмена данными между ними.
  • При интеграции систем — брокер служит связующим звеном между системами и также отвечает за обмен данными между ними.

До недавнего момента для работы с RMQ использовал очень удобную компоненту PinkRabbitMQ, однако после установки свежей платформы компонента стала часто отваливаться (на сервере под Linux) и решено было реализовать новые возможности платформы 1С для подключения к RabbitMQ с помощью WebSockets (тем более что давно хотел это сделать, да все не было времени).

Представляю к ознакомлению расширение в котором реализованы основные функции работы с брокером сообщений: установка соединения, отправка сообщений, чтение очередей и подтверждение сообщений.

Для начала, для тех кто собирается тестить с нуля опишу как установить и настроить RabbitMQ для работы с использованием WebSocket соединений (пришлось потратить некоторое время на исследования правильной настройки плагинов RMQ и изучение протокола STOMP, поэтому хочу избавить вас от этой рутины и привести некоторую минимально достаточную выжимку). 

Нам потребуется сервер RabbitMQ и свежая платформа 1С.

 

Итак, начнем.

 

  1. Установка RabbitMQ:

Идем на github Releases · rabbitmq/rabbitmq-server скачиваем последнюю версию RabbitMQ и запускаем установщик. Для установки соглашаемся скачать erlang, ждем окончания загрузки и завершаем установку RabbitMQ.

              Переходим в каталог установленной версии (для версии 3.13.6 это каталог «C:\Program Files\RabbitMQ Server\rabbitmq_server-3.13.6\sbin») открываем окно PowerShell (Shift + ПКМ) и выполняем команду:

.\rabbitmq-plugins list

По этой команде будет выведен список подключенных расширений.

Для удобства управления можно включить rabbitmq_management, для этого выполняем команду:

 .\rabbitmq-plugins enable rabbitmq_management

Включаем rabbitmq_web_stomp. Для этого выполняем команду:

.\rabbitmq-plugins enable rabbitmq_web_stomp

и перезагружаем сервер командой:

.\rabbitmq-server restart

После перезагрузки убеждаемся что все необходимые плагины подключены. Выполняем команду:

.\rabbitmq-plugins list


 

И убеждаемся что напротив плагинов rabbitmq_management и rabbitmq_web_stomp установлены значки «E*». «E» означает что плагин включен явно, «e» - что плагин включен неявно (необходим для запуска другого плагина) , «*» означает что плагин запущен.

После этих манипуляций можно в браузере перейти по пути localhost:15672 и увидеть окно входа в web интерфейс RabbitMQ:

 
 

 

 

 

 

 

 

Логинимся как пользователь guest и пароль guest. По умолчанию эти учетные данные работают только для localhost, извне с ними не подключиться, поэтому создаем нового пользователя:

 
 


Закладка «Admin» - «Add user» - задаем имя пользователя и пароль (я назначил имя: webuser, пароль: root) и указываем набор прав (я указал Admin – «administrator»)

и задаем ему права: кликаем на пользователя в списке и жмем: «Set permission» и «Set topic permission»:

Теперь мы видим что пользователю «webuser» назначены права на виртуальный хост «/» чего нам пока достаточно.

 

  1. Подключение и работа с брокером посредством веб сокетов в 1С

Работа с WebSocket соединениями в 1С происходит посредством объекта «WebSocketКлиентСоединения».

Подключение к RabbitMQ WebSTOMP по умолчанию производится по порту 15 674, а строка подключения выглядит так:

ws://server:port/ws

где ws - не защищенный сервер, wss – защищенный

например:

ws://10.20.4.17:15674/ws

или

wss://10.20.4.17:15674/ws

по localhost можно подключиться под именем guest с паролем guest, однако для подключения снаружи необходимо создать нового пользователя с необходимыми правами.

Для тех кто хочет полностью погрузиться во все прелести протокола STOMP – вот ссылка на официальную документацию: https://stomp.github.io/stomp-specification-1.2.html

Вкратце выглядит все так:

  1. Открываем соединение с сервером.

  2. Отправляем КАДР (специальным образом скомпонованное сообщение) «CONNECT» (или «STOMP» для протокола версии 1.2). В кадре необходимо указать:

    •  с какой версией STOMP мы можем работать

    • имя пользователя

    • пароль

    • виртуальный хост

    • параметры сердцебиения для поддержки соединения

  3. В ответ ждем КАДР «CONNECTED» после чего можем начать общение с брокером. В этом кадре сервер сообщает:

    • какую версию STOMP он поддерживает

    • версию сервера RabbitMQ

    • параметры сердцебиения

  4. Для отправки сообщения отправляем КАДР «SEND» с указанием:

    • направления: тип приемника (очередь, точка обмена, топик), имя приемника

    • тип содержимого

    • тело сообщения

  5. Для получения сообщений из очереди необходимо на нее подписаться. Для этого необходимо отправить КАДР «SUBSCRIBE» в котором следует указать:

    • имя (идентификатор) подписки

    • направление получения: тип приемника (очередь, точка обмена, топик), имя приемника

    • тип подтверждения: auto (все полученные сообщения сразу считаются подтвержденными), client (необходимо подтвердить все последние полученные сообщения), client-individual (необходимо подтвердить получение каждого сообщения)

После установки подписки начнется получение сообщений из очереди

  1. Для подтверждения сообщения используется кадр «ACK» в который передаются:

    • имя (идентификатор) подписки

    • имя подписки

 

Основные виды получаемых кадров:

  • «ERROR» - на стороне сервера произошла ошибка или ошибка обработки отправленного КАДРА

  • «CONNECTED» - соединение успешно установлено

  • «MESSAGE» - сообщение из очереди

  • «RECEIPT» - подтверждение приема сообщения

  • «» (пустое сообщение) – сердцебиение сервера

есть еще много разных кадров, подробнее в официальной документации

Почти все кадры устроены одинаково:

  • Команда

  • Заголовки

  • тело (не у всех кадров)

Команда отделяется от заголовков переводом строки, строки заголовков разделяются переводом строки, тело отделяется от заголовков двумя переводами строки, КАДР завершается символом нуля (  в 1С это Символ(0)  ).

Специальный кадр сердцебиения состоит только из переводов строк и завершения кадра.

Примеры кадров:

                CONNECT

accept-version:1.0,1.1,1.2

login:webuser

passcode:root

host:ptr_sl

heart-beat:1000,1000

               

                CONNECTED

server:RabbitMQ/3.13.6

session:session-Vx4C1sHsxP07z6oXRthBJQ

heart-beat:1000,1000

version:1.2

 

SEND

destination:/queue/que2

content-type:application/json

receipt:18f41e42-5542-483a-972d-bdf4532f0686

 

 

Проверка

               

                SUBSCRIBE

id:que2

destination:/queue/que2

ack:client-individual

 

                ACK      

                id: t@111

subscription: sub1

 

RECEIPT

receipt-id:18f41e42-5542-483a-972d-bdf4532f0686

 

Ниже приведен код основного серверного модуля расширения для демонстрации реализации интерфейса:

 

 

#Область Служебные_функции

Функция СимволРазделительСтрокКадра() Экспорт
    Возврат Символы.ПС;	
КонецФункции

Функция СимволРазделительЧастейКадра() Экспорт
    Возврат Символы.ПС + Символы.ПС;	
КонецФункции                       

Функция СимволЗавершениеКадра() Экспорт
    Возврат Символ(0);	
КонецФункции

Функция ДанныеКадра(вхКадр) Экспорт    
	
	тДанныеКадра = Новый Структура("Команда,Заголовки,Тело");
	
	тЧастиКадра = _СтроковыеФункцииКлиентСервер.РазложитьСтрокуВМассивПодстрок(вхКадр,СимволРазделительЧастейКадра(),Ложь,Истина);
	
	//тЧастиКадра = СтрРазделить(вхКадр,СимволРазделительЧастейКадра(),Ложь);
	
	Если тЧастиКадра.Количество() = 0 Тогда
		Возврат тДанныеКадра;
	КонецЕсли;          
	
	тСлужебнаяЧасть 		= тЧастиКадра[0];
	тСтрокиСлужебнойЧасти 	= СтрРазделить(тСлужебнаяЧасть,СимволРазделительСтрокКадра(),Ложь);
	
	тКомандаКадра = "";
	Если ЗначениеЗаполнено(тСтрокиСлужебнойЧасти) Тогда
		тКомандаКадра = тСтрокиСлужебнойЧасти[0];
	КонецЕсли;
	
	тЗаголовкиКадра = Новый Соответствие;
	Для тНомерСтроки = 1 по тСтрокиСлужебнойЧасти.Количество() - 1 Цикл
		
		тСтрока = тСтрокиСлужебнойЧасти[тНомерСтроки];
		
		тПервоеВхождениеРазделителя = СтрНайти(тСтрока,":");
		
		Если тПервоеВхождениеРазделителя = 0 Тогда
			Продолжить;
		КонецЕсли;     
		
		тСвойство = Лев(тСтрока,тПервоеВхождениеРазделителя - 1);
		тЗначение = Сред(тСтрока,тПервоеВхождениеРазделителя + 1);
		
		тЗаголовкиКадра.Вставить(тСвойство,тЗначение);
	КонецЦикла;
	
	тТелоКадра = "";
	Если тЧастиКадра.Количество() > 1 Тогда
		тТелоКадра = тЧастиКадра[1];
	КонецЕсли;
	
	//Убираем завершающий символ (0) чтобы текст можно было отправить на клиентскую часть
	тТелоКадра = СтрЗаменить(тТелоКадра,СимволЗавершениеКадра(),"");
	
	тДанныеКадра.Вставить("Команда",	ВРег(СокрЛП(тКомандаКадра)));
	тДанныеКадра.Вставить("Заголовки",	тЗаголовкиКадра);
	тДанныеКадра.Вставить("Тело",		тТелоКадра);
	
    Возврат тДанныеКадра;	
КонецФункции                    

Функция ПараметрыПротоколаSTOMПоддерживаемыеВерсии(вхЗначение)
	Если ЗначениеЗаполнено(вхЗначение) Тогда
		Возврат вхЗначение;		
	КонецЕсли;
    Возврат "1.0";
КонецФункции  

Функция ПараметрыПротоколаSTOMPКадрСоединения(вхЗначение)
	Если ЗначениеЗаполнено(вхЗначение) Тогда
		Возврат вхЗначение;	
	КонецЕсли;
    Возврат "CONNECT";
КонецФункции

Функция ПараметрыПротоколаSTOMPСердцебиение(вхЗначение)
	Если ЗначениеЗаполнено(вхЗначение) Тогда
		Возврат вхЗначение;	
	КонецЕсли;
    Возврат "0,0";
КонецФункции

Функция ЭкранироватьСимволы(вхСтрока)       
	тЭкранируемыеСимволы = Новый Соответствие;
	тЭкранируемыеСимволы.Вставить("\","\\");
	тЭкранируемыеСимволы.Вставить(":","\c");
	тЭкранируемыеСимволы.Вставить(Символ(10),"\n");
	тЭкранируемыеСимволы.Вставить(Символ(13),"\r");
	
	тСтрока = вхСтрока;
	Для Каждого тКлючЗначение из тЭкранируемыеСимволы Цикл
		тСтрока = СтрЗаменить(тСтрока,тКлючЗначение.Ключ,тКлючЗначение.Значение);
	КонецЦикла;
	
    Возврат тСтрока;	
КонецФункции

Функция КадрСоединения(вхКадр)
	
	Если ЗначениеЗаполнено(вхКадр) Тогда
		Возврат вхКадр;
	КонецЕсли;
	
    Возврат "CONNECT";	
КонецФункции

Функция ТипПриемника(вхПриемник) Экспорт
	
	тПриемник = вхПриемник;
	Если НЕ ЗначениеЗаполнено(вхПриемник) Тогда
		тПриемник = "queue";
	КонецЕсли;
	
    Возврат тПриемник;	
КонецФункции

Функция ЗначениеДополнительногоПараметраСоединения(вхСоединениеИлиКлюч,вхИмяПараметра,вхЗначениеПоУмолчанию = Неопределено) Экспорт
	тСоединение = ПолучитьСоединение(вхСоединениеИлиКлюч);
	
	Если Не ТипЗнч(тСоединение) = Тип("WebSocketКлиентСоединение") Тогда
		Возврат Неопределено;		
	КонецЕсли;
	
	тДопПараметры = тСоединение.Параметры.ДополнительныеПараметры;
	
	Если НЕ ТипЗнч(тДопПараметры) = Тип("Структура") Тогда
		Возврат вхЗначениеПоУмолчанию;
	КонецЕсли;
	
	тЗначение = _ОбщегоНазначенияКлиентСервер.СвойствоСтруктуры(тДопПараметры,вхИмяПараметра,вхЗначениеПоУмолчанию);
	
    Возврат тЗначение;	
КонецФункции      

Функция СтрокаJSON(вхДанные,вхВозврОшибка = Неопределено) Экспорт
	тЗаписьJSON 			= Новый ЗаписьJSON;
	тПараметрыЗаписиJSON 	= Новый ПараметрыЗаписиJSON();
	//             
	тСтрокаJSON = Неопределено;
	Попытка
		тЗаписьJSON.УстановитьСтроку(тПараметрыЗаписиJSON);
		ЗаписатьJSON(тЗаписьJSON, вхДанные);
		тСтрокаJSON = тЗаписьJSON.Закрыть();
	Исключение
		тОписаниеОшибки = ОписаниеОшибки();
		вхВозврОшибка = тОписаниеОшибки;
		Возврат Неопределено;
	КонецПопытки;
	Возврат тСтрокаJSON;
КонецФункции   

Функция ДанныеJSON(вхСтрокаJSON,вхВозврОшибка = Неопределено,вхПрочитатьВСоответствие = Ложь) Экспорт
	вхВозврОшибка = Неопределено;
	//
	тЧтениеJSON = Новый ЧтениеJSON;  
	Попытка
		тЧтениеJSON.УстановитьСтроку(вхСтрокаJSON);		
		тДанные = ПрочитатьJSON(тЧтениеJSON,вхПрочитатьВСоответствие); 
	Исключение        		
		вхВозврОшибка = ОписаниеОшибки();
		Возврат Неопределено;
	КонецПопытки;
	Возврат тДанные;
КонецФункции 

Функция ДанныеОбъектаВСтроку(вхОбъект,вхВозврИтоговыйТекст = "",вхСдвиг = 0,вхВыводитьТипДанных = Ложь) Экспорт
	тСдвигСтр = "";
	Для тСчетчикСдвигов = 1 по вхСдвиг Цикл
		тСдвигСтр = тСдвигСтр + Символ(9);
	КонецЦикла;
	//		                               
	Если ТипЗнч(вхОбъект) = Тип("Структура") Тогда            
		Если вхВыводитьТипДанных Тогда
			вхВозврИтоговыйТекст = вхВозврИтоговыйТекст + Строка(ТипЗнч(вхОбъект)) + ": ";		
		КонецЕсли;	
		Если ЗначениеЗаполнено(вхВозврИтоговыйТекст) Тогда
			вхВозврИтоговыйТекст = вхВозврИтоговыйТекст + Символы.ПС;  
		КонецЕсли;
		
		Для Каждого тЭлементСтруктуры из вхОбъект Цикл
			тСтрока = тСдвигСтр + тЭлементСтруктуры.Ключ + ": ";
			вхВозврИтоговыйТекст = вхВозврИтоговыйТекст + тСтрока;
			ДанныеОбъектаВСтроку(тЭлементСтруктуры.Значение,вхВозврИтоговыйТекст,вхСдвиг+1);
		КонецЦикла;
	ИначеЕсли ТипЗнч(вхОбъект) = Тип("Массив") Тогда            
		Если вхВыводитьТипДанных Тогда
			вхВозврИтоговыйТекст = вхВозврИтоговыйТекст + Строка(ТипЗнч(вхОбъект)) + " (" + Строка(вхОбъект.Количество()) + "):"; 		
		КонецЕсли;				
		Если ЗначениеЗаполнено(вхВозврИтоговыйТекст) Тогда
			вхВозврИтоговыйТекст = вхВозврИтоговыйТекст + Символы.ПС; 
		КонецЕсли;				
		
		тИндексЭлемента = -1;
		Для Каждого тЭлементМасива из вхОбъект Цикл
			тИндексЭлемента = тИндексЭлемента + 1;
			//
			тСтрока = тСдвигСтр + "("+Строка(тИндексЭлемента)+") " + ":";
			вхВозврИтоговыйТекст = вхВозврИтоговыйТекст + тСтрока;
			ДанныеОбъектаВСтроку(тЭлементМасива,вхВозврИтоговыйТекст,вхСдвиг+1);
		КонецЦикла; 
	ИначеЕсли ТипЗнч(вхОбъект) = Тип("Соответствие") Тогда            
		Если вхВыводитьТипДанных Тогда
			вхВозврИтоговыйТекст = вхВозврИтоговыйТекст + ТипЗнч(вхОбъект) + ": ";
		КонецЕсли;
		Если ЗначениеЗаполнено(вхВозврИтоговыйТекст) Тогда
			вхВозврИтоговыйТекст = вхВозврИтоговыйТекст + Символы.ПС;                 
		КонецЕсли;
		
		Для Каждого тЭлементСоответствия из вхОбъект Цикл
			тСтрока = тСдвигСтр + тЭлементСоответствия.Ключ + ": ";
			вхВозврИтоговыйТекст = вхВозврИтоговыйТекст + тСтрока;
			ДанныеОбъектаВСтроку(тЭлементСоответствия.Значение,вхВозврИтоговыйТекст,вхСдвиг+1);
		КонецЦикла;
	Иначе                             		
		тСтрока = " " + Строка(вхОбъект) + Символы.ПС;		
		вхВозврИтоговыйТекст = вхВозврИтоговыйТекст + тСтрока;
	КонецЕсли;
	Возврат вхВозврИтоговыйТекст;
КонецФункции

#КонецОбласти

Функция ПолучитьСоединение(Знач вхСоединениеИлиКлюч) Экспорт  
	
	тСоединение = вхСоединениеИлиКлюч;
	Если НЕ ТипЗнч(тСоединение) = Тип("WebSocketКлиентСоединение") Тогда
		тСоединение = WebSocketКлиентСоединения.ПолучитьСоединение(вхСоединениеИлиКлюч);
	КонецЕсли;

    Возврат тСоединение;	
КонецФункции

Процедура ОкрытьСоединение(Знач вхКлюч,Знач вхПараметрыПодключения,вхДополнительныеПараметры = Неопределено,вхВозвОписаниеОшибки = "") Экспорт
	
	тСоединение = ПолучитьСоединение(вхКлюч);
	
	Если тСоединение = Неопределено Тогда
		тПараметрыСоединения = вхПараметрыПодключения;
		
		тОбработчики = Новый ОбработчикиWebSocketКлиентСоединения("ПриОткрытииСоединения", "ПриПолученииСообщения","ПриОшибке", "ПриЗакрытииСоединения", RMQWebSocketsSTOMP);
		
		тДополнительныеПараметры = Новый Структура;
		тДополнительныеПараметры.Вставить("ПараметрыСоединения", тПараметрыСоединения);
		Если ТипЗнч(вхДополнительныеПараметры) = Тип("Структура") Тогда
			Для Каждого тКлючЗначение из вхДополнительныеПараметры Цикл
				тДополнительныеПараметры.Вставить(тКлючЗначение.Ключ,тКлючЗначение.Значение);
			КонецЦикла;
		КонецЕсли;
		
		тПараметры 	= Новый ПараметрыWebSocketКлиентСоединения;
		тПараметры.ДополнительныеПараметры 		= тДополнительныеПараметры;
		тПараметры.Заголовки					= тПараметрыСоединения.Заголовки;
		//тПараметры.ИспользоватьАутентификациюОС = Неопределено;
		//тПараметры.ИспользоватьПроксиОС			= Неопределено;
		//тПараметры.Прокси						= Неопределено; 	
		тПараметры.Пароль						= тПараметрыСоединения.Пароль;
		тПараметры.Пользователь					= тПараметрыСоединения.ИмяПользователя;
		тПараметры.Таймаут						= _ОбщегоНазначенияКлиентСервер.СвойствоСтруктуры(тПараметрыСоединения,"Таймаут",10);
		
		тЗащищенное = _ОбщегоНазначенияКлиентСервер.СвойствоСтруктуры(тПараметрыСоединения,"ЗащищенноеПодключение",Ложь);
		
		Если тЗащищенное = Истина Тогда
			тЗащищенное = "s";
		Иначе                           
			тЗащищенное = "";
		КонецЕсли;                      
		
		тАдресСервера 	= _ОбщегоНазначенияКлиентСервер.СвойствоСтруктуры(тПараметрыСоединения,"Сервер",	"");
		тПорт			= _ОбщегоНазначенияКлиентСервер.СвойствоСтруктуры(тПараметрыСоединения,"Порт",	15674);
		тПорт			= Формат(тПорт,"ЧГ=");
		
		тСтрокаСоединения = СтрШаблон("ws%1://%2:%3/ws",тЗащищенное,тАдресСервера,тПорт);
		
		Попытка
			тСоединение = WebSocketКлиентСоединения.ОткрытьСоединение(вхКлюч,тСтрокаСоединения,тОбработчики,тПараметры);
		Исключение
			вхВозвОписаниеОшибки = ОписаниеОшибки();
			Возврат;
		КонецПопытки;
	КонецЕсли;
	
КонецПроцедуры	

Процедура ЗакрытьСоединение(вхКлючИлиСоединение,вхВозвОписаниеОшибки = "") Экспорт                              
	
	тСоединение = Неопределено;
	Если ТипЗнч(вхКлючИлиСоединение) = Тип("Строка") Тогда
		тСоединение = WebSocketКлиентСоединения.ПолучитьСоединение(вхКлючИлиСоединение);
	КонецЕсли;
	
	Если тСоединение = Неопределено Тогда
		Возврат;
	КонецЕсли;       
	
	Попытка
		тСоединение.Закрыть();	
	Исключение 
		вхВозвОписаниеОшибки = ОписаниеОшибки();
	КонецПопытки;
КонецПроцедуры

Процедура УстановитьСоединение(Знач вхСоединение,вхВозвОписаниеОшибки = "")
	
	тПараметрыСоединения = ЗначениеДополнительногоПараметраСоединения(вхСоединение,"ПараметрыСоединения",Неопределено); 
	Если НЕ ТипЗнч(тПараметрыСоединения) = Тип("Структура") Тогда
		вхВозвОписаниеОшибки = СтрШаблон("Не удалось определить параметры соединения [%1]",вхСоединение.Ключ);
		Возврат;
	КонецЕсли;
	
	тКадрСоединения = ВРЕГ(ПараметрыПротоколаSTOMPКадрСоединения(тПараметрыСоединения.ПараметрыWebSTOMP["КадрСоединения"]));
	//тЗащищенное 	= ОбщегоНазначенияКлиентСервер.СвойствоСтруктуры(тПараметрыСоединения,"WebSocketЗащищенноеСоединение",Ложь);
	
	тСообщение = 
	тКадрСоединения + СимволРазделительСтрокКадра() +
	"accept-version:" + ПараметрыПротоколаSTOMПоддерживаемыеВерсии(тПараметрыСоединения.ПараметрыWebSTOMP["ПоддерживаемыеВерсии"]) + СимволРазделительСтрокКадра() +
	"login:" + тПараметрыСоединения.ИмяПользователя+ СимволРазделительСтрокКадра() +
	"passcode:" + ЭкранироватьСимволы(тПараметрыСоединения.Пароль) + СимволРазделительСтрокКадра() +
	"host:" + тПараметрыСоединения.ВиртуальныйХост + 
	СимволРазделительСтрокКадра() +
	"heart-beat:" + ПараметрыПротоколаSTOMPСердцебиение(тПараметрыСоединения.ПараметрыWebSTOMP["Сердцебиение"]) + 
	СимволРазделительЧастейКадра() + 
	СимволЗавершениеКадра();
	
	вхСоединение.ОтправитьСообщение(тСообщение);
КонецПроцедуры         

Процедура ЗавершитьОформлениеПодписокСоединения(Знач вхСоединение) 
	
	тДанныеОчередей = ЗначениеДополнительногоПараметраСоединения(вхСоединение,"ДанныеОчередей",Новый Массив);
	
	Для Каждого тДанныеОчередиКлючЗначение из тДанныеОчередей Цикл
		
		тДанныеОчереди = тДанныеОчередиКлючЗначение.Значение;
		
		тОписаниеОшибки = "";
		ПодписатьсяНаОчередь(вхСоединение.Ключ,ТипПриемника(тДанныеОчереди.ТипПриемника),тДанныеОчереди.ИмяПриемника,тДанныеОчереди.ИмяПодписки,,тОписаниеОшибки);
	КонецЦикла;
	
КонецПроцедуры   
	
Процедура ОтправитьСообщения(вхСоединение,вхВозвОписаниеОшибки = "")
	тДанныеСообщений = ЗначениеДополнительногоПараметраСоединения(вхСоединение,"ДанныеСообщений",Новый Массив);
	
	Для Каждого тДанныеСообщения из тДанныеСообщений Цикл
		ОтправитьСообщение(вхСоединение,ТипПриемника(тДанныеСообщения.ТипПриемника),тДанныеСообщения.ИмяПриемника,тДанныеСообщения.ТелоСообщения,тДанныеСообщения.Идентификатор,,вхВозвОписаниеОшибки);
	КонецЦикла;
КонецПроцедуры

Процедура ОтправитьСообщение(Знач вхСоединениеИлиКлюч,Знач вхПриемник = "queue",Знач вхИмяПриемника,Знач вхДанныеСообщения,вхИдентификаторСообщения = Неопределено,Знач вхТипСодержимого = "application/json",вхВозвОписаниеОшибки = "") Экспорт
	
	тСоединение = ПолучитьСоединение(вхСоединениеИлиКлюч);
	
	Если тСоединение = Неопределено Тогда
		вхВозвОписаниеОшибки = СтрШаблон("Не удалось определить соединение по ключу [%1]",вхСоединениеИлиКлюч);
		Возврат ;
	КонецЕсли;
	
	Если ТипЗнч(вхДанныеСообщения) = Тип("Строка") Тогда
		тДанныеКОтправкеJSON = вхДанныеСообщения;
	Иначе
		тДанныеКОтправкеJSON = СтрокаJSON(вхДанныеСообщения);
	КонецЕсли;
	
	//тЧислоСимволов		 = Формат(СтрДлина(тДанныеКОтправкеJSON),"ЧГ="); //Для бинарных данных
	
	тЗапросЧека  = "";
	Если ЗначениеЗаполнено(вхИдентификаторСообщения) Тогда
		тЗапросЧека =  	"receipt:" + СокрЛП(Строка(вхИдентификаторСообщения)) + СимволРазделительСтрокКадра();    
	КонецЕсли;
	
	тСообщение = 
	"SEND" 																	+ СимволРазделительСтрокКадра() +
    "destination:/" 				+ вхПриемник + "/" + вхИмяПриемника 	+ СимволРазделительСтрокКадра() +
	//"content-length:" 			+ тЧислоСимволов 						+ СимволРазделительСтрокКадра() +    
    "content-type:" 				+ вхТипСодержимого						+ СимволРазделительСтрокКадра() +						
	тЗапросЧека 					+ 
	
	СимволРазделительЧастейКадра() 	+    
	
    тДанныеКОтправкеJSON 
	
	+ 
	
	СимволЗавершениеКадра();
	
	тСоединение.ОтправитьСообщение(тСообщение);
	
КонецПроцедуры   

Процедура ОтправитьСердцебиение(вхСоединение) Экспорт   
	
	Если НЕ вхСоединение.ПолучитьСостояние() = СостояниеWebSocketСоединения.Открыто Тогда
		Возврат;
	КонецЕсли;
	
	тСообщение = СимволРазделительСтрокКадра();	
	
	вхСоединение.ОтправитьСообщение(тСообщение);

КонецПроцедуры  

Процедура ПодтвердитьСообщение(Знач вхКлючИлиСоединения,Знач вхИдентификаторПодписки,Знач вхИдентификаторСообщения,вхВозвОписаниеОшибки = "") Экспорт
	
	тСоединение = ПолучитьСоединение(вхКлючИлиСоединения);
	
	Если тСоединение = Неопределено Тогда     
		вхВозвОписаниеОшибки = СтрШаблон("Соединение с ключем %1 не найдено!",тСоединение.Ключ);
		Возврат;
	КонецЕсли;		
	
	тСообщение = 
	"ACK" 					+ СимволРазделительСтрокКадра() 	+
	"message-id:" 			+ вхИдентификаторСообщения 			+ СимволРазделительСтрокКадра() 	+
	"id:" 					+ вхИдентификаторСообщения 			+ СимволРазделительСтрокКадра() 	+
	"subscription:" 		+ вхИдентификаторПодписки 			+ 
	
	СимволРазделительЧастейКадра() 	+    
	СимволЗавершениеКадра();
	
	тСоединение.ОтправитьСообщение(тСообщение);

КонецПроцедуры 

Процедура ПодписатьсяНаОчередь(Знач вхСоединениеИлиКлюч,Знач вхПриемник = "queue",Знач вхИмяПриемника,Знач вхИдентификаторПодписки,Знач вхТипПодтверждения = "client-individual",вхВозвОписаниеОшибки = "") Экспорт
	
	тСоединение = ПолучитьСоединение(вхСоединениеИлиКлюч);
	
	Если тСоединение = Неопределено Тогда                                      
		Если тСоединение = Неопределено Тогда
			вхВозвОписаниеОшибки = СтрШаблон("Соединение по ключу [%1] не найдено!",вхСоединениеИлиКлюч);
			Возврат;
		КонецЕсли;
	КонецЕсли;
		
	тСообщение = 
	"SUBSCRIBE" + СимволРазделительСтрокКадра() +
	"id:" + вхИдентификаторПодписки + СимволРазделительСтрокКадра() +
	"destination:/"+ вхПриемник + "/" + вхИмяПриемника + СимволРазделительСтрокКадра() +
	"ack:" + вхТипПодтверждения + СимволРазделительЧастейКадра() + 
	СимволЗавершениеКадра();
	
	тСтатусСоединения = тСоединение.ПолучитьСостояние();
	Если НЕ тСтатусСоединения = СостояниеWebSocketСоединения.Открыто Тогда
		вхВозвОписаниеОшибки = СтрШаблон("Соединение [%1] в статусе: [%2]!",вхСоединениеИлиКлюч,тСтатусСоединения);
		Возврат;
	КонецЕсли;
	
	тСоединение.ОтправитьСообщение(тСообщение);
КонецПроцедуры 

Функция ОбработатьСообщение(вхСоединение, вхСообщение) Экспорт
	
	тДанныеКадра = ДанныеКадра(вхСообщение);
	
	Если тДанныеКадра.Команда = "ERROR" Тогда  
		
		
	ИначеЕсли тДанныеКадра.Команда = "CONNECTED" Тогда           
		
		ПриУстановкеWebSTOMPСоединения(вхСоединение,тДанныеКадра);
		
	ИначеЕсли тДанныеКадра.Команда = "MESSAGE" Тогда   
		
		тИдентификаторСообщения = тДанныеКадра.Заголовки["message-id"];
		
		ПриПолученииВходящегоСообщения(вхСоединение,тИдентификаторСообщения,тДанныеКадра);
		
	ИначеЕсли тДанныеКадра.Команда = "RECEIPT" Тогда   
		
		тИдентификаторСообщения = тДанныеКадра.Заголовки["receipt-id"];
		ПриПодтвержденииПриемаИсходящегоСообщения(вхСоединение,тИдентификаторСообщения,тДанныеКадра);
		
	ИначеЕсли НЕ ЗначениеЗаполнено(тДанныеКадра.Команда) Тогда 
		
		//Сердцебиение         
		ПриПолученииСерцебиения(вхСоединение);
		
	Иначе      
		
	КонецЕсли;        

    Возврат тДанныеКадра;	
КонецФункции

#Область Обработчики_событий

Процедура ПриОткрытииСоединения(вхСоединение) Экспорт
	
	УстановитьСоединение(вхСоединение);
КонецПроцедуры

Процедура ПриПолученииСообщения(вхСоединение, вхСообщение) Экспорт
	
	Попытка
		ОбработатьСообщение(вхСоединение, вхСообщение);
	Исключение
		
		тОписание = ОписаниеОшибки();
				
	КонецПопытки;
	
КонецПроцедуры   

Процедура ПриОшибке(вхСоединение,вхКодОшибки,вхОписаниеОшибки) Экспорт
	
	
КонецПроцедуры

Процедура ПриЗакрытииСоединения(вхСоединение,вхКодЗакрытия) Экспорт      
	
КонецПроцедуры

Процедура ПриУстановкеWebSTOMPСоединения(вхСоединение,вхДанныеКадра)
	
	//Отпраляем сообщения
	ОтправитьСообщения(вхСоединение);
	
	//Подключаемся к очередям
	ЗавершитьОформлениеПодписокСоединения(вхСоединение);

КонецПроцедуры 

Процедура ПриПодтвержденииПриемаИсходящегоСообщения(вхСоединение,вхИдентификаторСообщения,вхДанныеКадра)       
	
КонецПроцедуры

Процедура ПриПолученииВходящегоСообщения(вхСоединение,вхИдентификаторСообщения,вхДанныеКадра);
	
	тДанныеСообщения = Новый Структура;
	тДанныеСообщения.Вставить("ИдентификаторСообщения",	вхИдентификаторСообщения);
	тДанныеСообщения.Вставить("ИдентификаторПодписки",	вхДанныеКадра.Заголовки["subscription"]);
	тДанныеСообщения.Вставить("КлючСоединения",			вхСоединение.Ключ);
	тДанныеСообщения.Вставить("Направление",			вхДанныеКадра.Заголовки["destination"]); 
	тДанныеСообщения.Вставить("Тело",					вхДанныеКадра.Тело); 
	
	СообщенияОчередиОбработатьСообщениеWebSTOMP(вхСоединение,тДанныеСообщения.ИдентификаторПодписки,тДанныеСообщения);

КонецПроцедуры   

Процедура СообщенияОчередиОбработатьСообщениеWebSTOMP(вхСоединение,вхИдентификаторПодписки,вхДанныеСообщения,вхВозвОписаниеОшибки = "")
	тДанныеОчередей = ЗначениеДополнительногоПараметраСоединения(вхСоединение,"ДанныеОчередей",Новый Массив);
	
	тДанныеОчереди = тДанныеОчередей[вхИдентификаторПодписки];
	Если НЕ тДанныеОчереди = Неопределено Тогда
		Если тДанныеОчереди.Подтвердить = Истина Тогда
			ПодтвердитьСообщение(вхСоединение,вхИдентификаторПодписки,вхДанныеСообщения.ИдентификаторСообщения,вхВозвОписаниеОшибки)
		КонецЕсли;
	КонецЕсли;
	
КонецПроцедуры

Процедура ПриПолученииСерцебиения(вхСоединение)       
	
	Попытка                                                                                                                                          
		тПараметрыСоединения 		= ЗначениеДополнительногоПараметраСоединения(вхСоединение,"ПараметрыСоединения",Новый Структура);
		тПараметрыWebSTOMP			= _ОбщегоНазначенияКлиентСервер.СвойствоСтруктуры(тПараметрыСоединения,"ПараметрыWebSTOMP",Новый Соответствие);
		тПоддерживатьСердцебиение 	= тПараметрыWebSTOMP["ПоддерживатьСердцебиение"];
	Исключение                   
		тПоддерживатьСердцебиение = Ложь;
	КонецПопытки;
	
	Если (тПоддерживатьСердцебиение = Истина) ИЛИ (ВРег(тПоддерживатьСердцебиение) = "ДА") Тогда
		ОтправитьСердцебиение(вхСоединение);
	КонецЕсли;
КонецПроцедуры

#КонецОбласти

 

С какими неудобствами столкнулся:

  • Значение свойства "Дополнительные параметры" свойства "Параметры" объекта WebSocketКлиентСоединения восстанавливаются (возвращаются) в каждом событии (коллбэке) к значениям заданным при открытии соединения (инициализации). Поэтому передавать какие-то данные между колбэками приходится через записи БД (временное хранилище тоже не работает)
  • WebSocket соединение закрывается без предупреждения если КАДР содержит некорректные данные (команда, значения заголовков), что усложняет отладку.

В целом замена компоненты на WebSTOMP и новые встроенные возможности 1С по работе с WebSockets прошла успешно и результаты работы обмена с очередями RabbitMQ посредством протокола STOMP полностью удовлетворительны.

В прикрепленном файле расширения продемонстрированы основные приемы работы с протоколом STOMP с уведомлениями на стороне клиента. Расширение использует несколько общих функций из БСП.

Спасибо за внимание.

Проверено на следующих конфигурациях и релизах:

  • 1С:Библиотека стандартных подсистем, редакция 3.1, релизы 3.1.11.228

Вступайте в нашу телеграмм-группу Инфостарт

WebSockets RabbitMQ WebSTOMP STOMP WebSocketКлиентСоединение

См. также

WEB-интеграция Администрирование веб-серверов Платные (руб)

Веб-портал обеспечивает удобный доступ к конфигурации 1С:ITIL(ИТИЛ), 1С:ITILIUM, Управление IT-отделом 8 через интернет с любого устройства посредством браузера, увеличивая эффективность работы пользователей и снижая нагрузку на сервер. Быстрая инсталляция портала за пару часов, удобный и интуитивно понятный интерфейс и безопасность данных помогут упростить работу с порталом и ускорить выполнение бизнес-процессов компании.

128000 руб.

19.12.2023    5918    4    0    

12

WEB-интеграция Анализ продаж Системный администратор Программист Пользователь 1С v8.3 1С:ERP Управление предприятием 2 1С:Бухгалтерия 3.0 1С:Управление торговлей 11 1С:Комплексная автоматизация 2.х 1С:Управление нашей фирмой 3.0 1С:Розница 3.0 Управленческий учет Платные (руб)

Модуль "Подсистема интеграции AmoCRM с 1С" позволяет обеспечить единое информационное пространство, в котором пользователи могут эффективно управлять клиентской базой, следить за статусами сделок и поддерживать актуальность данных как в AmoCRM, так и в 1С.

60000 руб.

07.05.2019    36927    72    45    

31

Оптовая торговля Розничная торговля WEB-интеграция 1С:Управление торговлей 10 1С:Управление производственным предприятием 1С:Управление нашей фирмой 1.6 1С:Управление торговлей 11 1С:Комплексная автоматизация 2.х 1С:Управление нашей фирмой 3.0 Платные (руб)

Онлайн-заказ - это решение для автоматизации процесса оформления заказов на сайте в торговых организациях. Продукт обеспечивает легкое взаимодействие между компанией и клиентами через веб-интерфейс, интегрированный с 1С:Предприятие. Система позволяет снизить операционные расходы, повысить лояльность клиентов и оптимизировать работу отдела продаж.

57600 руб.

26.11.2024    4195    3    3    

6

Сайты и интернет-магазины WEB-интеграция Системный администратор Программист Пользователь 1С v8.3 1C:Бухгалтерия 1С:Управление торговлей 11 Автомобили, автосервисы Россия Управленческий учет Платные (руб)

Интеграционный модуль обмена между конфигурацией Альфа Авто 5 и Альфа Авто 6 и порталом AUTOCRM. Данный модуль универсален. Позволяет работать с несколькими обменами AUTOCRM разных брендов в одной информационной базе в ручном и автоматическом режиме.

36000 руб.

03.08.2020    20279    26    24    

22

WEB-интеграция Программист Бизнес-аналитик 1С v8.3 1С:ERP Управление предприятием 2 1С:Бухгалтерия 3.0 1С:Управление торговлей 11 1С:Комплексная автоматизация 2.х 1С:Управление нашей фирмой 3.0 1С:Розница 3.0 Оптовая торговля, дистрибуция, логистика ИТ-компания Платные (руб)

Модуль "Экспортер" — это расширение для 1С, предназначенное для автоматизации процессов выгрузки данных. Оно позволяет эффективно извлекать, преобразовывать и передавать данные из систем 1С в интеграционную платформу Spot2D. Подсистема упрощает настройку, снижает количество ручных операций и обеспечивает удобный контроль данных.

14400 руб.

20.12.2024    1999    12    2    

15
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. dsdred 3943 07.07.25 13:04 Сейчас в теме
2. user685777_megaivan02 5 07.07.25 15:10 Сейчас в теме
(1) Пожалуйста, сам искал внятное решение или намёки как реализовать и ничего не нашёл. Решил что кто-нибудь должен заинтересоваться
3. dsdred 3943 07.07.25 15:12 Сейчас в теме
(2) У меня в статье про вебсокеты люди спрашивали. Поэтому я думаю точно понадобится.
Оставьте свое сообщение