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

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

Для записи данных в 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.

9 Comments

  1. kiv1c

    хмм, а как вы тестировали? В данном случае вы в REST уже отправляете готовый элемент справочника в виде xml, никакой сложной обработки.

    если в веб-сервисе не писать никакой логики, а только с помощью десериализации из xml (по-моему с помощью СериализаторXDTO.ПрочитатьXML) создать элемент справочника, то откуда берется такая большая разница по скорости в 3-10 раз?

    Reply
  2. Fragster

    Формирование XML через конкатенацию — отвратительно. Можно очень просто сломать.

    Reply
  3. Dorosh

    (2) Согласен, но деваться некуда, другого способа нет. Еще один минус в сторону REST.

    Reply
  4. Fragster

    (3) начиная от ЗаписьXML и заканчивая СериализаторXDTO

    Reply
  5. Dorosh

    (4) И как с их помощью можно составить текст запроса для REST?

    Reply
  6. hulio

    (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>

    Показать

    Reply
  7. Serginio

    (3) Вообще то ODATA создаваласть для интеграции с любыми системами. А там как раз используют объекты

    Linq to ODATA

    Reply
  8. Трактор

    Не SOUP, а SOAP поправь опечатку.

    Reply
  9. vandalsvq

    А почему не использовать JSON? По мне так он намного проще. По времени записи наверняка xml не уступает.

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *