我正在尝试为网络服务实施POST
。我需要发送一个类型可变的文件(.docx
,.pdf
,.txt
)以及一个JSON格式的字符串。
我已成功发布文件,其代码类似于以下内容:
procedure DoRequest;
var
Http: TIdHTTP;
Params: TIdMultipartFormDataStream;
RequestStream, ResponseStream: TStringStream;
JRequest, JResponse: TJSONObject;
url: string;
begin
url := 'some_custom_service'
JRequest := TJSONObject.Create;
JResponse := TJSONObject.Create;
try
JRequest.AddPair('Pair1', 'Value1');
JRequest.AddPair('Pair2', 'Value2');
JRequest.AddPair('Pair3', 'Value3');
Http := TIdHTTP.Create(nil);
ResponseStream := TStringStream.Create;
RequestStream := TStringStream.Create(UTF8Encode(JRequest.ToString));
try
Params := TIdMultipartFormDataStream.Create;
Params.AddFile('File', ceFileName.Text, '').ContentTransfer := '';
Params.AddFormField('Json', 'application/json', '', RequestStream);
Http.Post(url, Params, ResponseStream);
JResponse := TJSONObject.ParseJSONValue(ResponseStream.DataString) as TJSONObject;
finally
RequestStream.Free;
ResponseStream.Free;
Params.Free;
Http.Free;
end;
finally
JRequest.Free;
JResponse.Free;
end;
end;
当我尝试发送包含文件名中的希腊字符和空格的文件时,会出现问题。有时会失败,有时会成功。
经过大量研究后,我注意到POST
标题由Indy的TIdFormDataField
类使用EncodeHeader()
函数编码。当帖子失败时,标题中的编码文件名被拆分,与未拆分的成功帖子相比。
例如:
Επιστολή εκπαιδευτικο.docx
编码为=?UTF-8?B?zpXPgM65z4PPhM6/zrvOriDOtc66z4DOsc65zrTOtc+Fz4TOuc66zr8uZG9j?='#$D#$A' =?UTF-8?B?eA==?=
,但失败。Επιστολή εκπαιδευτικ.docx
编码为
=?UTF-8?B?zpXPgM65z4PPhM6/zrvOriDOtc66z4DOsc65zrTOtc+Fz4TOuc66LmRvY3g=?=
,成功。Επιστολή εκπαιδευτικ .docx
编码为
=?UTF-8?B?zpXPgM65z4PPhM6/zrvOriDOtc66z4DOsc65zrTOtc+Fz4TOuc66?= .docx
,失败了。我尝试更改文件名的编码,AContentType
过程的AddFile()
和ContentTransfer
,但这些都没有改变行为,我仍然会收到错误当编码的文件名被拆分时。
这是某种错误,还是我错过了什么?
我的代码适用于除上述情况之外的每个案例。
我正在使用Delphi XE3和Indy10。
答案 0 :(得分:1)
EncodeHeader()
确实存在Unicode字符串的一些已知问题:
基本上,MIME编码的单词长度不能超过75个字符,因此长文本会被拆分。但是,当编码一个长Unicode字符串时,任何给定的Unicode字符可能使用1个或多个字节进行字符集编码,EncodeHeader()
还没有避免错误地将两个单独字节之间的多字节字符拆分为单独的编码字(是非法的,并且明确禁止RFC 2047 MIME规范。
但是,这不是您的示例中发生的事情。
在您的第一个示例中,'Επιστολή εκπαιδευτικο.docx'
太长而无法编码为单个MIME字,因此会将其拆分为'Επιστολή εκπαιδευτικο.doc'
'x'
个子字符串,然后单独编码。 这对于长文本来说是合法的(尽管您可能希望Indy将文本拆分为'Επιστολή'
' εκπαιδευτικο.doc'
,甚至'Επιστολή'
{{1} } ' εκπαιδευτικο'
。这可能是未来发布的可能性)。仅由空格分隔的相邻MIME单词意味着在解码时不会分隔空格而连接在一起,从而再次生成'.doc'
。如果服务器没有这样做,它的解码器有一个缺陷(可能是解码为'Επιστολή εκπαιδευτικο.docx'
而不是?)。
在第二个示例中,'Επιστολή εκπαιδευτικο.doc x'
足够短,可以编码为单个MIME字。
在第三个示例中,'Επιστολή εκπαιδευτικ.docx'
在第二个空格(而不是第一个)上被拆分为'Επιστολή εκπαιδευτικ .docx'
'Επιστολή εκπαιδευτικ'
子字符串,并且只需要对第一个子字符串进行编码。 这在MIME中是合法的。解码后,解码后的文本将与以下未编码的文本连接,保留它们之间的空白,从而再次生成' .docx'
。如果服务器没有这样做,它的解码器有一个缺陷(可能是解码为'Επιστολή εκπαιδευτικ .docx'
而不是?)。
如果您通过Indy的MIME标头编码器/解码器运行这些示例文件名,它们会正确解码:
'Επιστολή εκπαιδευτικ.docx'
所以问题似乎是在服务器端解码,而不是在Indy的客户端编码。
话虽如此,如果您使用的是最新版本的Indy 10(2011年11月或之后),var
s: String;
begin
s := EncodeHeader('Επιστολή εκπαιδευτικο.docx', '', 'B', 'UTF-8');
ShowMessage(s); // '=?UTF-8?B?zpXPgM65z4PPhM6/zrvOriDOtc66z4DOsc65zrTOtc+Fz4TOuc66zr8uZG9j?='#13#10' =?UTF-8?B?eA==?='
s := DecodeHeader(s);
ShowMessage(s); // 'Επιστολή εκπαιδευτικο.docx'
s := EncodeHeader('Επιστολή εκπαιδευτικ.docx', '', 'B', 'UTF-8');
ShowMessage(s); // '=?UTF-8?B?zpXPgM65z4PPhM6/zrvOriDOtc66z4DOsc65zrTOtc+Fz4TOuc66LmRvY3g=?='
s := DecodeHeader(s);
ShowMessage(s); // 'Επιστολή εκπαιδευτικ.docx'
s := EncodeHeader('Επιστολή εκπαιδευτικ .docx', '', 'B', 'UTF-8');
ShowMessage(s); // '=?UTF-8?B?zpXPgM65z4PPhM6/zrvOriDOtc66z4DOsc65zrTOtc+Fz4TOuc66?= .docx'
s := DecodeHeader(s);
ShowMessage(s); // 'Επιστολή εκπαιδευτικ .docx'
end;
具有TIdFormDataField
属性,默认为HeaderEncoding
(base64)在Unicode环境中。但是,分裂逻辑也会影响'B'
(引用可打印),因此可能适用于您,也可能不适合您(但您可以尝试):
'Q'
否则,解决方法可能是将值更改为with Params.AddFile('File', ceFileName.Text, '') do
begin
ContentTransfer := '';
HeaderEncoding := 'Q'; // <--- here
HeaderCharSet := 'utf-8';
end;
(8位),这有效地禁用了MIME编码(但不是字符集编码):
'8'
请注意,如果服务器不期望文件名的原始UTF-8字节,您可能仍会遇到问题(例如,with Params.AddFile('File', ceFileName.Text, '') do
begin
ContentTransfer := '';
HeaderEncoding := '8'; // <--- here
HeaderCharSet := 'utf-8';
end;
被解释为'Επιστολή εκπαιδευτικο.docx'
。