Как включить файл в TIdMultiPartFormDataStream для использования с Indy IdHTTP1.Post? - PullRequest
3 голосов
/ 20 апреля 2019

Какой правильный код мне нужно использовать для отправки одного или нескольких файлов любого типа вместе с другими параметрами, используя indy's 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); 

do были выполнены, электронное письмо не отправлялось, и ответ от сервераis

{"success": false, "error": "Один из файлов содержит недопустимые символы в имени файла."}

(Файл Afilename действительно существует в этом месте, и я попыталсяс одинарной и двойной обратной косой чертой)

Чтение других SO-сообщений по этой теме. Я также попытался заменить цикл обработки файлов в функции 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; 

Thвремя, даже если имя файла указано в Filenames.add(Afilename);, электронное письмо отправлено правильно, но получатель не видит вложения.

Среди многих других я прочитал эти возможные дублирующие вопросы SO

Http Post с indy

Опубликовать файл через https с использованием компонентов indy / delphi

опубликовать файл как часть формы

POST-запрос Nodejs multipart / form-data

и, в частности,

Использование компонента 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.GlobalProtocols.Я не писал это, я просто называю это.Но я воспроизвел это здесь в соответствии с просьбой

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 Ответ

2 голосов
/ 27 апреля 2019

Я вижу несколько проблем с вашим кодом.

Вы ошибочно экранировали \ символов в путях к файлам Это необходимо в таких языках, как 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 вместо использования имен, таких как file0, file1 и т. Д., Как показано в примерах 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 ничего не говорится о кодировке, необходимой для имен файлов, которые содержат не-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;
...