我正在尝试通过Indy 10.5.8在multipart / form数据中使用POST发送文件。我正在使用Delphi XE2,我一直在尝试将文件发布到服务器上。这是我第一次尝试这个,因为我对Indy的经验非常有限,我采用了以下代码片段:
unit MsMultiPartFormData;
interface
uses
SysUtils, Classes;
const
CONTENT_TYPE = 'multipart/form-data; boundary=';
CRLF = #13#10;
CONTENT_DISPOSITION = 'Content-Disposition: form-data; name="%s"';
FILE_NAME_PLACE_HOLDER = '; filename="%s"';
CONTENT_TYPE_PLACE_HOLDER = 'Content-Type: %s' + crlf + crlf;
CONTENT_LENGTH = 'Content-Length: %d' + crlf;
type
TMsMultiPartFormDataStream = class(TMemoryStream)
private
FBoundary: string;
FRequestContentType: string;
function GenerateUniqueBoundary: string;
public
procedure AddFormField(const FieldName, FieldValue: string);
procedure AddFile(const FieldName, FileName, ContentType: string; FileData: TStream); overload;
procedure AddFile(const FieldName, FileName, ContentType: string); overload;
procedure PrepareStreamForDispatch;
constructor Create;
property Boundary: string read FBoundary;
property RequestContentType: string read FRequestContentType;
end;
implementation
{ TMsMultiPartFormDataStream }
constructor TMsMultiPartFormDataStream.Create;
begin
inherited;
FBoundary := GenerateUniqueBoundary;
FRequestContentType := CONTENT_TYPE + FBoundary;
end;
procedure TMsMultiPartFormDataStream.AddFile(const FieldName, FileName,
ContentType: string; FileData: TStream);
var
sFormFieldInfo: string;
Buffer: PChar;
iSize: Int64;
begin
iSize := FileData.Size;
sFormFieldInfo := Format(CRLF + '--' + Boundary + CRLF + CONTENT_DISPOSITION +
FILE_NAME_PLACE_HOLDER + CRLF + CONTENT_LENGTH +
CONTENT_TYPE_PLACE_HOLDER, [FieldName, FileName, iSize, ContentType]);
{so: boundary + crlf + content-disposition+file-name-place-holder}
Write(Pointer(sFormFieldInfo)^, Length(sFormFieldInfo));
FileData.Position := 0;
GetMem(Buffer, iSize);
try
FileData.Read(Buffer^, iSize);
Write(Buffer^, iSize);
finally
FreeMem(Buffer, iSize);
end;
end;
procedure TMsMultiPartFormDataStream.AddFile(const FieldName, FileName,
ContentType: string);
var
FileStream: TFileStream;
begin
FileStream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
AddFile(FieldName, FileName, ContentType, FileStream);
finally
FileStream.Free;
end;
end;
procedure TMsMultiPartFormDataStream.AddFormField(const FieldName,
FieldValue: string);
var
sFormFieldInfo: string;
begin
sFormFieldInfo := Format(CRLF + '--' + Boundary + CRLF + CONTENT_DISPOSITION + CRLF + CRLF +
FieldValue, [FieldName]);
Write(Pointer(sFormFieldInfo)^, Length(sFormFieldInfo));
end;
function TMsMultiPartFormDataStream.GenerateUniqueBoundary: string;
begin
Result := '---------------------------' + FormatDateTime('mmddyyhhnnsszzz', Now);
end;
procedure TMsMultiPartFormDataStream.PrepareStreamForDispatch;
var
sFormFieldInfo: string;
begin
sFormFieldInfo := CRLF + '--' + Boundary + '--' + CRLF;
Write(Pointer(sFormFieldInfo)^, Length(sFormFieldInfo));
Position := 0;
end;
end.
我正在调用这样的代码:
function PostFile(filename, apikey: string): boolean;
var
ResponseStream: TMemoryStream;
MultiPartFormDataStream: TMsMultiPartFormDataStream;
begin
// Form5.IdHTTP1.HandleRedirects := true;
Form5.idHTTP1.ReadTimeout := 0;
// Form5.idHTTP1.IOHandler.LargeStream := True;
Result := false;
MultiPartFormDataStream := TMsMultiPartFormDataStream.Create;
ResponseStream := TMemoryStream.Create;
try
try
Form5.IdHttp1.Request.ContentType := MultiPartFormDataStream.RequestContentType;
MultiPartFormDataStream.AddFormField('apikey', apikey);
MultiPartFormDataStream.AddFile('file', filename, 'multipart/form-data');
MultiPartFormDataStream.PrepareStreamForDispatch;
MultiPartFormDataStream.Position := 0;
Form5.IdHTTP1.Post('http://www.updserver.tld/api//file/save', MultiPartFormDataStream, ResponseStream);
MultiPartFormDataStream.SaveToFile(ExtractFilePath(Application.ExeName) + 'a.txt');
Result := true;
except
on E:Exception do
begin
Form5.Close;
ShowMessage('Upload failed! '+E.Message);
end;
end;
finally
MultiPartFormDataStream.Free;
ResponseStream.Free;
end;
end;
文件被发送,但被服务器拒绝。仔细检查发送的数据表明数据有点腐败(我怀疑编码问题) - 我看到的是:
POST /api/file/save HTTP/1.0
Connection: keep-alive
Content-Type: multipart/form-data; boundary=---------------------------071312151405662
Content-Length: 11040172
Host: www.updserver.tld
Accept: text/html, */*
Accept-Encoding: identity
User-Agent: Mozilla/3.0 (compatible; Indy Library)
.
.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.0.7.1.3.1.2.1.5.1.4.0.5.6.6.2.
.
.C.o.n.t.e.n.t.-.D.i.s.p.o.s.i.t.i.o.n.:. .f.o.r.m.-.d.a.t.a.;. .n
.
.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.0.7.1.3.1.2.1.5.1.4.0.5.6.6.2.
.
.C.o.n.t.e.n.t.-.D.i.s.p.o.s.i.t.i.o.n.:. .f.o.r.m.-.d.a.t.a.;. .n.a.m.e.=.".f.i.l.e.".;. .f.i.l.e.n.a.m.e.=.".C.:.\.U.s.e........................>.......................................................v.......:...;...<.......[.......v.......................t.......o.......z............
...
...
从正常工作的Python客户端发送的常规标头如下所示:
POST https://updserver.tld/api/file/save HTTP/1.0
content-type: multipart/form-data; boundary=---------------------------071312151405662
content-length: 6613364
---------------------------071312151405662
Content-Disposition: form-data; name="apikey"
ac36fae9a406596[rest-of-api-key-goes-here]17966c42b60c8c4cd
---------------------------071312151405662
Content-Disposition: form-data; name="file"; filename="C:\Users\User\Desktop\Project1.exe"
Content-Type: application/octet-stream
对我做错了什么的想法?
提前致谢。
答案 0 :(得分:6)
问题的根源在于您的自定义TStream
代码与D2009 +版本的Delphi不兼容。 Delphi的String
和PChar
类型不再是Ansi,但代码假定它们仍然存在。它们现在是Unicode UTF-16。你没有正确解释,例如:
procedure TMsMultiPartFormDataStream.AddFile(const FieldName, FileName, ContentType: string; FileData: TStream);
var
sFormFieldInfo: AnsiString;
iSize: Int64;
begin
iSize := FileData.Size;
// NOTE: this will only work for ASCII filenames!!!!
//
// Non-ASCII filenames will get converted to Ansi, which can cause data loss.
// To send non-ASCII filenames correctly, you have to encode it to a charset
// first, such as UTF-8, and then encode the resulting bytes using
// MIME's RFC2047 encoding so the server can decode the filename properly
// on its end...
//
sFormFieldInfo := Format(CRLF + '--' + Boundary + CRLF + CONTENT_DISPOSITION +
FILE_NAME_PLACE_HOLDER + CRLF + CONTENT_LENGTH +
CONTENT_TYPE_PLACE_HOLDER, [FieldName, FileName, iSize, ContentType]);
{so: boundary + crlf + content-disposition+file-name-place-holder}
Write(sFormFieldInfo[1], Length(sFormFieldInfo) * SizeOf(AnsiChar));
if iSize > 0 then
begin
FileData.Position := 0;
CopyFrom(FileData, iSize);
end;
end;
procedure TMsMultiPartFormDataStream.AddFormField(const FieldName, FieldValue: string);
var
sFormFieldInfo: AnsiString;
begin
// NOTE: this will only work for ASCII text!!!!
//
// Non-ASCII text will get converted to Ansi, which can cause data loss.
// To send non-ASCII text correctly, you have to encode it to a charset
// first, such as UTF-8 and then encode the resulting bytes using
// MIME's 'quoted-printable' or 'base64' enoding, and then include
// appropriate 'charset' and Content-Transfer-Encoding' headers so the
// server can decode the data properly on its end...
//
sFormFieldInfo := Format(CRLF + '--' + Boundary + CRLF + CONTENT_DISPOSITION + CRLF + CRLF +
FieldValue, [FieldName]);
Write(sFormFieldInfo[1], Length(sFormFieldInfo) * AnsiString(AnsiChar));
end;
procedure TMsMultiPartFormDataStream.PrepareStreamForDispatch;
var
sFormFieldInfo: AnsiString;
begin
sFormFieldInfo := CRLF + '--' + Boundary + '--' + CRLF;
Write(sFormFieldInfo[1], Length(sFormFieldInfo) * SizeOf(AnsiChar));
Position := 0;
end;
话虽如此,我强烈建议你完全摆脱你的自定义TMsMultiPartFormDataStream
课程。它所做的只是模仿Indy自己的TIdMultipartFormDataStream
类的过时版本。只需使用Indy的本地TIdMultipartFormDataStream
类。它为您处理D2009 + Unicode,例如:
uses
..., IdMultipartFormData;
function PostFile(const filename, apikey: string): boolean;
var
ResponseStream: TMemoryStream;
MultiPartFormDataStream: TIdMultiPartFormDataStream;
begin
Result := False;
//Form5.IdHTTP1.HandleRedirects := true;
Form5.idHTTP1.ReadTimeout := 0;
//Form5.idHTTP1.IOHandler.LargeStream := True;
try
ResponseStream := TMemoryStream.Create;
try
MultiPartFormDataStream := TIdMultiPartFormDataStream.Create;
try
MultiPartFormDataStream.AddFormField('apikey', apikey);
MultiPartFormDataStream.AddFile('file', filename, 'application/octet-stream');
Form5.IdHTTP1.Post('http://www.updserver.tld/api/file/save', MultiPartFormDataStream, ResponseStream);
ResponseStream.SaveToFile(ExtractFilePath(Application.ExeName) + 'a.txt');
Result := True;
finally
MultiPartFormDataStream.Free;
end;
finally
ResponseStream.Free;
end;
except
on E:Exception do
begin
Form5.Close;
ShowMessage('Upload failed! ' + E.Message);
end;
end;
end;
end;
答案 1 :(得分:0)
所有这些.
个字符都代表00
个字节吗?因为这看起来像是ASCII-&gt; UTF16转换的结果。 Content-Disposition中的垃圾可能与转换相关,就像复制字节并使缓冲区结束错误一样,留下一个损坏的字符串。
如果您能够提供代码来一致地重现这一点,您应该在the Indy forums.上发布错误报告