IE2017

XMPP(jabber) на чистом 1С

Обмен - Обмен через XML

На сайте infostart, есть несколько реализаций работы 1С с протоколом XMPP (jabber), но в основном они на использование сторонних библиотек (нативных), которые надо регистрировать в операционке (и только в винде), или на основе других систем - php, python и т.п. Предлагаю пример реализации отправки сообщения через этот протокол только средствами 1С.

Предварительный разбор по 1С

  • Поддержка XML у 1С реализована хорошо, а протокол XMPP - это чистый XML.
  • XMPP сервер работает с клиентами через сокеты/потоки, на 1С реализовать это уже сложнее, но работу с сервером XMPP можно расширить через модули для работы по HTTP (BOSH - http-bind).
  • Работа с Base64 - реализован в 1С через команды: Base64Значение и Base64Строка.
  • Расчет хэш-суммы по алгоритму MD5, реализован в 8.3 объектом ХешированиеДанных.

Разбор по XMPP

Простая сессия представляет из себя следующую последовательность операций:

  • Соединение с сервером
  • Создание потока
  • Аутентификация
  • Привязывание (bind) потока к ресурсу (имя@сервер/ресурс)
  • Создание сессии
  • Рассылка статуса "доступен"
  • Отправка/получение сообщений, статусы, ростер(список контактов), "визитные карточки", работа с сервисами и транспортами и т.п.
  • Рассылка статуса "отключен"
  • Закрытие потока
  • Отключение от сервера

В качестве примера (он будет достаточен), реализуем самый минимум и заморачиваться с ростером, закрытием сессий - не будем. Самое последнее действие, которое реализеум - это просто отправка сообщения.  Оговоримся сразу, в примере буду использовать сервер jabber.ru, а ники вымышленные.

1 - Приветствие, получение кода сессии SID:

<body 
	content='text/xml; charset=utf-8' 
	from='myNickXMPP@jabber.ru'
	secure='true'
	hold='1'
	rid='678901'
	to='jabber.ru'
	route='http://jabber.ru/http-bind' 
	ver='1.10' wait='300' 
	xml:lang='en' xmpp:version='1.0' 
	xmlns='http://jabber.org/protocol/httpbind' 
	xmlns:xmpp='urn:xmpp:xbosh'/>

2 - Отправка серверу команды на авторизацию методом DIGEST-MD5:

<body rid='678902' sid='1234567890' xmlns='http://jabber.org/protocol/httpbind' xmlns:xmpp='urn:xmpp:xbosh'>
	<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>
</body>

3 - Авторизация методом DIGEST-MD5:

<body rid='678903' sid='1234567890' xmlns='http://jabber.org/protocol/httpbind'>
	<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
		МАССИВ ДАННЫХ АВТОРИЗАЦИИ ЗАКОДИРОВАННЫХ BASE64
	</response>
</body>

4 - После успешной авторизации, отправляем встречный ответ подтверждение:

<body rid='678904' sid='1234567890' xmlns='http://jabber.org/protocol/httpbind'>
	<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>
</body>

5 - Повторно посылаем приветствие и команду "рестарта":

<body 
	rid='678905' 
	sid='1234567890' 
	to='jabber.ru'
	xml:lang='en' 
	xmpp:restart='true' 
	xmlns='http://jabber.org/protocol/httpbind' 
	xmlns:xmpp='urn:xmpp:xbosh'/>

6 - Устанавливаем имя ресурса (отправим пустой):

<body rid='678906' sid='1234567890' xmlns='http://jabber.org/protocol/httpbind'>
	<iq id='bind_1' type='set' xmlns='jabber:client'>
		<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>
	</iq>
</body>

7 - Затем сервер требовал создать сессию... Это нужно для отсылки статусов, сообщений, да и вообще. Не будем отказывать:

<body rid='678907' sid='1234567890' xmlns='http://jabber.org/protocol/httpbind'>
	<iq id='bind_2' type='set' xmlns='jabber:client'>
		<session xmlns='urn:ietf:params:xml:ns:xmpp-session' />
	</iq>
</body>

8 - Ну и теперь выходим в онлайн и становимся видимыми для ваших контактов:

<body rid='678908' sid='1234567890' xmlns='http://jabber.org/protocol/httpbind'>
	<presence>
		<show/>
		<status>1c input</status>
		<priority>10</priority>
	</presence>
</body>

Одна из возникших проблем: на данный момент, мне не удалось отправить из 1С статус на русском языке, и таже самая проблема при отправке сообщения.

9 - Отправка сообщения другому пользователю:

<body rid='678909' sid='1234567890' xmlns='http://jabber.org/protocol/httpbind'>
	<message to='poluchatel@jabber.ru' from='myNickXMPP@jabber.ru' xmlns='jabber:client' type='chat'>
		<body>test message</body>
	</message>
