【问题标题】:How do I include a file in a TIdMultiPartFormDataStream for use with Indy IdHTTP1.Post?如何在 TIdMultiPartFormDataStream 中包含文件以与 Indy IdHTTP1.Post 一起使用?
【发布时间】:2019-09-10 02:24:18
【问题描述】:

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

有问题的帖子调用了商业公司 API (ElasticEmail) 中的一个函数,该函数向其中一个参数中保存的收件人发送电子邮件。 (我正在调用的函数的文档链接是here. 我有来自公司here 的 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 问题

Http Post with indy

Post a file through https using indy / delphi components

posting a file as part of a form

Nodejs POST request multipart/form-data

特别是

Using the Indy TidHttp component to send email file attachments through sendgrid

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

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

PS 我在英国,很抱歉延迟回复美国 cmets/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)。您从 Elastic 得到的错误非常冗长,“文件名中的字符无效”。尝试一些简单的东西,例如“c:\temp\test.txt”。还请附上GetMIMETypeFromFile 的代码,以便我们尝试重现问题...
  • @whosrdaddy GetMIMETypeFromFile() 是一个 Indy 函数。
  • 啊,汤姆布伦伯格打败了我。无论如何,我已经根据要求编辑了我的帖子以包含它。我同意我不明白为什么我需要双反斜杠。我尝试使用它们,因为 C# 示例有它们,并且在处理网络以及一些 http 调用之前我必须使用它们。无论如何,它们不会给出语法错误,无论我是否使用它们,我都会得到相同的结果。
  • 我确实尝试使用更简单的 pdf 文件名,文件名或路径中没有空格,但没有任何区别 - 已发送电子邮件但没有附件。但我认为问题不在于文件名本身。毕竟我不知道我的用户要附加什么文件,也不能坚持他们只附加简单的文件名。
  • 我还尝试注释掉Params.add('bodyHtml=' ... 这一行,以便没有发送html正文,因为我在某处听说这可能会导致垃圾邮件过滤器出现问题。但是否有 html 正文和文本没有区别。

标签: file delphi post idhttp


【解决方案1】:

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

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

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

【讨论】:

  • 我已经放弃了 elasticmail 并且正在尝试 mailgun 的 api。使用您的代码,我在FormData.AddFile('file'+IntToStr(i), filenames[i]); 行出现“实际参数不足”错误。在 Upload 中因为它需要 3 个参数,所以我将其更改为 MimeStr := GetMIMETypeFromFile(AttachFilePath + AttachFilename); FormData.Addfile('files['+AttachFilename+']', AttachFilePath + AttachFilename,MIMEStr); 现在我收到错误 HTTP/1.1 400 bad request。 url 和参数是正确的,我使用 `IdHTTP1.Request.CustomHeaders.Add(...)` 添加了正确的授权头
  • 顺便说一句,我使用的是 Indy 版本 10.2.5。用户 whosrdaddy 说我应该升级 Indy,但我很担心这样做,因为它看起来相当复杂,而且我不想破坏任何其他运行良好的项目。我的版本不应该也通过 POST 上传文件吗?
  • @user2834566 您使用的是非常旧的 Indy 10 版本,肯定需要升级。当前版本是 10.6.2.5498。是的,您的版本可以通过 POST 上传文件,但它可能存在错误,或者不太符合标准,这些问题多年来已得到修复。 10.2 和 10.6 之间发生了很多变化。
  • 你是对的雷米。昨天我冒险尝试使用隔夜 zip 文件升级到似乎是最新版本的版本。这给了我版本 10.5498。用于编译的 .bat 文件出现“设备未准备好”错误,但我能够单独编译所有代码包并在 Delphi 中安装设计包没有问题。现在您的代码可以完美运行(当然!)。我应该注意到你在其他帖子中关于升级的所有 cmets!
  • 支持 TIdMultiPartFormDataStream 处理。在搞砸了边界等之后,这是如此干净和友好。
猜你喜欢
  • 2019-12-10
  • 1970-01-01
  • 1970-01-01
  • 2010-10-05
  • 2017-07-14
  • 2015-09-11
  • 2016-06-25
  • 1970-01-01
相关资源
最近更新 更多