如何在 TIdMultiPartFormDataStream 中包含一个文件以用于 Indy IdHTTP1.Post?

How do I include a file in a TIdMultiPartFormDataStream for use with Indy IdHTTP1.Post?

使用 Indy 的 idHTTP.post 发送一个或多个任何类型的文件以及其他参数时需要使用的正确代码是什么? (使用 Delphi 2009 和 Indy 10)

所讨论的 post 调用商业公司 API (ElasticEmail) 中的一个函数,该函数向参数之一中保存的收件人发送电子邮件。 (关于我正在调用的函数的文档的 link 是 here. 我有来自公司 here 的 C# 和其他语言的示例代码,我试图在下面的 Delphi 代码中复制该代码。

如果在过程 btnSendbyElastic 中,我注释掉行 Filenames.add(Afilename); 以便函数 Upload 不尝试附加文件,那么正确的调用似乎是在电子邮件成功发送时进行的API。 但是,如果我保留该行,以便函数 UpLoad

中的行
MimeStr := GetMIMETypeFromFile(filenames[i]);
FormData.Addfile('file'+inttostr(i), filenames[i],MIMEStr); 

do 被执行,然后没有邮件被发送,服务器的响应是

{"success":false,"error":"One of files has invalid characters in file name."}

(文件 Afilename 确实存在于该位置,我已尝试使用单反斜杠和双反斜杠)

阅读关于此主题的其他 SO posts 我还尝试用以下循环替换 Function UpLoad 中的文件处理循环

for i := 0 to filenames.Count - 1 do
    begin
    MimeStr := GetMIMETypeFromFile(filenames[i]); 
    FormData.AddFile('file'+inttostr(i), filenames[i],MIMEStr);
    AttachmentContent  := TFileStream.Create(filenames[i],fmOpenRead);
    try
        FormData.AddFormField(AttachmentContent.ToString,filenames[i]);
    finally
        AttachmentContent.free;
    end;
end; 

这一次,即使使用 Filenames.add(Afilename); 中指定的文件名,电子邮件也能正确发送,但收件人看不到附件。

在许多其他问题中,我已经阅读了这些可能重复的 SO 问题

Nodejs POST request multipart/form-data

特别是

(这几乎正是我想要做的)但我仍然看不出我在代码中做错了什么以及我需要做些什么来纠正它。

这是我正在使用的代码(UPPER_CASE 标识符是在别处定义的常量)

PS 我在英国,很抱歉回复美国的时间延迟 comments/answers

function TForm1.Upload(url: string; params, filenames: Tstringlist): string;
var
 FormData : TIdMultiPartFormDataStream;
 MIMEStr, ResponseText : string;
 i : integer;
begin
  try
  FormData := TIdMultiPartFormDataStream.Create;
  for i := 0 to params.Count - 1 do
        FormData.AddFormField(params.Names[i],params.values[params.Names[i]]);
   for i := 0 to filenames.Count - 1 do
     begin
     MimeStr := GetMIMETypeFromFile(filenames[i]); 
     FormData.Addfile(filenames[i], filenames[i],MIMEStr);
     end;
  ResponseText :=IdHTTP1.Post(url, FormData);
  Memo1.Text := ResponseText; //debug
  finally
  FormData.free;
  end;
end;

procedure TForm1.btnSendbyElastic(Sender: TObject);
var
Params, Filenames : Tstringlist;
url, Afilename : string;
begin
Afilename := 'C:\Users\Admin\Documents\arrival and departure small.pdf';
Params := Tstringlist.Create;
Filenames  := Tstringlist.Create;
try
  Params.add('apikey=' + ELASTIC_MAIL_API_KEY) ;
  Params.add('from=' + ELASTIC_EMAIL_FROM_EMAIL) ;
  Params.add('fromname=' + ELASTIC_EMAIL_FROM_NAME) ;
  Params.add('Subject=' + 'The Subject') ;
  Params.add('bodyHtml=' + '<h1>Html Body</h1>') ;
  Params.add('bodyText=' + 'Text Body') ;
  Params.add('to=' + THE_RECIPIENT_ADDRESS) ;
  Filenames.add(Afilename); //*** comment out this line and an email is sent correctly
  url := ELASTIC_EMAIL_EMAIL_SEND  ;
  Upload (url , params, filenames );
finally
  Params.free;
  Filenames.free;
end;

函数GetMIMETypeFromFile定义在Indy单元idGlobalProtocols中。我没有写它,我只是调用它。不过我已经按照要求转载在这里了

function GetMIMETypeFromFile(const AFile: TIdFileName): string;
var
  MIMEMap: TIdMIMETable;
begin
  MIMEMap := TIdMimeTable.Create(True);
  try
    Result := MIMEMap.GetFileMIMEType(AFile);
  finally
    MIMEMap.Free;
  end;
end;

我发现您的代码存在一些问题。

您在文件路径中错误地转义了 \ 个字符。这在 C 和 C++ 等语言中是必需的,但在 Delphi 中根本不需要,所以去掉它。

改变这个:

Afilename := 'C:\Users\Admin\Documents\arrival and departure small.pdf';

为此:

Afilename := 'C:\Users\Admin\Documents\arrival and departure small.pdf';

我看到的下一个问题是您在将文件附件字段添加到 TIdMultipartFormDataStream 时没有正确命名它们。

调用 AddFile() 时,您将按原样将完整的文件路径传递给 AFieldName 参数,而不是使用 file0file1 等名称如 Elastic 的示例所示。

改变这个:

FormData.Addfile(filenames[i], filenames[i],MIMEStr);

对此1:

FormData.AddFile('file'+IntToStr(i), filenames[i], MIMEStr);

1:仅供参考,无需手动调用 GetMIMETypeForFile(),如果您不提供字符串,AddFile() 会在内部为您调用 GetMIMETypeForFile() AContentType参数,例如FormData.AddFile('file'+IntToStr(i), filenames[i]);

当您尝试使用 AddFormField() 而不是 AddFile() 添加附件时,您犯了类似的错误。您将每个文件的实际数据内容用于 AFieldName 参数,而不是将内容用于 AFieldValue 参数。

在这种情况下,更改为:

FormData.AddFormField(AttachmentContent.ToString,filenames[i]);

为此:

FormData.AddFormField('file'+IntToStr(i), AttachmentContent.ToString, '', MIMEStr, filenames[i]);

或者,由于您自己打开 TFileStream 对象,您可以使用重载的 AddFormField() 方法,该方法将 TStream 作为输入(只是确保不要释放 TStream 个对象,直到您使用完 TIdMultipartFormDataStream!):

AttachmentContent := TFileStream.Create(filenames[i], fmOpenRead);
FormData.AddFormField('file'+IntToStr(i), MIMEStr, '', AttachmentContent, filenames[i]);

话虽如此,试试这样的东西:

function TForm1.Upload(url: string; params, filenames: TStrings): string;
var
 FormData : TIdMultiPartFormDataStream;
 ResponseText : string;
 i : integer;
begin
  FormData := TIdMultiPartFormDataStream.Create;
  try
    for i := 0 to params.Count - 1 do
      FormData.AddFormField(params.Names[i], params.ValueFromIndex[i]);

    for i := 0 to filenames.Count - 1 do
      FormData.AddFile('file'+IntToStr(i), filenames[i]);

    ResponseText := IdHTTP1.Post(url, FormData);
    Memo1.Text := ResponseText; //debug
  finally
    FormData.Free;
  end;
end;

procedure TForm1.btnSendbyElastic(Sender: TObject);
var
  Params, Filenames : TStringList;
  url, Afilename : string;
begin
  Afilename := 'C:\Users\Admin\Documents\arrival and departure small.pdf';
  Params := TStringList.Create;
  try
    Params.Add('apikey=' + ELASTIC_MAIL_API_KEY);
    Params.Add('from=' + ELASTIC_EMAIL_FROM_EMAIL);
    Params.Add('fromname=' + ELASTIC_EMAIL_FROM_NAME);
    Params.Add('Subject=' + 'The Subject');
    Params.Add('bodyHtml=' + '<h1>Html Body</h1>');
    Params.Add('bodyText=' + 'Text Body');
    Params.Add('to=' + THE_RECIPIENT_ADDRESS);

    Filenames := TStringList.Create;
    try
      Filenames.Add(Afilename);

      url := ELASTIC_EMAIL_EMAIL_SEND;
      Upload(url, params, filenames);
    finally
      Filenames.Free;
    end;
  finally
    Params.Free;
  end;
end;

最后,Elastic 的文档没有说明包含 non-ASCII/reserved 个字符的文件名所需的编码。关于在通过 HTTP 传输时应如何对此类文件名进行编码,存在相互冲突的标准。默认情况下,TIdMultipartFormDataStream 根据 RFC 2047 对文件名进行编码。如果这对 Elastic 来说是个问题(你的示例文件名中有 space 个字符,我忘记了 TIdMultipartFormDataStream RFC 是否由于 spaces 对文件名进行了编码,希望不会),您可以通过将受影响文件的 TIdFormDataField.HeaderEncoding 属性 设置为 '8' (对于 8 位)来禁用 TIdMultipartFormDataStream 的默认编码,然后您可以设置 TIdFormDataField.FileName 属性 到你想要的任何编码:

with FormData.AddFile('file'+IntToStr(i), filenames[i]) do
begin
  HeaderEncoding := '8';
  FileName := EncodeFilenameMyWay(ExtractFileName(filenames[i]));
end;