Когда мы разрабатываем или поддерживаем веб-сервисы на платформе 1С:Предприятие, мы можем столкнуться с ситуацией, когда входящий запрос содержит некорректные данные. Например, если веб-сервис ожидает числовой параметр, а клиент передает текстовую строку. В таких случаях на клиенте может появиться ошибка типа "Несоответствие типов (параметр номер '1')", а в журнале регистрации 1С мы увидим лишь записи о начале и завершении сеанса, но не сможем найти информацию о самом некорректном запросе или ошибке выполнения нашей процедуры веб-сервиса. Более того, даже если мы попробуем записать что-либо в журнал регистрации в самой первой строке процедуры веб-сервиса, эта запись не появится, что указывает на то, что до кода нашей процедуры дело просто не доходит.
Давайте выясним причину такого поведения. Проблема заключается в том, что валидация входящих данных для веб-сервиса происходит на более низком уровне, чем выполнение кода нашей процедуры в 1С. Взаимодействие с веб-сервисами 1С основывается на стандарте XDTO (XML Data Type Objects). Описание веб-сервиса содержит информацию о типах данных, которые ожидаются для каждого параметра. Когда веб-сервер (Apache или IIS) получает запрос к веб-сервису 1С, модуль расширения 1С для веб-сервера пытается десериализовать входящий XML-запрос и привести его к типам, описанным в XDTO-пакете нашего веб-сервиса. Если на этом этапе происходит несоответствие типов (например, в теге, который должен содержать число, приходит текст), возникает ошибка валидации XDTO. Эта ошибка происходит до того, как запрос будет передан для обработки в ядро 1С:Предприятия. Таким образом, код нашей процедуры веб-сервиса в 1С просто не запускается, и, соответственно, у нас нет возможности средствами 1С перехватить и залогировать сам некорректный запрос или ошибку валидации.
Именно поэтому стандартный журнал регистрации 1С остается пустым в отношении деталей ошибки. Ответ с ошибкой "HTTP/1.1 500 Internal server error" и сообщением о несоответствии типов данных XDTO формируется на уровне веб-сервера и отправляется клиенту. Мы видим, что ошибка генерируется еще до того, как запрос попадает в среду выполнения 1С. Поэтому, чтобы получить текст некорректного запроса, нам необходимо использовать механизмы, работающие на уровне веб-сервера или выше.
Перед нами стоит задача – перехватить и зафиксировать текст входящего запроса, даже если он содержит ошибки валидации и не доходит до нашего кода в 1С. Рассмотрим несколько подходов, которые помогут нам в этом:
Каждый из этих методов имеет свои особенности, преимущества и недостатки, которые мы подробно разберем.
Поскольку ошибка валидации XDTO возникает на уровне веб-сервера, логично предположить, что именно веб-сервер может предоставить нам необходимую информацию. Мы можем настроить веб-сервер таким образом, чтобы он логировал полное тело входящего HTTP-запроса, включая его содержимое. Это позволит нам увидеть, какой именно запрос привел к ошибке, даже если он не был обработан 1С.
Для Apache HTTP Server существуют несколько способов логирования тела запроса:
mod_dumpio:
error.log.mod_dumpio скомпилирован и загружен (например, в файле httpd.conf должна быть строка LoadModule dumpio_module modules/mod_dumpio.so).httpd.conf или в файле виртуального хоста):
LogLevel dumpio:trace7
Уровень trace7 является самым подробным. После этих изменений Apache будет записывать все данные, проходящие через него, в error.log. Это может быть очень объемный лог, поэтому мы рекомендуем использовать его только для отладки.
mod_dumpio, мы можем также использовать переменную %{requestbody} в директиве LogFormat для записи тела запроса непосредственно в лог доступа (обычно access.log). Пример:
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %{requestbody}n" combined_body
CustomLog "logs/access.log" combined_body
%{requestbody}n позволяет нам добавить тело запроса в пользовательский формат лога.
mod_security:
mod_security – это межсетевой экран веб-приложений (WAF) для Apache. Помимо своих основных функций по безопасности, он может быть настроен на логирование полезной нагрузки POST-запросов.mod_security, например, с помощью директивы SecAuditLogParts, которая определяет, какие части транзакции будут записываться в журнал аудита.Для веб-сервера IIS стандартные логи обычно не включают данные POST-запросов (тело запроса), фиксируя только заголовки и строку запроса. Однако есть несколько способов получить эту информацию:
IHttpModule на C#):
IHttpModule из пространства имен System.Web). Этот модуль будет перехватывать входящие HTTP-запросы на различных этапах конвейера обработки IIS.HttpRequest.InputStream) и прочитать его содержимое до того, как оно будет обработано другими модулями или средой выполнения 1С.web.config.
Концептуальный пример кода C# для IHttpModule:
using System;
using System.IO;
using System.Web;
public class RequestBodyLoggerModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(OnBeginRequest);
}
private void OnBeginRequest(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
HttpRequest request = application.Context.Request;
// Логировать только POST запросы и только для определенного пути, если нужно
if (request.HttpMethod == "POST" /* && request.Path.Contains("/ws/MyWebService") */)
{
try
{
request.InputStream.Position = 0; // Перемещаем указатель в начало потока
using (StreamReader reader = new StreamReader(request.InputStream, request.ContentEncoding))
{
string requestBody = reader.ReadToEnd();
// Здесь мы можем записать requestBody в наш лог-файл.
// Например, File.AppendAllText("C:\\Logs\\RequestBodyLog.txt",
// DateTime.Now.ToString() + " " + request.Url.AbsoluteUri +
// Environment.NewLine + requestBody + Environment.NewLine + "---" + Environment.NewLine);
// Важно: после прочтения потока, если он не кэшируется IIS,
// его нужно сбросить в начало, чтобы другие обработчики могли его прочитать.
request.InputStream.Position = 0;
}
}
catch (Exception ex)
{
// Логирование ошибок чтения потока
}
}
}
public void Dispose() { }
}
Затем этот модуль необходимо зарегистрировать в файле web.config вашего веб-приложения 1С:
<configuration>
<system.webServer>
<modules>
<add name="RequestBodyLoggerModule" type="RequestBodyLoggerModule" />
</modules>
</system.webServer>
</configuration>
ASP.NET и WWW Server событий.NGINX часто используется как обратный прокси перед веб-сервером (Apache/IIS), на котором опубликован веб-сервис 1С. NGINX предлагает гибкие возможности для логирования, включая тело запроса:
$request_body:
$request_body в формат лога NGINX (директива log_format) для записи тела запроса.proxy_request_buffering off; (если NGINX выступает как прокси) или с использованием модулей, кэширующих тело запроса.nginx.conf:
http {
log_format custom_body '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'request_body: "$request_body"';
server {
listen 80;
server_name your_domain.com;
access_log /var/log/nginx/access_body.log custom_body;
location / {
proxy_pass http://localhost:8080; # Адрес вашего Apache/IIS с 1С
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Для логирования тела запроса NGINX должен его буферизовать
# или не буферизовать, в зависимости от сценария.
# Для $request_body обычно требуется, чтобы NGINX прочитал тело.
# Если тело запроса очень большое, это может повлиять на производительность.
# client_body_buffer_size 128k; # Размер буфера для тела запроса
# client_max_body_size 10m; # Максимальный размер тела запроса
}
}
}
Важно помнить, что NGINX по умолчанию не читает полностью тело запроса, если оно не требуется для проксирования или других операций. Для того чтобы переменная $request_body была заполнена, NGINX должен быть настроен на чтение тела запроса.
ngx.req.read_body() и ngx.req.get_body_data()), анализировать его и записывать в лог только в определенных условиях, например, только для ошибочных запросов или запросов к конкретным URI.http или server):
http {
# ... другие настройки ...
server {
listen 80;
server_name your_domain.com;
access_log /var/log/nginx/access_custom.log combined; # Стандартный лог
location /ws/ {
# Обязательно для чтения тела запроса в Lua
lua_need_request_body on;
lua_code_cache off; # Отключить кэш для отладки
access_by_lua_block {
ngx.req.read_body() -- Читаем тело запроса
local body_data = ngx.req.get_body_data()
if body_data then
-- Здесь мы можем проверить содержимое body_data
-- и при необходимости записать его в отдельный лог
-- Например, если body_data содержит определенные ошибки,
-- или просто для всех запросов к WS
ngx.log(ngx.INFO, "Incoming WS Request Body: ", body_data)
end
}
proxy_pass http://localhost:8080; # Адрес вашего Apache/IIS с 1С
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
С помощью Lua мы можем реализовать сложную логику, например, логировать тело запроса только в случае, если вышестоящий proxy_pass вернул ошибку (например, статус 500).
Важно понимать, что постоянное логирование полного тела каждого запроса может значительно увеличить объем логов и повлиять на производительность сервера. Поэтому мы настоятельно рекомендуем настраивать такое логирование выборочно: либо только для отладки, либо только для запросов, которые приводят к ошибкам, либо для определенных URL.
Если по каким-либо причинам мы не можем или не хотим настраивать логирование на уровне веб-сервера, существует обходной путь, который позволяет нам получить текст запроса непосредственно средствами 1С. Этот метод заключается в создании собственного HTTP-сервиса в 1С, который будет выступать в роли прокси для нашего основного веб-сервиса.
Концепция: Мы публикуем HTTP-сервис 1С на том же адресе, куда должны приходить запросы к нашему проблемному веб-сервису. Этот HTTP-сервис будет перехватывать все входящие запросы. Внутри его обработчика мы сможем получить полный текст входящего запроса, залогировать его, а затем сформировать новый запрос и отправить его уже на наш реальный веб-сервис 1С. Получив ответ от веб-сервиса, HTTP-сервис передаст его обратно клиенту.
/ws/MyProxyService/*).Запрос и Ответ.Запрос (тип HttpСервисЗапрос).Запрос.ПолучитьТелоКакСтроку() мы можем получить полный текст входящего HTTP-запроса, включая его SOAP-содержимое.
// Обработчик HTTP-сервиса
Процедура ОбработатьЗапрос(Запрос, Ответ)
// 1. Получаем и логируем текст входящего запроса
ТекстВходящегоЗапроса = "";
Попытка
ТекстВходящегоЗапроса = Запрос.ПолучитьТелоКакСтроку();
// Запись в журнал регистрации 1С
ЗаписьЖурналаРегистрации(НСтр("ru='Входящий HTTP-запрос'"),
УровеньЖурналаРегистрации.Информация,
,
,
"URI: " + Запрос.URI + Символы.ПС +
"Метод: " + Запрос.Метод + Символы.ПС +
"Тело: " + ТекстВходящегоЗапроса);
// Или запись в регистр сведений, файл и т.д.
Исключение
ЗаписьЖурналаРегистрации(НСтр("ru='Ошибка при получении тела запроса'"),
УровеньЖурналаРегистрации.Ошибка,
,
,
"URI: " + Запрос.URI + Символы.ПС +
"Метод: " + Запрос.Метод + Символы.ПС +
"Ошибка: " + ОписаниеОшибки());
КонецПопытки;
// 2. Формируем новый запрос для целевого веб-сервиса 1С
АдресЦелевогоWS = "http://localhost/MyBase/ws/MyWebService.1cws"; // Укажите реальный адрес вашего WS
HTTPЗапросКWS = Новый HTTPЗапрос(АдресЦелевогоWS);
HTTPЗапросКWS.УстановитьТелоИзСтроки(ТекстВходящегоЗапроса, КодировкаТекста.UTF8, "text/xml");
// Копируем заголовки, если это необходимо (например, Content-Type, SOAPAction)
Для Каждого Заголовок Из Запрос.Заголовки Цикл
// Мы можем выборочно копировать заголовки, избегая те, что 1С может добавить сам
Если НЕ Найти("Host,Content-Length,Connection", Заголовок.Ключ) Тогда
HTTPЗапросКWS.Заголовки.Вставить(Заголовок.Ключ, Заголовок.Значение);
КонецЕсли;
КонецЦикла;
// 3. Отправляем запрос на целевой веб-сервис
HTTPКлиент = Новый HTTPКлиент();
Попытка
HTTPОтветОтWS = HTTPКлиент.Отправить(HTTPЗапросКWS);
// 4. Обрабатываем ответ от целевого веб-сервиса
Если HTTPОтветОтWS.КодСостояния = 200 Тогда
Ответ.УстановитьТелоИзСтроки(HTTPОтветОтWS.ПолучитьТелоКакСтроку(), HTTPОтветОтWS.КодировкаТела, HTTPОтветОтWS.Заголовок("Content-Type"));
Для Каждого Заголовок Из HTTPОтветОтWS.Заголовки Цикл
Если НЕ Найти("Transfer-Encoding,Content-Length", Заголовок.Ключ) Тогда
Ответ.Заголовки.Вставить(Заголовок.Ключ, Заголовок.Значение);
КонецЕсли;
КонецЦикла;
Ответ.КодСостояния = HTTPОтветОтWS.КодСостояния;
Иначе
// Если WS вернул ошибку, мы можем залогировать ее и передать клиенту
ТекстОшибкиWS = HTTPОтветОтWS.ПолучитьТелоКакСтроку();
ЗаписьЖурналаРегистрации(НСтр("ru='Ошибка от целевого WS'"),
УровеньЖурналаРегистрации.Ошибка,
,
,
"URI: " + Запрос.URI + Символы.ПС +
"Код состояния: " + HTTPОтветОтWS.КодСостояния + Символы.ПС +
"Тело ошибки: " + ТекстОшибкиWS);
Ответ.УстановитьТелоИзСтроки(ТекстОшибкиWS, HTTPОтветОтWS.КодировкаТела, HTTPОтветОтWS.Заголовок("Content-Type"));
Ответ.КодСостояния = HTTPОтветОтWS.КодСостояния;
КонецЕсли;
Исключение
// Ошибка при отправке HTTP-запроса к целевому WS (например, недоступность)
ЗаписьЖурналаРегистрации(НСтр("ru='Ошибка отправки запроса к целевому WS'"),
УровеньЖурналаРегистрации.Ошибка,
,
,
ОписаниеОшибки());
Ответ.КодСостояния = 500;
Ответ.УстановитьТелоИзСтроки("<faultstring>Прокси: Ошибка при обращении к целевому WS</faultstring>", КодировкаТекста.UTF8, "text/xml");
КонецПопытки;
КонецПроцедуры
/ws/MyWebService.1cws, то наш HTTP-сервис должен быть опубликован как /ws/MyWebService/* или /ws/MyWebService.1cws (если веб-сервис переименован). При этом исходный веб-сервис 1С нужно будет опубликовать по другому, внутреннему адресу, к которому будет обращаться наш прокси HTTP-сервис.Преимущества этого подхода:
Недостатки:
Независимо от выбранного нами подхода, есть несколько общих рекомендаций, которые помогут нам эффективно управлять логированием:
exchangeId в SOAP-заголовках или уникальный ID, генерируемый при получении запроса). Это поможет нам связать запросы и ответы в различных логах (веб-сервера, 1С, сторонних систем) для более простого анализа и трассировки проблем.Выбирая между логированием на уровне веб-сервера и использованием HTTP-сервиса 1С как прокси, мы должны учитывать наши возможности по настройке инфраструктуры, необходимость глубокой кастомизации логики и требования к производительности. Оба подхода позволяют нам получить доступ к тексту некорректного WS-запроса и значительно упростить отладку проблем интеграции.