Delphi TIdHTTP错误后响应的字符编码

Delphi TIdHTTP错误后响应的字符编码,delphi,post,decoding,indy10,idhttp,Delphi,Post,Decoding,Indy10,Idhttp,我正在使用Delphi7和Indy10.6.2.5459向服务器发出POST请求。除了发生EIdHTTPProtocolException时的响应编码之外,所有这些都可以正常工作 当我没有引发EIdHTTPProtocolException时,我可以像这样解码响应以正确获得特殊字符: responseBody := ''; responseContent := TStringStream.Create(''); try try IdHTTP.Post(GetUrlMetodo(ASn

我正在使用Delphi7和Indy10.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,所以本机字符串类型是AnsiString,这一点很重要,因为这意味着Indy在解码字符串时必须做更多的工作

当TIdHTTP将HTTP响应正文解析为字符串时,首先使用服务器报告的响应字符集将其解码为Unicode。例如,如果服务器以UTF-8编码发送响应,则需要在内容类型头的charset属性中指定该响应

在Delphi/FreePascal的前Unicode版本中,Unicode数据随后被转换为ANSI,以适应AnsiString。在这些编译器版本中,返回字符串的TIdHTTP方法有一个可选的ADestEncoding参数,用于指定希望将Unicode数据转换为哪个AnsiString编码。如果未指定,则使用Indy的默认编码,默认情况下为US-ASCII。请参阅IdGlobal单位中的global GIdDefaultTextEncoding变量

您真的应该让Indy为您处理这个解码,因为不能保证任何给定的响应都是UTF-8编码的。但是,您可以指定希望Indy的输出始终仅为UTF-8编码的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的自动解码逻辑,将原始响应正文按原样接收到TStream而不是字符串中。在这种情况下,您负责确保检查响应的字符集,以了解如何正确解码原始数据。它可能并不总是UTF-8。Indy具有ReadStringFromStream和ReadStringAsCharset函数,允许您在从TStream读取字符串时指定编码/字符集

现在,为了回答您的问题,为什么您不能正确解码EIdHTTPProtocolException.ErrorMessage?嗯,因为它已经被TIDWTTP为您解码了

然而,这里有一个难点——当解码错误响应以放入EIdHTTPProtocolException时,当前无法从引发异常的代码中访问ADestEncoding参数,因此使用Indy的默认编码,默认情况下是US-ASCII。这就是为什么你会看到特殊角色被转换成?同样,这只影响Delphi/FreePascal的Unicode之前版本

您有两个解决此问题的选项:

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

GIdDefaultTextEncoding := encUTF8;        
...
try
  ...
  responseBody := ...;
except
  on E: EIdHTTPProtocolException do
    responseBody := UTF8Decode(E.ErrorMessage);
end;
由于您正在将成功和失败响应保存到同一个ResponseBy变量中,因此您可以完全禁用EIdHTTPProtocolException的使用,并删除try/except块。您可以通过在调用Post之前在TIdHTTP.HTTPOptions属性中启用hoNoProtocolErrorException和hoWantProtocolErrorContent标志来实现这一点。您可以检查TIdHTTP.ResponseCode属性以区分成功响应和失败响应:

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

因为您使用的是Delphi7,所以本机字符串类型是AnsiString,这一点很重要,因为这意味着Indy在解码字符串时需要做更多的工作

当TIdHTTP将HTTP响应正文解析为字符串时,首先使用服务器报告的响应字符集将其解码为Unicode。例如,如果服务器以UTF-8编码发送响应,则需要在内容类型头的charset属性中指定该响应

在Delphi/FreePascal的前Unicode版本中,Unicode数据随后被转换为ANSI,以适应AnsiString。在这些编译器版本中,返回字符串的TIdHTTP方法有一个可选的ADestEncoding参数,用于指定希望将Unicode数据转换为哪个AnsiString编码。如果未指定,则使用Indy的默认编码,默认情况下为US-ASCII。请参阅IdGlobal单位中的global GIdDefaultTextEncoding变量

您真的应该让Indy为您处理这个解码,因为不能保证任何给定的响应都是UTF-8编码的。但是,您可以指定希望Indy的输出始终为UTF-8 enco ded仅适用于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的自动解码逻辑,将原始响应正文按原样接收到TStream而不是字符串中。在这种情况下,您负责确保检查响应的字符集,以了解如何正确解码原始数据。它可能并不总是UTF-8。Indy具有ReadStringFromStream和ReadStringAsCharset函数,允许您在从TStream读取字符串时指定编码/字符集

现在,为了回答您的问题,为什么您不能正确解码EIdHTTPProtocolException.ErrorMessage?嗯,因为它已经被TIDWTTP为您解码了

然而,这里有一个难点——当解码错误响应以放入EIdHTTPProtocolException时,当前无法从引发异常的代码中访问ADestEncoding参数,因此使用Indy的默认编码,默认情况下是US-ASCII。这就是为什么你会看到特殊角色被转换成?同样,这只影响Delphi/FreePascal的Unicode之前版本

您有两个解决此问题的选项:

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

GIdDefaultTextEncoding := encUTF8;        
...
try
  ...
  responseBody := ...;
except
  on E: EIdHTTPProtocolException do
    responseBody := UTF8Decode(E.ErrorMessage);
end;
由于您正在将成功和失败响应保存到同一个ResponseBy变量中,因此您可以完全禁用EIdHTTPProtocolException的使用,并删除try/except块。您可以通过在调用Post之前在TIdHTTP.HTTPOptions属性中启用hoNoProtocolErrorException和hoWantProtocolErrorContent标志来实现这一点。您可以检查TIdHTTP.ResponseCode属性以区分成功响应和失败响应:

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

我使用了第一个选项,它成功了。非常感谢你,我从你的回答中学到了很多。我使用了第一个选项,它奏效了。非常感谢你,我从你的回答中学到了很多。