Общие сведения.
Существует множество способов реализации обмена данными сторонних программ с 1С с использованием HTTP-сервисов. Данная реализация позволяет определить один HTTP-сервис и просто добавлять нужные процедуры в общий модуль без переработки (изменения существующих или добавления новых) HTTP-сервисов. В качестве формата протокола используется JSON.
Ниже приведено краткое описание JSON-RPC 2.0.
Запрос должен содержать три обязательных свойства:
- method - Строка с именем вызываемого метода.
- params - Массив данных, которые должны передаваться методу, как параметры.
- id - Значение любого типа, которое используется для установки соответствия между запросом и ответом.
Так же должна быть передана версия протокола (в нашем случае "2.0") в параметре jsonrpc. В спецификации протокола этот параметр не указан, как обязательный, но в реализации данной публикации его указание обязательно.
Пример запроса для вызова процедуры ПолучитьСчетаКлиента(ИНН, НачалоПериода, КонецПериода) (данная процедура использована в качестве примера, конкретные процедуры определяет разработчик):
{"jsonrpc": "2.0", "method": "ПолучитьСчетаКлиента", "params": ["7705260674", "2019-09-01", "2019-09-30"], "id": 1}
Сервер (в нашем случае - решение на 1С) отсылает ответ, содержащий следующие свойства:
- result - Результат выполнения метода. Если во время выполнения метода произошла ошибка, то это свойство должно быть установлено в null.
- error - Код ошибки, если произошла ошибка во время выполнения метода, иначе null.
- id - То же значение идентификатора, что и в запросе, к которому относится данный ответ (используется для сопоставления самого запроса и его результата, например, если асинхронно отправляется сразу несколько запросов).
Пример ответа:
{"jsonrpc": "2.0", "result": [{"uid": "422f9d5c-1032-11e5-92f1-0050568b35ac", "num": "ТД00-000001", "date": "2019-09-11T16:06:15", "sum": 127125}], "id": 1}
Более подробно с протоколом JSON-RPC можно ознакомиться на википедии.
В данной публикации удалённый вызов процедур 1С реализован в виде расширения. В этом расширении присутствует HTTP-сервис rpc_JSONRPC2. После публикации http-сервиса на веб-сервере сторонние системы (в качестве которой может выступать и другая база 1С) смогут выполнять запросы по адресу http://<адрес_базы>/hs/jsonrpc2.
Вызываемые удалённые процедуры должны располагаться в общем модуле расширения rpc_УдаленныеПроцедурыПереопределяемый.
Особенности реализации 1С.
Для упрощения создания собственных удалённых процедур были реализованы следующие особенности:
- Передаваемые в запросе JSON строковые значения параметров true и false преобразуются в тип 1С Булево.
- Передаваемые в запросе JSON строковые значения параметров типа 2019-01-10Т10:23:54 (дата в формате XML) преобразуются в тип 1С Дата.
- Передаваемые в запросе JSON строковые значения параметров типа GUID преобразуются в тип 1С УникальныйИдентификатор.
- Удалённая процедура (в 1С функция) должна возвращать только значения, которые могут быть сериализованы в JSON. Исключение составляет тип ТаблицаЗначений. В типовом механизме сериализации JSON этот тип не может использоваться, но в данном решении для упрощения возврата ответа (чтобы разработчик не писал лишний код) таблица значений преобразуется в массив структур.
Для передачи ошибок используется вызов исключения.
Например, вот так можно реализовать обработку ошибок в приведённой выше в качестве примера процедуре ПолучитьСчетаКлиента:
Функция ПолучитьСчетаКлиента(ИНН, НачалоПериода, КонецПериода) Экспорт
Контрагент = НайтиКонтрагентаПоИНН(ИНН);
Если Контрагент.Пустая() Тогда
ТекстСообщения = СтрШаблон(НСтр("ru='Не найден клиент с ИНН: %1'"), ИНН);
ВызватьИсключение ТекстСообщения;
КонецЕсли;
...
КонецФункции
В таком случае, если в запросе будет передан ИНН контрагента, отсутствующего в информационной базе, то клиентом будет получен ответ следующего вида:
{"jsonrpc": "2.0", "error": {"code": -32800, "message": "Не найден клиент с ИНН: 0777123413"}, "id": 1}
Примечание. В данной реализации используются следующие коды ошибок:
- -32600, "Invalid JSON-RPC" - возвращается, если передан неверный формат сообщения (не удалось прочитать JSON) или отсутствуют обязательные параметры;
- -32700, "Parse error" - возвращается, если переданы неверные параметры (например, отсутствует параметр method или параметр params не является массивом и т.п.);
- -32601, "Procedure not found" - вызываемая функция отсутствует в общем модуле rpc_УдаленныеПроцедурыПереопределяемый;
- -32800, <текст ошибки> - ошибка, возникшая во время выполнения метода (см. пример ответа с ошибкой выше).
Перейдём от теории к практике.
В качестве примера будем использовать конфигурацию Управление торговлей, редакция 11 (конкретный релиз для примера не имеет значения).
Предположим, что в организации используется данная конфигурация и есть стороннее веб-приложение, реализующее личный кабинет клиента. Необходимо выводить клиенту список его счетов на оплату за определённый период, содержащий следующую информацию: уникальный идентификатор (для возможности дальнейших действий со счётом), номер, дату и сумму счёта. Веб-программист просит предоставить ему интерфейс для получения необходимых данных.
В данном случае порядок действий будет следующим:
1. Добавляем в конфигурацию расширение из этой публикации и публикуем HTTP-сервис rpc_JSONRPC2.
2. Открываем общий модуль расширения rpc_УдаленныеПроцедурыПереопределяемый и реализуем процедуру ПолучитьСчетаКлиента:
Функция ПолучитьСчетаКлиента(ИНН, НачалоПериода, КонецПериода) Экспорт
Контрагент = НайтиКонтрагентаПоИНН(ИНН); // Реализация данной функции не представляет проблем, оставим её "за скобками".
Если Контрагент.Пустая() Тогда
ТекстСообщения = СтрШаблон(НСтр("ru='Не найден клиент с ИНН: %1'"), ИНН);
ВызватьИсключение ТекстСообщения;
КонецЕсли;
ТаблицаСчетов = Новый ТаблицаЗначений;
ТаблицаСчетов.Колонки.Добавить("uid", Новый ОписаниеТипов("УникальныйИдентификатор"));
ТаблицаСчетов.Колонки.Добавить("num", Новый ОписаниеТипов("Строка",, Новый КвалификаторыСтроки(11)));
ТаблицаСчетов.Колонки.Добавить("date", Новый ОписаниеТипов("Дата",,, Новый КвалификаторыДаты(ЧастиДаты.ДатаВремя)));
ТаблицаСчетов.Колонки.Добавить("sum", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));
Запрос = Новый Запрос(
"ВЫБРАТЬ
| СчетНаОплатуКлиенту.Ссылка КАК Ссылка,
| СчетНаОплатуКлиенту.Номер КАК num,
| СчетНаОплатуКлиенту.Дата КАК date,
| СчетНаОплатуКлиенту.СуммаДокумента КАК sum
|ИЗ
| Документ.СчетНаОплатуКлиенту КАК СчетНаОплатуКлиенту
|ГДЕ
| СчетНаОплатуКлиенту.Контрагент = &Контрагент
| И СчетНаОплатуКлиенту.Дата МЕЖДУ &НачалоПериода И &КонецПериода");
Запрос.УстановитьПараметр("Контрагент", Контрагент);
Запрос.УстановитьПараметр("НачалоПериода", НачалоДня(НачалоПериода));
Запрос.УстановитьПараметр("КонецПериода", КонецДня(КонецПериода));
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
СтрокаТаблицы = ТаблицаСчетов.Добавить();
ЗаполнитьЗначенияСвойств(СтрокаТаблицы, Выборка);
СтрокаТаблицы.uid = Выборка.Ссылка.УникальныйИдентификатор();
КонецЦикла;
Возврат ТаблицаСчетов;
КонецФункции
3. Важный момент. Чтобы была возможность выполнять удаленный вызов данной функции нужно добавить её имя в процедуру УдаленныеПроцедуры общего модуля rpc_УдаленныеПроцедуры. Ниже приведён пример такой процедуры:
Функция УдаленныеПроцедуры() Экспорт
УдаленныеПроцедуры = Новый Массив;
///////////////////////////////////////////////////////////////////////////////////////////////////////
// Необходимо добавить следующие строки:
//
УдаленныеПроцедуры.Добавить("ПолучитьСчетаКлиента");
УдаленныеПроцедуры.Добавить("АннулироватьСчетКлиента");
///////////////////////////////////////////////////////////////////////////////////////////////////////
Возврат УдаленныеПроцедуры;
КонецФункции
Это сделано для безопасности с целью исключения выполнения произвольного кода, непредусмотренного разработчиком. Дело в том, что для вызова процедур используется метод 1С Выполнить:
Выполнить("Результат = rpc_УдаленныеПроцедурыПереопределяемый." + ИмяМетода + "(" + ... + ")";
Злоумышленник может в параметре JSON params передать строку, например для описанной выше функции
{"jsonrpc": "2.0", "method": "ПолучитьСчетаКлиента(\"7705260674\", '20190901', '20190930'); ВредныйМодуль.УдалитьВсе()", ...}
В результате чего будет выполнен код:
Выполнить("Результат = rpc_УдаленныеПроцедурыПереопределяемый.ПолучитьСчетаКлиента(""7705260674"", '20190901', '20190930'); ВредныйМодуль.УдалитьВсе() ...
Т.е. после выполнения предусмотренного разработчиком метода "ПолучитьСчетаКлиента" начнёт выполняться произвольный код, который выполняться не должен!
Благодаря добавлению имён процедур в методе УдаленныеПроцедуры общего модуля rpc_УдаленныеПроцедуры такая возможность исключается. Для системы в таком случае имя метода будет не "ПолучитьСчетаКлиента", а ПолучитьСчетаКлиента("7705260674", '20190901, '20190930); ВредныйМодуль.УдалитьВсе(). Такая строка не определена в методе УдаленныеПроцедуры общего модуля rpc_УдаленныеПроцедуры, поэтому ни какой код выполняться не будет, а будет выдано сообщение об ошибке.
Теперь веб-программисту достаточно будет выполнить POST запрос по адресу http://<адрес_базы>/hs/jsonrpc2 в формате вида
{"jsonrpc": "2.0", "method": "ПолучитьСчетаКлиента", "params": ["7705260674", "2019-09-01", "2019-09-30"], "id": 1}
чтобы получить необходимые данные.
Всё работает, всё хорошо, но возникает новое требование - дать возможность клиенту из личного кабинета аннулировать счёт на оплату. И вот разработчик личного кабинета просит разработчика 1С реализовать такую возможность.
Для этого достаточно вновь открыть общий модуль расширения rpc_УдаленныеПроцедурыПереопределяемый и реализовать новую процедуру АннулироватьСчетКлиента(ИдентификаторСчета).
Процедура АннулироватьСчетКлиента(ИдентификаторСчета) Экспорт
СчетКлиента = Документы.СчетНаОплатуКлиенту.ПолучитьСсылку(ИдентификаторСчета);
МассивСчетов = Новый Массив;
МассивСчетов.Добавить(СчетКлиента);
Попытка
Документы.СчетНаОплатуКлиенту.УстановитьПризнакАннулирован(МассивСчетов);
Исключение
ЗаписьЖурналаРегистрации("JSON-RPC.АнулироватьСчетКлиента",
УровеньЖурналаРегистрации.Ошибка,,,
КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
КонецПроцедуры
Теперь, когда клиент в личном кабинете в меню, например, выберет команду "Аннулировать счёт", веб-разработчик просто выполнит POST запрос к базе 1С вида
{"jsonrpc": "2.0", "method": "АнулироватьСчетКлиента", "params": ["422f9d5c-1032-11e5-92f1-0050568b35ac"]}
Хочу обратить внимание, что данная процедура не предполагает возвращение результата, поэтому передавать параметр id не нужно (в терминах JSON-RPC это называется уведомлением). Примечание. Использовано для примера, в реальности, конечно, нужно возвращать ошибку, если по какой-то причине не удалось аннулировать счёт, например, если отсутствует счёт с таким уникальным идентификатором.
Расширение предназначено для любой конфигурации на платформе не ниже 8.3.6. Протестировано на платформе версии 8.3.15.1656.