2012-07-16 67 views
2

我试图通过Indy 10.5.8在多部分/表单数据中使用POST发送文件。我正在使用Delphi XE2,并且我一直在尝试将文件发布到服务器。这是杉杉的时间我已经试过这一点,因为我与印经验是非常有限的,我把下面的代码片段:在使用Indy进行POST时使用标题垃圾10.5.8

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

有您为什么要写一个模仿Indy的'TIdMultipartFormDataStream'类自己的T流类,而不是仅仅使用Indy的'TIdMultipartFormDataStream'类的,是什么原因呢? – 2012-07-16 21:03:12

+0

不,这只是样本代码的不幸选择。我立刻将其删除,并将其替换为Indy的原创课程,并且工作正常。 – 2012-07-17 07:30:28

回答

6

bug报告问题的根源是,您的自定义TStream代码与D2009 +德尔福的版本兼容。 Delphi的StringPChar类型不再是Ans​​i,但代码假设它们仍然是。它们现在是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; 
+0

谢谢@Remy,你是男人。修复了我的代码。 – 2012-07-17 07:31:56

0

所有这些.字符是否代表00字节?因为这看起来像是ASCII-> UTF16转换的结果。 Content-Disposition中的垃圾可能与转换一样,就像复制字节一样,并使缓冲区结尾错误,给您带来一个损坏的字符串。

如果你能拿出代码来始终如一地重现此,你应该张贴在the Indy forums.

+0

看来这些点是00字节。有什么办法可以避免转换吗? – 2012-07-16 18:59:39

+1

问题是您的自定义'TStream'代码没有考虑到Delphi字符串不再是Ans​​i,它们现在是Unicode。该代码将16位Unicode数据(只有一半的数据)写入流中,而不是写入8位Ansi数据。那是所有这些空值都来自哪里。完全摆脱你自定义的'TStream'代码,改用Indy的原生'TIdMultipartFormDataStream'类。它知道如何正确处理Unicode字符串。 – 2012-07-16 21:15:18