При работе с внешними REST API в 1С одной из наиболее частых задач является получение токена авторизации. Этот токен необходим для последующих запросов к защищенным ресурсам API. Однако в процессе реализации механизма получения токена разработчики часто сталкиваются с различными HTTP-ошибками, такими как 404 (Not Found) или 400 (Bad Request). В этой статье мы подробно разберем типичные ошибки и покажем, как их исправить, на примере получения токена с использованием объекта HTTPЗапрос в 1С.
Рассмотрим ситуацию, когда мы пытаемся получить токен, отправляя POST-запрос на специальный endpoint API, и получаем в ответ код состояния 404, а затем 400. Мы проанализируем причины этих ошибок и пошагово исправим код, чтобы успешно получить токен.
Прежде чем углубляться в код, важно понимать общие принципы получения токена OAuth 2.0, особенно при использовании типа предоставления "Resource Owner Password Credentials" (grant_type=password), который подразумевает передачу логина и пароля пользователя.
grant_type): Для получения токена мы должны указать, какой "грант" мы используем. В нашем случае это password. Этот параметр передается в теле запроса.username) и пароль (password) пользователя также должны быть переданы в теле запроса.Content-Type заголовка: Когда параметры (grant_type, username, password) передаются в теле запроса в формате key=value&key2=value2, заголовок Content-Type должен быть установлен как application/x-www-form-urlencoded.ключ1=значение1&ключ2=значение2.@, &, +, пробелы и другие, которые могут быть неправильно интерпретированы сервером, если не закодированы.Рассмотрим исходный код, который демонстрирует проблему получения токена:
&НаСервере
Функция Tokens()
ПараметрыURL = Новый Структура;
АдресЗапроса = ""; // Здесь будет адрес, но используется POSTЗапрос
// Исходное неверное формирование строки запроса для конструктора HTTPЗапрос
POSTЗапрос = "POST /api/token HTTP/1.1";
Хост = "portal-doctor.eleks.com";
// Переменные для формирования тела запроса (пока не используются)
username = Объект.Логин + "@@@" + Объект.installation;
grant_type = "password&username=" + Объект.Логин + "@@@" + Объект.installation + "&password="+Объект.Пароль;
// Инициализация HTTPСоединения
HTTPСоединение = новый HTTPСоединение(Хост, , , ,, , Новый ЗащищенноеСоединениеOpenSSL);
// Исходные заголовки, включающие параметры, которые должны быть в теле
Заголовки = Новый Соответствие;
Заголовки.Вставить("grant_type", "password");
Заголовки.Вставить("username", "api1CKryvyiRh@@@d8b5df4b-4c01-4eda-9302-2f8328af808a");
Заголовки.Вставить("password", "7#M0*0Iyl");
Заголовки.Вставить("Content-Type", "application/x-www-form-urlencoded");
Заголовки.Вставить("Host", Хост);
Заголовки.Вставить("Content-Length", "107"); // Примерная длина, может быть некорректной
HTTPЗапрос = Новый HTTPЗапрос(POSTЗапрос, Заголовки); // Здесь проблема с POSTЗапрос
HTTPОтвет = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);
Если HTTPОтвет.КодСостояния = 200 Тогда
Ответ = HTTPОтвет.ПолучитьТелоКакСтроку;
ЧтениеJSON = новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(Ответ);
Token = ПрочитатьJSON(ЧтениеJSON, Ложь);
Сообщить("Ура.");
Возврат Token;
Иначе
ВызватьИсключение "Произошла ошибка обращения к серверу," + "Токен не получен" + Символы.ПС + "Статус ответа сервера: " + HTTPОтвет.КодСостояния;
КонецЕсли;
КонецФункции
При выполнении этого кода мы получаем ошибку с кодом состояния 404 (Not Found). Выясним причину этой ошибки и исправим ее.
HTTPЗапрос.
Основная проблема 404 ошибки в исходном коде заключается в неправильном использовании первого параметра конструктора объекта HTTPЗапрос. Мы передаем туда полную строку HTTP-запроса: "POST /api/token HTTP/1.1". Однако, конструктор HTTPЗапрос ожидает в качестве первого параметра только путь к ресурсу (URL-path), т.е. "/api/token". Метод HTTP (POST), протокол (HTTP/1.1) и хост определяются другими параметрами и методом HTTPСоединение.ОтправитьДляОбработки().
Давайте исправим строку, определяющую ресурс:
// Было:
// POSTЗапрос = "POST /api/token HTTP/1.1";
// HTTPЗапрос = Новый HTTPЗапрос(POSTЗапрос, Заголовки);
// Стало:
ПутьКРесурсу = "/api/token"; // Только путь к ресурсу
HTTPЗапрос = Новый HTTPЗапрос(ПутьКРесурсу, Заголовки);
После этого исправления, повторно запустив код, мы заметим, что ошибка 404 исчезла. Однако теперь сервер возвращает другую ошибку: 400 (Bad Request).
Ошибка 400 (Bad Request) обычно указывает на то, что сам запрос сформирован неправильно, а не на проблемы с доступностью ресурса (как 404) или авторизацией (как 401). В нашем случае, наиболее вероятная причина 400 ошибки заключается в неправильном размещении параметров авторизации (grant_type, username, password).
Как мы уже выяснили, при использовании Content-Type: application/x-www-form-urlencoded, все эти параметры должны передаваться в теле запроса, а не в его заголовках. В исходном коде эти параметры ошибочно помещены в коллекцию Заголовки.
Нам необходимо убрать следующие строки из формирования заголовков:
// Эти строки должны быть удалены из заголовков:
// Заголовки.Вставить("grant_type", "password");
// Заголовки.Вставить("username", "api1CKryvyiRh@@@d8b5df4b-4c01-4eda-9302-2f8328af808a");
// Заголовки.Вставить("password", "7#M0*0Iyl");
Заголовок Content-Length также лучше удалить, так как он может быть некорректным, и сервер сам вычислит его. Если он необходим, его следует вычислять после формирования тела запроса.
УстановитьТелоИзСтроки.
Теперь, когда мы убрали параметры из заголовков, нам нужно передать их в теле запроса. Для этого мы будем использовать переменную grant_type, которая уже была определена в исходном коде, но не использовалась. Однако, ее определение также требует коррекции.
Критически важный момент: строка тела запроса должна начинаться с grant_type=, а не просто с password. То есть, каждый параметр должен иметь вид ключ=значение.
Корректное формирование строки тела запроса будет выглядеть так:
// Корректируем формирование строки для тела запроса
username_val = Объект.Логин + "@@@" + Объект.installation;
password_val = Объект.Пароль;
// Важно: каждый параметр должен быть в формате "ключ=значение"
ТелоЗапросаСтрока = "grant_type=password"
+ "&username=" + username_val
+ "&password=" + password_val;
// Передаем сформированную строку в тело запроса
HTTPЗапрос.УстановитьТелоИзСтроки(ТелоЗапросаСтрока);
Метод HTTPЗапрос.УстановитьТелоИзСтроки() по умолчанию отправляет данные в кодировке UTF-8, что в большинстве случаев является правильным выбором для REST API.
В нашем примере логин пользователя содержит символ @, а пароль может содержать другие специальные символы (#, *, & и т.д.). Эти символы, если они не закодированы, могут нарушить структуру URL-кодированного тела запроса. Например, символ & является разделителем параметров, а = - разделителем ключа и значения.
Для корректной передачи таких значений, мы должны их URL-кодировать. В 1С для этого используется функция КодироватьURL():
// Использование КодироватьURL для значений параметров
username_val = КодироватьURL(Объект.Логин + "@@@" + Объект.installation);
password_val = КодироватьURL(Объект.Пароль);
ТелоЗапросаСтрока = "grant_type=password"
+ "&username=" + username_val
+ "&password=" + password_val;
HTTPЗапрос.УстановитьТелоИзСтроки(ТелоЗапросаСтрока);
Это гарантирует, что все специальные символы будут правильно интерпретированы сервером и не приведут к ошибке 400.
Теперь, собрав все исправления, мы получаем рабочий код для получения токена. Обратите внимание, что мы также учли рекомендации по безопасному хранению учетных данных (логин и пароль), не вставляя их напрямую в код, а используя переменные Объект.Логин и Объект.Пароль.
&НаСервере
Функция Tokens()
// 1. Определение хоста и пути к ресурсу
Хост = "portal-doctor.eleks.com";
ПутьКРесурсу = "/api/token"; // Исправлено: только путь к ресурсу
// 2. Формирование тела запроса с URL-кодированием
// Предполагаем, что Объект.Логин, Объект.installation и Объект.Пароль содержат необходимые данные
ЛогинПользователя = Объект.Логин; // Например, "api1CKryvyiRh"
ИдентификаторУстановки = Объект.installation; // Например, "d8b5df4b-4c01-4eda-9302-2f8328af808a"
ПарольПользователя = Объект.Пароль; // Например, "7#M0*0Iyl"
// Собираем полное имя пользователя в требуемом формате, затем кодируем его
ПолноеИмяПользователя = ЛогинПользователя + "@@@" + ИдентификаторУстановки;
ЗакодированноеИмяПользователя = КодироватьURL(ПолноеИмяПользователя);
ЗакодированныйПароль = КодироватьURL(ПарольПользователя);
// Формируем строку тела запроса в формате application/x-www-form-urlencoded
// Каждый параметр должен быть в формате "ключ=значение"
ТелоЗапросаСтрока = "grant_type=password"
+ "&username=" + ЗакодированноеИмяПользователя
+ "&password=" + ЗакодированныйПароль;
// 3. Инициализация HTTPСоединения с защищенным соединением для HTTPS
HTTPСоединение = Новый HTTPСоединение(Хост, , , , , , Новый ЗащищенноеСоединениеOpenSSL);
// 4. Формирование заголовков запроса
Заголовки = Новый Соответствие;
// Важно: "Content-Type" должен быть "application/x-www-form-urlencoded"
Заголовки.Вставить("Content-Type", "application/x-www-form-urlencoded");
// Заголовок "Host" обычно автоматически добавляется HTTPСоединением, но можно явно указать
Заголовки.Вставить("Host", Хост);
// Заголовки "grant_type", "username", "password" удалены, так как они в теле
// Заголовок "Content-Length" также не нужен, если используется УстановитьТелоИзСтроки,
// так как 1С сама его рассчитает.
// 5. Создание HTTPЗапроса
HTTPЗапрос = Новый HTTPЗапрос(ПутьКРесурсу, Заголовки);
// 6. Установка тела запроса из сформированной строки
HTTPЗапрос.УстановитьТелоИзСтроки(ТелоЗапросаСтрока, КодировкаТекста.UTF8); // Явно указываем кодировку UTF8
// 7. Отправка запроса и обработка ответа
Попытка
HTTPОтвет = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);
Если HTTPОтвет.КодСостояния = 200 Тогда
ОтветJSONСтрока = HTTPОтвет.ПолучитьТелоКакСтроку();
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(ОтветJSONСтрока);
// Если ответ содержит JSON, то парсим его
РезультатЗапроса = ПрочитатьJSON(ЧтениеJSON, Ложь);
Сообщить("Успешное получение токена.");
Возврат РезультатЗапроса; // Возвращаем результат, который может быть структурой с токеном
Иначе
// В случае ошибки, выводим детальную информацию
ТекстОшибки = "Произошла ошибка обращения к серверу. Токен не получен." + Символы.ПС
+ "Статус ответа сервера: " + HTTPОтвет.КодСостояния + Символы.ПС
+ "Тело ответа сервера: " + HTTPОтвет.ПолучитьТелоКакСтроку(); // Получаем тело ответа для диагностики
ВызватьИсключение ТекстОшибки;
КонецЕсли;
Исключение
ВызватьИсключение "Ошибка при отправке HTTP-запроса: " + ОписаниеОшибки();
КонецПопытки;
Возврат Неопределено;
КонецФункции
При работе с REST API и возникновении проблем с получением токенов или других данных, мы рекомендуем следовать следующим практическим советам:
Postman является незаменимым инструментом для тестирования REST API. Мы всегда начинаем отладку с него. Он позволяет отправлять запросы и просматривать ответы в удобном формате. Если запрос работает в Postman с кодом 200, это означает, что сам API доступен, и вы используете правильные параметры, заголовки и тело запроса. В Postman есть функция "Code" (кнопка обычно справа), которая может сгенерировать пример кода запроса для различных языков и форматов, включая обычный HTTP. Это позволяет нам увидеть точное формирование запроса, включая URL-кодирование, и сравнить его с тем, что мы отправляем из 1С.
Каждое REST API имеет свою специфику. Endpoints, требуемые параметры, их формат, расположение (заголовки или тело запроса), а также тип авторизации могут отличаться. Документация — наш лучший друг в этом процессе.
Когда мы получаем ошибку (например, 400, 401, 500), мы всегда должны анализировать не только HTTPОтвет.КодСостояния, но и HTTPОтвет.ПолучитьТелоКакСтроку(). Сервер часто возвращает в теле ответа подробное описание ошибки в формате JSON или XML, что значительно ускоряет диагностику проблемы. Например, для ошибки 400, тело ответа может содержать сообщение типа {"error": "invalid_grant", "error_description": "Bad credentials"}.
ЗащищенноеСоединениеOpenSSL для HTTPS:
Подавляющее большинство REST API работают через HTTPS. Убедитесь, что вы правильно инициализируете HTTPСоединение с Новый ЗащищенноеСоединениеOpenSSL, как показано в примере кода. Без этого вы не сможете установить защищенное соединение.
Никогда не хардкодьте (жестко не прописывайте) логины и пароли в коде. Получайте их из настроек конфигурации, защищенных хранилищ или пользовательского ввода.
Мы снова подчеркиваем важность URL-кодирования для всех значений параметров, передаваемых в теле запроса (особенно для application/x-www-form-urlencoded) и в параметрах URL. Используйте функцию КодироватьURL() для любых строковых значений, которые могут содержать специальные символы.
Следуя этим рекомендациям и пошагово устраняя ошибки, вы сможете успешно интегрировать 1С с внешними REST API и получать необходимые токены авторизации. Для интерактивной проверки и отладки таких обращений внутри базы можно использовать инструмент для тестирования REST API на платформе 1С.