Delphi和Indy与utf8相关联。

3

我在访问一些采用UTF-8字符集的网站时遇到了问题,例如当我尝试访问这个网站:www

点击查看例子

所有的UTF-8字符都没有正确编码。以下是我的访问程序:

var
  Web     : TIdHTTP;
  Sito    : String;
  hIOHand : TIdSSLIOHandlerSocketOpenSSL;

begin
  Url := TIdURI.URLEncode(Url);


  try
    Web := TIdHTTP.Create(nil);
    hIOHand := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
    hIOHand.DefStringEncoding := IndyTextEncoding_UTF8;
    hIOHand.SSLOptions.SSLVersions := [sslvTLSv1,sslvTLSv1_1,sslvTLSv1_2,sslvSSLv2,sslvSSLv3,sslvSSLv23];
    Web.IOHandler := hIOHand;
    Web.Request.CharSet := 'utf-8';


    Web.Request.UserAgent := INET_USERAGENT;       //Custom user agent string
    Web.RedirectMaximum := INET_REDIRECT_MAX;      //Maximum redirects
    Web.HandleRedirects := INET_REDIRECT_MAX <> 0; //Handle redirects
    Web.ReadTimeOut := INET_TIMEOUT_SECS * 1000;   //Read timeout msec
    try
      Sito := Web.Get(Url);
      Web.Disconnect;
    except
      on e : exception do
        Sito := 'ERR: ' +Url+#32+e.Message;
    end;
  finally
    Web.Free;
    hIOHand.Free;
  end;

我尝试了所有的解决方案,但是在Sito var中,我总是发现错误的字符,例如“name”的正确值是

"name": "Aire d'adhésion du Parc national du Mercantour",

但是在Get指令之后,我得到的是:

"name": "Aire d'adhésion du Parc national du Mercantour",

您有任何想法吗?这是我的错误所在吗?谢谢!


Delphi XE6中的变量为:Web: TIdHTTP; Sito: String; - ondertol
就你的问题而言,这里链接无法使用。 - Rudy Velthuis
我已经更改了文本中的链接,并添加了变量部分。谢谢。 - ondertol
1
在HTTP中,你不应该设置DefStringEncoding。在SSL中,你不应该启用sslvSSLv2sslvSSLv3sslvSSLv23。你不应该设置Request.CharSet。而且你不需要调用Disconnect - Remy Lebeau
1个回答

7
在 Delphi 2009+,也就是包括 XE6 版本之后的 Delphi 中,string 是以 UTF-16 编码的 UnicodeString
您正在使用重载版本的 TIdHTTP.Get(),它返回一个string。它使用响应报文所声明的字符集将已发送的文本解码为UTF-16。如果文本无法正确解码,则很可能是响应没有正确地声明字符集所致。如果使用了错误的字符集,文本将无法正确解码。
事实上,有关的URL发送的响应Content-Type头部设置为application/json,但没有指定任何charset。默认的application/json字符集为UTF-8,但是Indy并不知道这一点,因此它使用自己的内部默认值,而这个默认值不是UTF-8。这就是当存在非ASCII字符时,文本无法正确解码的原因。
在这种情况下,如果您知道字符集始终为UTF-8,可以选择以下几种解决方法:
  • you can set Indy's default charset to UTF-8 by setting the global GIdDefaultTextEncoding variable in the IdGlobal unit:

    GIdDefaultTextEncoding := encUTF8;
    
  • you can use the TIdHTTP.OnHeadersAvailable event to change the TIdHTTP.Response.Charset property to 'utf-8' if it is blank or incorrect.

    Web.OnHeadersAvailable := CheckResponseCharset;
    
    ...
    
    procedure TMyClass.CheckResponseCharset(Sender: TObject; AHeaders: TIdHeaderList; var VContinue: Boolean);
    var
      Response: TIdHTTPResponse;
    begin
      Response := TIdHTTP(Sender).Response;
      if IsHeaderMediaType(Response.ContentType, 'application/json') and (Response.Charset = '') then
        Response.Charset := 'utf-8';
      VContinue := True;
    end;
    
  • you can use the other overloaded version of TIdHTTP.Get() that fills an output TStream instead of returning a string. Using a TMemoryStream or TStringStream, you can decode the raw bytes yourself using UTF-8:

    MStrm := TMemoryStream.Create;
    try
      Web.Get(Url, MStrm);
      MStrm.Position := 0;
      Sito := ReadStringFromStream(MStrm, IndyTextEncoding_UTF8);
    finally
      SStrm.Free;
    end;
    

    SStrm := TStringStream.Create('', TEncoding.UTF8);
    try
      Web.Get(Url, SStrm);
      Sito := SStrm.DataString;
    finally
      SStrm.Free;
    end;
    

完美的Remy,你太棒了!我使用了最后一个解决方案:使用TStringStream将GET指令更改为:Web.Get(Url,TStr); Sito := UTF8Decode(TStr.DataString) - TStr是我的TStringStream - 一切都很完美!所有字符都被完美地解码了!非常感谢你!! - ondertol
@ondertol 在这种情况下使用 TStringStream正确 方法是在其构造函数中使用 TEncoding.UTF8,而不是使用 UTF8Decode()。我已经更新了我的答案,并提供了示例。 - Remy Lebeau
Remy... 谢谢你的重要帮助,我已经按照你的代码审核了程序,只有一条指令 TStr := TStringStream.Create(TEncoding.UTF8); 给出了编译器错误信息“不存在可使用这些参数调用的'Create'的重载版本”,为解决此问题,我插入了一个字符串作为第一个参数,如 TStr:= TStringStream.Create(Stg,TEncoding.UTF8); …然后一切都很完美。再次感谢! - ondertol

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接