首页 > 解决方案 > 如何在 TIdMultiPartFormDataStream 中包含文件以与 Indy IdHTTP1.Post 一起使用?

问题描述

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

有问题的帖子调用了商业公司 API (ElasticEmail) 中的一个函数,该函数向其中一个参数中保存的收件人发送电子邮件。(关于我正在调用的函数的文档的链接在这里。我在这里 有来自公司的 C# 和其他语言的示例代码, 我试图在下面的 Delphi 代码中复制该代码。

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

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

执行,然后不发送电子邮件,服务器的响应是

{"success":false,"error":"其中一个文件的文件名中包含无效字符。"}

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

阅读有关此主题的其他 SO 帖子,我还尝试将 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 问题

带有 indy 的 Http Post

使用 indy / delphi 组件通过 https 发布文件

将文件作为表单的一部分发布

Nodejs POST 请求多部分/表单数据

特别是

使用 Indy TidHttp 组件通过 sendgrid 发送邮件文件附件

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

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

PS我在英国,所以很抱歉延迟回复美国的评论/答案

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;

标签: filedelphipostidhttp

解决方案


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

您错误地转义\了文件路径中的字符。这在 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参数,而不是像 Elastic 示例中所示的那样使用file0file1等名称。

改变这个:

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()将 a 作为输入的重载方法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 的文档没有说明文件名中包含非 ASCII/保留字符所需的编码。对于通过 HTTP 传输时如何编码此类文件名,存在相互冲突的标准。默认情况下,TIdMultipartFormDataStream根据RFC 2047对文件名进行编码。如果这对 Elastic 来说是个问题(您的示例文件名中有空格字符,我忘记了TIdMultipartFormDataStreamRFC 是否因为空格对文件名进行编码,希望不会),您可以TIdMultipartFormDataStream通过设置受影响文件的TIdFormDataField.HeaderEncoding属性为'8'(对于 8 位),然后您可以将该TIdFormDataField.FileName属性设置为您想要的任何编码:

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

推荐阅读