Мы сталкиваемся с проблемами при развертывании нашей серверной части RAD Server в конечной производственной среде (Internet Information Server). Все работает нормально, если мы запускаем наш пакет через EMSDevServer.exe в той же папке \Inetpub\RADServer\EMSServer, но при запуске этого пакета через IIS некоторые действия выполняются неправильно. Конкретно, Fast-Reports всегда возвращает пустые PDF-файлы нулевого размера, а вызовы WinCrypt возвращают ошибку AV.
Учитывая, что под EMSDevServer.exe все работает нормально, на той же папке и сервере, что и IIS, то проблемы должны быть в его разрешениях и настройках. Первый работает на переднем плане под учетной записью администратора, а второй — в фоновом режиме под учетной записью LocalSystem.
Мы написали код Fast-Reports в соответствии с блогом Embarcadero https://blogs.embarcadero.com/creating-pdf-reports-in-rad-server/ , и он отлично работает под EMSDevServer, он возвращает только под IIS. пустые файлы. Мы настроили IIS, следуя этому другому блогу https://blogs.embarcadero.com/how-to-deploy-your-rad-server-project-on-windows-with-iis/, и скомпилировали наш RAD-сервер. пакет на 32-битной Delphi 12.1.
Мы можем использовать ProcessExplorer, чтобы получить список всех зависимостей DLL, которые использует наш запуск EMSDevServer, но как мы можем узнать, какие DLL являются проблемными из этого списка?, а какие из них не имеют доступа при запуске IIS? Например, даже копирование ADVAPI32.dll в папку Inetpub\RADServer\EMSServer не решает проблему AV при вызове функции CryptDecryt, поэтому этот вызов должен иметь другие заблокированные подзависимости, но как мы можем их идентифицировать?
В качестве альтернативы мы также изменили учетную запись в настройках удостоверения для пула приложений RADServer в IIS, чтобы наш пакет запускался под учетной записью администратора. Наши проблемные функции (Fast-Reports и CryptDecrypt) по-прежнему не работают, но, как ни странно, поведение немного изменилось. Fast-Reports по-прежнему возвращает PDF-файлы нулевого размера, но начальный вызов CryptDecrypt теперь выполняется правильно при инициализации пакета (он настраивает пароль для создания определения пулового соединения), но все последующие вызовы CryptDecrypt в области и вызов конечной точки теперь возвращает ошибку «файл не найден» вместо AV. Он ведет себя по-разному в потоке запуска/загрузки, чем в потоках последующих запросов.
Подводя итог, нам нужно знать рекомендуемые шаги для выявления нарушенных зависимостей в IIS и способы их решения (простое копирование этих DLL в папку Inetpub\RADServer\EMSServer?).
Некоторые форумы рекомендуют включить TRESTRequest.SynchronizedEvents, но в нашем случае это мало что дает. Порекомендуете ли вы его?, какие проблемы он должен решить?.
Знаете ли вы, поможет ли использование Apache для Windows вместо IIS в решении этих проблем?
Наконец, мы пытаемся решить эти проблемы полностью вслепую, потому что ведение журнала не работает, поэтому, если Fast-Reports выдает какое-либо исключение, мы этого не видим. Что может быть не так, если установка имени файла в записи [Server.Logging] файла EMSServer.ini не активирует ведение журнала? Предыдущие тесты, скомпилированные и развернутые с использованием Delphi 11.3, не имели проблем с активацией ведения журнала, но наша текущая версия, скомпилированная и развернутая под 12.1, похоже, не способна активировать ведение журнала IIS.
PS: Я отправил запрос в службу поддержки Embarcadero, поделюсь здесь решением, если они помогут нам его найти.
@fisi-pjm Это хорошее решение для разработки в той же среде, что и в производстве. Я буду иметь это в виду, чтобы сделать этот переход. Спасибо.
Ожидается, что некоторые API не работают под IIS, поскольку код выполняется в системном сеансе с ограничениями, docs.lextudio.com/blog/… IIS Express не является IIS, поэтому скрывает те же проблемы, что и EMSServerDev.exe.
@LexLi EMSServerDev.exe скрывает еще больше проблем. по крайней мере, это мой опыт. Если системный сеанс является проблемой, не будет ли решением проблемы запуск пула приложений с правами пользователя?
Спасибо за совет @fisi-pjm. В конце концов я понял, что моя проблема не имеет ничего общего с FastReports, но я вернул их как TSmartPointer<TStream>, и под IIS они были освобождены до того, как IIS смог их прочитать.
Оказывается, в моей программе создания PDF-файлов из динамически создаваемых FastReports не было ничего плохого, и не было никаких редких сломанных зависимостей. Fastreports работал нормально, проблема заключалась в возврате этих данных в EndpointResponse (но вслепую, без журналов, мне потребовалось много времени, чтобы понять это).
Если кто-то хочет генерировать отчеты со стороны сервера полностью динамически, это моя процедура. Для удобства я использую SmartPointers, поэтому компоненты освобождаются сами (но их можно легко удалить).
unit MyReportUtilsUnit;
interface
uses
System.Classes,
System.SysUtils,
System.Types,
frxClass,
frxExportPDF,
Data.DB,
FireDAC.Comp.DataSet,
SmartPointerClass;
type
TKeyValue = record
Key: string;
Value: variant;
end;
TKeyValueArray = array of TKeyValue;
TDatasetArray = array of TFDDataset;
TMyReportUtils = record
class function BuildReport(ReportOwner: TComponent; Template: TBytes; DataQuerys: TDatasetArray; Variables: TKeyValueArray): TfrxReport; static;
class function ExportToPDF(ReportOwner: TComponent; Template: TBytes; DataQuerys: TDatasetArray; Variables: TKeyValueArray): TStream; static;
end;
var
MyReportUtils: TMyReportUtils;
implementation
uses
System.Variants,
System.Diagnostics,
System.Math,
System.IOUtils,
frxDBSet,
frxBarcode,
frxTableObject;
class function TMyReportUtils.BuildReport(ReportOwner: TComponent; Template: TBytes; DataQuerys: TDatasetArray; Variables: TKeyValueArray): TfrxReport;
var
Report: TfrxReport;
StreamTemplate: TSmartPointer<TBytesStream>;
fdbDataset: TfrxDBDataset;
begin
Report := TfrxReport.Create(ReportOwner);
Report.Clear;
Report.EngineOptions.UseFileCache := False;
Report.ShowProgress := False;
Report.EngineOptions.SilentMode := True;
StreamTemplate := TBytesStream.Create(Template);
StreamTemplate.Value.Position := 0;
if StreamTemplate.Value.Size > 0 then
Report.LoadFromStream(StreamTemplate.Value);
for var InputItem in Variables do
Report.Variables.AddVariable('Variables', InputItem.Key, VarToStr(InputItem.Value));
Report.DataSets.Clear;
for var DataQuery in DataQuerys do
begin
fdbDataset := TfrxDBDataset.Create(ReportOwner);
fdbDataset.UserName := DataQuery.Name;
fdbDataset.Name := 'fdb' + DataQuery.Name;
fdbDataset.CloseDatasource := False;
fdbDataset.DataSet := DataQuery;
Report.DataSets.Add(fdbDataset);
end;
Report.PrepareReport(True); // ToDo: Check why it needs to be prepared twice in order to load its datasets
Report.PrepareReport(True);
Result := Report;
end;
class function TMyReportUtils.ExportToPDF(ReportOwner: TComponent; Template: TBytes; DataQuerys: TDatasetArray; Variables: TKeyValueArray): TStream;
var
frReport: TfrxReport;
frPDF: TSmartPointer<TfrxPDFExport>;
begin
Result := TBytesStream.Create;
frPDF := TfrxPDFExport.Create(ReportOwner);
frPDF.Value.Stream := Result;
frPDF.Value.Compressed := True;
frPDF.Value.EmbeddedFonts := False;
frPDF.Value.Outline := False;
frPDF.Value.OpenAfterExport := False;
frPDF.Value.ShowProgress := False;
frPDF.Value.ShowDialog := False;
frPDF.Value.PrintOptimized := False;
frPDF.Value.PictureDPI := 600;
frPDF.Value.Quality := 95;
frPDF.Value.UseFileCache := False;
frPDF.Value.Background := False;
frPDF.Value.CenterWindow := False;
frPDF.Value.DataOnly := False;
frPDF.value.EmbedFontsIfProtected := False;
frPDF.Value.FitWindow := False;
frPDF.value.HideMenubar := False;
frPDF.Value.HideToolbar := False;
frPDF.Value.HideWindowUI := False;
frPDF.Value.OverwritePrompt := False;
frPDF.Value.PdfA := False;
frPDF.Value.PrintScaling := False;
frPDF.Value.HTMLTags := False;
frPDF.Value.Transparency := False;
frReport := BuildReport(ReportOwner, Template, DataQuerys, Params);
frReport.Export(frPDF);
Result.Position := 0;
end;
end.
Моя проблема заключалась в том, что BuildReport возвращал TSmartPointer, который отлично работал на EMSDevServer, но под IIS выглядел так, как будто он был освобожден до того, как IIS смог прочитать его значение.
Изменение этой функции для возврата TStream вместо TSmartPointer решило проблему.
Конечная точка изначально была
procedure TdmTestResource.GetReport(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
begin
var Template: TBytes;
var ReportDatasets: TDatasetArray;
var ReportVariables: TKeyValueArray;
GetData(Self, Template, ReportDatasets, ReportVariables);
var StreamSmartPtr := MyReportUtils.ExportToPDF(Self, Template, ReportDatasets, ReportVariables);
AResponse.Body.SetStream(StreamSmartPtr.Value, 'application/pdf', False);
end;
А чтобы вернуть данные из потока вместо TSmartPointer, нужно было всего лишь изменить его на:
procedure TdmTestResource.GetReport(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
begin
var Template: TBytes;
var ReportDatasets: TDatasetArray;
var ReportVariables: TKeyValueArray;
GetData(Self, Template, ReportDatasets, ReportVariables);
var Stream := MyReportUtils.ExportToPDF(Self, Template, ReportDatasets, ReportVariables);
AResponse.Body.SetStream(Stream, 'application/pdf', True);
end;
Используемые SmartPointers рекомендованы Марко Канту.
Сожалеем о проблемах, с которыми вы столкнулись. Два года назад у меня был проект с RAD Server, и я столкнулся с похожими проблемами. К сожалению, EMSServerDev.exe не работает так же, как IIS, чему я научился на собственном горьком опыте. В то время я изменил настройки разработки в Delphi, чтобы использовать IIS Express вместо EMSServerDev.exe. После внесения этого изменения в IIS все заработало как положено. Я написал сообщение в блоге об этой проблеме на немецком форуме DelphiPraxis, который вы можете найти здесь: delphipraxis.net/1498757-post29.html. Я надеюсь, что это поможет вам с вашей проблемой