Как обеспечить корректную отправку JSON-тела при синхронных POST-запросах из 1С 7.7 через прокси-сервер?

Программист 1С v7.7 Торговля и дистрибуция
← На главную

При работе с устаревшими версиями программного обеспечения, такими как 1С 7.7, особенно при попытках интеграции с современными веб-сервисами, мы часто сталкиваемся с рядом специфических проблем. Одной из таких задач является выгрузка большого объема данных, например, списка товаров, на внешний сайт (в данном случае, WooCommerce) через JSON-API. Ситуация усложняется, когда сервер 1С работает на старой операционной системе (например, Windows Server 2003), которая не поддерживает актуальные версии протокола HTTPS, необходимые для безопасного соединения с современными веб-ресурсами. В такой конфигурации многие разработчики прибегают к использованию прокси-сервера, например, на базе Flask, чтобы обойти ограничения HTTPS и осуществить передачу данных.

Однако даже при наличии прокси-сервера могут возникать непредвиденные проблемы. В этой статье мы подробно рассмотрим случай, когда при выполнении серии последовательных (синхронных) POST-запросов из 1С 7.7, лишь последний запрос из серии отрабатывает корректно, содержа в себе тело JSON, тогда как все предыдущие запросы приходят на целевой сервер с пустым телом, вызывая ошибки HTTP 400 "Bad Request". Мы проанализируем возможные причины такого поведения и предложим комплексные решения.

Постановка проблемы: Отправка JSON из 1С 7.7 и ошибки 400

Наш сценарий предполагает следующую цепочку взаимодействия:

  1. Источник данных: 1С 7.7.
  2. Целевая система: WooCommerce (веб-сайт, ожидающий JSON).
  3. Промежуточное звено: Прокси-сервер на Flask.
  4. Операционная система: Windows Server 2003 (для сервера 1С).
  5. Проблема: При попытке выгрузить большой список товаров, разделенный на порции (например, по 100 штук), и отправке их серией POST-запросов, только последний запрос в серии содержит корректное JSON-тело. Остальные запросы приходят на прокси или целевой сервер с пустым телом, что приводит к HTTP-ошибкам 400.

Автор проблемы, столкнувшийся с описанной ситуацией, предоставил фрагменты логов, подтверждающие это: большинство запросов завершаются с кодом 400 и отсутствующим телом, тогда как один из запросов проходит успешно с кодом 200 и полным JSON-телом:


10.0.0.2 - - [29/Oct/2020 22:31:59] "POST /wp-json/wc/v3/products/batch/ HTTP/1.1" 400 -
db.test.ru
ck_693982c64ef09d3870cf5f8ec2b353e5eef37056
cs_062c1995b091e17f70e4087b9499264fb892edbb
OLOLO
... (много подобных записей с 400) ...
10.0.0.2 - - [29/Oct/2020 22:31:59] "POST /wp-json/wc/v3/products/batch/ HTTP/1.1" 400 -
db.test.ru
ck_693982c64ef09d3870cf5f8ec2b353e5eef37056
cs_062c1995b091e17f70e4087b9499264fb892edbb
OLOLO
{'create': [{'type': 'simple', 'regular_price': '4322.00', 'status': 'publish', 'tax_status': 'none', 'virtual': False, 'catalog_visibility': 'visible', 'featured': False, 'manage_stock': True, 'sku': '', 'downloadable': False, 'name': 'Абажур подвесной Паутина А-33'}]}
https://db.test.ru/wp-json/wc/v3/products/batch/?consumer_key=ck_693982c64ef09d3870cf5f8ec2b353e5eef37056&consumer_secret=cs_062c1995b091e17f70e4087b9499264fb892edbb&;
 with body {'create': [{'type': 'simple', 'regular_price': '4322.00', 'status': 'publish', 'tax_status': 'none', 'virtual': False, 'catalog_visibility': 'visible', 'featured': False, 'manage_stock': True, 'sku': '', 'downloadable': False, 'name': 'Абажур подвесной Паутина А-33'}]}
10.0.0.2 - - [29/Oct/2020 22:32:02] "POST /wp-json/wc/v3/products/batch/ HTTP/1.1" 200 -

