POST 错误响应的 TIdHTTP 字符编码

TIdHTTP character encoding of POST error response

我正在使用 Delphi 7 和 Indy 10.6.2.5459 向服务器发出 POST 请求。一切正常,除了发生 EIdHTTPProtocolException 时的响应编码。

当我没有得到 EIdHTTPProtocolException 时,我可以像这样解码响应以正确获取特殊字符:

responseBody := '';
responseContent := TStringStream.Create('');
try
  try
    IdHTTP.Post(GetUrlMetodo(ASngpcCloudRequest.Tipo), requestBody, responseContent);
    responseBody := UTF8Decode(responseContent.DataString);
  except
    on E: EIdHTTPProtocolException do
      responseBody := UTF8Decode(E.ErrorMessage);
  end;
finally
  FreeAndNil(responseContent);
end;

但是,当引发 EIdHTTPProtocolException 时,E.ErrorMessage 属性 具有 ? 而不是预期的特殊字符,即使在使用 UTF8Decode() 时也是如此。

那么,我怎样才能正确解码 E.ErrorMessage

由于您使用的是 Delphi 7,因此本机 string 类型是 AnsiString,这一点很重要,因为这意味着 Indy 在解码字符串时必须做更多的工作。

TIdHTTP 将 HTTP 响应 body 解析为 string 时,首先使用服务器报告的响应字符集将其解码为 Unicode。例如,如果服务器以 UTF-8 编码发送响应,则需要在 Content-Type header.

charset 属性中指定

在 Delphi/FreePascal 的 pre-Unicode 版本中,Unicode 数据随后被转换为 ANSI 以适应 AnsiString。在这些编译器版本中,return 和 stringTIdHTTP 方法有一个可选的 ADestEncoding 参数,让您指定要将 Unicode 数据转换为哪种 AnsiString 编码.如果不指定,则使用Indy的默认编码,默认为US-ASCII(参见IdGlobal单元中的全局GIdDefaultTextEncoding变量)。

你真的应该让 Indy 为你处理这个解码,因为不能保证任何给定的响应都是 UTF-8 编码的。但是,您可以指定您希望 Indy 的输出始终采用 UTF-8 编码(仅限 pre-Unicode 版本),例如:

try
  responseBody := UTF8Decode(
    IdHTTP.Post(GetUrlMetodo(ASngpcCloudRequest.Tipo), requestBody,
      IndyTextEncoding_UTF8)
  );
except
  on E: EIdHTTPProtocolException do
    responseBody := E.ErrorMessage;
end;

如果您曾经升级到 Delphi 的 Unicode 版本,您可以简单地删除额外的 UTF-8 步骤:

try
  responseBody := IdHTTP.Post(GetUrlMetodo(ASngpcCloudRequest.Tipo), requestBody);
except
  on E: EIdHTTPProtocolException do
    responseBody := E.ErrorMessage;
end;

在您在问题中提供的示例中,您正在绕过 TIdHTTP 的自动解码逻辑,接收原始响应 body as-is 变成 TStream 而不是 string。在这种情况下,您有责任确保检查响应的 charset 以了解如何正确解码原始数据。它可能并不总是 UTF-8。 Indy 具有 ReadStringFromStream()ReadStringAsCharset() 函数,允许您在从 TStream.

读取 string 时指定 encoding/charset

现在,回答你的问题,为什么你不能正确解码 EIdHTTPProtocolException.ErrorMessage?嗯,因为 已经TIdHTTP 为你解码了。

HOWEVER,这里是难点 - 在解码错误响应以放入 EIdHTTPProtocolException 时,ADestEncoding 参数当前无法从代码访问这会引发异常,因此会使用 Indy 的默认编码,默认情况下为 US-ASCII。这就是为什么您看到 "special" 个字符被转换为 ?(同样,这只影响 Delphi/FreePascal 的 pre-Unicode 版本)。

您有几个选项可以解决此问题:

  1. 在调用 Post() 之前将全局 IdGlobal.GIdDefaultTextEncoding 变量设置为 encUTF8。这样,如果 EIdHTTPProtocolException 被引发,它的 ErrorMessage 将被 UTF-8 编码。请注意,这确实会在全球范围内影响 Indy,并且在 Delphi 的 pre-Unicode 版本中比在 Unicode 版本中的影响要大得多,所以要小心。

    GIdDefaultTextEncoding := encUTF8;        
    ...
    try
      ...
      responseBody := ...;
    except
      on E: EIdHTTPProtocolException do
        responseBody := UTF8Decode(E.ErrorMessage);
    end;
    
  2. 因为您将成功和失败响应都保存到同一个 responseBody 变量中,您最好完全禁用 EIdHTTPProtocolException,并删除您的 try/except块。您可以通过在调用 Post() 之前启用 TIdHTTP.HTTPOptions 属性 中的 hoNoProtocolErrorExceptionhoWantProtocolErrorContent 标志来执行此操作。您可以检查 TIdHTTP.ResponseCode 属性 以区分成功和失败响应:

    IdHTTP.HTTPOptions := IdHTTP.HTTPOptions + [hoNoProtocolErrorException, hoWantProtocolErrorContent];
    responseBody := UTF8Decode(
      IdHTTP.Post(GetUrlMetodo(ASngpcCloudRequest.Tipo), requestBody,
        IndyTextEncoding_UTF8)
    );