以下的內容在 Delphi 10.2 (含)以上版本已經解決,新版使用者可以略過不看。
===========================================
TWebRequest 遇到 Unicode
出來江湖混,該還的還是要還。
前端寫久了,還是遇到了 Unicode 解析問題。
以 HTTP Method GET 為例:
http://{local}/action?id=許功蓋堃
已知:
Delphi 2009 以上的 string = UTF-16 格式。
而取得的內容是這樣的:
看到這個心都涼了半截 |
仔細攻略了 IdHTTPWebBrokerBridge 和 HTTPApp 兩個單元後,才發現裡頭大部份的函式及屬性都是 AnsiString 而非 String 基礎類別。
而 IdHTTPWebBrokerBridge 單元主要只應用在 HTTP Server 上,搬上 IIS 後就和它無關,所以可以直接忽略這個單元不看,雖然我也是看了它很久很久才驚覺這個答案。(眼神死)
接著再把注意力轉到 HTTPApp 單元,最關鍵的就在這句話:
一律使用 AnsiString,Unicode 都不 Unicode 了 |
ExtractHeaderFields 這個函式遇到要解析的內容僅僅只做了百分比符號轉換,還少做了一道Unicode / UTF-8 (UTF8) 編碼轉換的工作。
於是我們的 Unicode 文字就被轉成亂碼直出了。
現在,到底是該 Debug HTTPApp 還是有其它 3rd party 的解法呢?
HTTPApp.pas 的低耦合性,適合提出來獨立處理。
來看看這段可以怎麼改:
or
Strings.Add(UTF8ToString(HTTPDecode(AnsiString(DoStripQuotes(ExtractedField)))))
Strings.Add(IdURI.TIdURI.URLDecode(AnsiString(DoStripQuotes(ExtractedField))))
都可以解決 TWebRequest 屬性 QueryFields/ContentFields 取得資料的亂碼問題。
你以為以上就結束了嗎?別忘了還有 TWebResponse 呢!
============== 2021/10/05 更新 ==============
在 uses 裡引用 UTF8ContentParser 此單元,上述問題都迎刃而解,這單元真的很少人提過,以下是來自檔案中的說明內容:
TUTF8ContentParser is a WebRequest content parser that parses UTF-8 requests.文件中顯示 TWebResponse 也能一併解決,但測試發佈【不帶 BOM 的 UTF-8 檔案】時仍有亂碼出現。主要還是舊版 WebBroker 預設會以 windows-1252 編碼輸出,另一個原因是 Ansistring 沒有設定 codepage 的關係。
TUTF8ContentParser class automatically replace the default content parser when this unit (UTF8ContentParser)
is used in a web application. You should only use UTF8ContentParser in web applications that generate UTF-8
responses.
To generated UTF-8 encoded responses, set Response.ContentType as follows before setting Response.Content.
Response.ContentType := 'text/html; charset=UTF-8';
Note that, if your application uses the ReqMulti unit to parse multipart content, ReqMulti must appear in the application
uses list after UTF8ContentParser.
TWebResponse 遇到 Unicode
TWebResponse.Content 雖然是 string 類別,但裡面是這樣寫的:
procedure TWebResponse.SetUnicodeContent(const AValue: string);
var
AAnsiString: AnsiString;
Bytes: TBytes;
begin
Bytes := EncodingGetBytes(ContentType, AValue);
SetLength(AAnsiString, Length(Bytes));
if Length(AAnsiString) > 0 then
SetString(AAnsiString, PAnsiChar(@Bytes[0]), Length(Bytes));
SetContent(AAnsiString);
end;
這裡有兩個狀況:
1. TWebResponse.ContentType 只會抓一組設定
往往後端回傳完整 HTML 頁面時,ContentType 會設定如下:text/html; charset="utf-8"
但在讀取 ContentType 時卻只會讀到前面的【text/html】。
這也是內容處理錯誤的第一個問題。
2. SetContent 只吃 AnsiString
1 2 3 4 5 6 7 8 9 10 |
procedure TWebResponse.SetUnicodeContent(const AValue: string); var AAnsiString, LContentType: AnsiString; Bytes: TBytes; begin LContentType := 'CHARSET="UTF-8"'; Bytes := EncodingGetBytes(LContentType, AValue); AAnsiString := AnsiString(TEncoding.UTF8.GetString(Bytes)); SetContent(AAnsiString); // Alway only AnsiString... This is bug... end; |
看到這裡就知完全無解,只能放任這個 Bug 繼續橫行,轉由 ContentStream
自行轉碼輸出的手段了。
所幸 Delphi 10.2 後已經解決這個問題,舊版本可以先參考本文的處理方式應急。
希望它能對你有用。 ^_^
============== 2021/10/05 更新 ==============
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
function MBCSString(const s: UnicodeString; CodePage: Word): RawByteString; var enc: TEncoding; bytes: TBytes; begin enc := TEncoding.GetEncoding(CodePage); try bytes := enc.GetBytes(s); SetLength(Result, Length(bytes)); Move(Pointer(bytes)^, Pointer(Result)^, Length(bytes)); SetCodePage(Result, CodePage, False); finally enc.Free; end; end; |
在 Delphi 裡使用【多位元組字元集(MBCS) 的支援】方案。
只要使用 Response.Content := MBCSString,可以確實解決此問題。
和你分享 😉
See also:
- Decode from URL encoded format
- How to convert UTF-8 byte[] to string?
- How to convert strings to array of byte and back
- Getting non-ASCII characters into WebBroker response in Delphi
- HTTPApp.TWebRequest.Query - Delphi DocWiki
- Converting UnicodeString to AnsiString
沒有留言:
張貼留言