Как исправить ошибку 400 Bad Request при GET-запросе с кириллицей через WinHttp в 1С?

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

При интеграции 1С с современными веб-сервисами (например, Яндекс.Доставка, личные кабинеты маркетплейсов или государственные информационные системы) разработчики часто сталкиваются с необходимостью отправки HTTP-запросов. Одним из классических инструментов для этого в среде Windows является COM-объект WinHttp.WinHttpRequest.5.1. Однако при попытке отправить простой GET-запрос, содержащий параметры на кириллице (например, название города "Великие Луки" или артикул товара с русскими буквами), сервер неожиданно возвращает ошибку 400 Bad Request.

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

Выясняем причину: почему возникает ошибка 400 Bad Request?

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

Согласно международному стандарту RFC 3986, определяющему синтаксис URI (Uniform Resource Identifier), строка запроса может содержать только ограниченный набор символов латинского алфавита, цифры и несколько специальных знаков. Любые другие символы, включая национальные алфавиты (кириллицу, умлауты, иероглифы) и пробелы, должны подвергаться так называемому процентному кодированию (Percent-Encoding или URL-Encoding).

Процесс URL-кодирования состоит из двух этапов:

  1. Исходная строка переводится в последовательность байтов в определенной кодировке (в современном вебе стандартом де-факто является кодировка UTF-8).
  2. Каждый байт, значение которого выходит за рамки разрешенного ASCII-диапазона, представляется в виде трехсимвольного сочетания "%XX", где XX — шестнадцатеричное представление этого байта.

Когда мы передаем строку запроса вида "https://api.delivery.yandex.ru/location?term=(Великие Луки)" напрямую в метод Open объекта WinHttp, происходит следующее:

  1. Компонент WinHttp.WinHttpRequest.5.1 не выполняет автоматическое URL-кодирование передаваемого адреса.
  2. При отправке пакета в сеть компонент преобразует символы кириллицы в байты, используя системную кодировку по умолчанию (для русскоязычной операционной системы Windows это кодировка Windows-1251).
  3. В результате на веб-сервер (в случае с Яндекс.Доставкой это обычно высокопроизводительный сервер Nginx) приходит запрос, содержащий "сырые" байты в кодировке Windows-1251, разделенные пробелом.
  4. Поскольку сервер Nginx строго следует стандартам RFC и ожидает либо валидный URL-encoded UTF-8, либо возвращает ошибку при наличии недопустимых символов в URI, он мгновенно отвергает запрос и возвращает клиенту стандартную страницу с ошибкой 400 Bad Request (Your browser sent an invalid request). Запрос даже не доходит до бизнес-логики самого веб-сервиса.

Разбор исходного проблемного кода

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


WinHttp = Новый COMОбъект("WinHttp.WinHttpRequest.5.1");
WinHttp.Option(2, "utf-8"); // Установка кодировки ответа
WinHttp.SetTimeouts(60000, 60000, 60000, 60000);
Токен = "AgAAAAA1Zf1NAAXdyDRcIbXFMkuhsmB9GdSdaj0";

// Ошибка затаилась именно в этой строке: передается "сырой" текст на кириллице с пробелами
WinHttp.Open("GET", "https://api.delivery.yandex.ru/location?term=(Великие Луки)", 0);

WinHttp.setRequestHeader("Content-Type", "application/json");
WinHttp.SetRequestHeader("Authorization", "OAuth " + Токен);

WinHttp.Send();
WinHttp.WaitForResponse();
ОтветТекст = WinHttp.ResponseText();

В данном коде установка опции кодировки UTF-8 (WinHttp.Option(2, "utf-8")) влияет исключительно на интерпретацию тела *ответа* сервера, но никак не решает проблему кодирования параметров самого *запроса*. Для успешного выполнения запроса нам необходимо предварительно преобразовать строку параметров.

Решение №1. Использование встроенных функций платформы 1С