</body>

Как видите ничего сложного - нет, рассматривать ответ от сервера мы тут не будем, этого в инете достаточно. Рабочий код(пример) выложен ниже.

Разбор полёта

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

Хотел бы обратить внимание (на двух) уже на одной проблеме, которой пока не нашел решение(не забываем, что реализация на чистом 1С, без COM). Без решения данной проблемы - пример всёравно работает.

  1. Как сказано выше, не удалось отправить сообщение и статус на кириллице, поэтому отсылаем только на латинице.
  2. Генератор случайных чисел - работает коряво и часто вылетает по ошибке, поэтому вставил уже сгенерированый и закодированный случайны код - oUIOt7lGFY0Q3Z+/1qo0j9lhpB1MscW9Fu4MJM/nNhc=.

Вся сложность взаимодействия с XMPP из 1С, это создания ключа авторизации по особому способу:

Функция строкаРеспонсе(структураДляОтвета)
	// функция создания поля response для challenge
	СтрокаВозвр="";
	
	authzid = Ложь;
	authzid_знач = "";
	Для  каждого првСтрк из структураДляОтвета Цикл
		Если првСтрк.Ключ = "authzid" Тогда
			authzid=Истина;
			authzid_знач = првСтрк.Значение;
		КонецЕсли;
	КонецЦикла;
	
	realm="";
	Если НЕ структураДляОтвета.realm="-" Тогда
		realm=структураДляОтвета.realm;
	КонецЕсли;
	
	раскНЕХ64 = мд5бз64(Объект.Пользователь+":"+realm+":"+Объект.Пароль);
	Сообщить(раскНЕХ64);
		
	а1 = ":"+структураДляОтвета.nonce+":"+структураДляОтвета.cnonce;
	Если authzid Тогда
		а1 = а1+":"+authzid_знач;
	КонецЕсли;
	а2="AUTHENTICATE:"+структураДляОтвета.digest_uri;
	
	общаяСтрока = ""+мд5(а1,раскНЕХ64)+
		":"+ структураДляОтвета.nonce +
		":"+ структураДляОтвета.nc +
		":"+ структураДляОтвета.cnonce +
		":"+ структураДляОтвета.qop +
		":"+ мд5(а2);
		
	Сообщить("rD="+общаяСтрока);
	СтрокаВозвр = мд5(общаяСтрока);
	
	Возврат СтрокаВозвр;
КонецФункции

Функция СлучайноеЧисло(Мин,Макс,флЦелое = 0)
	ГСЧ = Новый ГенераторСлучайныхЧисел();
	СлучайноеЧисло = ГСЧ.СлучайноеЧисло(1, 12000);
	//вместо Randomize
	Для н = 1 По СлучайноеЧисло Цикл
		Уник = Новый УникальныйИдентификатор;
	КонецЦикла;
	//генерируем GUID
	Уник = СокрЛП(Новый УникальныйИдентификатор);
	//оставляем только цифры
	Уник = СтрЗаменить(Уник,"-","");
	Уник = СтрЗаменить(Уник,"a","");
	Уник = СтрЗаменить(Уник,"b","");
	Уник = СтрЗаменить(Уник,"c","");
	Уник = СтрЗаменить(Уник,"d","");
	Уник = СтрЗаменить(Уник,"e","");
	Уник = СтрЗаменить(Уник,"f","");
	//знаменатель должен иметь такое же количество нулей + 1
	Знаменатель = 10;
	Для н = 2 По (СтрДлина(СтрЗаменить(Уник,Символы.НПП,""))) Цикл
		Знаменатель = Знаменатель * 10;
	КонецЦикла;
	Случ = (Число(Уник)) / Знаменатель; //здесь получается дробное случайное число от 0 до 1
	//преобразуем его в случайное число из заданного интервала, округляем до целого
	
	ЧислоИзИнтервала = Мин(Макс(Окр(Мин + (Макс-Мин)*Случ),Мин),Макс);
	
	Возврат ЧислоИзИнтервала; 
КонецФункции

