使用 RAD 服务器更改查询数据库时出现 EMS 错误

问题描述

我一直在使用 RAD 服务器作为我们组织的时钟应用程序。从昨天早上 7:00 左右开始,对其中一家公司的 API 调用开始失败,并显示以下错误消息:

First chance exception at $00007FFAB7A24ED9. Exception class EHTTPProtocolException with message 'HTTP/1.1 500 Internal Server Error'. Process TGCTimeClock.exe (21552)
First chance exception at $00007FFAB7A24ED9. Exception class TCustomrESTResponse.EJSONValueError with message 'Response content is not valid JSON'. Process TGCTimeClock.exe (21552)
First chance exception at $00007FFAB7A24ED9. Exception class EEMSClientHTTPError with message 'EMS Error: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-str'. Process TGCTimeClock.exe (21552)

由于我为 2 个独立的公司运行 Time Clock 应用程序,因此我将数据库名称传递给我的端点以访问正确的数据。客户端调用如下:

      dmTimeClockClient.FDMTEmployee.Close;
      dmTimeClockClient.EMSFDClientEmployee.GetEndpoint.Params.Clear;
      dmTimeClockClient.EMSFDClientEmployee.Resource := 'TimeClock/GetEmployees';
      dmTimeClockClient.EMSFDClientEmployee.GetEndpoint.AddParameter('DEPARTMENT_ID',IntToStr(DepartmentID));
      dmTimeClockClient.EMSFDClientEmployee.GetEndpoint.AddParameter('Database',dbname);
      dmTimeClockClient.EMSFDClientEmployee.GetData;

      dmTimeClockClient.FDMTCostCenters.Close;
      dmTimeClockClient.EMSFDClientCostCenters.GetEndpoint.Params.Clear;
      dmTimeClockClient.EMSFDClientCostCenters.Resource := 'TimeClock/GetCostCenters';
      dmTimeClockClient.EMSFDClientCostCenters.GetEndpoint.AddParameter('DEPARTMENT_ID',IntToStr(DepartmentID));
      dmTimeClockClient.EMSFDClientCostCenters.GetEndpoint.AddParameter('Database',dbname);
      dmTimeClockClient.EMSFDClientCostCenters.GetData;

服务器端程序和查询非常简单:

