Как исправить ошибку 301 Redirect при получении данных с сайта по API в 1С:Предприятие 8.3?

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

Что такое 301 Redirect и почему Postman работает иначе

При попытке получить данные с внешнего сайта по API из 1С:Предприятие, мы можем столкнуться с HTTP-кодом состояния 301 Moved Permanently. Этот код не является ошибкой в прямом смысле, а представляет собой инструкцию от сервера, которая указывает, что запрошенный ресурс был окончательно перемещен на новый URL-адрес. Сервер ожидает, что клиент обновит свои запросы и в дальнейшем будет обращаться уже по новому адресу.

Мы часто замечаем, что такие запросы успешно выполняются в сторонних инструментах, например, в Postman, но завершаются ошибкой 301 при попытке реализации в коде 1С. Причина проста: большинство современных HTTP-клиентов, включая Postman и веб-браузеры, автоматически обрабатывают редиректы. Это означает, что при получении ответа 301, они немедленно извлекают новый URL из заголовка Location HTTP-ответа и автоматически выполняют следующий запрос по этому новому адресу, не сообщая пользователю о промежуточном перенаправлении.

Стандартные объекты 1С для работы с HTTP-запросами, такие как HTTPСоединение и HTTPЗапрос, по умолчанию не выполняют автоматическое следование редиректам для всех типов HTTP-статусов, включая 301. Поэтому, если сервер отвечает 301, мы должны явно обработать этот ответ в нашем коде: прочитать заголовок Location и сформировать новый запрос к указанному там URL.

