2

Indy 10.5.8 経由で multipart/form データの POST を使用してファイルを送信しようとしています。私は Delphi XE2 を使用しており、ファイルをサーバーに POST しようとしています。これを試したのはこれが初めてで、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

私が間違っていることについて何か考えはありますか?

前もって感謝します。

4

2 に答える 2

6

問題の根本は、カスタムTStreamコードが Delphi の D2009 以降のバージョンと互換性がないことです。Delphi のStringPChar型は ANSI ではなくなりましたが、コードは 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;
于 2012-07-16T21:10:06.890 に答える
0

.これらの文字はすべて00バイトを表していますか? それは ASCII->UTF16 変換の結果のように見えるからです。また、Content-Disposition のガベージは、バイトをコピーしてバッファの最後を間違って取得し、破損した文字列を残すような変換に関連している可能性があります。

これを一貫して再現するコードを考え出すことができる場合は、Indy フォーラムにバグ レポートを投稿する必要があります。

于 2012-07-16T17:48:01.173 に答える