procedure TdmTimeClock.GetCostCenters(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
var ms: TMemoryStream;
begin
  ms := TMemoryStream.Create;

  qryCostCenters.Close;
  qryCostCenters.ParamByName('DEPARTMENT_ID').Value := StrToInt(ARequest.Params.Values['DEPARTMENT_ID']);
  dbname := ARequest.Params.Values['Database'];
  qryCostCenters.Open;

  FDSACostCenters.SavetoStream(ms,TFDStorageFormat.sfJSON);

// Response owns stream
  AResponse.Body.SetStream(ms,'application/vnd.embarcadero.firedac+json',True);
end;

procedure TdmTimeClock.GetEmployees(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
var ms: TMemoryStream;
begin
  ms := TMemoryStream.Create;

  qryEmpInfo.Close;
  qryEmpInfo.ParamByName('DEPARTMENT_ID').Value := StrToInt(ARequest.Params.Values['DEPARTMENT_ID']);
  dbname := ARequest.Params.Values['Database'];
  qryEmpInfo.Open;

  FDSAEmpInfo.SavetoStream(ms,True);
end;

查询的 before open 事件中,我使用 INI 文件设置数据库参数,如下所示:

procedure TdmTimeClock.qryBeforeOpen(DataSet: TDataSet);
begin
  SetConnectionStr(FDConnectionSTIKS);
end;

procedure SetConnectionStr(var FDConnectionSTIKS: TFDConnection);
var ConfigIni: tinifile;
    DBServerName,Path: string;
begin
  Path := GetCurrentDir;
  ConfigIni := tiniFile.Create(System.IoUtils.TPath.Combine(Path,'Config.ini'));
  DBServerName := ConfigIni.ReadString(AppNode,'ServerName','ZEUS');

  with FDConnectionSTIKS.Params as TFDPhysMSsqlConnectionDefParams do
  begin
    DriverID := 'MSsql';
    Server := DBServerName;
    Database := dbname;
    UserName := DBUserID;
    Password := DBPassword;
  end;

  ConfigIni.Free;
end;

我在谷歌上搜索错误消息,看起来 DOCTYPE 信息在某种程度上被截断了,因为我发现了以下 DOCTYPE 示例

DOCTYPE html PUBLIC "-I /W3C/ /DTD XHTML 1.0 Strict! /EN". "http://w.t.~.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"

“ict.dtd”在错误消息中被截断。

我找不到定义此 DOCTYPE 的任何地方,或者为什么它与一个数据库不同。被访问的表是相同的。我还有 2 个其他“测试”数据库可以正常工作。这从昨天早上开始发生,但 bpl 或应用程序没有变化。不过,当时我们的 IIS 服务器上似乎发生了 Windows 更新。

另一件奇怪的事情是第一次调用'GetEmployees' 成功,但第二次调用GetCostCenters 失败。如果我注释掉对 GetEmployees 的调用,对 GetCostCenters 的调用以及随后的类似调用仍然会失败。

IIS 日志文件包含以下条目:

2021-07-20 14:09:24 192.168.1.162 GET /RADServer/EMSServer.dll/Security/Login Database=TR_WESTEK&USERNAME=lenm&PASSWORD=******** 80 - 216.123.235.214 Embarcadero+RESTClient/1.0 - 200 0 0 31
2021-07-20 14:10:04 192.168.1.162 GET /RADServer/EMSServer.dll/TimeClock/GetEmployees DEPARTMENT_ID=1&Database=TR_WESTEK 80 - 216.123.235.214 Embarcadero+RESTClient/1.0 - 200 0 0 46
2021-07-20 14:10:08 192.168.1.162 GET /RADServer/EMSServer.dll/TimeClock/GetCostCenters DEPARTMENT_ID=1&Database=TR_WESTEK 80 - 216.123.235.214 Embarcadero+RESTClient/1.0 - 500 0 0 93

对测试数据库的成功调用生成以下日志条目:

2021-07-20 14:04:10 192.168.1.162 GET /RADServer/EMSServer.dll/Security/Login Database=WESTEK_TEST&USERNAME=lenm&PASSWORD=******** 80 - 216.123.235.214 Embarcadero+RESTClient/1.0 - 200 0 0 15
2021-07-20 14:04:20 192.168.1.162 GET /RADServer/EMSServer.dll/TimeClock/GetEmployees DEPARTMENT_ID=1&Database=WESTEK_TEST 80 - 216.123.235.214 Embarcadero+RESTClient/1.0 - 200 0 0 125
2021-07-20 14:04:35 192.168.1.162 GET /RADServer/EMSServer.dll/TimeClock/GetCostCenters DEPARTMENT_ID=1&Database=WESTEK_TEST 80 - 216.123.235.214 Embarcadero+RESTClient/1.0 - 200 0 0 46
2021-07-20 14:04:38 192.168.1.162 GET /RADServer/EMSServer.dll/TimeClock/GetShifts Database=WESTEK_TEST 80 - 216.123.235.214 Embarcadero+RESTClient/1.0 - 200 0 0 62
2021-07-20 14:04:38 192.168.1.162 GET /RADServer/EMSServer.dll/TimeClock/GetPayGroups Database=WESTEK_TEST 80 - 216.123.235.214 Embarcadero+RESTClient/1.0 - 200 0 0 62
2021-07-20 14:04:38 192.168.1.162 GET /RADServer/EMSServer.dll/TimeClock/GetPayPeriods Database=WESTEK_TEST&TaxYear=2020&PayGroupID=0 80 - 216.123.235.214 Embarcadero+RESTClient/1.0 - 200 0 0 46
2021-07-20 14:04:38 192.168.1.162 GET /RADServer/EMSServer.dll/TimeClock/GetAbsenteeCodes Database=WESTEK_TEST 80 - 216.123.235.214 Embarcadero+RESTClient/1.0 - 200 0 0 46
2021-07-20 14:04:38 192.168.1.162 GET /RADServer/EMSServer.dll/TimeClock/GetTimeClock DEPARTMENT_ID=1&START_DATE=07%2F20%2F2021&END_DATE=07%2F20%2F2021&Database=WESTEK_TEST 80 - 216.123.235.214 Embarcadero+RESTClient/1.0 - 200 0 0 62

以下是我使用的 try/except 块:

    try
      dmTimeClockClient.FDMTEmployee.Close;
      dmTimeClockClient.EMSFDClientEmployee.GetEndpoint.Params.Clear;
      dmTimeClockClient.EMSFDClientEmployee.Resource := 'TimeClock/GetEmployees';
      dmTimeClockClient.EMSFDClientEmployee.GetEndpoint.AddParameter('DEPARTMENT_ID',dbname);
      dmTimeClockClient.EMSFDClientCostCenters.GetData;

      dmTimeClockClient.FDMTShifts.Close;
      dmTimeClockClient.EMSFDClientShifts.GetEndpoint.Params.Clear;
      dmTimeClockClient.EMSFDClientShifts.Resource := 'TimeClock/GetShifts';
      dmTimeClockClient.EMSFDClientShifts.GetEndpoint.AddParameter('Database',dbname);
      dmTimeClockClient.EMSFDClientShifts.GetData;

      dmTimeClockClient.FDMTPayGroups.Close;
      dmTimeClockClient.EMSFDClientPayGroups.GetEndpoint.Params.Clear;
      dmTimeClockClient.EMSFDClientPayGroups.Resource := 'TimeClock/GetPayGroups';
      dmTimeClockClient.EMSFDClientPayGroups.GetEndpoint.AddParameter('Database',dbname);
      dmTimeClockClient.EMSFDClientPayGroups.GetData;

      cbPayGroup.ItemIndex := 0;

      dmTimeClockClient.FDMTPayPeriods.Close;
      dmTimeClockClient.EMSFDClientPayPeriods.GetEndpoint.Params.Clear;
      dmTimeClockClient.EMSFDClientPayPeriods.Resource := 'TimeClock/GetPayPeriods';
      dmTimeClockClient.EMSFDClientPayPeriods.GetEndpoint.AddParameter('Database',dbname);
      dmTimeClockClient.EMSFDClientPayPeriods.GetEndpoint.AddParameter('TaxYear',cbTaxYear.ListBox.ListItems[cbTaxYear.ItemIndex].Text);
      dmTimeClockClient.EMSFDClientPayPeriods.GetEndpoint.AddParameter('PayGroupID',IntToStr(cbPayGroup.ListBox.ListItems[cbPayGroup.ItemIndex].Tag));
      dmTimeClockClient.EMSFDClientPayPeriods.GetData;

      dmTimeClockClient.FDMTAbsenteeCodes.Close;
      dmTimeClockClient.EMSFDClientAbsenteeCodes.GetEndpoint.Params.Clear;
      dmTimeClockClient.EMSFDClientAbsenteeCodes.Resource := 'TimeClock/GetAbsenteeCodes';
      dmTimeClockClient.EMSFDClientAbsenteeCodes.GetEndpoint.AddParameter('Database',dbname);
      dmTimeClockClient.EMSFDClientAbsenteeCodes.GetData;
    except
      on E: Exception do
      begin
        TThread.Synchronize(nil,procedure
          begin
            ShowMessageDlg('An Error Occured: ' + E.Message,ditinformation,MsgType);
          end);
      end;
    end;

以下是成功和不成功调用的捕获流量的屏幕截图。

enter image description here

成功的标题

enter image description here

不成功的标题

enter image description here

以下是直接从应用程序运行时的流量。调用 GetCostCenters 失败;与任何其他后续调用一样。第一次调用 GetEmployees 总是成功的。

enter image description here

以下是在我的应用程序执行代码之前使用浏览器对不同数据库运行相同调用时的流量。只要我从浏览器运行调用到不同的数据库调用就会成功。

enter image description here

我运行的是 Windows 10、Rad Studio 10.4.2。 我的 IIS 服务器“Windows Server 2016”; IIS 10。

任何帮助将不胜感激。

解决方法

好吧,我设法解决了我的问题。但是,我不知道为什么它突然开始发生或为什么它只影响一个数据库。服务器端显然发生了错误,但我似乎无法获得合法的错误消息。我终于尝试使用 SQL Server Profiler 监控 SQL 查询。

我发现我的员工查询正在与其他所有查询一起执行。原因是员工查询在我的服务器模块上保持活动状态。当我执行 GetCostCenters 调用时,将执行员工查询,然后执行成本中心查询。我假设产生了错误。我仍然不知道实际的错误消息是什么。

这仅在使用 IIS 时发生。使用 localhost 进行测试时,分析器没有显示 Employee Query 被多次执行。

在我停用员工查询并将更改安装到 IIS 后,一切都按预期工作。

我对回复的人表示感谢和歉意。