При попытке получить данные с внешнего сайта по 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.
Чтобы успешно интегрироваться с API, который возвращает 301 редирект, мы должны тщательно проанализировать поведение сервера и корректно реализовать логику следования за перенаправлением. Рассмотрим шаги, которые помогут нам в этом.
Прежде всего, нам нужно понять, какой именно запрос ожидает сервер и на какой адрес он нас перенаправляет. В нашем случае 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, мы обязательно должны использовать Новый ЗащищенноеСоединение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());
Если 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"
После того как мы правильно сформировали соединение и запрос, мы отправляем его и анализируем ответ.
// Отправляем первый запрос, используя метод Получить для 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Запроса соответственно.
В большинстве случаев для простых 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 ВашТокен");
Помните, что в конструктор HTTPСоединение мы передаем только доменное имя (например, "www.ltcompany.com" или "ltcompany.com"), без протокола ("http://" или "https://") и без пути ("/api/..."). Протокол определяется наличием или отсутствием Новый ЗащищенноеСоединениеOpenSSL() в качестве седьмого параметра.
Путь и GET-параметры (например, "/api/v2/families/?categories__id=253&language=ru") передаются в конструктор HTTPЗапроса.
Для GET-запросов параметры передаются в URL. Используйте метод HTTPСоединение.Получить(HTTPЗапрос). Тело запроса для GET обычно не требуется, и его наличие может быть проигнорировано или вызвать ошибку на сервере.
Для POST-запросов параметры или данные (например, JSON) передаются в теле запроса с помощью HTTPЗапрос.УстановитьТелоИзСтроки(), и используется метод HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос).
Не путайте эти подходы! Если сервер ожидает GET, а мы отправляем POST с телом, это приведет к ошибкам.
ЗащищенноеСоединениеOpenSSL:
Если целевой API использует HTTPS (что очень распространено), обязательно используйте Новый ЗащищенноеСоединениеOpenSSL() в качестве последнего параметра конструктора HTTPСоединения. Отсутствие этого параметра приведет к ошибкам соединения или постоянным 301 редиректам, если сервер ожидает HTTPS.
Когда Postman работает, а 1С — нет, незаменимым инструментом для отладки является сниффер сетевого трафика, такой как Fiddler или Wireshark. Они позволяют перехватить и детально проанализировать HTTP/HTTPS запросы и ответы, отправляемые из Postman и из 1С. Сравнивая эти трафики, вы сможете выявить малейшие различия в заголовках, телах запросов, протоколах или URL-адресах, которые могут быть причиной проблемы. Это один из самых эффективных способов диагностики подобных ситуаций.
Если после всех попыток проблема сохраняется, мы рекомендуем связаться с разработчиками API. Уточните следующие моменты:
Content-Type)?Последовательно применяя эти шаги и внимательно проверяя все составляющие запроса, мы сможем успешно преодолеть 301 редирект и получить данные с API в 1С:Предприятие. Всегда помните о различиях в поведении автоматических клиентов и ручной реализации, и тщательно анализируйте ответы сервера.