Если ваше прикладное решение работает под управлением платформы 1С:Предприятие версии 8.2.16 и выше (включая все релизы 8.3), самым изящным и правильным решением будет использование штатных функций глобального контекста для работы с URL.

Платформа 1С предоставляет готовую функцию КодироватьСтроку(), которая берет на себя всю рутину по преобразованию символов в нужную кодировку и их процентному представлению. Рассмотрим, как изменится наш код:


WinHttp = Новый COMОбъект("WinHttp.WinHttpRequest.5.1");
WinHttp.Option(2, "utf-8");
WinHttp.SetTimeouts(60000, 60000, 60000, 60000);
Токен = "AgAAAAA1Zf1NAAXdyDRcIbXFMkuhsmB9GdSdaj0";

// Исходные параметры запроса
ПараметрПоиска = "(Великие Луки)";

// Кодируем параметр в формат URL-encoded с использованием кодировки UTF-8
ЗакодированныйПараметр = КодироватьСтроку(ПараметрПоиска, СпособКодированияСтроки.КодировкаURL, "UTF-8");

// Формируем безопасный URI для отправки
АдресЗапроса = "https://api.delivery.yandex.ru/location?term=" + ЗакодированныйПараметр;

WinHttp.Open("GET", АдресЗапроса, 0);
WinHttp.setRequestHeader("Content-Type", "application/json");
WinHttp.SetRequestHeader("Authorization", "OAuth " + Токен);

WinHttp.Send();
WinHttp.WaitForResponse();
ОтветТекст = WinHttp.ResponseText();

При использовании функции КодироватьСтроку исходная строка "(Великие Луки)" превратится в строку вида "%28%D0%92%D0%B5%D0%BB%D0%B8%D0%BA%D0%B8%D0%B5%20%D0%9B%D1%83%D0%BA%D0%B8%29". Такой адрес полностью соответствует стандарту RFC 3986, легко интерпретируется веб-сервером Яндекса и возвращает успешный HTTP-статус 200 с телом ответа.

Решение №2. Реализация функции URL-кодирования для старых версий 1С (ручной разбор)

В случае, если вам приходится поддерживать крайне старые конфигурации на релизах платформы ниже 8.2.16, где функция КодироватьСтроку еще отсутствует, задачу преобразования придется решать собственными силами. Для этого напишем универсальную функцию, которая разбирает строку посимвольно, переводит ее в UTF-8 и кодирует в шестнадцатеричный формат.

Рассмотрим алгоритм такой функции подробнее:


Функция ПреобразоватьСтрокуВ_URL_UTF8(Знач ИсходнаяСтрока) Экспорт
    Результат = "";
    
    // Для преобразования символов в байты UTF-8 воспользуемся объектом "Adodb.Stream"
    Поток = Новый COMОбъект("Adodb.Stream");
    Поток.Type = 2; // Текстовый режим
    Поток.Charset = "utf-8";
    Поток.Open();
    Поток.WriteText(ИсходнаяСтрока);
    Поток.Position = 0;
    Поток.Type = 1; // Переключаем в бинарный режим для чтения байтов
    
    ДвоичныеДанные = Поток.Read();
    Поток.Close();
    
    // Перебираем полученные байты и кодируем их
    Для Индекс = 0 По ДвоичныеДанные.ВГраница() Цикл
        Байт = ДвоичныеДанные.Получить(Индекс);
        
        // Символы латиницы, цифры и некоторые спецсимволы не требуют кодирования
        Если (Байт >= 48 И Байт <= 57) ИЛИ // 0-9
             (Байт >= 65 И Байт <= 90) ИЛИ // A-Z
             (Байт >= 97 И Байт <= 122) ИЛИ // a-z
             Байт = 45 ИЛИ Байт = 95 ИЛИ Байт = 46 ИЛИ Байт = 126 Тогда // '-', '_', '.', '~'
            Результат = Результат + Символ(Байт);
        Иначе
            // Все остальные символы преобразуем в шестнадцатеричный вид с префиксом %
            Результат = Результат + "%" + СимволВHex(Байт);
        КонецЕсли;
    КонецЦикла;
    
    Возврат Результат;
