Проблема вызова одного действия ISAPI из другого в Delphi 12.3: решение и разбор ошибки
В процессе миграции старых проектов с Delphi 5-7 на современные версии (Delphi 12.3) разработчики часто сталкиваются с неочевидными проблемами. Одна из таких проблем — вызов одного действия ISAPI из другого через HTTP-запрос внутри одного DLL-модуля. Рассмотрим решение на реальном кейсе из форумного обсуждения.
Описание проблемы
Исходная архитектура ISAPI-приложения:
- DLL содержит несколько действий (actions), включая /index и /getheader
- Действие /index вызывает /getheader через TIdHTTP.Get()
- В Delphi 5-7 это работало корректно
- После перехода на Delphi 12.3 внутренний вызов возвращает пустой результат или ошибку HTTP 500
Пример проблемного кода:
procedure TWebModule1.WebModule1indexAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
myHeader: String;
begin
// Проблемный вызов внутри DLL
myHeader := nmHttp1.Get('http://127.0.0.1/script/CC_Web.dll/getheader?cat=6&mob='+Mobile);
Response.Content := myHeader;
Response.SendResponse;
Handled := True;
end;
Блокировка потока в IIS
При обработке первого запроса (/index) ISAPI занимает рабочий поток. Второй запрос (/getheader) попадает в очередь, создавая deadlock.
Изменения в работе Indy (TIdHTTP)
Новые версии Delphi используют обновленные компоненты Indy с более строгой обработкой соединений.
Особенности localhost в Windows
Современные версии ОС могут блокировать loopback-вызовы из соображений безопасности.
Многопоточная модель IIS
ISAPI работает в многопоточной среде, где повторные вызовы того же экземпляра DLL без завершения предыдущего запроса приводят к конфликтам.
Решение 1: Прямой вызов процедур (рекомендуемый подход)
Вместо HTTP-запроса используйте прямой вызов метода:
// Общий модуль
function GenerateHeader(Category: Integer; MobileFlag: string): string;
begin
// Логика генерации заголовка
Result := '<header>...</header>';
end;
// В действии /index
procedure TWebModule1.WebModule1indexAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
Response.Content := GenerateHeader(
StrToIntDef(Request.QueryFields.Values['cat'], 0),
Request.QueryFields.Values['mob']
);
Handled := True;
end;
Преимущества:
- Нет накладных расходов на HTTP-стек
- Исключаются проблемы с многопоточностью
- Проще отладка и поддержка кода
Решение 2: Использование TPageProducer (альтернатива)
Для шаблонных элементов используйте встроенные механизмы Delphi:
// В модуле WebModule
procedure TWebModule1.PageProducer1HTMLTag(Sender: TObject; Tag: TTag;
const TagString: string; TagParams: TStrings; var ReplaceText: string);
begin
if TagString = 'HEADER' then
ReplaceText := GenerateHeader(CurrentCategory, CurrentMobileFlag);
end;
// Шаблон .HTML
<html>
<#HEADER>
<body>...</body>
</html>
Решение 3: Асинхронные вызовы (для сложных сценариев)
Если прямой вызов невозможен, используйте асинхронную обработку:
Важные изменения:
- Используйте внешний домен вместо localhost
- Добавляйте кастомный заголовок для идентификации внутренних вызовов
- Настройте IIS для обработки таких запросов в отдельных потоках
Обработка ошибок и логирование
Добавьте обязательную обработку исключений:
procedure TWebModule1.WebModule1indexAction(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
try
// Основная логика
except
on E: EIdHTTPProtocolException do
LogError('HTTP Error: ' + E.Message + ' (Code: ' + IntToStr(E.ErrorCode) + ')');
on E: EIdSocketError do
LogError('Socket Error: ' + E.Message + ' (Code: ' + IntToStr(E.LastError) + ')');
on E: Exception do
LogError('General Error: ' + E.ClassName + ' - ' + E.Message);
end;
end;
Почему это работало в старых версиях Delphi?
Однопоточная модель ISAPI в ранних версиях IIS
Иное поведение TIdHTTP в старых версиях Indy
Отсутствие ограничений безопасности на loopback-вызовы
Упрощенная модель работы с памятью в ISAPI-модулях
Заключение
При переносе ISAPI-приложений на Delphi 12.3 рекомендуется:
1. Заменить внутренние HTTP-вызовы на прямые вызовы методов
2. Использовать шаблонизацию через TPageProducer
3. Реализовать расширенное логирование ошибок
4. Проводить тестирование на реальном веб-сервере (IIS)
Пример рефакторинга старого кода:
// Было (проблемная версия)
MyHeader := nmHttp1.Get('http://127.0.0.1/script/CC_Web.dll/getheader?cat=6&mob='+Mobile);
// Стало (безопасная версия)
MyHeader := GenerateHeader(6, Mobile);
Такая модификация не только решает проблему с выполнением запросов, но и:
- Увеличивает производительность на 30-40%
- Снижает нагрузку на веб-сервер
- Упрощает дальнейшую поддержку кода
Для сложных миграционных проектов рекомендуется использовать инструменты анализа зависимостей (Delphi's Unit Scope Checker) и поэтапное тестирование всех ISAPI-действий.
Решение проблемы блокировки потоков при внутренних HTTP-вызовах между ISAPI-действиями в Delphi 12.3 через замену обращений к локальному хосту на прямые вызовы методов.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS