Как передать массив данных из клиентского кода на сервер

· 10 мин

Содержание

Массив передаётся на сервер как обычный параметр серверного метода: объявляете процедуру или функцию с директивой &НаСервере (или &НаСервереБезКонтекста) и вызываете её из клиентского кода, подставив массив в аргумент. Платформа сама сериализует значение и доставит его в серверный контекст.

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

Вызов серверного метода и передача параметра

В управляемом приложении клиентский и серверный код живут в разных контекстах. Связывает их директива компиляции над процедурой или функцией:

  • 🖥 &НаКлиенте — выполняется на клиенте, имеет доступ к форме и её элементам;
  • 🗄 &НаСервере — выполняется на сервере, видит контекст формы;
  • ⚡ &НаСервереБезКонтекста — на сервере, но без данных формы; работает быстрее;
  • 🔁 &НаКлиентеНаСервереБезКонтекста — общий модуль-подобный код, доступен с обеих сторон.

Чтобы передать массив, достаточно вызвать серверный метод и подставить его в параметр:

&НаКлиенте
Процедура ОбработатьСписок(Команда)
    Товары = Новый Массив;
    Товары.Добавить("Молоко");
    Товары.Добавить("Хлеб");
    Товары.Добавить(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)обмен с внешней системойда, документирован
ЗаписатьJSONREST, веб-сервисыда, стандарт

Выбор способа зависит от того, куда едут данные:

Что делать с массивом после сборки на клиенте?

Большие объёмы данных и временное хранилище

В клиент-серверном режиме каждый вызов &НаСервере — это обращение по сети к серверу приложений. Отсюда два правила производительности.

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

// Плохо: сто обращений к серверу
Для Каждого Элемент Из Товары Цикл
    ОбработатьЭлементНаСервере(Элемент); // сетевой вызов внутри цикла
КонецЦикла;

// Хорошо: один обмен
ОбработатьВсеНаСервере(Товары);

Гоняйте только нужное. Контекстный вызов &НаСервере тащит на сервер и обратно данные формы. Если форма тяжёлая, а методу нужен лишь массив — ставьте &НаСервереБезКонтекста, и по сети поедут только параметры и результат.

Для действительно крупных массивов, картинок и файлов используют временное хранилище. Данные кладутся в него на сервере или клиенте, а дальше по сети передаётся только короткий адрес-строка:

&НаКлиенте
Процедура ЗагрузитьБольшойНабор()
    Адрес = ПоместитьВоВременноеХранилище(БольшойМассив, УникальныйИдентификатор);
    ОбработатьПоАдресу(Адрес);
КонецПроцедуры

&НаСервереБезКонтекста
Процедура ОбработатьПоАдресу(Адрес)
    Данные = ПолучитьИзВременногоХранилища(Адрес);
    // работа с данными
КонецПроцедуры

Второй параметр ПоместитьВоВременноеХранилище управляет временем жизни. Если указать УникальныйИдентификатор формы, значение живёт, пока открыта форма; если передать новый УникальныйИдентификатор — до конца серверного вызова. Это удобно для загрузки файлов: данные не раздувают контекст формы при каждом обмене.

Обработка ошибок при передаче

Две типичные ошибки возникают именно на границе клиент-сервер.

Первая — несериализуемое значение в массиве. В тексте ошибки вы увидите формулировку про невозможность передать значение типа или про сериализацию. Лечится проверкой содержимого: уберите из массива объекты, менеджеры и элементы формы, оставьте ссылки и примитивы.

Вторая — исключение в самом серверном методе. Чтобы оно не уронило всю операцию и чтобы понять причину, оборачивайте серверную логику в Попытка:

&НаСервереБезКонтекста
Функция ПосчитатьНаСервере(ВходнойМассив)
    Попытка
        // основная логика
        Возврат ВходнойМассив.Количество();
    Исключение
        ЗаписьЖурналаРегистрации("ОбработкаМассива",
            УровеньЖурналаРегистрации.Ошибка, , ,
            ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
        ВызватьИсключение;
    КонецПопытки;
КонецФункции

Текст исключения, возникшего на сервере, доходит до клиента, но детали (стек, строку) удобнее искать в журнале регистрации. Пишите туда ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()) — это даёт полную картину.

Для отладки передачи поставьте точку останова и на клиентском вызове, и на первой строке серверного метода. В отладчике видно, с каким содержимым массив ушёл и каким пришёл. Если отладка идёт по клиент-серверной базе, в настройках информационной базы в кластере должна быть разрешена отладка, а в конфигураторе — выбран нужный предмет отладки.

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

Проверка и безопасность входящих данных

Клиентскому коду доверять нельзя. Форму можно открыть, подменить значения и отправить на сервер массив с любым содержимым. Поэтому всё, что приходит на сервер, проверяют заново уже на сервере — независимо от того, что «должно было» прийти с клиента.

Минимальный набор проверок для серверного метода, принимающего массив:

  1. Тип параметра. Убедитесь, что пришёл именно массив: Если ТипЗнч(Входной) <> Тип("Массив") Тогда ВызватьИсключение "Ожидался массив"; КонецЕсли;.
  2. Границы. Проверьте количество элементов, прежде чем работать: пустой массив и массив на миллион строк — разные сценарии.
  3. Содержимое. Если ждёте ссылки на справочник, проверяйте тип каждого элемента и что ссылка не пустая.
  4. Права. Перед записью или чтением проверяйте права текущего пользователя — РольДоступна() или ПравоДоступа(). Серверный метод не должен выполнять действие, на которое у пользователя нет прав, даже если клиент его «попросил».

Правило простое: клиент подсказывает, что сделать, а решение принимает сервер. Любое важное условие — права, суммы, статусы документов — перепроверяйте в серверном контексте.

Отдельно про ЗначениеИзСтрокиВнутр и десериализацию: не восстанавливайте значения из строк, пришедших из недоверенного источника, не проверив их. Десериализация произвольной строки — это исполнение заложенной в неё структуры данных, и подсунутая строка может привести к неожиданному поведению.

Ещё один практический момент — размер. Клиент может прислать массив на сотни тысяч элементов, и серверный метод честно примет его в память. Для методов, открытых внешним потребителям (HTTP-сервисы, веб-сервисы), задавайте разумный предел по количеству и при превышении возвращайте понятную ошибку, а не падайте по нехватке памяти. Это часть той же логики: сервер решает, что он готов обработать, а не слепо выполняет присланное.

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