Автор подозревает, что 1С 7.7 не дожидается завершения каждого запроса и создает некую очередь, что может приводить к потере тела запроса. Проблема усугубляется необходимостью обхода ограничений HTTPS на старой ОС Windows Server 2003, для чего используется Flask-прокси.

Анализ кода 1С 7.7 и объекта WinHttp.WinHttpRequest.5.1

Для выполнения HTTP-запросов в 1С 7.7 часто используется COM-объект WinHttp.WinHttpRequest.5.1. Автор предоставил следующий код функции, отвечающей за отправку POST-запросов:


Функция POST_Req(Сервер, JSON, СпЗаголовки, пСтатус) Экспорт
    
    WinHttp = СоздатьОбъект("WinHttp.WinHttpRequest.5.1");
    
    WinHttp.open("POST",Сервер); 
    
    Для Н=1 По СпЗаголовки.РазмерСписка() Цикл
        Значение="";
        Тип = СпЗаголовки.ПолучитьЗначение(Н,Значение);
        WinHttp.SetRequestHeader(СокрЛП(Тип), СокрЛП(Значение));
        
        Сообщить(Тип + СимволТабуляции + Значение);
    КонецЦикла;
    
    Попытка
        WinHttp.send(JSON); 
        пСтатус = WinHttp.status;
        Если WinHttp.status = 200 Тогда  
            Сообщить(" HTTP " + WinHttp.Status + " " + WinHttp.StatusText);
            Возврат WinHttp.ResponseText;
        Иначе
            Сообщить("Ошибка отправки запроса","!");
            Сообщить("HTTP " + WinHttp.Status + " " + WinHttp.StatusText);
            Возврат WinHttp.ResponseText;
            //Возврат 0;
        КонецЕсли;
    Исключение
        Сообщить(ОписаниеОшибки());
        Возврат 0;
    КонецПопытки;
КонецФункции

Давайте разберем ключевые аспекты этого кода:

  1. Создание объекта: WinHttp = СоздатьОбъект("WinHttp.WinHttpRequest.5.1"); – корректное создание объекта для HTTP-запросов.
  2. Открытие запроса: WinHttp.open("POST",Сервер); – здесь ключевым является отсутствие третьего параметра. По умолчанию, если этот параметр не указан, метод Open устанавливает запрос как синхронный. Это означает, что выполнение кода 1С должно приостановиться до получения ответа от сервера. Однако, несмотря на это, проблемы все равно возникают.
  3. Установка заголовков: Цикл по СпЗаголовки устанавливает HTTP-заголовки. Крайне важно убедиться, что среди этих заголовков присутствует Content-Type: application/json. Отсутствие или некорректное значение этого заголовка может привести к тому, что принимающий сервер не сможет правильно распарсить тело запроса, воспринимая его как пустое или неверное.
  4. Отправка запроса: WinHttp.send(JSON); – отправляет тело запроса.

Один из предложенных вариантов решения проблемы (сообщение 12) касался явного указания синхронности в методе Open:

Решение 1: Явное указание синхронности запроса

Попробуйте модифицировать строку WinHttp.open("POST",Сервер); на WinHttp.open("POST",Сервер,0);. Третий параметр метода Open принимает булево значение: True (или 1) для асинхронного запроса, False (или 0) для синхронного. Хотя по умолчанию отсутствие этого параметра подразумевает синхронность, явное указание 0 может устранить возможные неявные поведения или особенности объекта WinHttp в старых средах.


// ...
WinHttp = СоздатьОбъект("WinHttp.WinHttpRequest.5.1");
WinHttp.open("POST",Сервер,0); // Явно указываем синхронный режим
// ...

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

Возможные причины потери JSON-тела и пути решения

Проблема, когда тело запроса теряется для всех, кроме последнего запроса, является крайне необычной для синхронных HTTP-вызовов и может указывать на несколько точек отказа:

