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

07.07.25

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

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

Файлы

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

Наименование Скачано Купить файл
Использование WebSockets для работы с RabbitMQ посредством протокола WebSTOMP
.cfe 32,32Kb
6 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С:Управление торговлей 10 1С:Управление производственным предприятием 1С:Управление нашей фирмой 1.6 1С:Управление торговлей 11 1С:Комплексная автоматизация 2.х 1С:Управление нашей фирмой 3.0 Платные (руб)

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

57600 руб.

26.11.2024    5963    4    3    

7

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    3335    17    2    

19

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

В расширении реализован механизм интеграции между системой поставщика и Личным кабинетом СДТ. Реализован обмен заказами и реализациями (накладными), предусмотрено отслеживание статусов документов. Расширение предназначено для 1С:УТ 11.4.

35856 руб.

27.11.2024    1789    1    0    

1

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

Обработка является альтернативой механизму, разработанному фирмой 1С и заполняющему реквизиты контрагента по ИНН или наименованию. Не требуется действующей подписки ИТС. Вызывается как внешняя дополнительная обработка, т.е. используется, непосредственно, из карточки контрагента. Заполнение по ИНН или наименованию реквизитов контрагента по данным сайта ФНС (egrul.nalog.ru) для БП 2.0, БП 3.0, БГУ 1.0, БГУ 2.0, УТ 10.3, УТ 11.x, КА 1.1, КА 2.x, УПП 1.x, ERP 2.x, УНФ 1.5, УНФ 1.6, УНФ 3.0, ДО 2.1

5196 руб.

28.04.2016    97292    109    218    

359

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

Универсальное расширение конфигурации для автоматической загрузки и заполнения реквизитов контрагентов (партнеров) из ОГРН для 1С:ERP Управление предприятием 2 (1С:ERP Управление предприятием 2, редакция 2.4), 1С:ERP Управление предприятием 2 (1С:ERP Управление предприятием 2, редакция 2.2), 1С:Управление торговлей 8 (Управление торговлей, редакция 11.5), 1С:Управление торговлей 8 (Управление торговлей, редакция 11.4), 1С:Управление торговлей 8 (Управление торговлей, редакция 11.3), 1С:Управление торговлей 8 (Управление торговлей, редакция 11.2), 1С:Комплексная автоматизация 8 (1С:Комплексная автоматизация, редакция 2.4), 1С:Комплексная автоматизация 8 (1С:Комплексная автоматизация, редакция 2.2), 1С:Комплексная автоматизация 8 (1С:Комплексная автоматизация, редакция 2.0) и 1С:Бухгалтерия 8 (Бухгалтерия предприятия, редакция 3.0).

5000 руб.

08.11.2017    69410    415    298    

84

WEB-интеграция Программист 1С v8.3 Бухгалтерский учет 1С:Бухгалтерия 3.0 Бытовые услуги, сервис Платные (руб)

Внешняя обработка разработана для автоматизации передачи данных между сервисом Vetmanager с 1С: Бухгалтерия 3.0. Решение позволяет загружать документы и справочники из Ветменеджер в 1С:Бухгалтерию, сокращая время на ручной ввод данных и минимизируя ошибки.

12000 руб.

02.02.2021    20203    58    52    

36
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. dsdred 4011 07.07.25 13:04 Сейчас в теме
2. megaivan02 27 07.07.25 15:10 Сейчас в теме
(1) Пожалуйста, сам искал внятное решение или намёки как реализовать и ничего не нашёл. Решил что кто-нибудь должен заинтересоваться
3. dsdred 4011 07.07.25 15:12 Сейчас в теме
(2) У меня в статье про вебсокеты люди спрашивали. Поэтому я думаю точно понадобится.
4. artbear 1572 29.07.25 13:41 Сейчас в теме
Спасибо за детальную статью.
Для отправки сообщения требуется регистрация/авторизация