КонецФункции

Функция СимволВHex(Знач Число)
    HexСимволы = "0123456789ABCDEF";
    СтаршийРазряд = Цел(Число / 16);
    МладшийРазряд = Число % 16;
    Возврат Сред(HexСимволы, СтаршийРазряд + 1, 1) + Сред(HexСимволы, МладшийРазряд + 1, 1);
КонецФункции

Эта функция полностью независима от версии платформы 1С и гарантирует корректное кодирование параметров GET-запроса на любом релизе, использующем операционную систему Windows.

Решение №3. Использование движка JavaScript через COM-объекты

Еще один популярный "обходной путь", который часто применяли разработчики до появления штатных функций кодирования, — делегирование задачи кодирования встроенному в Windows интерпретатору JavaScript. В JS есть готовая функция encodeURIComponent(), которая идеально справляется с URL-кодированием в UTF-8.

Для реализации этого метода мы можем использовать объект MSScriptControl.ScriptControl:


Скрипт = Новый COMОбъект("MSScriptControl.ScriptControl");
Скрипт.Language = "JScript";
ЗакодированныйПараметр = Скрипт.Eval("encodeURIComponent('" + ПараметрПоиска + "')");

Важное предупреждение: Данный способ отлично работает на стороне 32-битного клиентского приложения 1С. Однако, если ваш код выполняется на стороне 64-битном сервере 1С, вы неизбежно столкнетесь с критической ошибкой "Класс не зарегистрирован" (0x80040154). Это связано с тем, что Microsoft не выпускала 64-битной версии библиотеки MSScriptControl.

Если вам крайне необходимо использовать JavaScript именно на 64-битном сервере, воспользуйтесь альтернативным COM-объектом htmlfile, который лишен этого ограничения:


HTMLДокумент = Новый COMОбъект("htmlfile");
HTMLДокумент.write("<meta http-equiv='x-ua-compatible' content='IE=9'>");
ЗакодированныйПараметр = HTMLДокумент.parentWindow.encodeURIComponent(ПараметрПоиска);

Особенности авторизации в сервисах Яндекса

Как видно из обсуждения на форуме, автор проблемы также испытывал трудности с правильной передачей авторизационного токена. Для успешного взаимодействия с API Яндекс.Доставки (и большинством других API Яндекса) крайне важно строго соблюдать формат заголовка авторизации.

Яндекс использует протокол OAuth 2.0. Токен авторизации должен передаваться в заголовке запроса с именем Authorization. Значение этого заголовка должно иметь строго определенный вид:

Authorization: OAuth <ваш_токен>

Обратите внимание на следующие важные детали:

Почему в современных реалиях стоит полностью отказаться от WinHttp?

Хотя мы успешно решили проблему кодирования кириллицы при работе с WinHttp.WinHttpRequest.5.1, использование этого COM-объекта в современном коде на платформе 1С накладывает множество серьезных рисков и ограничений.

Рассмотрим ключевые причины, по которым следует переписать интеграцию на штатные механизмы 1С:

  1. Проблемы с безопасностью и протоколами TLS: Современные веб-сервисы массово отказываются от поддержки устаревших протоколов шифрования TLS 1.0 и 1.1, требуя использования TLS 1.2 и TLS 1.3. Объект WinHttp использует системные криптопровайдеры операционной системы Windows. Если ваш сервер 1С запущен на старой ОС (например, Windows Server 2008 или 2012), WinHttp по умолчанию попытается установить соединение по TLS 1.0, что вызовет мгновенный разрыв соединения со стороны веб-сервиса. Для решения этой проблемы приходится вручную вносить изменения в реестр операционной системы Windows, что не всегда возможно в корпоративной среде.
  2. Отсутствие кроссплатформенности: Архитектура современных ИТ-систем все чаще предполагает отказ от Windows в пользу операционных систем на базе Linux (например, Astra Linux, Ubuntu Server, RedOS) для серверной части 1С. Любой код, использующий COM-объекты (такие как WinHttp.WinHttpRequest, ActiveX или Adodb.Stream), на сервере под управлением Linux полностью перестанет работать, что потребует экстренного переписывания всего интеграционного модуля.
  3. Удобство поддержки и отладки: Штатные объекты 1С для работы с HTTP-запросами предоставляют гораздо более удобный интерфейс, строгую типизацию, встроенные средства обработки ошибок и кроссплатформенную кодировку "из коробки".