1. Проблемы на стороне 1С 7.7 и объекта WinHttp

  1. Некорректная очистка/переинициализация объекта: Хотя объект WinHttp.WinHttpRequest.5.1 создается на каждой итерации функции POST_Req, в некоторых случаях (особенно в очень старых или специфически настроенных средах) при быстром последовательном создании и использовании COM-объектов могут возникать проблемы с внутренним управлением ресурсами или состоянием объекта, что приводит к некорректной передаче тела запроса.
  2. Проблемы с кодировкой JSON: 1С 7.7 по умолчанию работает с кодировкой Windows-1251. Однако большинство современных веб-сервисов (включая WooCommerce) ожидают JSON в кодировке UTF-8. Если 1С отправляет JSON в Windows-1251, а прокси-сервер или целевой сервер пытаются парсить его как UTF-8, это может привести к ошибкам парсинга. В результате JSON может быть воспринят как невалидный или отсутствующий. Убедитесь, что JSON-строка, формируемая в 1С, корректно кодируется в UTF-8 перед отправкой. Это часто требует использования внешней компоненты или специальной функции для преобразования кодировки.
  3. Отсутствие или некорректный заголовок Content-Type: Как упомянуто ранее, критически важно, чтобы для каждого запроса был установлен заголовок Content-Type: application/json. Без него сервер не будет знать, как интерпретировать входящие данные.

2. Проблемы на стороне Flask-прокси

Flask-прокси играет ключевую роль в этой схеме. Если проблемы возникают после того, как запрос ушел из 1С, следующим подозреваемым является прокси:

  1. Потеря тела запроса прокси-сервером: Прокси, особенно самописный, может некорректно обрабатывать или пересылать тело POST-запроса. Если Flask-скрипт не считывает тело запроса от 1С должным образом для каждого входящего обращения, оно может быть пустым при пересылке на WooCommerce.
  2. Особенности обработки последовательных запросов в Flask: В некоторых редких случаях, при очень быстром поступлении последовательных запросов, Flask или его underlying WSGI-сервер (например, Gunicorn, Werkzeug) могут иметь особенности в обработке входных потоков данных. Это теоретически может приводить к тому, что тело запроса (например, request.data) может быть пустым для не последних запросов. Хотя это маловероятно для стандартной настройки, это стоит иметь в виду.

Методы отладки и диагностики

Чтобы точно определить, на каком этапе теряется JSON-тело, мы рекомендуем использовать следующие инструменты и подходы:

1. Использование сетевого сниффера (Fiddler)

Fiddler – это мощный инструмент для перехвата и анализа HTTP/HTTPS трафика. Мы можем использовать его для:

  1. Проверки исходящих запросов из 1С: Настройте Fiddler на машине, где работает 1С, чтобы он перехватывал запросы, идущие на Flask-прокси. Это покажет, отправляет ли 1С корректное JSON-тело в каждом запросе.
  2. Проверки входящих/исходящих запросов Flask-прокси: Установите Fiddler на сервере, где работает Flask-прокси, или настройте прокси на пересылку трафика через Fiddler. Это позволит увидеть, получает ли Flask корректные тела запросов от 1С и, что самое главное, пересылает ли он их дальше на WooCommerce с правильным телом и заголовками.

В Fiddler мы сможем увидеть не только заголовки, но и полное содержимое тела каждого POST-запроса, а также коды ответов.

2. Детальное логирование в Flask-прокси

В ваш Flask-скрипт необходимо добавить расширенное логирование. Это поможет нам отследить, что именно получает Flask от 1С и что он отправляет в WooCommerce:

  1. Логирование входящих запросов от 1С: Для каждого входящего POST-запроса от 1С записывайте в лог:
    • URL запроса.
    • HTTP-заголовки (особенно Content-Type).
    • Полное тело запроса (request.data или request.get_json()).

    Пример (упрощенный):

    
    from flask import Flask, request, jsonify
    import logging
    
    app = Flask(__name__)
    logging.basicConfig(level=logging.INFO)
    
    @app.route('/wp-json/wc/v3/products/batch/', methods=['POST'])
    def proxy_woocommerce():
        app.logger.info(f"Received POST request from 1C to {request.url}")
        app.logger.info(f"Headers: {request.headers}")
        try:
            # Попытка получить JSON, если Content-Type: application/json
            json_data = request.get_json(silent=True)
            if json_data:
                app.logger.info(f"JSON Body received from 1C: {json_data}")
            else:
                # Если это не JSON, или парсинг не удался, пробуем сырые данные
                raw_data = request.data
                if raw_data:
                    app.logger.info(f"Raw Body received from 1C (length {len(raw_data)}): {raw_data.decode('utf-8', errors='ignore')}")
                else:
                    app.logger.warning("No body received from 1C!")
        except Exception as e:
            app.logger.error(f"Error parsing JSON from 1C: {e}")
            app.logger.info(f"Raw Body that caused error: {request.data.decode('utf-8', errors='ignore')}")
            
        # ... Здесь логика пересылки на WooCommerce ...
        # ... Логирование ответа от WooCommerce ...
        return jsonify({"message": "Proxy processing..."}), 200
    

    Это позволит нам точно понять, получает ли Flask тело запроса от 1С для каждого из проблемных вызовов.

  2. Логирование исходящих запросов на WooCommerce: Перед тем как Flask отправит запрос на WooCommerce, запишите в лог те же данные: URL, заголовки и тело запроса. Это покажет, что именно Flask передает дальше.

