Как передать массив данных из клиентского кода на сервер
Содержание
Массив передаётся на сервер как обычный параметр серверного метода: объявляете процедуру или функцию с директивой &НаСервере (или &НаСервереБезКонтекста) и вызываете её из клиентского кода, подставив массив в аргумент. Платформа сама сериализует значение и доставит его в серверный контекст.
Подводных камней два: передать можно не любой тип, и в клиент-серверном варианте каждый вызов сервера — это обращение по сети. Ниже — рабочие приёмы, список допустимых типов, что делать с большими данными и почему входные данные на сервере обязательно проверять заново.
Вызов серверного метода и передача параметра
В управляемом приложении клиентский и серверный код живут в разных контекстах. Связывает их директива компиляции над процедурой или функцией:
- 🖥
&НаКлиенте— выполняется на клиенте, имеет доступ к форме и её элементам; - 🗄
&НаСервере— выполняется на сервере, видит контекст формы; - ⚡
&НаСервереБезКонтекста— на сервере, но без данных формы; работает быстрее; - 🔁
&НаКлиентеНаСервереБезКонтекста— общий модуль-подобный код, доступен с обеих сторон.
Чтобы передать массив, достаточно вызвать серверный метод и подставить его в параметр:
&НаКлиенте
Процедура ОбработатьСписок(Команда)
Товары = Новый Массив;
Товары.Добавить("Молоко");
Товары.Добавить("Хлеб");
Товары.Добавить(42);
Результат = ПосчитатьНаСервере(Товары);
Сообщить("Обработано позиций: " + Формат(Результат, "ЧГ=0"));
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПосчитатьНаСервере(ВходнойМассив)
Счётчик = 0;
Для Каждого Элемент Из ВходнойМассив Цикл
Счётчик = Счётчик + 1;
КонецЦикла;
Возврат Счётчик;
КонецФункции
При вызове платформа упаковывает массив, передаёт его на сервер, а обратно возвращает результат функции. На сервер уходит копия значения. Если серверный метод изменит переданный массив, изменения вернутся на клиент только при условии, что параметр не помечен ключевым словом Знач:
&НаКлиенте
Процедура ДополнитьСписок()
Товары = Новый Массив;
Товары.Добавить("Молоко");
ДобавитьПозицииНаСервере(Товары); // массив изменится и на клиенте
Сообщить("Теперь позиций: " + Формат(Товары.Количество(), "ЧГ=0"));
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ДобавитьПозицииНаСервере(Список) // без Знач — параметр возвращается
Список.Добавить("Хлеб");
Список.Добавить("Сахар");
КонецПроцедуры
Если тот же параметр объявить как Знач Список, серверные изменения на клиент не попадут — метод будет работать с локальной копией. Поэтому для «передать туда и забрать обратно» параметр оставляют без Знач, а для «только прочитать» — помечают Знач, чтобы случайно не затереть данные на клиенте.
Если серверной процедуре не нужны данные формы, ставьте &НаСервереБезКонтекста. Тогда платформа не гоняет туда-обратно весь контекст формы, и вызов становится ощутимо легче.
Если одна и та же обработка массива нужна в нескольких формах, не копируйте её в каждый модуль формы. Вынесите логику в экспортный метод серверного общего модуля (у модуля в свойствах включён флаг «Сервер», флаг «Клиент» выключен) и вызывайте её из &НаСервереБезКонтекста-метода формы. Массив при этом передаётся параметром точно так же — правила сериализации не меняются, зато код не дублируется.
Когда передача действительно идёт по сети
В клиент-серверной базе клиент и сервер приложений — разные процессы, часто на разных машинах, и каждый вызов &НаСервере — это сетевой обмен. В файловой базе клиент и «сервер» работают в одном процессе, сеть не задействована, но код всё равно обязан делить контексты директивами. Писать код нужно так, будто база всегда клиент-серверная: иначе рабочая база «на сервере» начнёт тормозить там, где файловая на разработке летала. Это главная причина экономить на числе серверных вызовов.
Какие типы можно передать, а какие нет
Через границу «клиент — сервер» проходят только сериализуемые значения. Массив попадает в список разрешённых, но важно, что лежит внутри него.
Передаются без проблем:
- примитивы —
Строка,Число,Дата,Булево,Неопределено; - коллекции —
Массив,Структура,Соответствие,СписокЗначений,ТаблицаЗначений,ДеревоЗначений; - ссылки на объекты (
СправочникСсылка,ДокументСсылкаи т. п.); УникальныйИдентификатор,ХранилищеЗначения,ДвоичныеДанные;- системные перечисления и фиксированные варианты коллекций.
Не передаются:
- ⛔ прикладные объекты —
СправочникОбъект,ДокументОбъект,РегистрНакопленияНаборЗаписей. Их на клиенте нет; передавайте ссылку, а объект получайте уже на сервере через.ПолучитьОбъект(); - ⛔ форма, её данные, элементы формы;
- ⛔ менеджеры и COM-объекты, файловые потоки.
Если в массиве окажется хотя бы один непередаваемый элемент, вызов завершится ошибкой сериализации, а не молча «потеряет» этот элемент. Поэтому перед отправкой полезно убедиться, что массив содержит только простые значения и ссылки.
Вложенность допускается: массив структур, где в структуре лежит ещё один массив, передаётся целиком, если все листья коллекции — сериализуемые типы.
Этим удобно пользоваться, когда нужно отправить на сервер или вернуть с него сразу несколько наборов. Вместо нескольких вызовов упакуйте массивы в одну структуру и передайте её одним параметром:
Пакет = Новый Структура;
Пакет.Вставить("Добавленные", МассивНовых);
Пакет.Вставить("Удалённые", МассивУдалённых);
Результат = ПрименитьИзмененияНаСервере(Пакет);
Серверная функция тем же способом возвращает структуру с несколькими массивами внутри — и всё это за один сетевой обмен. Учтите только, что некоторые методы платформы возвращают фиксированные коллекции (ФиксированныйМассив, ФиксированнаяСтруктура): их нельзя менять напрямую, нужно создать из них обычный Массив конструктором Новый Массив(ФиксированныйМассив).
Сериализация вручную: внутреннее представление, XDTO и JSON
Иногда массив нужно не просто передать в соседний метод, а сохранить в реквизит, положить в регистр сведений или отправить во внешнюю систему. Тогда его превращают в строку.
Внутреннее представление. Пара ЗначениеВСтрокуВнутр / ЗначениеИзСтрокиВнутр сериализует почти любое сериализуемое значение в строку и обратно:
СтрокаДанных = ЗначениеВСтрокуВнутр(Товары);
// ... хранение или передача ...
ВосстановленныйМассив = ЗначениеИзСтрокиВнутр(СтрокаДанных);
Работает быстро и в одну строчку. Но формат внутреннего представления — недокументированный и может меняться между версиями платформы. Для долговременного хранения внутреннее представление использовать не стоит: после обновления платформы старые строки могут не прочитаться. Для временной передачи в рамках одной версии — приемлемо.
Сериализатор XDTO. Документированный способ. Превращает значение в XML и обратно:
Запись = Новый ЗаписьXML;
Запись.УстановитьСтроку();
СериализаторXDTO.ЗаписатьXML(Запись, Товары);
СтрокаXML = Запись.Закрыть();
XML-представление стабильно, его можно отдать во внешний сервис и прочитать там же средствами XDTO. Платит за это объёмом: XML многословнее.
JSON. Если данные уходят в веб-сервис или REST, массив удобно сериализовать в JSON через ЗаписатьJSON и СериализаторXDTO, либо собрать структуру вручную. JSON читают почти все внешние системы — это стандарт обмена.
Коротко, чем способы отличаются:
| Способ | Куда уместен | Формат стабилен |
|---|---|---|
| Параметр метода | передать в соседний серверный метод | не хранится, вопрос снят |
ЗначениеВСтрокуВнутр | временная передача в рамках версии | нет, не документирован |
ХранилищеЗначения | хранение значения в реквизите/регистре | да, штатный контейнер |
СериализаторXDTO (XML) | обмен с внешней системой | да, документирован |
ЗаписатьJSON | REST, веб-сервисы | да, стандарт |
Выбор способа зависит от того, куда едут данные:
&НаСервере. Ручная сериализация не нужна — платформа всё сделает сама.ХранилищеЗначения (Новый ХранилищеЗначения(Товары)) — это документированный контейнер для хранения произвольных значений в базе.СериализаторXDTO или в JSON через ЗаписатьJSON — оба формата читаемы на стороне получателя.ПоместитьВоВременноеХранилище и передайте только адрес.Большие объёмы данных и временное хранилище
В клиент-серверном режиме каждый вызов &НаСервере — это обращение по сети к серверу приложений. Отсюда два правила производительности.
Не вызывайте сервер в цикле. Худший вариант — перебирать массив на клиенте и для каждого элемента дёргать серверную функцию. Сто элементов превратятся в сто сетевых обменов. Передайте весь массив одним вызовом и разберите его на сервере:
// Плохо: сто обращений к серверу
Для Каждого Элемент Из Товары Цикл
ОбработатьЭлементНаСервере(Элемент); // сетевой вызов внутри цикла
КонецЦикла;
// Хорошо: один обмен
ОбработатьВсеНаСервере(Товары);
Гоняйте только нужное. Контекстный вызов &НаСервере тащит на сервер и обратно данные формы. Если форма тяжёлая, а методу нужен лишь массив — ставьте &НаСервереБезКонтекста, и по сети поедут только параметры и результат.
Для действительно крупных массивов, картинок и файлов используют временное хранилище. Данные кладутся в него на сервере или клиенте, а дальше по сети передаётся только короткий адрес-строка:
&НаКлиенте
Процедура ЗагрузитьБольшойНабор()
Адрес = ПоместитьВоВременноеХранилище(БольшойМассив, УникальныйИдентификатор);
ОбработатьПоАдресу(Адрес);
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ОбработатьПоАдресу(Адрес)
Данные = ПолучитьИзВременногоХранилища(Адрес);
// работа с данными
КонецПроцедуры
Второй параметр ПоместитьВоВременноеХранилище управляет временем жизни. Если указать УникальныйИдентификатор формы, значение живёт, пока открыта форма; если передать новый УникальныйИдентификатор — до конца серверного вызова. Это удобно для загрузки файлов: данные не раздувают контекст формы при каждом обмене.
Обработка ошибок при передаче
Две типичные ошибки возникают именно на границе клиент-сервер.
Первая — несериализуемое значение в массиве. В тексте ошибки вы увидите формулировку про невозможность передать значение типа или про сериализацию. Лечится проверкой содержимого: уберите из массива объекты, менеджеры и элементы формы, оставьте ссылки и примитивы.
Вторая — исключение в самом серверном методе. Чтобы оно не уронило всю операцию и чтобы понять причину, оборачивайте серверную логику в Попытка:
&НаСервереБезКонтекста
Функция ПосчитатьНаСервере(ВходнойМассив)
Попытка
// основная логика
Возврат ВходнойМассив.Количество();
Исключение
ЗаписьЖурналаРегистрации("ОбработкаМассива",
УровеньЖурналаРегистрации.Ошибка, , ,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;
КонецФункции
Текст исключения, возникшего на сервере, доходит до клиента, но детали (стек, строку) удобнее искать в журнале регистрации. Пишите туда ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()) — это даёт полную картину.
Для отладки передачи поставьте точку останова и на клиентском вызове, и на первой строке серверного метода. В отладчике видно, с каким содержимым массив ушёл и каким пришёл. Если отладка идёт по клиент-серверной базе, в настройках информационной базы в кластере должна быть разрешена отладка, а в конфигураторе — выбран нужный предмет отладки.
Полезно сразу понимать, на чьей стороне случилась ошибка. Если сообщение появляется ещё до входа в серверный метод — проблема в сериализации параметров на клиенте, разбирайте содержимое массива. Если точка останова на первой строке сервера сработала, а исключение возникло дальше — причина в серверной логике, и её видно в журнале регистрации. Такое разделение экономит время: не приходится гадать, где именно «сломалось».
Проверка и безопасность входящих данных
Клиентскому коду доверять нельзя. Форму можно открыть, подменить значения и отправить на сервер массив с любым содержимым. Поэтому всё, что приходит на сервер, проверяют заново уже на сервере — независимо от того, что «должно было» прийти с клиента.
Минимальный набор проверок для серверного метода, принимающего массив:
- Тип параметра. Убедитесь, что пришёл именно массив:
Если ТипЗнч(Входной) <> Тип("Массив") Тогда ВызватьИсключение "Ожидался массив"; КонецЕсли;. - Границы. Проверьте количество элементов, прежде чем работать: пустой массив и массив на миллион строк — разные сценарии.
- Содержимое. Если ждёте ссылки на справочник, проверяйте тип каждого элемента и что ссылка не пустая.
- Права. Перед записью или чтением проверяйте права текущего пользователя —
РольДоступна()илиПравоДоступа(). Серверный метод не должен выполнять действие, на которое у пользователя нет прав, даже если клиент его «попросил».
Правило простое: клиент подсказывает, что сделать, а решение принимает сервер. Любое важное условие — права, суммы, статусы документов — перепроверяйте в серверном контексте.
Отдельно про ЗначениеИзСтрокиВнутр и десериализацию: не восстанавливайте значения из строк, пришедших из недоверенного источника, не проверив их. Десериализация произвольной строки — это исполнение заложенной в неё структуры данных, и подсунутая строка может привести к неожиданному поведению.
Ещё один практический момент — размер. Клиент может прислать массив на сотни тысяч элементов, и серверный метод честно примет его в память. Для методов, открытых внешним потребителям (HTTP-сервисы, веб-сервисы), задавайте разумный предел по количеству и при превышении возвращайте понятную ошибку, а не падайте по нехватке памяти. Это часть той же логики: сервер решает, что он готов обработать, а не слепо выполняет присланное.
Можно ли передать на сервер таблицу значений так же, как массив?
ТаблицаЗначений — сериализуемый тип, она передаётся параметром точно так же. Главное, чтобы в её ячейках не оказалось непередаваемых объектов — только примитивы и ссылки.Почему изменения массива на сервере не видны на клиенте?
Знач.Чем `&НаСервереБезКонтекста` лучше для передачи массива?
Как передать на сервер картинку или большой файл вместе с данными?
ПоместитьВоВременноеХранилище и передайте методу только адрес-строку. Так контекст формы не распухает, а по сети идёт минимум.Безопасно ли использовать `ЗначениеВСтрокуВнутр` для хранения массива в базе?
ХранилищеЗначения или сериализация XDTO: формат внутреннего представления не документирован и может измениться после обновления платформы.