Современное решение: переход на штатные объекты HTTPСоединение и HTTPЗапрос

Давайте выполним полный рефакторинг нашего исходного кода и перепишем его с использованием современных, надежных и кроссплатформенных объектов платформы 1С — HTTPСоединение и HTTPЗапрос. Этот код будет одинаково успешно работать как на клиенте, так и на сервере, под управлением как Windows, так и Linux, без необходимости настройки системного реестра.


// Шаг 1. Задаем параметры подключения и авторизации
Сервер = "api.delivery.yandex.ru";
Токен = "AgAAAAA1Zf1NAAXdyDRcIbXFMkuhsmB9GdSdaj0";
ПараметрПоиска = "(Великие Луки)";

// Шаг 2. Кодируем параметры запроса для безопасной передачи в URI
ЗакодированныйТерм = КодироватьСтроку(ПараметрПоиска, СпособКодированияСтроки.КодировкаURL, "UTF-8");
ПутьРесурса = "/location?term=" + ЗакодированныйТерм;

// Шаг 3. Настраиваем защищенное соединение (SSL/TLS)
// Объект ЗащищенноеСоединениеOpenSSL автоматически определяет и использует наиболее актуальные протоколы TLS
ЗащищенноеСоединение = Новый ЗащищенноеСоединениеOpenSSL();

// Шаг 4. Создаем HTTP-соединение со стандартным таймаутом в 60 секунд
Соединение = Новый HTTPСоединение(Сервер, , , , , 60, ЗащищенноеСоединение);

// Шаг 5. Формируем запрос и заполняем заголовки
Запрос = Новый HTTPЗапрос(ПутьРесурса);
Запрос.Заголовки.Вставить("Content-Type", "application/json");
Запрос.Заголовки.Вставить("Authorization", "OAuth " + Токен);

Попытка
    // Шаг 6. Отправляем GET-запрос на сервер
    Ответ = Соединение.Получить(Запрос);
    
    // Шаг 7. Обрабатываем полученный ответ
    Если Ответ.КодСостояния = 200 Тогда
        // Сервер успешно вернул данные. Получаем тело ответа в кодировке UTF-8
        ОтветТекст = Ответ.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8);
        
        // Здесь может быть дальнейшая обработка JSON-данных через СредстваJSON
        // Выполним разбор полученной строки, если это необходимо
    Иначе
        ВызватьИсключение "Ошибка обращения к веб-сервису. Код состояния: " + Ответ.КодСостояния;
    КонецЕсли;
Исключение
    // Обработка непредвиденных ошибок (проблемы с сетью, недоступность сервера)
    ЗаписьЖурналаРегистрации("ИнтеграцияЯндексДоставка",
        УровеньЖурналаРегистрации.Ошибка, , , 
        "Произошла ошибка при отправке запроса: " + ОписаниеОшибки());
    ВызватьИсключение "Не удалось связаться с сервером Яндекс.Доставки.";
КонецПопытки;

Проанализируем преимущества обновленного кода:

Мы подробно разобрали причины возникновения ошибки "400 Bad Request" при работе с HTTP-запросами из 1С, рассмотрели тонкости URL-кодирования кириллических символов и предложили несколько вариантов решения проблемы. Настоятельно рекомендуем отдавать предпочтение штатным возможностям платформы 1С для построения долговечных, безопасных и легко сопровождаемых интеграционных решений.

← На главную