如何在 TIdHTTPServer 中接收包含外来字符的查询字符串
How to receive query string containing foreign characters in TIdHTTPServer
我在 Delphi XE2 中使用 TIdHTTPServer
作为基本的 HTML 服务器来获取来自网络的请求,处理它们并返回所需的响应。
问题是当有人打开像 localhost:5678/book?name=Петров
这样的页面时,我无法正确接收名称“Петров”。
至此程序就简单了:
procedure TMain.IdHTTPServer1CommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
Aux_S1 : String;
Aux_S2 : String;
begin
Aux_S1 := ARequestInfo.Params[0];
Aux_S2 := System.UTF8Decode(ARequestInfo.Params[0]);
end;
Aux_S1
是 'name=Ð'#[=14=]9F'еÑ'#[=14=]82'Ñ'#[=14=]80'ов'
Aux_S2
是 'name=�?е�?�?ов'
有些字母显示正确,但有些字母显示不正确。
我做错了什么,或者我应该如何处理这些请求?
A URL 不允许包含 non-ASCII 个字符。此类字符必须 charset-encoded 成字节,然后在放入 URL 时以 %HH
格式编码。因此,您的客户实际使用的 URL 更像是这样:
http://localhost:5678/book?name=%D0%9F%D0%B5%D1%82%D1%80%D0%BE%D0%B2
%D0%9F%D0%B5%D1%82%D1%80%D0%BE%D0%B2
是 UTF-8 percent-encoded 格式的 Петров
。
A URL 无法指定用于此类编码的字符集。由服务器决定。不过,UTF-8 是最常用的字符集编码。
TIdHTTPServer
在触发 OnCommandGet
事件之前自动解析和解码 URL 查询字符串,如果 ParseParams
属性 为真默认情况下)。所以不要直接在参数字符串上调用 UTF8Decode()
,因为它不会起作用。
不幸的是,TIdHTTPServer
目前不允许您指定使用哪个字符集来解码查询字符串(在 TODO 列表中)。它所做的是检查请求是否在 Content-Type
header 中包含 charset
属性,如果是则使用它(尽管这不是标准的 HTTP 服务器行为),否则它使用Indy 的 built-in 8 位编码代替。
后一种情况通常发生在 GET
请求中,因为它们不携带 Content-Type
header。不过,这对您有利(请参阅下文)。字符串值:
'Ð'#[=11=]9F'еÑ'#[=11=]82'Ñ'#[=11=]80'ов'
实际上 Петров
的原始 UTF-8 字节在解码为 UnicodeString
:
时被解释为 8 位 "characters"
#[=12=]D0 #[=12=]9F #[=12=]D0 #[=12=]B5 #[=12=]D1 #[=12=]82 #[=12=]D1 #[=12=]80 #[=12=]D0 #[=12=]BE #[=12=]D0 #[=12=]B2
因此,您可以 "fix" 通过手动将解码的参数字符串转换回原始字节,然后将它们解码为 UTF-8 回字符串来 "fix" 这种解码不匹配,例如:
procedure TMain.IdHTTPServer1CommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
Aux_S1: String;
begin
// if you are not using Indy 10.6+, you can replace
// IndyTextEncoding_UTF8 with TIdTextEncoding.UTF8,
// and IndyTextEncoding_8bit with Indy8BitEncoding...
//
//Aux_S1 := TIdTextEncoding.UTF8.GetString(ToBytes(ARequestInfo.Params[0], Indy8BitEncoding));
Aux_S1 := IndyTextEncoding_UTF8.GetString(ToBytes(ARequestInfo.Params[0], IndyTextEncoding_8bit));
end;
或者,将 ParseParams
设置为 false 并手动解码 ARequestInfo.QueryParams
字符串(来自 URL 的原始 percent-encoded 数据)改为:
procedure DecodeParams(const AValue: String; Params: TStrings);
var
i, j : Integer;
s: string;
// if you are not using Indy 10.6+, you can replace
// IIdTextEncoding with TIdTextEncoding...
//
//LEncoding: TIdTextEncoding;
LEncoding: IIdTextEncoding;
begin
// Convert special characters
// ampersand '&' separates values {Do not Localize}
Params.BeginUpdate;
try
Params.Clear;
// if you are not using Indy 10.6+, you can replace
// IndyTextEncoding_UTF8 with TIdTextEncoding.UTF8...
//
//LEncoding := TIdTextEncoding.UTF8;
LEncoding := IndyTextEncoding_UTF8;
i := 1;
while i <= Length(AValue) do
begin
j := i;
while (j <= Length(AValue)) and (AValue[j] <> '&') do {do not localize}
begin
Inc(j);
end;
s := Copy(AValue, i, j-i);
// See RFC 1866 section 8.2.1. TP
s := ReplaceAll(s, '+', ' '); {do not localize}
Params.Add(TIdURI.URLDecode(s, LEncoding));
i := j + 1;
end;
finally
Params.EndUpdate;
end;
end;
procedure TMain.IdHTTPServer1CommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
Aux_S1: String;
begin
DecodeParams(LRequestInfo.QueryParams, ARequestInfo.Params);
Aux_S1 := ARequestInfo.Params[0];
end;
我在 Delphi XE2 中使用 TIdHTTPServer
作为基本的 HTML 服务器来获取来自网络的请求,处理它们并返回所需的响应。
问题是当有人打开像 localhost:5678/book?name=Петров
这样的页面时,我无法正确接收名称“Петров”。
至此程序就简单了:
procedure TMain.IdHTTPServer1CommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
Aux_S1 : String;
Aux_S2 : String;
begin
Aux_S1 := ARequestInfo.Params[0];
Aux_S2 := System.UTF8Decode(ARequestInfo.Params[0]);
end;
Aux_S1
是 'name=Ð'#[=14=]9F'еÑ'#[=14=]82'Ñ'#[=14=]80'ов'
Aux_S2
是 'name=�?е�?�?ов'
有些字母显示正确,但有些字母显示不正确。
我做错了什么,或者我应该如何处理这些请求?
A URL 不允许包含 non-ASCII 个字符。此类字符必须 charset-encoded 成字节,然后在放入 URL 时以 %HH
格式编码。因此,您的客户实际使用的 URL 更像是这样:
http://localhost:5678/book?name=%D0%9F%D0%B5%D1%82%D1%80%D0%BE%D0%B2
%D0%9F%D0%B5%D1%82%D1%80%D0%BE%D0%B2
是 UTF-8 percent-encoded 格式的 Петров
。
A URL 无法指定用于此类编码的字符集。由服务器决定。不过,UTF-8 是最常用的字符集编码。
TIdHTTPServer
在触发 OnCommandGet
事件之前自动解析和解码 URL 查询字符串,如果 ParseParams
属性 为真默认情况下)。所以不要直接在参数字符串上调用 UTF8Decode()
,因为它不会起作用。
不幸的是,TIdHTTPServer
目前不允许您指定使用哪个字符集来解码查询字符串(在 TODO 列表中)。它所做的是检查请求是否在 Content-Type
header 中包含 charset
属性,如果是则使用它(尽管这不是标准的 HTTP 服务器行为),否则它使用Indy 的 built-in 8 位编码代替。
后一种情况通常发生在 GET
请求中,因为它们不携带 Content-Type
header。不过,这对您有利(请参阅下文)。字符串值:
'Ð'#[=11=]9F'еÑ'#[=11=]82'Ñ'#[=11=]80'ов'
实际上 Петров
的原始 UTF-8 字节在解码为 UnicodeString
:
#[=12=]D0 #[=12=]9F #[=12=]D0 #[=12=]B5 #[=12=]D1 #[=12=]82 #[=12=]D1 #[=12=]80 #[=12=]D0 #[=12=]BE #[=12=]D0 #[=12=]B2
因此,您可以 "fix" 通过手动将解码的参数字符串转换回原始字节,然后将它们解码为 UTF-8 回字符串来 "fix" 这种解码不匹配,例如:
procedure TMain.IdHTTPServer1CommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
Aux_S1: String;
begin
// if you are not using Indy 10.6+, you can replace
// IndyTextEncoding_UTF8 with TIdTextEncoding.UTF8,
// and IndyTextEncoding_8bit with Indy8BitEncoding...
//
//Aux_S1 := TIdTextEncoding.UTF8.GetString(ToBytes(ARequestInfo.Params[0], Indy8BitEncoding));
Aux_S1 := IndyTextEncoding_UTF8.GetString(ToBytes(ARequestInfo.Params[0], IndyTextEncoding_8bit));
end;
或者,将 ParseParams
设置为 false 并手动解码 ARequestInfo.QueryParams
字符串(来自 URL 的原始 percent-encoded 数据)改为:
procedure DecodeParams(const AValue: String; Params: TStrings);
var
i, j : Integer;
s: string;
// if you are not using Indy 10.6+, you can replace
// IIdTextEncoding with TIdTextEncoding...
//
//LEncoding: TIdTextEncoding;
LEncoding: IIdTextEncoding;
begin
// Convert special characters
// ampersand '&' separates values {Do not Localize}
Params.BeginUpdate;
try
Params.Clear;
// if you are not using Indy 10.6+, you can replace
// IndyTextEncoding_UTF8 with TIdTextEncoding.UTF8...
//
//LEncoding := TIdTextEncoding.UTF8;
LEncoding := IndyTextEncoding_UTF8;
i := 1;
while i <= Length(AValue) do
begin
j := i;
while (j <= Length(AValue)) and (AValue[j] <> '&') do {do not localize}
begin
Inc(j);
end;
s := Copy(AValue, i, j-i);
// See RFC 1866 section 8.2.1. TP
s := ReplaceAll(s, '+', ' '); {do not localize}
Params.Add(TIdURI.URLDecode(s, LEncoding));
i := j + 1;
end;
finally
Params.EndUpdate;
end;
end;
procedure TMain.IdHTTPServer1CommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
Aux_S1: String;
begin
DecodeParams(LRequestInfo.QueryParams, ARequestInfo.Params);
Aux_S1 := ARequestInfo.Params[0];
end;