Альтернативные решения и обходные пути

Если после тщательной отладки проблема с пустым телом запроса не решается или если существуют более фундаментальные ограничения, рассмотрите следующие альтернативные подходы:

1. Использование Msxml2.ServerXMLHTTP

Вместо WinHttp.WinHttpRequest.5.1, попробуйте использовать Msxml2.ServerXMLHTTP (или Msxml2.ServerXMLHTTP.6.0, если доступно). Этот COM-объект часто рекомендуется для серверных приложений и может обладать большей стабильностью и лучшей поддержкой HTTP-протоколов, чем WinHttp, особенно в устаревших средах. Он устанавливается вместе с пакетами MSXML (Microsoft XML Core Services).


Функция POST_Req_ServerXMLHTTP(Сервер, JSON, СпЗаголовки, пСтатус) Экспорт
    
    // Попробуйте ServerXMLHTTP.6.0, если доступно, иначе Msxml2.ServerXMLHTTP
    WinHttp = СоздатьОбъект("Msxml2.ServerXMLHTTP"); 
    
    WinHttp.open("POST",Сервер,0); // Явно синхронный режим
    
    Для Н=1 По СпЗаголовки.РазмерСписка() Цикл
        Значение="";
        Тип = СпЗаголовки.ПолучитьЗначение(Н,Значение);
        WinHttp.SetRequestHeader(СокрЛП(Тип), СокрЛП(Значение));
        Сообщить(Тип + СимволТабуляции + Значение);
    КонецЦикла;

    // Важно для JSON:
    WinHttp.SetRequestHeader("Content-Type", "application/json; charset=utf-8");
    
    Попытка
        WinHttp.send(JSON); 
        пСтатус = WinHttp.status;
        Если WinHttp.status = 200 Тогда  
            Сообщить(" HTTP " + WinHttp.Status + " " + WinHttp.StatusText);
            Возврат WinHttp.ResponseText;
        Иначе
            Сообщить("Ошибка отправки запроса","!");
            Сообщить("HTTP " + WinHttp.Status + " " + WinHttp.StatusText);
            Возврат WinHttp.ResponseText;
        КонецЕсли;
    Исключение
        Сообщить(ОписаниеОшибки());
        Возврат 0;
    КонецПопытки;
КонецФункции

Не забудьте явно установить заголовок Content-Type, включая указание кодировки UTF-8, так как ServerXMLHTTP может не делать этого автоматически.

2. Использование внешнего исполняемого файла cURL

Это очень надежный и гибкий подход. Вы можете использовать внешний инструмент командной строки cURL.exe, который доступен для Windows. Алгоритм будет следующим:

  1. В 1С 7.7 формируем JSON-строку.
  2. Сохраняем JSON-строку во временный файл (например, temp.json) на диске. Обязательно убедитесь, что файл сохраняется в кодировке UTF-8.
  3. Вызываем cURL.exe через КомандаСистемы(), передавая ему путь к JSON-файлу и все необходимые параметры (URL, заголовки, метод POST).
  4. cURL отправляет запрос и записывает ответ в другой временный файл, который 1С затем может прочитать.

Пример вызова cURL (концептуально):


