Обмен данными между клиентом и сервером в 1С 8.3: от вызова процедуры до HTTP-сервиса
Содержание
Внутри одной базы данные с клиента на сервер 1С передают параметрами серверной процедуры: на клиенте вызываете метод с директивой &НаСервере или &НаСервереБезКонтекста, а платформа сама сериализует аргументы и вернёт результат. Файлы и большие двоичные данные кладут во временное хранилище. Если клиент внешний — сайт, мобильное приложение, другая система, — данные принимают через HTTP-сервис в формате JSON или XML.
Почти все проблемы с этой темой сводятся к трём вещам: на клиент пытаются отдать несериализуемый тип, сервер дёргают в цикле, а исключение на сервере теряется без записи в журнал. Ниже разобраны все способы с рабочими примерами кода и тем, где каждый из них ломается.
Как устроено взаимодействие клиента и сервера в 1С 8.3
В управляемом приложении код формы выполняется в двух местах. Клиентская часть работает на компьютере пользователя, в тонком клиенте или браузере. Серверная — в рабочем процессе кластера или в файловом режиме внутри того же процесса, но по тем же правилам.
Где выполняется метод, решает директива компиляции перед ним:
| Директива | Где выполняется | Доступ к данным формы | Цена вызова |
|---|---|---|---|
&НаКлиенте | на клиенте | да | без обращения к серверу |
&НаСервере | на сервере | да, форма передаётся целиком | высокая |
&НаСервереБезКонтекста | на сервере | нет, только параметры | низкая |
&НаКлиентеНаСервереБезКонтекста | там, откуда вызвали | нет | без отдельного вызова |
Ключевое ограничение: клиент не видит базу данных. На клиенте нет объектов Запрос, СправочникОбъект, ТаблицаЗначений. Ссылки (СправочникСсылка) передавать можно, а вот прочитать реквизит по ссылке на клиенте нельзя — для этого нужен вызов сервера.
💡 Правило для быстрой проверки: если метод не использует реквизиты формы, ставьте &НаСервереБезКонтекста. Платформа не будет гонять на сервер и обратно данные формы, и вызов станет в разы легче на больших формах с табличными частями.
Передача данных через вызов серверной процедуры
Самый частый сценарий — передать значения с формы на сервер, что-то посчитать или записать и вернуть результат.
Параметры и возвращаемое значение
Через параметры передаются примитивы (Строка, Число, Дата, Булево), ссылки, Структура, Соответствие, Массив, ФиксированнаяСтруктура, ДвоичныеДанные, ХранилищеЗначения, ТабличныйДокумент. Пример: отправить на сервер список номенклатуры с количеством и получить суммы.
&НаКлиенте
Процедура РассчитатьСуммы(Команда)
Строки = Новый Массив;
Для Каждого Строка Из Объект.Товары Цикл
Строки.Добавить(Новый Структура("Номенклатура, Количество",
Строка.Номенклатура, Строка.Количество));
КонецЦикла;
Итог = РассчитатьНаСервере(Строки, Объект.Дата);
Сообщить(СтрШаблон("Итого: %1", Формат(Итог, "ЧДЦ=2; ЧН=0")));
КонецПроцедуры
&НаСервереБезКонтекста
Функция РассчитатьНаСервере(Строки, ДатаРасчета)
Итог = 0;
Для Каждого Элемент Из Строки Цикл
Цена = ПолучитьЦену(Элемент.Номенклатура, ДатаРасчета);
Итог = Итог + Цена * Элемент.Количество;
КонецЦикла;
Возврат Итог;
КонецФункции
Функция ПолучитьЦену здесь — ваша серверная функция чтения цены из регистра. Важно другое: весь массив уходит на сервер одним вызовом, а не по строке.
Работа с данными формы
Когда нужно изменить сам объект формы, используйте &НаСервере и пару методов преобразования. РеквизитФормыВЗначение("Объект") даёт полноценный ДокументОбъект, с которым можно работать как обычно, а ЗначениеВРеквизитФормы(ДокОбъект, "Объект") возвращает изменения на форму.
&НаСервере
Процедура ЗаполнитьНаСервере()
ДокОбъект = РеквизитФормыВЗначение("Объект");
ДокОбъект.ЗаполнитьТовары(); // экспортная процедура модуля объекта
ЗначениеВРеквизитФормы(ДокОбъект, "Объект");
КонецПроцедуры
Общие модули с флагом «Вызов сервера»
Если серверную логику вызывают из многих форм, вынесите её в общий модуль. В свойствах модуля включите флажки «Сервер» и «Вызов сервера», а экспортные функции объявите с Экспорт. Без флажка «Вызов сервера» клиентский код такой модуль просто не увидит.
Каждая экспортная функция серверного модуля с вызовом сервера — открытая точка входа. Проверяйте в ней права пользователя (ПравоДоступа) и не доверяйте пришедшим параметрам: их можно подменить.
Файлы, табличные документы и двоичные данные
Большой объём через параметры гнать нельзя: платформа передаёт его целиком при каждом вызове. Для файлов и тяжёлых данных служит временное хранилище.
Временное хранилище
ПоместитьВоВременноеХранилище(Значение, УникальныйИдентификатор) кладёт значение на сервер и возвращает адрес — обычную строку. Эту строку и передают между клиентом и сервером. На сервере данные читают через ПолучитьИзВременногоХранилища(Адрес).
Второй параметр — уникальный идентификатор формы. Пока форма открыта, данные живут в хранилище. Закрыли форму — хранилище очистилось. Без этого параметра значение удалится после завершения текущего серверного вызова.
Загрузка файла с компьютера пользователя
В платформе 8.3.18 и новее удобнее асинхронный метод ПоместитьФайлНаСерверАсинх. В более ранних релизах — НачатьПомещениеФайла с обработчиком оповещения.
&НаКлиенте
Асинх Процедура ЗагрузитьФайл(Команда)
Параметры = Новый ПараметрыДиалогаПомещенияФайлов;
Параметры.Фильтр = "Таблицы Excel (*.xlsx)|*.xlsx";
Описание = Ждать ПоместитьФайлНаСерверАсинх(, , Параметры, УникальныйИдентификатор);
Если Описание = Неопределено Тогда
Возврат; // пользователь отменил выбор
КонецЕсли;
ОбработатьФайлНаСервере(Описание.Адрес, Описание.СсылкаНаФайл.Имя);
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ОбработатьФайлНаСервере(Адрес, ИмяФайла)
Данные = ПолучитьИзВременногоХранилища(Адрес);
ИмяВременного = ПолучитьИмяВременногоФайла("xlsx");
Данные.Записать(ИмяВременного);
Таблица = Новый ТабличныйДокумент;
Таблица.Прочитать(ИмяВременного);
// разбор Таблица.Область(...) по строкам
УдалитьФайлы(ИмяВременного);
КонецПроцедуры
Табличный документ
ТабличныйДокумент сериализуется и спокойно возвращается с сервера на клиент — так работают все печатные формы. Если документ огромный (сотни страниц отчёта), формируйте его на сервере прямо в реквизит формы типа ТабличныйДокумент, а не возвращайте функцией в отдельную переменную, чтобы не передавать его дважды.
Сравнение способов передачи
| Способ | Когда подходит | Ограничения |
|---|---|---|
Параметры &НаСервереБезКонтекста | небольшие структуры, ссылки, числа | только сериализуемые типы |
&НаСервере с данными формы | изменение объекта на форме | форма уходит на сервер целиком |
| Временное хранилище | файлы, двоичные данные, тяжёлые таблицы | живёт, пока жива форма |
| HTTP-сервис | внешний клиент, другая система | нужна публикация и авторизация |
Как сделать HTTP-сервис для внешнего клиента
Когда клиент не 1С — сайт, приложение курьера, скрипт на Python, — данные принимают через HTTP-сервис. Это обычный REST-эндпоинт внутри конфигурации.
Создание сервиса в конфигураторе
- В дереве конфигурации откройте ветку
Общие → HTTP-сервисыи добавьте новый. - Задайте «Корневой URL», например
orders. - Добавьте шаблон URL, например
/{id}или просто/. - В шаблоне создайте методы:
GETдля выдачи,POSTдля приёма данных. - Для каждого метода назначьте обработчик — платформа создаст функцию в модуле сервиса.
Обработчик, принимающий JSON
Функция ЗаказыPOST(Запрос)
Ответ = Новый HTTPСервисОтвет(200);
Ответ.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
Попытка
Тело = Запрос.ПолучитьТелоКакСтроку("UTF-8");
Чтение = Новый ЧтениеJSON;
Чтение.УстановитьСтроку(Тело);
Данные = ПрочитатьJSON(Чтение, Истина); // Истина — в Соответствие
Чтение.Закрыть();
НомерЗаказа = СоздатьЗаказ(Данные); // ваша функция записи документа
Ответ.УстановитьТелоИзСтроки(
СтрШаблон("{""number"": ""%1""}", НомерЗаказа), "UTF-8");
Исключение
Ответ.КодСостояния = 400;
Ответ.УстановитьТелоИзСтроки("{""error"": ""bad request""}", "UTF-8");
ЗаписьЖурналаРегистрации("HTTP.Заказы", УровеньЖурналаРегистрации.Ошибка, , ,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
Возврат Ответ;
КонецФункции
Параметры из шаблона URL читают через Запрос.ПараметрыURL["id"], а параметры строки запроса — через Запрос.ПараметрыЗапроса.
Публикация
Сервис заработает только после публикации базы на веб-сервере (IIS или Apache) с флажком публикации HTTP-сервисов. Адрес будет вида http://сервер/имя_публикации/hs/orders/. Обращаются к нему под пользователем 1С с базовой авторизацией; заведите для интеграции отдельного пользователя с минимальной ролью.
Публикуйте сервис только по HTTPS. При базовой авторизации логин и пароль идут в каждом запросе, и по голому HTTP их видно любому, кто слушает сеть.
Если клиент — тоже 1С
Из другой базы такой сервис вызывают через HTTPСоединение и HTTPЗапрос:
Соединение = Новый HTTPСоединение("server.local", 443, "integration", "Пароль", , 30,
Новый ЗащищенноеСоединениеOpenSSL);
Запрос = Новый HTTPЗапрос("/base/hs/orders/");
Запрос.Заголовки.Вставить("Content-Type", "application/json");
Запрос.УстановитьТелоИзСтроки(ТекстJSON, "UTF-8");
Результат = Соединение.ОтправитьДляОбработки(Запрос);
Если Результат.КодСостояния <> 200 Тогда
ВызватьИсключение Результат.ПолучитьТелоКакСтроку("UTF-8");
КонецЕсли;
Код выполняйте на сервере: так соединение не зависит от сетевых настроек компьютера пользователя.
JSON или XML: что выбрать и как сериализовать
Оба формата платформа читает и пишет штатно. Выбор зависит от того, кто на другой стороне.
- 🟢 JSON — для сайтов, мобильных приложений, большинства современных API. Компактнее, проще разбирать.
- 🟡 XML — когда этого требует контрагент или формат закреплён регламентом обмена, а также для обмена между базами 1С через XDTO.
Запись и чтение JSON
Данные = Новый Структура;
Данные.Вставить("number", "А-125");
Данные.Вставить("date", ТекущаяДатаСеанса());
Данные.Вставить("sum", 15400.50);
Запись = Новый ЗаписьJSON;
Запись.УстановитьСтроку();
ЗаписатьJSON(Запись, Данные);
ТекстJSON = Запись.Закрыть();
ЗаписатьJSON понимает примитивы, Структура, Соответствие со строковыми ключами и Массив. Ссылки на справочники он не сериализует: передавайте код, артикул или УникальныйИдентификатор() ссылки строкой, а на стороне приёма ищите объект по нему.
При чтении второй параметр ПрочитатьJSON со значением Истина возвращает Соответствие. Это надёжнее, чем Структура: ключи вида order-id или 1c_code в структуру не помещаются и вызовут ошибку.
💡 Даты в JSON удобно отдавать в ISO-формате. Задайте Новый НастройкиСериализацииJSON и укажите формат даты ISO в свойстве ФорматСериализацииДаты, затем передайте настройки третьим параметром в ЗаписатьJSON.
XML и XDTO
Для обмена между базами 1С проще всего СериализаторXDTO: он умеет писать ссылки, объекты и наборы записей так, что другая база с той же структурой их прочитает.
Запись = Новый ЗаписьXML;
Запись.УстановитьСтроку("UTF-8");
СериализаторXDTO.ЗаписатьXML(Запись, ДокументОбъект);
ТекстXML = Запись.Закрыть();
Для произвольного XML от контрагента используйте ЧтениеXML в цикле Пока Чтение.Прочитать() Цикл или пакет XDTO, загруженный из схемы XSD.
Ошибки, транзакции и журнал регистрации
Если исключение возникло на сервере и не перехвачено, клиент получит окно с текстом ошибки, а данные останутся в том состоянии, в каком упали. Для HTTP-сервиса всё хуже: внешний клиент увидит код 500 без пояснений.
Транзакция при приёме данных
Запись нескольких объектов из одного пакета делайте в транзакции, чтобы пакет записался целиком или не записался вовсе:
НачатьТранзакцию();
Попытка
Для Каждого Позиция Из Данные["items"] Цикл
ЗаписатьПозицию(Позиция); // ваша процедура записи
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации("Обмен.ПриемПакета", УровеньЖурналаРегистрации.Ошибка, , ,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;
НачатьТранзакцию() ставьте непосредственно перед Попытка, а ОтменитьТранзакцию() — первой строкой в блоке Исключение. Не пишите вызов ОтменитьТранзакцию() без последующего ВызватьИсключение во вложенных процедурах — внешний код решит, что всё прошло успешно, и попытается писать дальше в уже отменённую транзакцию.
Что писать в журнал
- 📌 Имя события с префиксом подсистемы —
Обмен.ПриемПакета,HTTP.Заказы. Так журнал легко отфильтровать. - 📌
ПодробноеПредставлениеОшибкивместоОписаниеОшибки()— в нём есть стек вызовов и номер строки модуля. - 📌 Идентификатор пакета или внешний номер документа, чтобы найти, какой именно запрос упал.
Смотрят записи в Администрирование → Обслуживание → Журнал регистрации или в конфигураторе через Администрирование → Журнал регистрации, фильтруя по имени события.
Как не перегрузить сервер лишними вызовами
Каждый вызов сервера — это сетевой круг. В локальной сети он незаметен, а через интернет или RDP-шлюз десяток вызовов на одно действие пользователь ощущает как подвисание формы.
- ⚡ Не вызывайте сервер в цикле на клиенте. Соберите данные в массив и отправьте одним вызовом.
- ⚡ Не используйте
&НаСервере, где хватит&НаСервереБезКонтекста. - ⚡ Данные, которые не меняются за сеанс (настройки, константы), получайте один раз и храните в общем модуле с повторным использованием возвращаемых значений «На время сеанса».
- ⚡ В обработчике
ПриИзмененииполя не делайте по вызову на каждое поле — объединяйте пересчёты в одну серверную процедуру.
Чтобы увидеть число вызовов, включите в параметрах конфигуратора отображение показателей производительности при запуске 1С:Предприятия. В окне клиента появится счётчик вызовов сервера и объёма переданных данных — проверьте, сколько вызовов даёт одна команда.
Можно ли передать на клиент объект документа?
ДокументОбъект существует только на сервере. На клиент передают ссылку, данные формы или структуру с нужными реквизитами.