2013-04-11 33 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

相關問題