Обмен данными между клиентом и сервером в 1С 8.3: от вызова процедуры до HTTP-сервиса

· 10 мин

Содержание

Внутри одной базы данные с клиента на сервер 1С передают параметрами серверной процедуры: на клиенте вызываете метод с директивой &НаСервере или &НаСервереБезКонтекста, а платформа сама сериализует аргументы и вернёт результат. Файлы и большие двоичные данные кладут во временное хранилище. Если клиент внешний — сайт, мобильное приложение, другая система, — данные принимают через HTTP-сервис в формате JSON или XML.

Почти все проблемы с этой темой сводятся к трём вещам: на клиент пытаются отдать несериализуемый тип, сервер дёргают в цикле, а исключение на сервере теряется без записи в журнал. Ниже разобраны все способы с рабочими примерами кода и тем, где каждый из них ломается.

Как устроено взаимодействие клиента и сервера в 1С 8.3

В управляемом приложении код формы выполняется в двух местах. Клиентская часть работает на компьютере пользователя, в тонком клиенте или браузере. Серверная — в рабочем процессе кластера или в файловом режиме внутри того же процесса, но по тем же правилам.

Где выполняется метод, решает директива компиляции перед ним:

ДирективаГде выполняетсяДоступ к данным формыЦена вызова
&НаКлиентена клиентедабез обращения к серверу
&НаСерверена сервереда, форма передаётся целикомвысокая
&НаСервереБезКонтекстана серверенет, только параметрынизкая
&НаКлиентеНаСервереБезКонтекстатам, откуда вызвалинетбез отдельного вызова

Ключевое ограничение: клиент не видит базу данных. На клиенте нет объектов Запрос, СправочникОбъект, ТаблицаЗначений. Ссылки (СправочникСсылка) передавать можно, а вот прочитать реквизит по ссылке на клиенте нельзя — для этого нужен вызов сервера.

💡 Правило для быстрой проверки: если метод не использует реквизиты формы, ставьте &НаСервереБезКонтекста. Платформа не будет гонять на сервер и обратно данные формы, и вызов станет в разы легче на больших формах с табличными частями.

Передача данных через вызов серверной процедуры

Самый частый сценарий — передать значения с формы на сервер, что-то посчитать или записать и вернуть результат.

Параметры и возвращаемое значение

Через параметры передаются примитивы (Строка, Число, Дата, Булево), ссылки, Структура, Соответствие, Массив, ФиксированнаяСтруктура, ДвоичныеДанные, ХранилищеЗначения, ТабличныйДокумент. Пример: отправить на сервер список номенклатуры с количеством и получить суммы.

&НаКлиенте
Процедура РассчитатьСуммы(Команда)
	
	Строки = Новый Массив;
	Для Каждого Строка Из Объект.Товары Цикл
		Строки.Добавить(Новый Структура("Номенклатура, Количество",
			Строка.Номенклатура, Строка.Количество));
	КонецЦикла;
	
	Итог = РассчитатьНаСервере(Строки, Объект.Дата);
	Сообщить(СтрШаблон("Итого: %1", Формат(Итог, "ЧДЦ=2; ЧН=0")));
	
КонецПроцедуры

&НаСервереБезКонтекста
Функция РассчитатьНаСервере(Строки, ДатаРасчета)
	
	Итог = 0;
	Для Каждого Элемент Из Строки Цикл
		Цена = ПолучитьЦену(Элемент.Номенклатура, ДатаРасчета);
		Итог = Итог + Цена * Элемент.Количество;
	КонецЦикла;
	Возврат Итог;
	
КонецФункции

Функция ПолучитьЦену здесь — ваша серверная функция чтения цены из регистра. Важно другое: весь массив уходит на сервер одним вызовом, а не по строке.

Работа с данными формы

Когда нужно изменить сам объект формы, используйте &НаСервере и пару методов преобразования. РеквизитФормыВЗначение("Объект") даёт полноценный ДокументОбъект, с которым можно работать как обычно, а ЗначениеВРеквизитФормы(ДокОбъект, "Объект") возвращает изменения на форму.

&НаСервере
Процедура ЗаполнитьНаСервере()
	
	ДокОбъект = РеквизитФормыВЗначение("Объект");
	ДокОбъект.ЗаполнитьТовары(); // экспортная процедура модуля объекта
	ЗначениеВРеквизитФормы(ДокОбъект, "Объект");
	
КонецПроцедуры

Общие модули с флагом «Вызов сервера»

Если серверную логику вызывают из многих форм, вынесите её в общий модуль. В свойствах модуля включите флажки «Сервер» и «Вызов сервера», а экспортные функции объявите с Экспорт. Без флажка «Вызов сервера» клиентский код такой модуль просто не увидит.

Каждая экспортная функция серверного модуля с вызовом сервера — открытая точка входа. Проверяйте в ней права пользователя (ПравоДоступа) и не доверяйте пришедшим параметрам: их можно подменить.

