POST我正在使用带有 Indy 10.6.2.5459 的 Delphi 7向服务器发出请求。一切正常,除了发生时的响应编码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;

但是,当 anEIdHTTPProtocolException被引发时,该E.ErrorMessage属性具有?而不是预期的特殊字符,即使在使用UTF8Decode().

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


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

TIdHTTP将 HTTP 响应主体解析为 时string,首先使用服务器报告的响应字符集将其解码为 Unicode。例如,如果服务器以 UTF-8 编码发送响应,则需要在标头charset的属性中指定它Content-Type

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

你真的应该让 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;

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

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

In the example you provided in your question, you are bypassing TIdHTTP's automated decoding logic, receiving the raw response body as-is into a TStream instead of a string. In which case, you are then responsible for making sure you check the response's charset to know how to decode the raw data properly. It may not always be UTF-8. Indy has ReadStringFromStream() and ReadStringAsCharset() functions which allow you to specify an encoding/charset when reading a string from a TStream.

Now, to answer your question, why can't you decode the EIdHTTPProtocolException.ErrorMessage correctly? Well, because it has already been decoded by TIdHTTP for you.

HOWEVER, here is the rub - when decoding an error response to put into EIdHTTPProtocolException, the ADestEncoding parameter is NOT currently accessible from the code that is raising the exception, so Indy's default encoding gets used instead, and that is US-ASCII by default. So that is why you are seeing "special" characters getting converted to ? (again, this only affects pre-Unicode versions of Delphi/FreePascal).

You have a couple of options to work around this issue:

  1. set the global IdGlobal.GIdDefaultTextEncoding variable to encUTF8 before calling Post(). This way, if EIdHTTPProtocolException is raised, its ErrorMessage will be UTF-8 encoded. Note that this does affect Indy globally, and has much more influence in pre-Unicode versions of Delphi than in Unicode versions, so be careful with it.

    GIdDefaultTextEncoding := encUTF8;        
    ...
    try
      ...
      responseBody := ...;
    except
      on E: EIdHTTPProtocolException do
        responseBody := UTF8Decode(E.ErrorMessage);
    end;
    
  2. since you are saving both success and failure responses to the same responseBody variable, you may as well just disable the use of EIdHTTPProtocolException altogether, and remove your try/except block. You can do this by enabling the hoNoProtocolErrorException and hoWantProtocolErrorContent flags in the TIdHTTP.HTTPOptions property before calling Post(). You can check the TIdHTTP.ResponseCode property to differentiate between success and failure responses:

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

I used the first option and It worked. Thank you very much, I learned a lot with your answer.