Пошаговое решение проблемы 301 редиректа в 1С

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

  1. Анализируем URL и тип запроса: HTTP, HTTPS, www, параметры GET/POST.

    Прежде всего, нам нужно понять, какой именно запрос ожидает сервер и на какой адрес он нас перенаправляет. В нашем случае Postman успешно обрабатывал запрос по адресу, который мог начинаться с http://www.domain.com/api/v2/families/?categories__id=253&language=ru. Это, как правило, GET-запрос с параметрами, переданными непосредственно в URL (после знака вопроса).

    Когда мы получаем 301 ответ в 1С, заголовок Location часто содержит адрес, например, https://www.domain.com/api/v2/families/?categories__id=253&language=ru. Это сразу указывает на несколько важных изменений:

    • Переход с HTTP на HTTPS: Вместо незащищенного соединения сервер ожидает защищенное.
    • Возможное добавление "www": Доменное имя может быть преобразовано из "domain.com" в "www.domain.com".
    • Сохранение параметров GET: Параметры по-прежнему должны быть в URL.
  2. Правильно инициализируем HTTPСоединение.

    Объект HTTPСоединение предназначен для установки соединения с конкретным хостом. В его конструкторе мы указываем доменное имя (без протокола и пути) и, при необходимости, порт и параметры защищенного соединения.

    Если сервер перенаправляет нас на HTTPS, мы обязательно должны использовать Новый ЗащищенноеСоединениеOpenSSL(). При этом 1С сама понимает, что для HTTPS по умолчанию используется порт 443, и явно указывать его не обязательно, если он стандартный. Если целевой хост в ответе Location содержит "www", мы должны использовать его.

    Пример для инициализации, если мы знаем, что целевой адрес HTTPS и содержит "www":

    
    // Искомый домен, который будет использоваться в HTTPСоединение
    // Например, "www.ltcompany.com" или "ltcompany.com"
    // Начинаем с того, что нам известно или с чего Postman начинал успешно
    ПеременныйХост = "ltcompany.com"; 
    
    // Изначальный путь с параметрами
    ПолныйПутьЗапроса = "/api/v2/families/?categories__id=253&language=ru";
    
    // Создаем HTTPСоединение.
    // Если изначально мы предполагаем HTTP (как Postman),
    // то создаем без ЗащищенноеСоединениеOpenSSL.
    // Если сразу знаем, что нужен HTTPS, то включаем его.
    // В нашем случае, так как редирект был на HTTPS,
    // сразу создадим защищенное соединение.
    HTTPСоединение = Новый HTTPСоединение(ПеременныйХост, , , , , , Новый ЗащищенноеСоединениеOpenSSL());
    
  3. Формируем HTTPЗапрос с учетом типа метода и параметров.

    Если Postman выполнял GET-запрос, мы должны использовать метод Получить() объекта HTTPСоединение. Параметры запроса (например, ?categories__id=253&language=ru) должны быть частью пути, передаваемого в конструктор HTTPЗапроса.

    Ошибочно передавать GET-параметры в теле запроса с помощью HTTPЗапрос.УстановитьТелоИзСтроки() и затем вызывать HTTPСоединение.ОтправитьДляОбработки(), так как это, как правило, соответствует POST-запросу. Сервер, ожидающий GET, не сможет корректно обработать тело POST-запроса.

    
    // Для GET-запроса параметры включаются непосредственно в путь
    HTTPЗапрос = Новый HTTPЗапрос(ПолныйПутьЗапроса); // Например, "/api/v2/families/?categories__id=253&language=ru"
    
  4. Отправляем первый запрос и следуем редиректу.

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

    
    // Отправляем первый запрос, используя метод Получить для GET-запросов
    HTTPОтвет = HTTPСоединение.Получить(HTTPЗапрос);
    
    // Переменные для хранения информации о редиректе
    ПовторноеВыполнениеЗапроса = Ложь;
    НовыйХост = ПеременныйХост;
    НовыйПуть = ПолныйПутьЗапроса;
    НовоеЗащищенноеСоединениеТребуется = Неопределено;
    
    // Проверяем код состояния
    Если HTTPОтвет.КодСостояния = 301 Тогда
        // Извлекаем новый URL из заголовка Location
        НовыйАдресПолностью = HTTPОтвет.Заголовки.Получить("Location");
    
        // Проверяем, что НовыйАдресПолностью не пустой
        Если ЗначениеЗаполнено(НовыйАдресПолностью) Тогда
            ПовторноеВыполнениеЗапроса = Истина;
    
            // Разбираем НовыйАдресПолностью на протокол, хост и путь
            // Например, из "https://www.ltcompany.com/api/v2/families/?categories__id=253&language=ru"
            // нужно получить "https" (или "http"), "www.ltcompany.com" и "/api/v2/families/?categories__id=253&language=ru"
    
            Если СтрНачинаетсяС(НовыйАдресПолностью, "https://") Тогда
                НовоеЗащищенноеСоединениеТребуется = Истина;
                ВременныйАдрес = Сред(НовыйАдресПолностью, СтрДлина("https://") + 1);
            ИначеЕсли СтрНачинаетсяС(НовыйАдресПолностью, "http://") Тогда
                НовоеЗащищенноеСоединениеТребуется = Ложь;
                ВременныйАдрес = Сред(НовыйАдресПолностью, СтрДлина("http://") + 1);
            Иначе
                // Если протокол не указан или неизвестен, оставляем как есть,
                // или пытаемся угадать, например, из текущего соединения.
                // Для надежности лучше запросить HTTP/HTTPS явно.
                // В данном сценарии, так как Postman работал с HTTP, а Location был HTTPS,
                // мы ожидаем один из этих двух протоколов.
                Сообщить("Не удалось определить протокол нового адреса: " + НовыйАдресПолностью);
                ПовторноеВыполнениеЗапроса = Ложь; // Прерываем попытку редиректа
            КонецЕсли;
    
            Если ПовторноеВыполнениеЗапроса Тогда
                // Находим первый слеш после домена, чтобы отделить хост от пути
                ПозицияСлеша = Найти(ВременныйАдрес, "/");
                Если ПозицияСлеша > 0 Тогда
                    НовыйХост = Лев(ВременныйАдрес, ПозицияСлеша - 1);
                    НовыйПуть = Сред(ВременныйАдрес, ПозицияСлеша);
                Иначе
                    // Если слешей нет, весь адрес — это хост
                    НовыйХост = ВременныйАдрес;
                    НовыйПуть = "/"; // Или пустая строка, в зависимости от API
                КонецЕсли;
    
                // Создаем новое HTTPСоединение для редиректа
                Если НовоеЗащищенноеСоединениеТребуется Тогда
                    HTTPСоединение = Новый HTTPСоединение(НовыйХост, , , , , , Новый ЗащищенноеСоединениеOpenSSL());
                Иначе
                    HTTPСоединение = Новый HTTPСоединение(НовыйХост);
                КонецЕсли;
    
                // Формируем новый HTTPЗапрос
                HTTPЗапрос = Новый HTTPЗапрос(НовыйПуть);
    
                // Отправляем запрос снова
                HTTPОтвет = HTTPСоединение.Получить(HTTPЗапрос);
            КонецЕсли;
        Иначе
            Сообщить("Ошибка: Заголовок Location пуст при 301 редиректе.");
        КонецЕсли;
    КонецЕсли;
    
    // Теперь обрабатываем окончательный HTTPОтвет
    Сообщить("Код состояния: "+HTTPОтвет.КодСостояния);
    Сообщить("Тело ответа: "+HTTPОтвет.ПолучитьТелоКакСтроку());
    // ... дальнейшая обработка JSON ...
    

    Критическая ошибка: В ходе обсуждения пользователь столкнулся с проблемой, когда пытался обрезать новый адрес функцией Лев(НовыйАдрес,25). Это привело к обрезанию протокола и части домена, что, естественно, делало дальнейший запрос некорректным. Всегда используйте полный URL из заголовка Location и правильно парсите его на хост и путь для HTTPСоединения и HTTPЗапроса соответственно.

  5. Учитываем заголовки HTTP.

    В большинстве случаев для простых GET-запросов, где параметры находятся в URL, дополнительные заголовки не требуются. Однако, если сервер API требует специфические заголовки (например, Accept, User-Agent, или заголовки аутентификации вроде Authorization), их нужно добавить к объекту HTTPЗапрос.

    Заголовок Host обычно устанавливается автоматически на основе хоста HTTPСоединения. Попытка вручную установить его в HTTPЗапрос.Заголовки может привести к конфликтам и ошибкам 500 (Internal Server Error), как это было замечено в ходе обсуждения. Мы рекомендуем не устанавливать заголовок Host вручную, если нет особых требований. Если в Postman были "скрытые" заголовки (например, Accept-Encoding, Connection), 1С также может добавлять некоторые из них автоматически.

    
    // Пример добавления заголовков, если они необходимы
    // HTTPЗапрос.Заголовки.Вставить("Accept", "application/json");
    // HTTPЗапрос.Заголовки.Вставить("Authorization", "Bearer ВашТокен");
    

Важные нюансы и частые ошибки

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

← На главную