Как программно скачать XML-файл из СБИС в 1С, если возникает ошибка 302/307 (редирект)?

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

При работе с API СБИС часто возникает задача загрузки документов, например, XML-файлов с кодами маркировки. API возвращает ссылку на файл, но при попытке скачать его программно с помощью стандартных средств 1С, система может возвращать коды состояния 302 или 307. Эти коды означают временное или постоянное перенаправление (редирект), которое стандартный объект HTTPСоединение не всегда обрабатывает корректно без дополнительных настроек.

В этой инструкции мы подробно разберем, почему возникает такая ситуация и как правильно настроить HTTP-запрос в 1С для успешной загрузки файла из СБИС. Новичкам рекомендуем курс по работе с HTTP-запросами.

Анализ проблемы: почему возникает редирект?

Изначальная проблема, с которой сталкиваются разработчики, заключается в попытке скачать файл напрямую с сервера, указанного в ссылке, например, disk.sbis.ru. Это приводит к ошибке, и причина кроется в архитектуре серверов СБИС.

Рассмотрим ключевые серверы и их роли:

Когда вы получаете ссылку на файл, она ведет на файловый сервер disk.sbis.ru. Однако этот сервер не занимается проверкой прав доступа и авторизацией. При обращении к нему он перенаправляет ваш запрос на основной сервер online.sbis.ru, который уже проверяет вашу сессию (по специальному заголовку) и решает, можно ли вам отдать этот файл. Именно этот механизм перенаправления и вызывает коды 302/307.

Чтобы избежать этой цепочки редиректов и проблем с их обработкой в 1С, правильный подход — сразу обращаться к основному серверу online.sbis.ru, передавая ему путь к файлу и все необходимые данные для авторизации.

Шаг 1: Формирование правильного HTTP-запроса

Для успешной загрузки файла необходимо сформировать GET-запрос, который будет содержать все необходимые данные для сервера. В этом поможет Post1С - аналог Postman на платформе 1С. Разберем по шагам, что для этого нужно.

  1. Использовать правильный сервер. В объекте HTTPСоединение в качестве сервера указываем online.sbis.ru.
  2. Выбрать верный метод запроса. Для скачивания файлов используется метод GET. В 1С это соответствует методу Получить() у объекта HTTPСоединение. Использование метода ОтправитьДляОбработки() (POST) в данном случае некорректно.
  3. Передать обязательные заголовки. Это самый важный пункт. Без корректных заголовков сервер не сможет вас аутентифицировать и отклонит запрос.
    • <strong>X-SBISSessionID</strong> — ключевой заголовок, содержащий идентификатор вашей активной сессии. Его нужно получить на этапе авторизации в СБИС API и передавать с каждым запросом (удобно тестировать через обработку для тестирования http сервисов).
    • <strong>User-Agent</strong> — рекомендуемый заголовок, который идентифицирует ваше приложение. Например, можно указать "1С".
    • <strong>Content-Type</strong> — хотя у GET-запроса нет тела, для API СБИС является стандартной практикой указывать значение application/json-rpc;charset=utf-8.

Шаг 2: Реализация на 1С и получение файла

Теперь посмотрим на пример кода, который реализует описанный выше подход. Этот код успешно скачивает файл во временное хранилище.


// Предполагается, что переменная "Сессия" уже содержит действующий ID сессии СБИС,
// а "ДлиннаяСтрокаИзСсылки" - это путь к файлу, полученный от API.

СерверИсточник = "online.sbis.ru";
// Получаем путь к файлу из полной ссылки, которую вернул СБИС
// Например, если ссылка "https://disk.sbis.ru/disk/api/v1/длинная_строка"
// то нам нужна только часть "/disk/api/v1/длинная_строка"
АдресРесурса = "/disk/api/v1/длинная_длинная_строка"; 

ВременноеИмяВходящегоФайла = ПолучитьИмяВременногоФайла("xml");

// Создаем HTTP-запрос с методом GET (по умолчанию)
Адрес = Новый HTTPЗапрос(АдресРесурса);

// Устанавливаем критически важные заголовки
Адрес.Заголовки.Вставить("Content-Type", "application/json-rpc;charset=utf-8");
Адрес.Заголовки.Вставить("User-Agent", "1С");
Адрес.Заголовки.Вставить("X-SBISSessionID", Сессия); // ID вашей сессии

// Устанавливаем соединение с основным сервером СБИС
НТТР = Новый HTTPСоединение(СерверИсточник, 443, , , , 15, Новый ЗащищенноеСоединениеOpenSSL);

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

// Проверяем, что сервер ответил успешно
Если Ответ.КодСостояния = 200 Тогда
    Сообщить("Файл успешно загружен во временный каталог: " + ВременноеИмяВходящегоФайла);
    // Дальнейшая обработка файла...
Иначе
    Сообщить("Сервер вернул ошибку. Код состояния: " + Ответ.КодСостояния);
    // Для отладки можно получить тело ответа, если оно есть
    ТекстОшибки = ПолучитьТекстИзДвоичныхДанных(Ответ.ПолучитьТелоКакДвоичныеДанные());
    Сообщить("Тело ответа: " + ТекстОшибки);
КонецЕсли;

Шаг 3: Извлечение имени файла и перемещение

После того как файл успешно скачан во временный каталог, ему присваивается системное имя. Однако СБИС передает оригинальное имя файла в заголовке ответа content-disposition. Его можно извлечь и использовать, чтобы сохранить файл под его настоящим именем.

