Использование REST web-сервисов в "1C:Предприятии 8". Личный опыт. Часть 2.

Опубликовал Dorosh Dorosh (Dorosh) в раздел Обмен - Интеграция с WEB

Запись документов и справочников.

Для записи данных в REST используются: метод POST протокола HTTP для создания новых элементов и метод PATCH HTTP для обновления существующих данных. Например, запишем в базу контрагента. Сначала устанавливаем HTTPСоединение с базой. Создаем объект HTTPЗапрос. Подготовим параметр АдресРесурса запроса:

СтрокаЗапроса = "/Base1C/odata/standard.odata/Catalog_Контрагенты";

Для PATCH вызова допишем:

Если НЕ ПустаяСтрока(ID) Тогда
     СтрокаЗапроса = СтрокаЗапроса + "(guid'" + ID + "')";
КонецЕсли;

Подготовим заголовки запроса и создадим его.

Заголовки = Новый Соответствие;
Заголовки.Вставить("Accept", "application/atom+xml,application/xml");
Заголовки.Вставить("Accept-Charset", "UTF-8");
Запрос = Новый HTTPЗапрос(СтрокаЗапроса, Заголовки);

Потом формируем текст запроса, для обоих методов он одинаков.  

ТекстЗапроса = "<?xml  version=""1.0"" encoding=""UTF-8""?>
|<entry  xmlns=""http://www.w3.org/2005/Atom""
|        xmlns:d=""http://schemas.microsoft.com/ado/2007/08/dataservices""
|        xmlns:m=""http://schemas.microsoft.com/ado/2007/08/dataservices/metadata""
|        xmlns:georss=""http://www.georss.org/georss""
|        xmlns:gml=""http://www.opengis.net/gml"">
|  <content type=""application/xml"">
|      <m:properties>"
      + ?(ПустаяСтрока(ID), "", "
|          <d:Ref_Key>" + ID + "</d:Ref_Key>")
      + ?(ПустаяСтрока(Code), "", "
|         <d:Code>" + Code + " </d:Code>") + "
|         <d:DeletionMark>false</d:DeletionMark>"
      + ?(ПустаяСтрока(Name), "", "
|         <d:Description>" + Name + "</d:Description>"
      + ?(ПустаяСтрока(INN),                             "", "
|                                             <d:ИНН>" + INN + "</d:ИНН>")
);
Если IsGroup Тогда
     ТекстЗапроса = ТекстЗапроса + "
     |         <d:IsFolder>true</d:IsFolder>
     |       </m:properties>
     |   </content>
     |</entry>                                         
     |";
Иначе
     Если ПустаяСтрока(INN) ИЛИ СтрДлина(INN)=12 Тогда
          ЮридическоеФизическоеЛицо="ФизическоеЛицо";
     Иначе
          ЮридическоеФизическоеЛицо="ЮридическоеЛицо";
     КонецЕсли;
     ТекстЗапроса = ТекстЗапроса + "
     |         <d:IsFolder>false</d:IsFolder>"
         + ?(ПустаяСтрока(FullName), "", "
     |          <d:НаименованиеПолное>" + FullName + "</d:НаименованиеПолное>")
         + ?(ПустаяСтрока(INN),                             "", "
     |          <d:ИНН>" + INN + "</d:ИНН>")
         + ?(ПустаяСтрока(KPP),                             "", "
     |          <d:КПП>" + KPP + "</d:КПП>") + "
     |          <d:ЮридическоеФизическоеЛицо>" + ЮридическоеФизическоеЛицо + "</d:ЮридическоеФизическоеЛицо>" + "
     |       </m:properties>
     |   </content>
     |</entry>                                         
     |";
КонецЕсли;


Текст запроса начинается с обязательной шапки. Дальше, в тэге <m:properties> перечисляются записываемые реквизиты. Каждый реквизит записывается тэгом "</d:название реквизита>. Реквизиты ссылочного типа после имени получают суффикс _Key. Значение типа перечисление записывается строковым представлением.

Теперь выполним HTTP запрос:

Запрос.УстановитьТелоИзСтроки(ТекстЗапроса);
Если ПустаяСтрока(ID) Тогда
     Ответ = HTTPсоединение.ВызватьHTTPМетод("POST", Запрос);        // Создаем новый элемент
Иначе
     Ответ = HTTPсоединение.ВызватьHTTPМетод("PATCH", Запрос);     // Update'им существующий
КонецЕсли;

Проанализируем ответ:

ОтветСтрокой = Ответ.ПолучитьТелоКакСтроку();
Если Ответ.КодСостояния > 299 Тогда
     ТекстОшибки = "Error, код ошибки: " + Ответ.КодСостояния + "
     |" + ОтветСтрокой;
ИначеЕсли ПустаяСтрока(ID) Тогда
    // GUID не был передан заранее, значит нужно найти в ответе и передать назад
    КолСтрок = СтрЧислоСтрок(ОтветСтрокой);
    Для НомерСтроки=1 По КолСтрок Цикл
        СтрокаАнализа = СтрПолучитьСтроку(ОтветСтрокой, НомерСтроки);
        ПозицияНачала = СтрНайти(СтрокаАнализа, "<d:Ref_Key>");
        Если ПозицияНачала > 0 Тогда
             ПозицияНачала = ПозицияНачала + 11;
             ID = Сред(СтрокаАнализа, ПозицияНачала, 36);
             Прервать;
        КонецЕсли;
     КонецЦикла;
     БулевРезФун = Истина;
     ТекстОшибки = "OK. Был создан новый элемент с GUID='" + ID + "'";
Иначе
     БулевРезФун = Истина;
     ТекстОшибки = "OK. Успешно обновлен элемент с GUID='" + ID + "'";
КонецЕсли;


Теперь документ.  Запишем платежку.

ТекстЗапроса = "<?xml  version=""1.0"" encoding=""UTF-8""?>
|<entry  xmlns=""http://www.w3.org/2005/Atom""
|        xmlns:d=""http://schemas.microsoft.com/ado/2007/08/dataservices""
|        xmlns:m=""http://schemas.microsoft.com/ado/2007/08/dataservices/metadata""
|        xmlns:georss=""http://www.georss.org/georss""
|        xmlns:gml=""http://www.opengis.net/gml"">
|  <content type=""application/xml"">
|      <m:properties>
|          <d:Ref_Key>" +GUID + "</d:Ref_Key>")
|         <d:Number>" +Number + " </d:Number>") + "
|       <d:DeletionMark>false</d:DeletionMark>
|       <d:РаспределятьОплатуАвтоматически>true</d:РаспределятьОплатуАвтоматически>
|                             <d:Организация_Key>" + OrgID + "</d:Организация_Key>
|                             <d:ВалютаДокумента_Key>" + ВалютаДокумента + "</d:ВалютаДокумента_Key>
|                             <d:Date>" + XMLСтрока(Date) + "</d:Date>
|                             <d:СуммаДокумента>" +Summa + "</d:СуммаДокумента>
|                             <d:Комментарий> Создан из мобильного клиента " + ТекущаяДата() + "</d:Комментарий>";


С реквизитами составного типа немного сложнее:

Если ЗначениеЗаполнено(Контрагент) Тогда
     ТекстЗапроса = ТекстЗапроса + "
     |                             <d:Контрагент_Type>StandardODATA.Catalog_Контрагенты</d:Контрагент_Type>                                                                            
     |       <d:Контрагент>" +Контрагент + " </d:Контрагент>";                                     
ИначеЕсли ЗначениеЗаполнено(Физлицо) Тогда
     ТекстЗапроса = ТекстЗапроса + "
     |                             <d:Контрагент_Type>StandardODATA.Catalog_ФизическиеЛица</d:Контрагент_Type>                                                                   
     |       <d:Контрагент>" + Физлицо + " </d:Контрагент>";                                         
Иначе
     ТекстОшибки = "Error - должен быть заполнен контрагент";
     Возврат БулевРезФун;
КонецЕсли;


Для реквизитов составного типа сначала задается тип записываемого значения, тэгом <d:название реквизита_Type>. Потом ему присваивается значение. Любопытно, но значение реквизита в этом случае задается без суффикса _Key.

Теперь нужно описать табличную часть документа.

ТекстЗапроса = ТекстЗапроса + "
|             <d:РасшифровкаПлатежа m:type=""Collection(StandardODATA.Document_ПоступлениеНаРасчетныйСчет_РасшифровкаПлатежа_RowType)"">

Тэг  <d:РасшифровкаПлатежа указывает имя табличной части. Тэг m:type указывает тип реквизита РасшифровкаПлатежа. Теперь в цикле опишем значения строк табличной части:

Для НомерСтроки=1 По Док. РасшифровкаПлатежа.Количество()-1 Цикл
    ТекстЗапроса = ТекстЗапроса + "
    |             <d:element m:type=""StandardODATA.Document_ПоступлениеНаРасчетныйСчет_Товары_RowType"">
    |                             <d:LineNumber>"+НомерСтроки+</d:LineNumber>
    |                             <d:КурсВзаиморасчетов>1</d:КурсВзаиморасчетов>
    |                             <d:КратностьВзаиморасчетов>1</d:КратностьВзаиморасчетов>
    |                             <d:СуммаПлатежа>" + Summa[НомерСтроки-1] + "</d:СуммаПлатежа>
    |                             <d:СуммаВзаиморасчетов>" + Summa[НомерСтроки-1] + "</d:СуммаВзаиморасчетов>
    |                             <d:СтавкаНДС>БезНДС</d:СтавкаНДС>
    |             </d:element>";
КонецЦикла;


Тэг d:element используется для описание строк табличной части. Внутри описания элемента тэг m:type указывает тип строки табличной части. Тэг d:LineNumber необходим для указания номера строки.

Закончим текст запроса.

ТекстЗапроса = ТекстЗапроса + "
|            </d:РасшифровкаПлатежа>
|       </m:properties>
|   </content>
|</entry>                                         
|";

Подготовим АдресРесурса:

СтрокаЗапроса = "/"/Base1C/odata/standard.odata/Document_ПоступлениеНаРасчетныйСчет";

и заголовки HTTP запроса:

Заголовки = Новый Соответствие;
Заголовки.Вставить("Accept", "application/atom+xml,application/xml");
Заголовки.Вставить("Accept-Charset", "UTF-8");
Заголовки.Вставить("1C_OData_DataLoadMode", Истина);


Для документов доступна запись в режиме ОбменДанными.Загрузка=Истина;

Наконец-то можно выполнить запрос:

Запрос = Новый HTTPЗапрос(СтрокаЗапроса, Заголовки);
Запрос.УстановитьТелоИзСтроки(ТекстЗапроса);
Ответ = HTTPсоединение.ОтправитьДляОбработки(Запрос);

Обработаем ответ:

ОтветСтрокой = Ответ.ПолучитьТелоКакСтроку();
КолСтрок     = СтрЧислоСтрок(ОтветСтрокой);
Если Ответ.КодСостояния > 299 Тогда
     ТекстОшибки = "Error, код ошибки: " + Ответ.КодСостояния + "
     |" + ОтветСтрокой;
     Возврат БулевРезФун;
Иначе
     Если ПустаяСтрока(Number) Тогда
          Number = " Не удалось определить № документа ";
          Для НомерСтроки=1 По КолСтрок Цикл
              СтрокаАнализа = СтрПолучитьСтроку(ОтветСтрокой, НомерСтроки);
              ПозицияКонца = СтрНайти(СтрокаАнализа,"</d:Number>");
              Если ПозицияКонца > 0 Тогда
                   ПозицияНачала = СтрНайти(СтрокаАнализа,">") + 1;
                   Number = Сред(СтрокаАнализа, ПозицияНачала, ПозицияКонца - ПозицияНачала);
              КонецЕсли;
           КонецЦикла;
      КонецЕсли;
      БулевРезФун = Истина;                                            
      ТекстОшибки = "OK. Успешно создано поступление на расчетный счет с номером='" + Number + "'";
КонецЕсли;


Заключение.

Итак, для чего нужны такие хлопоты? Главный и огромный плюс REST - быстродействие. Тесты на живых данных показали, по сравнению с привычными SOUP сервисами REST отрабатывали в 3-10 раз быстрее. Ради такого прироста можно помучиться и перетерпеть все неудобства REST.

См. также

Комментарии
1. Иван Коротеев (kiv1c) 311 12.12.16 10:56 Сейчас в теме
хмм, а как вы тестировали? В данном случае вы в REST уже отправляете готовый элемент справочника в виде xml, никакой сложной обработки.
если в веб-сервисе не писать никакой логики, а только с помощью десериализации из xml (по-моему с помощью СериализаторXDTO.ПрочитатьXML) создать элемент справочника, то откуда берется такая большая разница по скорости в 3-10 раз?
2. Антонио Антонио (Fragster) 658 12.12.16 12:36 Сейчас в теме
Формирование XML через конкатенацию - отвратительно. Можно очень просто сломать.
speshuric; spogo; hulio; +3 Ответить 1
3. Dorosh Dorosh (Dorosh) 91 12.12.16 14:03 Сейчас в теме
(2) Согласен, но деваться некуда, другого способа нет. Еще один минус в сторону REST.
4. Антонио Антонио (Fragster) 658 12.12.16 15:44 Сейчас в теме
(3) начиная от ЗаписьXML и заканчивая СериализаторXDTO
5. Dorosh Dorosh (Dorosh) 91 12.12.16 19:15 Сейчас в теме
(4) И как с их помощью можно составить текст запроса для REST?
6. Антон Чарушкин (hulio) 21 13.12.16 10:33 Сейчас в теме
(5) Полагаю, как то так (накидал пример на коленке):
Запись = Новый ЗаписьXML;
Запись.УстановитьСтроку("UTF-8");

Запись.ЗаписатьОбъявлениеXML();

Запись.ЗаписатьНачалоЭлемента("entry");
Запись.ЗаписатьАтрибут("xmlns"			, "http://www.w3.org/2005/Atom");
Запись.ЗаписатьАтрибут("xmlns:d"		, "http://schemas.microsoft.com/ado/2007/08/dataservices");
Запись.ЗаписатьАтрибут("xmlns:m"		, "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata");
Запись.ЗаписатьАтрибут("xmlns:georss"	, "http://www.georss.org/georss");
Запись.ЗаписатьАтрибут("xmlns:gml"		, "http://www.opengis.net/gml");

Запись.ЗаписатьНачалоЭлемента("content");
Запись.ЗаписатьАтрибут("type", "application/xml");

Запись.ЗаписатьНачалоЭлемента("m:properties");

	Запись.ЗаписатьНачалоЭлемента("d:Ref_Key");
	Запись.ЗаписатьТекст(Строка(Новый УникальныйИдентификатор));
	Запись.ЗаписатьКонецЭлемента();

	Запись.ЗаписатьНачалоЭлемента("d:Code");
	Запись.ЗаписатьТекст("0001");
	Запись.ЗаписатьКонецЭлемента();
	
	Запись.ЗаписатьНачалоЭлемента("d:Description");
	Запись.ЗаписатьТекст("Ромашка, ООО");
	Запись.ЗаписатьКонецЭлемента();
	
	Запись.ЗаписатьНачалоЭлемента("d:INN");
	Запись.ЗаписатьТекст("1234567890");
	Запись.ЗаписатьКонецЭлемента();

Запись.ЗаписатьКонецЭлемента(); // m:properties

Запись.ЗаписатьКонецЭлемента(); // content

Запись.ЗаписатьКонецЭлемента(); // entry

СтрокаXML = Запись.Закрыть();
...Показать Скрыть


Получим что типа такого:
<?xml version="1.0" encoding="UTF-8"?>
<entry xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">
	<content type="application/xml">
		<m:properties>
			<d:Ref_Key>286353be-14d9-4b5e-8bbd-3e8b641e9ef7</d:Ref_Key>
			<d:Code>0001</d:Code>
			<d:Description>Ромашка, ООО</d:Description>
			<d:INN>1234567890</d:INN>
		</m:properties>
	</content>
</entry>
...Показать Скрыть
7. Сергей Смирнов (Serginio) 532 13.12.16 11:00 Сейчас в теме
(3) Вообще то ODATA создаваласть для интеграции с любыми системами. А там как раз используют объекты
Linq to ODATA
8. Трактор Трактор (Трактор) 1112 14.12.16 09:58 Сейчас в теме
Не SOUP, а SOAP поправь опечатку.
VasilVtoroy; GreenDragon; +2 Ответить
9. Александр Анисков (vandalsvq) 686 15.12.16 07:20 Сейчас в теме
А почему не использовать JSON? По мне так он намного проще. По времени записи наверняка xml не уступает.
shmellevich; artbear; BigB; VasilVtoroy; +4 Ответить