Файлы, табличные документы и двоичные данные

Большой объём через параметры гнать нельзя: платформа передаёт его целиком при каждом вызове. Для файлов и тяжёлых данных служит временное хранилище.

Временное хранилище

ПоместитьВоВременноеХранилище(Значение, УникальныйИдентификатор) кладёт значение на сервер и возвращает адрес — обычную строку. Эту строку и передают между клиентом и сервером. На сервере данные читают через ПолучитьИзВременногоХранилища(Адрес).

Второй параметр — уникальный идентификатор формы. Пока форма открыта, данные живут в хранилище. Закрыли форму — хранилище очистилось. Без этого параметра значение удалится после завершения текущего серверного вызова.

Загрузка файла с компьютера пользователя

В платформе 8.3.18 и новее удобнее асинхронный метод ПоместитьФайлНаСерверАсинх. В более ранних релизах — НачатьПомещениеФайла с обработчиком оповещения.

&НаКлиенте
Асинх Процедура ЗагрузитьФайл(Команда)
	
	Параметры = Новый ПараметрыДиалогаПомещенияФайлов;
	Параметры.Фильтр = "Таблицы Excel (*.xlsx)|*.xlsx";
	
	Описание = Ждать ПоместитьФайлНаСерверАсинх(, , Параметры, УникальныйИдентификатор);
	Если Описание = Неопределено Тогда
		Возврат; // пользователь отменил выбор
	КонецЕсли;
	
	ОбработатьФайлНаСервере(Описание.Адрес, Описание.СсылкаНаФайл.Имя);
	
КонецПроцедуры

&НаСервереБезКонтекста
Процедура ОбработатьФайлНаСервере(Адрес, ИмяФайла)
	
	Данные = ПолучитьИзВременногоХранилища(Адрес);
	ИмяВременного = ПолучитьИмяВременногоФайла("xlsx");
	Данные.Записать(ИмяВременного);
	
	Таблица = Новый ТабличныйДокумент;
	Таблица.Прочитать(ИмяВременного);
	// разбор Таблица.Область(...) по строкам
	
	УдалитьФайлы(ИмяВременного);
	
КонецПроцедуры

Табличный документ

ТабличныйДокумент сериализуется и спокойно возвращается с сервера на клиент — так работают все печатные формы. Если документ огромный (сотни страниц отчёта), формируйте его на сервере прямо в реквизит формы типа ТабличныйДокумент, а не возвращайте функцией в отдельную переменную, чтобы не передавать его дважды.

Сравнение способов передачи

СпособКогда подходитОграничения
Параметры &НаСервереБезКонтекстанебольшие структуры, ссылки, числатолько сериализуемые типы
&НаСервере с данными формыизменение объекта на формеформа уходит на сервер целиком
Временное хранилищефайлы, двоичные данные, тяжёлые таблицыживёт, пока жива форма
HTTP-сервисвнешний клиент, другая системанужна публикация и авторизация

Как сделать HTTP-сервис для внешнего клиента

Когда клиент не 1С — сайт, приложение курьера, скрипт на Python, — данные принимают через HTTP-сервис. Это обычный REST-эндпоинт внутри конфигурации.

Создание сервиса в конфигураторе

  1. В дереве конфигурации откройте ветку Общие → HTTP-сервисы и добавьте новый.
  2. Задайте «Корневой URL», например orders.
  3. Добавьте шаблон URL, например /{id} или просто /.
  4. В шаблоне создайте методы: GET для выдачи, POST для приёма данных.
  5. Для каждого метода назначьте обработчик — платформа создаст функцию в модуле сервиса.

Обработчик, принимающий 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С:Предприятия. В окне клиента появится счётчик вызовов сервера и объёма переданных данных — проверьте, сколько вызовов даёт одна команда.

Можно ли передать на клиент объект документа?
Нет. ДокументОбъект существует только на сервере. На клиент передают ссылку, данные формы или структуру с нужными реквизитами.
Чем отличается `&НаСервере` от `&НаСервереБезКонтекста`?
Первая директива передаёт на сервер все данные формы и возвращает их обратно. Вторая передаёт только параметры, поэтому работает быстрее, но не видит реквизиты и элементы формы.
Какой максимальный размер файла можно загрузить через временное хранилище?
Жёсткого лимита платформы на уровне кода нет, ограничивают память рабочего процесса и настройки веб-сервера при работе через публикацию. Файлы в сотни мегабайт лучше передавать частями или через общий сетевой каталог.
Нужна ли отдельная лицензия для HTTP-сервиса?
Отдельной нет, но сеанс HTTP-сервиса занимает клиентскую лицензию, как и обычный пользователь. При частых обращениях включите в публикации повторное использование сеансов и учитывайте эти сеансы при расчёте лицензий.
Как вызвать HTTP-сервис без авторизации?
В публикации на веб-сервере можно задать пользователя по умолчанию, но для обмена с внешними системами так делать не стоит: любой, кто знает адрес, получит доступ к данным.