Функция _2()
          //--//--//--
              Если узл.ИмяУзла = "challenge" Тогда
				неХЭШстрока = расшф64(узл.ТекстовоеСодержимое);
				струк = строк_струк(неХЭШстрока);
				Если струк.количество()>0 Тогда
					// нормально разобрали, теперь добавляем доп.параметры
					
					// 1-добавление к challenge digest-uri если он не пришел в ответе от сервера
					digest_uri = Ложь;
					Для  каждого првСтрк из струк Цикл
						Если првСтрк.Ключ = "digest-uri" Тогда
							digest_uri=Истина;
						КонецЕсли;
					КонецЦикла;
					Если НЕ digest_uri Тогда
						струк.Вставить("digest_uri", "xmpp/"+Объект.Домен);
					КонецЕсли;
					// 2-генерация cnonce - уникальный номер сессии
					гуид = Новый УникальныйИдентификатор();
					уникномерсесс = СтрЗаменить(гуид,"-","");
					струк.Вставить("cnonce", зашф64(уникномерсесс));
					струк.Вставить("nc", "00000001");
					// 3-проверка в challenge -> qop пришел в ответе от сервера и правильный ли он
					qop = Ложь;
					Для  каждого првСтрк из струк Цикл
						Если првСтрк.Ключ = "qop" Тогда
							qop=Истина;
						КонецЕсли;
					КонецЦикла;
					Если НЕ qop Тогда
						струк.Вставить("qop", "auth");
					Иначе
						Если НЕ струк.qop = "auth" Тогда
							струк.qop = "auth";
						КонецЕсли;
					КонецЕсли;
					// 3-добавление к challenge realm если он не пришел в ответе от сервера
					realm = Ложь;
					Для  каждого првСтрк из струк Цикл
						Если првСтрк.Ключ = "realm" Тогда
							realm=Истина;
						КонецЕсли;
					КонецЦикла;
					Если НЕ realm Тогда
						струк.Вставить("realm", "-"); // надо учитывать что
					КонецЕсли;
				КонецЕсли;
			
				Если струк.realm = "-" Тогда
					Знач_realm="";
				Иначе
					Знач_realm=струк.realm;
				КонецЕсли;
				
				// 4-создание кодированной строки response на основании некоторых данных из challenge и логина-пароля
				респонсеСтр=строкаРеспонсе(струк);
				
				// формируем строку для запроса №3
				строкаВозвратаДля3 = 
					"username="""+Объект.Пользователь+""","+
					"realm="""+Знач_realm+""","+
					"nonce="""+струк.nonce+""","+
					"cnonce="""+струк.cnonce+""","+
					"nc="+струк.nc+","+
					"qop="+струк.qop+","+
					"digest-uri="""+струк.digest_uri+""","+
					"response="+респонсеСтр+","+
					"charset=utf-8";
					
				Объект.challenge_actv=Истина;
			КонецЕсли;
          //--//--//--
КонецФункции

P.S. Есть пару идей использования протокола: можно организовать свой домашний сервер ботов на 1С, который может обрабатывать некую информацию и результат обработки отсылать пользователю в том-же xml или json. Например: по заказу сделали отчет или некую конфу, закодировали часть кода и ключ раскодирования отдавать пользователю по его запросу; можно также ограничить по количеству запусков, не более 10 раз и хватит с клиента. Причем, серверную часть своего бота можете ораганизовать на любом языке (не только 1С). Приложить свои усилия по доработке можно на github.

Скачать файлы

Наименование Файл Версия Размер
Пример реализации отправки сообщения
.epf 14,18Kb
26.12.16
6
.epf 14,18Kb 6 Скачать

См. также

Комментарии
1. Сергѣй Батанов (baton_pk) 207 28.12.16 10:14 Сейчас в теме
За находчивость +, теперь о грустном:

	ТестЗапроса = ""<body rid='""+Формат(Объект.РИД,""ЧГ=1000000"")+""' sid='""+Формат(Объект.СИД,""ЧГ=1000000"")+""' xmlns='http://jabber.org/protocol/httpbind'>
	|<presence><show/><status>1c input</status><priority>10</priority></presence>
	//|<presence><show/><status>1С вход</status><priority>10</priority></presence>
	|</body>"";
...Показать Скрыть


И охота XML рисовать руками? ЗаписьXML или XDTO - это же так удобно.
2. Дмитрий Кодник (kodnik) 65 28.12.16 10:32 Сейчас в теме
(1) С вами согласен, а так как это пример и его цель - показать возможности 1С, то намеренно оставил XML запросы в текстовом виде для наглядности и простоты запросов XMPP.
3. Серега (SerVer1C) 52 28.12.16 13:17 Сейчас в теме
"...не получается нормально сгенерировать 32 символа с кодами от 0 до 255 и потом закодировать данную строку методом Base64, часто происходит вылет 1С по ошибке. Нужна реализация функции СлучайноеЧисло - по другому алгоритму."
А в чем сложность? Берешь 2 ГУИДа, склеиваешь, убираешь черточки - получаешь число из 64-х шестнадцатиричных знаков, каждая пара которых представляет собой одно число в диапазоне от 0 до 255.
4. Дмитрий Кодник (kodnik) 65 28.12.16 17:33 Сейчас в теме
(3) Точно!!! Но два ГУИДа - это много (64символа), хватит и одного 32 символа.
гуид = Новый УникальныйИдентификатор();
уникномерсесс = СтрЗаменить(гуид,"-","");
струк.Вставить("cnonce", зашф64(уникномерсесс));
...Показать Скрыть

Оставьте свое сообщение