Дополним наш код логикой получения имени файла и его перемещения в постоянный каталог.


Если Ответ.КодСостояния = 200 Тогда
    // Получаем заголовок с информацией о файле
    ЗаголовокКонтент = Ответ.Заголовки["content-disposition"];
    
    ИмяФайлаСБИС = "";
    // Пример строки в заголовке: attachment; filename*="UTF-8''имя_файла.xml"
    // Парсим строку, чтобы извлечь имя файла. Способ может отличаться
    // в зависимости от точного формата заголовка.
    // В данном случае имя файла находится в кавычках.
    МассивСтрок = СтрРазделить(ЗаголовокКонтент, "'", Ложь);
    Если МассивСтрок.Количество() > 1 Тогда
        ИмяФайлаСБИС = МассивСтрок[1];
    КонецЕсли;

    Если ПустаяСтрока(ИмяФайлаСБИС) Тогда
        ИмяФайлаСБИС = "sbis_document.xml"; // Имя по умолчанию, если не удалось извлечь
    КонецЕсли;

    ПутьНазначения = "C:\SBIS_Downloads\"; // Укажите ваш каталог
    ПолноеИмяФайла = ПутьНазначения + ИмяФайлаСБИС;

    // Перемещаем файл из временного каталога в постоянный с правильным именем
    ПереместитьФайл(ВременноеИмяВходящегоФайла, ПолноеИмяФайла);
    
    Сообщить("Файл сохранен как: " + ПолноеИмяФайла);
КонецЕсли;

Бонус: Разбор полученного XML-файла с кодами маркировки

Часто конечной целью загрузки XML-файла является извлечение из него данных. В теме на форуме был приведен полезный пример разбора XML-документа от СБИС для получения кодов маркировки. Рассмотрим этот код.

В этом примере для разбора документа используются объекты ЧтениеXML и ФабрикаXDTO, что является эффективным способом работы с XML известной структуры.


// Предполагается, что ВременноеИмяВходящегоФайла содержит путь к нашему XML

ТЧСбисНом = Новый ТаблицаЗначений;
ТЧСбисНом.Колонки.Добавить("НаименованиеСбис");
ТЧСбисНом.Колонки.Добавить("КодСбис");
ТЧСбисНом.Колонки.Добавить("КодМаркировки");

ЧтениеXMLФайл = Новый ЧтениеXML;
ЧтениеXMLФайл.ОткрытьФайл(ВременноеИмяВходящегоФайла);

НаименованиеСбис = "";
КодСбис = "";
КоличествоСбис = 0;

Пока ЧтениеXMLФайл.Прочитать() Цикл
    Если ЧтениеXMLФайл.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
        Поле = ЧтениеXMLФайл.Имя;
        
        // Находим корневой узел таблицы с товарами
        Если Поле = "ТаблСчФакт" Тогда
            Пока Истина Цикл
                Попытка
                    Фабрика = Новый ФабрикаXDTO;
                    // Читаем узел с товаром в объект XDTO
                    ОбXDTO = Фабрика.ПрочитатьXML(ЧтениеXMLФайл);
                    
                    Попытка // Первый вариант структуры XML
                        НаименованиеСбис = ОбXDTO.СведТов.НаимТов;
                        КоличествоСбис = Число(ОбXDTO.СведТов.КолТов);
                        КодСбис = ОбXDTO.СведТов.ДопСведТов.КодТов;
                        
                        // Перебираем коды маркировки для одного товара
                        Пока КоличествоСбис > 0 Цикл
                            НовСтр = ТЧСбисНом.Добавить();
                            НовСтр.НаименованиеСбис = НаименованиеСбис;
                            НовСтр.КодСбис = КодСбис;
                            НовСтр.КодМаркировки = ОбXDTO.СведТов.ДопСведТов.НомСредИдентТов.КИЗ[КоличествоСбис - 1];
                            КоличествоСбис = КоличествоСбис - 1;
                        КонецЦикла;
                        
                    Исключение // Второй (альтернативный) вариант структуры XML
                        Для Каждого СтрДТ Из ОбXDTO.СведТов Цикл
                            НаименованиеСбис = СтрДТ.НаимТов;
                            КоличествоСбис = Число(СтрДТ.КолТов);
                            КодСбис = СтрДТ.ДопСведТов.КодТов;
                            
                            Пока КоличествоСбис > 0 Цикл
                                НовСтр = ТЧСбисНом.Добавить();
                                НовСтр.НаименованиеСбис = НаименованиеСбис;
                                НовСтр.КодСбис = КодСбис;
                                НовСтр.КодМаркировки = СтрДТ.ДопСведТов.НомСредИдентТов.КИЗ[КоличествоСбис - 1];
                                КоличествоСбис = КоличествоСбис - 1;
                            КонецЦикла;
                        КонецЦикла;
                    КонецПопытки;
                    
                Исключение
                    // Если больше не можем прочитать узлы, выходим из цикла
                    Прервать;
                КонецПопытки;
            КонецЦикла;
            
            // После обработки таблицы товаров выходим из основного цикла
            Прервать;
        КонецЕсли;
    КонецЕсли;
КонецЦикла;

ЧтениеXMLФайл.Закрыть();

Этот код последовательно читает XML, находит таблицу с товарами и для каждой товарной позиции извлекает наименование, код и все связанные с ней коды маркировки (КИЗ), помещая их в таблицу значений.

← На главную