Как передать файл с клиента на сервер и прочитать JSON без создания временного файла в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

Основная проблема, с которой столкнулся автор, — это получение пустых данных при попытке прочитать JSON из потока в памяти, а также ошибка "Непредвиденный символ" при переходе на работу со строками. Проанализируем эти ситуации по шагам.

Ошибка 1: Пустые данные при чтении из потока в памяти

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


// Неправильный код
ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресВХ);
Поток = Новый ПотокВПамяти(); 
Запись = Новый ЗаписьДанных(Поток, КодировкаТекста.UTF8);
Запись.Записать(ДвоичныеДанные);
Запись.Закрыть();

// Попытка чтения, которая вернет пустоту
ЧтениеJson = Новый ЧтениеJSON;    
ЧтениеJson.ОткрытьПоток(Поток, КодировкаТекста.UTF8); 
Пока ЧтениеJSON.Прочитать() Цикл
    // Этот код не выполнится
КонецЦикла;

В чем причина ошибки? Проблема кроется в работе с объектом ПотокВПамяти. Когда вы записываете в него данные, внутренний "указатель" (текущая позиция) смещается в конец потока. Соответственно, когда вы сразу после записи пытаетесь начать чтение, вы начинаете его с самого конца, где данных уже нет. Поэтому чтение закономерно возвращает пустоту.

Правильное решение — не создавать новый пустой поток, а получить поток для чтения непосредственно из объекта ДвоичныеДанные. Для этого существует специальный метод ОткрытьПотокДляЧтения(). Он возвращает уже готовый к чтению поток, позиция в котором установлена на начало.

Посмотрим на исправленный и корректный код:


&НаСервере
Функция ПрочитатьДанныеИзХранилища(АдресВХ)

    ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресВХ);
    Если ДвоичныеДанные = Неопределено Тогда
        Возврат Неопределено;
    КонецЕсли;

    // Создаем поток для чтения напрямую из двоичных данных
    ПотокЧтения = ДвоичныеДанные.ОткрытьПотокДляЧтения();

    // Открываем чтение JSON из этого потока
    ЧтениеJSON = Новый ЧтениеJSON;
    ЧтениеJSON.ОткрытьПоток(ПотокЧтения);

    // Здесь будет происходить парсинг данных
    Пока ЧтениеJSON.Прочитать() Цикл
        // ... ваша логика обработки JSON
    КонецЦикла;

    ЧтениеJSON.Закрыть();
    ПотокЧтения.Закрыть();

    // Возвращаем результат
    // Например, можно десериализовать объект:
    // Возврат ПрочитатьJSON(ЧтениеJSON, Истина);

КонецФункции

Этот подход является наиболее прямым и эффективным при работе с двоичными данными, полученными из временного хранилища. Для визуальной проверки структуры и содержимого входящих файлов можно применить обработку для просмотра данных файлов JSON в дереве значений.

Ошибка 2: "Непредвиденный символ при чтении JSON"

Пытаясь обойти первую проблему, автор решил передавать на сервер не двоичные данные, а сразу строку. На клиенте файл был прочитан, преобразован в строку и передан на сервер для парсинга.

Клиентский код:


// Код на клиенте, который может вызывать ошибку
ДвоичныеДанные = Новый ДвоичныеДанные(ПолноеИмяФайла); 
ЧтениеДанных = Новый ЧтениеДанных(ДвоичныеДанные);
СтрокаJSON = ЧтениеДанных.ПрочитатьСимволы();

Серверный код:


// Код на сервере
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(СтрокаJSON); // Здесь возникает ошибка

В чем причина ошибки? Ошибка "Непредвиденный символ" почти всегда указывает на то, что в начале строки JSON присутствуют "мусорные" символы, которые не являются частью стандарта (стандарт ожидает, что строка начнется с { или [).

Самая частая причина — это наличие метки порядка байтов (BOM). Это невидимый служебный символ (U+FEFF), который некоторые текстовые редакторы добавляют в начало файлов в кодировке UTF-8. Метод ПрочитатьСимволы() из ЧтениеДанных может включить этот символ в итоговую строку, а парсер ЧтениеJSON его не понимает и выдает ошибку.

Правильное решение — использовать на клиенте более надежный способ чтения текстовых файлов, который корректно обрабатывает кодировки и BOM. Для этого отлично подходит объект ТекстовыйДокумент.

Исправленный клиентский код для чтения файла в строку:


&НаКлиенте
Процедура ПрочитатьФайлИПередатьНаСервер(ПолноеИмяФайла)

    ТекстовыйДокумент = Новый ТекстовыйДокумент;
    // Метод Прочитать() сам определит кодировку и уберет BOM
    ТекстовыйДокумент.Прочитать(ПолноеИмяФайла, КодировкаТекста.UTF8);
    
    СтрокаJSON = ТекстовыйДокумент.ПолучитьТекст();
    
    // Передаем "чистую" строку на сервер
    ОбработатьСтрокуJSONНаСервере(СтрокаJSON);

КонецПроцедуры

После такой подготовки строки серверный код с использованием ЧтениеJSON.УстановитьСтроку() отработает без ошибок, так как получит текст без BOM и других служебных символов.

Рекомендации и современные подходы к передаче файлов

Хотя описанные выше методы решают проблему парсинга, стоит упомянуть более современные и удобные способы самой передачи файлов, особенно если речь идет о файлах размером больше нескольких мегабайт.

Использование асинхронной передачи файлов

Стандартный метод ПоместитьВоВременноеХранилище является синхронным. Это означает, что на время передачи файла интерфейс пользователя будет заблокирован ("зависнет"). Для улучшения пользовательского опыта следует использовать асинхронные методы.

Основной метод для этого — НачатьПомещениеФайлаНаСервер. Его ключевые преимущества:

  1. Асинхронность: Загрузка происходит в фоновом режиме, не блокируя интерфейс.
  2. Интерактивность: Платформа сама показывает стандартный диалог выбора файла и индикатор прогресса.
  3. Надежность: Механизм хорошо работает в разных клиентах, включая веб-клиент.

Посмотрим на пример использования:


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

&НаКлиенте
Процедура ПослеЗагрузкиФайла(Результат, ДополнительныеПараметры) Экспорт

    Если Результат = Неопределено Тогда
        // Пользователь отменил выбор файла
        Возврат;
    КонецЕсли;
    
    Если Результат.Ошибка Тогда
        // Произошла ошибка при загрузке
        ПоказатьПредупреждение(Неопределено, Результат.ТекстОшибки);
        Возврат;
    КонецЕсли;
    
    // Файл успешно помещен во временное хранилище на сервере
    // Вызываем серверную процедуру для его обработки
    ОбработатьЗагруженныйФайлНаСервере(Результат.Адрес);
    
КонецПроцедуры

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

Обработка очень больших файлов

Для файлов размером в сотни мегабайт или гигабайты даже асинхронная передача может быть не лучшим решением, так как временное хранилище потребляет оперативную память сервера. В таких случаях применяют более сложный подход: файл на клиенте разбивается на части (чанки) и передается на сервер последовательно, где эти части "склеиваются" в итоговый файл или обрабатываются потоково.

В заключение, правильный выбор метода зависит от задачи. Для небольших файлов можно передавать строку, предварительно корректно прочитав ее. Для файлов среднего размера лучше передавать ДвоичныеДанные и использовать асинхронный метод НачатьПомещениеФайлаНаСервер. А для очень больших объемов данных стоит рассмотреть потоковую обработку по частям. Для организации обмена между конфигурациями без написания собственных алгоритмов подойдёт обработка для выгрузки и загрузки данных в формате JSON.

← На главную