2013-04-11 152 views
1

此代码启动一个HTTP服务器,该服务器侦听端口8080上的请求。使用Delphi 2009编译时,中文文本呈现正确。但是,使用Free Pascal 2.6.0时,浏览器将显示中文而不是中文TIdHTTPServer与Free Pascal的UTF-8响应

使用Indy和Free Pascal编写Unicode/UTF-8 HTTP响应的正确方法是什么?

program IdHTTPUnicode; 

{$APPTYPE CONSOLE} 

uses 
    IdHTTPServer, IdCustomHTTPServer, IdContext, IdSocketHandle, IdGlobal, 
    SysUtils; 

type 
    TMyServer = class (TIdHTTPServer) 
    public 
    procedure InitComponent; override; 
    procedure DoCommandGet(AContext: TIdContext; 
     ARequestInfo: TIdHTTPRequestInfo; 
     AResponseInfo: TIdHTTPResponseInfo); override; 
    end; 

procedure Demo; 
var 
    Server: TMyServer; 
begin 
    Server := TMyServer.Create(nil); 
    try 
    try 
     Server.Active := True; 
    except 
     on E: Exception do 
     begin 
     WriteLn(E.ClassName + ' ' + E.Message); 
     end; 
    end; 
    WriteLn('Hit any key to terminate.'); 
    ReadLn; 
    finally 
    Server.Free; 
    end; 
end; 

procedure TMyServer.InitComponent; 
var 
    Binding: TIdSocketHandle; 
begin 
    inherited; 

    Bindings.Clear; 
    Binding := Bindings.Add; 
    Binding.IP := '127.0.0.1'; 
    Binding.Port := 8080; 
    Binding.IPVersion := Id_IPv4; 
end; 

procedure TMyServer.DoCommandGet(AContext: TIdContext; 
    ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); 
const 
    UNI = '中文'; 
begin 
    AResponseInfo.ContentText := '<html>' + UNI + '</html>'; 
    AResponseInfo.ContentType := 'text/html'; 
    AResponseInfo.CharSet := 'UTF-8'; 
end; 

begin 
    Demo; 
end. 

在调试器中,我可以看到在方法TIdIOHandler.Write不同的代码被执行,为的Free Pascal,STRING_IS_ANSI定义:

procedure TIdIOHandler.Write(const AOut: string; AByteEncoding: TIdTextEncoding = nil 
    {$IFDEF STRING_IS_ANSI}; ASrcEncoding: TIdTextEncoding = nil{$ENDIF} 
); 
begin 
    if AOut <> '' then begin 
    AByteEncoding := iif(AByteEncoding, FDefStringEncoding); 
    {$IFDEF STRING_IS_ANSI} 
    ASrcEncoding := iif(ASrcEncoding, FDefAnsiEncoding, encOSDefault); 
    {$ENDIF} 
    Write(
     ToBytes(AOut, -1, 1, AByteEncoding 
     {$IFDEF STRING_IS_ANSI}, ASrcEncoding{$ENDIF} 
     ) 
    ); 
    end; 
end; 
+0

是什么'ASrcEncoding'上.WRITE的进入? Delphi 2009+对于字符串和FPC 2.6通常使用UTF-16(但并不总是AFAIK)使用UTF-8 – 2013-04-11 13:38:57

+0

@ Arioch'ASrcEncoding为零,这意味着Write将使用encOSDefault(即我的Windows系统上的Ansi)。你确定'string'是Free Pascal 2.6.0上的Unicode吗?我查过的一些页面表明它仍然是Ansi。所以我猜这个代码不能通过使用ContentText属性来工作,我需要一个字节流。 – mjn 2013-04-11 13:48:37

+0

因为我知道它在2.4或2.6左右发生了变化 - 并且!它取决于编译器选项。至少在默认情况下,我的Win7 x64使用最近的CodeTyphon构建它是UTF-8(但是对于基于象形文字的语言,它可能会不同)。 (并且谈到编码 - 没有像Unicode这样的字眼)。那么,我认为即使在目标Delphi和FPC上,Indy也得不到支持,我甚至不会尝试使用它......尝试在FPC上将FDefAnsiEncoding设置为UTF-8。而且afair正确的是RFC的小写“utf-8”。 – 2013-04-11 13:51:42

回答

5

FreePascal字符串不像UTF-16编码那样在Delphi 2009+中编码。在FreePascal和Delphi 2007及更早版本中,您的代码需要考虑实际的字符串编码。这就是为什么Indy为这些平台公开额外的基于Ansi的参数/属性的原因。

TIdHTTPServer写出使用TIdIOHandler.Write()ContentText,在ASrcEncoding参数不使用非Unicode平台,所以你将不得不使用TIdIOHandler.DefAnsiEncoding属性,而不是让Write()知道ContentText的编码是什么,例如:

