RAD 服务器部署到 IIS 的问题

问题描述 投票:0回答:1

将 RAD 服务器后端部署到最终生产环境(Internet 信息服务器)时,我们遇到问题。如果我们通过同一 \Inetpub\RADServer\EMSServer 文件夹中的 EMSDevServer.exe 执行包,则一切运行正常,但通过 IIS 运行该包时,一些操作无法正确执行。具体来说,Fast-Reports 始终返回 0 大小的空白 PDF,并且对 WinCrypt 的调用返回 AV 错误。

考虑到在 EMSDevServer.exe 下一切运行正常,与 IIS 位于同一文件夹和服务器上,那么问题一定出在其权限和设置上。前者在管理员帐户下在前台运行,而后者在本地系统帐户下在后台运行。

我们按照 Embarcadero 的博客 https://blogs.embarcadero.com/creating-pdf-reports-in-rad-server/ 编写了快速报告,它在 EMSDevServer 下工作得很好,它只是在 IIS 下返回空文件。我们已经按照另一个博客https://blogs.embarcadero.com/how-to-deploy-your-rad-server-project-on-windows-with-iis/配置了 IIS,并编译了我们的 RAD 服务器包在 32 位 Delphi 12.1 上。

我们可以使用 ProcessExplorer 获取我们的 EMSDevServer 运行使用的所有 DLL 依赖项的列表,但是我们如何知道该列表中哪些是有问题的 DLL?以及 IIS 运行无权访问的 DLL?例如,即使将 ADVAPI32.dll 复制到 Inetpub\RADServer\EMSServer 文件夹中,也无法解决调用其 CryptDecryt 函数时的 AV 问题,因此该调用肯定还有其他被阻止的子依赖项,但我们如何识别它们呢?.

或者,我们还更改了 IIS 中 RADServer 应用程序池的身份设置中的帐户,以使我们的包在管理员帐户下运行。我们有问题的函数(Fast-Reports 和 CryptDecrypt)仍然无法工作,但奇怪的是行为发生了轻微的变化。 Fast-Reports 仍然返回 0 大小的 PDF,但对 CryptDecrypt 的启动调用现在可以在包初始化时正确运行(它配置用于创建池连接定义的密码),但所有后续 CryptDecrypt 调用的范围端点调用现在返回“文件未找到”错误而不是 AV。它在启动/加载线程上的行为与在后请求线程上的行为不同。

总而言之,我们需要了解在 IIS 下识别损坏的依赖关系的推荐步骤以及如何解决它们(只需将这些 DLL 复制到 Inetpub\RADServer\EMSServer 文件夹?)。

一些论坛建议启用 TRESTRequest.SynchronizedEvents,但这在我们的案例中似乎没有多大作用。你会推荐它吗?它应该解决什么样的问题?.

您知道使用 Apache for Windows 而不是 IIS 是否有助于解决这些问题吗?

最后,我们试图完全盲目地解决这些问题,因为日志记录不起作用,所以如果 Fast-Reports 引发任何异常,我们不会看到它。在 EMSServer.ini 的 [Server.Logging] 条目上设置文件名不会激活该日志记录,这可能是什么错误?使用 Delphi 11.3 编译和部署的先前测试在激活该日志记录时没有任何问题,但我们当前的版本(在 12.1 下编译和部署)似乎无法激活该 IIS 日志记录。

PS:我已经向 Embarcadero 提交了支持请求,如果他们帮助我们找到解决方案,我将在这里分享解决方案。

delphi iis delphi-12-athens rad-server
1个回答
0
投票

事实证明,我从动态创建的 FastReports 生成 PDF 的例程没有任何问题。问题在于将数据返回到 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;

要从 Stream 而不是 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;

使用的 SmartPointer 是 Marco Cantu 推荐的。

https://github.com/marcocantu/DelphiSessions/blob/master/DelphiLanguageCodeRage2018/02_SmartPointers/SmartPointerClass.pas

© www.soinside.com 2019 - 2024. All rights reserved.