// В 1С 7.7
ПутьКФайлуJSON = "C:\Temp\upload.json";
ТекстЗаписи = СоздатьОбъект("Текст");
ТекстЗаписи.ДобавитьСтроку(JSON);
ТекстЗаписи.Записать(ПутьКФайлуJSON, "UTF-8"); // Важно: сохраняем в UTF-8

URL = "https://db.test.ru/wp-json/wc/v3/products/batch/";
ConsumerKey = "ck_...";
ConsumerSecret = "cs_...";

Команда = "cURL -X POST """ + URL + "?"
        + "consumer_key=" + ConsumerKey + "&"
        + "consumer_secret=" + ConsumerSecret + """ "
        + "-H ""Content-Type: application/json"" "
        + "-d @""" + ПутьКФайлуJSON + """ "
        + "-o C:\Temp\response.txt";

КомандаСистемы(Команда);

// Затем читаем C:\Temp\response.txt для получения ответа

Этот подход полностью снимает с 1С проблемы с HTTP-стеком и HTTPS, перекладывая их на проверенный и актуальный инструмент.

3. Использование Stunnel для обхода HTTPS на Windows Server 2003

Stunnel — это прокси-сервер, который создает зашифрованный туннель. Если основная проблема заключается именно в поддержке HTTPS на Windows Server 2003, stunnel может быть отличным решением:

  1. Устанавливаем stunnel на Windows Server 2003.
  2. Настраиваем stunnel как клиент, который будет прослушивать определенный локальный порт (например, 127.0.0.1:8080) по HTTP.
  3. Когда 1С отправляет запрос на 127.0.0.1:8080 (по HTTP), stunnel перехватывает его, оборачивает в HTTPS-соединение и отправляет на целевой сервер (WooCommerce).
  4. Stunnel обрабатывает HTTPS-ответ от WooCommerce, расшифровывает его и отправляет обратно в 1С по HTTP.

Таким образом, 1С работает только с HTTP на локальной машине, а stunnel берет на себя всю сложность работы с HTTPS. Это упрощает логику 1С и позволяет избежать проблем с SSL/TLS на старой ОС.

4. Внешние компоненты для 1С 7.7

Для 1С 7.7 существуют разработанные сообществом внешние компоненты (add-ins), которые предоставляют более современные и надежные функции для работы с HTTP/HTTPS, JSON и UTF-8. Например, на сайте Infostart можно найти такие компоненты. Использование готовой компоненты может значительно сократить время на разработку и отладку, а также обеспечить совместимость с актуальными стандартами безопасности и обмена данными.

Резюме и рекомендации по устранению проблемы

Проблема потери JSON-тела при последовательных запросах из 1С 7.7 через Flask-прокси является комплексной и требует систематического подхода к отладке. Мы предлагаем действовать следующим образом:

  1. Начните с явного указания синхронности: Внесите изменение WinHttp.open("POST",Сервер,0); в ваш код 1С.
  2. Проверьте заголовок Content-Type: Убедитесь, что для каждого запроса корректно устанавливается заголовок Content-Type: application/json; charset=utf-8.
  3. Используйте Fiddler для трассировки: Это ваш основной инструмент для определения точки отказа. Посмотрите, покидает ли JSON-тело 1С и доходит ли оно до Flask-прокси.
  4. Детально логируйте Flask-прокси: Убедитесь, что Flask получает тело запроса от 1С и корректно передает его в WooCommerce.
  5. Рассмотрите кодировку JSON: Если JSON-тело доходит до Flask, но WooCommerce все равно возвращает 400, проблема может быть в кодировке. Убедитесь, что JSON формируется и отправляется в UTF-8.
  6. Если проблема не решается: Перейдите к альтернативным решениям. Попробуйте Msxml2.ServerXMLHTTP, затем cURL или stunnel. Использование cURL или stunnel часто является наиболее надежным способом обхода ограничений старых систем и обеспечения корректного взаимодействия с современными веб-сервисами.

Помните, что комплексная диагностика на каждом этапе взаимодействия (1С → Flask → WooCommerce) поможет локализовать проблему и применить наиболее эффективное решение. Успешная интеграция требует терпения и внимательности к деталям.

← На главную