问题
What is the correct code that I need to use to send one or more files of any type along with other parameters using Indy's idHTTP.post? (using Delphi 2009 and Indy 10)
The post in question calls a function in a commercial company's API (ElasticEmail) that sends out emails to the recipients held in one of the parameters. (A link to the documentation on the function I am calling is here. I have example code in C# and other languages from the company here and I have tried to replicate that code in my Delphi code below.
If, in Procedure btnSendbyElastic, I comment out the line Filenames.add(Afilename);
so that the function Upload makes no attempt to attach a file,then the correct call seems to be made as the email gets sent successfully by the API.
However, if I leave that line in so that the lines in function UpLoad
MimeStr := GetMIMETypeFromFile(filenames[i]);
FormData.Addfile('file'+inttostr(i), filenames[i],MIMEStr);
do get executed, then no email is sent and the response from the server is
{"success":false,"error":"One of files has invalid characters in file name."}
(The file Afilename does exist at that location and I have tried with single and double backslashes)
Reading other SO posts on this topic I also tried replacing the file processing loop in Function UpLoad with the following loop instead
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;
This time, even with a filename specified in Filenames.add(Afilename);
, the email is sent correctly but the recipient sees no attachment.
Among many others, I have read these possible duplicate SO questions
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
and in particular
Using the Indy TidHttp component to send email file attachments through sendgrid
(which is almost exactly what I am trying to do) but I still cannot see what I am doing wrong in my code and what I need to do to correct it.
Here is the code I am using (UPPER_CASE identifiers are constants defined elsewhere)
PS I'm in the UK so apologies for the time delay in responding to US 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;
The function GetMIMETypeFromFile
is defined in the Indy unit idGlobalProtocols. I didn't write it, I just call it. But I have reproduced it here as requested
function GetMIMETypeFromFile(const AFile: TIdFileName): string;
var
MIMEMap: TIdMIMETable;
begin
MIMEMap := TIdMimeTable.Create(True);
try
Result := MIMEMap.GetFileMIMEType(AFile);
finally
MIMEMap.Free;
end;
end;
回答1:
I see a few problems with your code.
You are erroneously escaping \
characters in your file paths. That is necessary in languages like C and C++, but is not needed in Delphi at all, so get rid of it.
Change this:
Afilename := 'C:\\Users\\Admin\\Documents\\arrival and departure small.pdf';
To this:
Afilename := 'C:\Users\Admin\Documents\arrival and departure small.pdf';
The next problem I see is you are not naming the file attachment fields correctly when adding them to the TIdMultipartFormDataStream
.
When calling AddFile()
, you are passing the complete file path as-is to the AFieldName
parameter, instead of using names like file0
, file1
, etc like shown in Elastic's examples.
Change this:
FormData.Addfile(filenames[i], filenames[i],MIMEStr);
To this 1:
FormData.AddFile('file'+IntToStr(i), filenames[i], MIMEStr);
1: FYI, there is no need to call GetMIMETypeForFile()
manually, AddFile()
calls GetMIMETypeForFile()
internally for you if you do not provide a string for the AContentType
parameter, eg FormData.AddFile('file'+IntToStr(i), filenames[i]);
You made a similar mistake when you tried to use AddFormField()
instead of AddFile()
to add attachments. You used each file's actual data content for the AFieldName
parameter, instead of using the content for the AFieldValue
parameter.
In that case, change this:
FormData.AddFormField(AttachmentContent.ToString,filenames[i]);
To this:
FormData.AddFormField('file'+IntToStr(i), AttachmentContent.ToString, '', MIMEStr, filenames[i]);
Or, since you were opening TFileStream
objects yourself, you could use the overloaded AddFormField()
method that takes a TStream
as input (just be sure NOT to free the TStream
objects until after you are done using the TIdMultipartFormDataStream
!):
AttachmentContent := TFileStream.Create(filenames[i], fmOpenRead);
FormData.AddFormField('file'+IntToStr(i), MIMEStr, '', AttachmentContent, filenames[i]);
With that said, try something more like this:
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;
Lastly, Elastic's documentation does not say anything about the encoding needed for filenames that contain non-ASCII/reserved characters in it. And there are conflicting standards as to how such filenames should be encoded when transmitted over HTTP. By default, TIdMultipartFormDataStream
encodes filenames according to RFC 2047. If that turns out to be a problem for Elastic to handle (your example filename has space characters in it, I forget whether TIdMultipartFormDataStream
RFC-encodes a filename due to spaces or not, hopefully not), you can disable TIdMultipartFormDataStream
's default encoding by setting an affected file's TIdFormDataField.HeaderEncoding
property to '8'
(for 8-bit) and then you can set the TIdFormDataField.FileName
property to whatever encoding you want:
with FormData.AddFile('file'+IntToStr(i), filenames[i]) do
begin
HeaderEncoding := '8';
FileName := EncodeFilenameMyWay(ExtractFileName(filenames[i]));
end;
来源:https://stackoverflow.com/questions/55768206/how-do-i-include-a-file-in-a-tidmultipartformdatastream-for-use-with-indy-idhttp