procedure TMyServer.DoCommandGet(AContext: TIdContext; 
    ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); 
const 
    UNI: WideString = '中文'; 
begin 
    AResponseInfo.ContentText := UTF8Encode('<html>' + UNI + '</html>'); 
    AResponseInfo.ContentType := 'text/html'; 

    // this tells TIdHTTPServer what to encode bytes to during socket transmission 
    AResponseInfo.CharSet := 'utf-8'; 

    // this tells TIdHTTPServer what encoding the ContentText is using 
    // so it can be decoded to Unicode prior to then being charset-encoded 
    // for output. If the input and output encodings are the same, the 
    // Ansi string data gets transmitted as-is without decoding/reencoding... 
    AContext.Connection.IOHandler.DefAnsiEncoding := IndyUTF8Encoding; 
end; 

或者更一般:

{$I IdCompilerDefines.inc} 

procedure TMyServer.DoCommandGet(AContext: TIdContext; 
    ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); 
const 
    UNI{$IFNDEF STRING_IS_UNICODE}: WideString{$ENDIF} = '中文'; 
begin 
    {$IFDEF STRING_IS_UNICODE} 
    AResponseInfo.ContentText := '<html>' + UNI + '</html>'; 
    {$ELSE} 
    AResponseInfo.ContentText := UTF8Encode('<html>' + UNI + '</html>'); 
    {$ENDIF} 
    AResponseInfo.ContentType := 'text/html'; 
    AResponseInfo.CharSet := 'utf-8'; 
    {$IFNDEF STRING_IS_UNICODE} 
    AContext.Connection.IOHandler.DefAnsiEncoding := IndyUTF8Encoding; 
    {$ENDIF} 
end; 
+0

非常感谢这些解决方案!我发现将IndyUF8Encoding分配给DefAnsiEncoding已经修复了它。使用UTF8Encode()不是必需的 - 它会导致“双重编码” - 所以它看起来像Free Pascal中的字符串至少使用我的编译器设置(-Mdelphi)进行UTF-8编码。 – mjn 2013-04-11 18:58:03

+0

我在FreePascal的文档或wiki中没有看到任何字符串是UTF-8编码的东西。尽管如此,我发现很多参考文献都说弦乐是Ansi。什么编译器指令启用UTF-8编码的字符串?单独将FPC置于Delphi模式不应该这样做。 – 2013-04-11 19:24:17

+0

'DefAnsiEncoding:= IndyUTF8Encoding'和'DefAnsiEncoding:='utf-8'有什么区别? – 2013-04-12 06:50:12

0

在默认情况下,现代FreePascal的字符串是UTF- 8除非你调整了编辑器选项。

因此它似乎在iif(ASrcEncoding, FDefAnsiEncoding, encOSDefault);encOSDefault是错误的。 如果你喜欢或我想更好的将是(由RFC AFAIR低的情况下),设定DefAnsiEncoding := 'utf-8';

要对安全起见,你可以在程序开始检查UTF-8模式,您可以修复它在INDY源检测。设置一些非拉丁不变的常数(比如中文,希腊或西里尔文 - 不管),并检查它是否为UTF8:http://compaspascal.blogspot.ru/2009/03/utf-8-automatic-detection.html

但是总的来说,我认为你可能会尝试找到一些关心FPC和Linux的库比印地更多。即使在德尔福,Indy似乎停滞不前,甚至被抛弃。也许Synopse mORMot(查找DataSnap性能测试文章)可以帮助您或某个与CodeTyphon发行版一起提供的库。

+0

'iif(ASrcEncoding,FDefAnsiEncoding,encOSDefault);'首先检查'ASrcEncoding'是否为零。如果是这样,那么它检查'FDefAnsiEncoding'是否为零。如果是这样,那么它返回'IndyOSDefaultEncoding'。如果'string'是Ansi并且是UTF-8编码的,那么'ASrcEncoding'或'IOHandler.DefAnsiEncoding'需要设置为'IndyUTF8Encoding',而不是''utf-8'',因为它们是'TIdTextEncoding'对象而不是字符串值。 – 2013-04-11 17:53:51

+1

是什么让你觉得“Indy在我看来停滞不前,甚至在Delphi上被抛弃了”? Indy正在积极开发,即将发布新版本的Delphi/C++ Builder/RADStudio XE4版本,甚至准备在不久的将来开始Indy 11的工作。 – 2013-04-11 18:10:19

+0

FPC中的字符串不是UTF-8,而是默认的1字节系统编码(意味着Windows上的ANSI)。 Lazarus在其中填充utf8,尽管 – 2013-04-11 20:58:11

相关问题