RSS

Delphi-приложение отправляющее данные на сервер методом POST (Indy)

Всем привет!

При разработки очередного Web-проекта встала задача – реализовать клиентское ПО на Delphi, которое бы передавало данные на сервер, используя метод POST. Приложение должно передавать текст и загружать файлы на Web-сервер.

Реализация такой отправки данных с использованием серверных языков Web разработки (например, PHP) довольно проста, а вот если необходимо написать прикладное, многопользовательское ПО взаимодействующее с сервером, то тут уже чуточку сложнее. Способ прямого подключения к БД и по FTP к серверу из Delphi – отпадает т.к. это не безопасно, не надежно (смена паролей, данных подключения и т.д.) и создает доп. проблемы с совместимостью ПО на стороне клиента. Для решения задачи я решил написать на языке PHP скрипты (серверную часть), которые будут обрабатывать входящие POST запросы и выдавать результат обратно клиенту (приложение на Delphi). Преимущества такого подхода в том, что все подключения и обработки данных происходят на сервере, что намного безопаснее прямого «коннекта».

Начав «гуглить» было выдано множество разрозненной информации, в основном это были форумы, но все это было кусками. Одно точно определил, что использоваться будет Indy, а именно компонент  IdHTTP с реализованным методом POST. По сути все просто, данный метод принимает два параметра Url ресурса и DataStream(поток данных), в ответ же отдает результат в текстовом виде(так же это может быть HTML-код страницы). Основное заключалось в правильном формировании DataStream(потока передаваемых данных), но по ходу вылезли еще дополнительные подводные камни, а именно русская кодировка (будь она не ладна). Вот тут-то и началось веселье на несколько часов блуждания на просторах сети. В общем, хватит болтовни, давайте перейдем к практике и реализации софта.

Итак, программа проста. Она должна отправить на сервер данные методом POST, данные содержат «Заголовок» (строка), «Описание» (многострочный текст) и графический файл (jpg,png,gif-бинарные данные). Сервер должен принять эти данные, обработать, сохранить графический файл на сервере и вернуть ответ. В качестве ответа вернем Delphi приложению, тот же текст только с добавлением меток и ссылку на загруженный файл. Больше ничего.

Начнем с реализации серверной части (подобие API сайта). Откройте любой текстовый редактор (блокнот) и в нем пропишите следующий код:

<?php
//Задаем заголовок ответа
header("Content-Type: text/html; charset=utf-8");

//Проверяем в поступивших данных наличие данных поля "title"
if (!empty($_POST['title'])){
    echo 'Заголовок: '.$_POST['title'].'<br>';
} else {
    echo 'Заголовок: Отсутствует'.'<br>';
}

//Проверяем в поступивших данных наличие данных поля "content"
if (!empty($_POST['content'])){
    echo 'Содержимое: '.$_POST['content'].'<br>';
} else {
    echo 'Содержимое: Отсутствует'.'<br>';
}

//Проверяем в поступивших данных наличие прикрепленного файла "file"
if (!empty($_FILES['file'])) {
  
 $finfo = pathinfo($_FILES['file']['name']); //получаем инфо о файле (имя, расширение и т.д.)
  
  //Проверяем тип файла в списке допустимых типов( ИМПРОВИЗАЦИЯ :) )
  if (stripos('jpgpnggif',$finfo['extension'])==0){
   echo '>>>>>>>Недопустимый тип файла<<<<<<<<';
   exit;  //Если не допустим тип, полностью останавливаем скрипт
  }
   
    
   $fname = 'files/' . 'testimgfile.' . $finfo['extension']; //формируем путь и новое имя файла
   move_uploaded_file($_FILES['file']['tmp_name'],$fname);//сохраняем временный файл 'tmp_name' в файл $fname
   echo 'https://'.$_SERVER['HTTP_HOST'].'/'.$fname; //возвращаем полный путь к файлу
   
} 
  
?>

Скрипт необходимо сохранить с именем «indypost1.php».

Обратите внимание! При сохранении (через блокнот) необходимо указать кодировку «UTF-8», иначе будут проблемы с отображением кириллицы!

Скрипт постарался снабдить подробными комментариями. Скопируйте этот скрипт на свой Web-сервер, если такового нет, для теста можете воспользоваться моим скриптом, расположен он по адресу: https://api.programm-school.ru/indypost1.php

Далее открываем Delphi, набрасываем следующий макет формы:

delphi_13.06.2016_13:56:47

В макете используются следующие компоненты: Label, Button(2шт.), Edit(2шт.), Memo(2шт.), CheckBox, OpenDialog, IdHTTP. Задайте следующим компонентам имена (свойство “Name”):

  1. Edit(заголовок) – Name= title;
  2. Edit(путь к файлу) Name = imgfile;
  3. Memo(Содержание) Name = content;
  4. Memo(Результат) – Name = response;
  5. Button(…) – Name = chkfile;
  6. Button(POST) – Name = PostBut;
  7. OpenDialog(Диалог выбора файла) – Name = PictDialog;

IdHTTP1 и CheckBox1 оставим не меняя (надоело! :)))).

Чтобы случайно не «подредактировать» путь в Edit(imgfile), выставим ему свойство ReadOnly в True. Так же, у imgfile и chkfile свойство Enabled выставьте в false. Активировать их будем с помощью CheckBox т.е. предоставим возможность выбора – загружать изображение или нет.

Для OpenDialog(PictDialog) необходимо задать фильтр (свойство Filter) следующим образом:

Собственно визуальная подготовка окончена! Приступаем к кодированию!

В проекте мы будем формировать поток данных с помощью типа идущего в комплекте с Indy – TidMultiPartFormDataStream. Хотя и попадались варианты реализации на TStream, но работать с TidMultiPartFormDataStream – проще!

Чтобы этот тип стал доступен нашему проекту, необходимо в Uses добавить следующую библиотеку: IdMultipartFormData.

Для CheckBox1 создайте событие OnClick (двойным нажатием мыши по объекту) и пропишите в это событие следующий код:

procedure TForm1.CheckBox1Click(Sender: TObject);
begin
//делаем активными или неактивными элемнты пути файла и кнопки диалога
imgfile.Enabled:=CheckBox1.Checked;
chkfile.Enabled:=CheckBox1.Checked;
end;

Здесь мы активируем объекты imgfile и chkfile в зависимости от наличия галочки (если галочка стоит, то объекты становятся активными).

Теперь организуем выбор изображения. Для этого создайте событие OnClick на кнопке chkfile (так же двойным кликом по объекту) и пропишите следующее:

procedure TForm1.chkfileClick(Sender: TObject);
begin
//открываем диалог и вносим полный путь к файлу в imgfile(TEdit)
if PictDialog.Execute then  imgfile.Text:= PictDialog.FileName;
end;

Это событие вызовет диалог выбора изображения и если пользователь нажмет «Открыть», то путь на этот файл будет добавлен в imgfile.

И вот мы подошли к финальной кнопке “POST”. Создайте событие OnClick для данной кнопки и добавьте следующий код:

procedure TForm1.PostButClick(Sender: TObject);
var dataPost:TIdMultiPartFormDataStream;

begin
   dataPost:=TIdMultiPartFormDataStream.Create;


   dataPost.AddFormField('title',title.Text,'utf-8').ContentTransfer := '8bit';
    dataPost.AddFormField('content',content.Text,'utf-8').ContentTransfer := '8bit';

  if  CheckBox1.Checked and (trim(imgfile.Text)='') then //проверка выбран файл или нет
      begin
            ShowMessage('Необходимо выбрать графический файл!');
            exit;
      end;

   if CheckBox1.Checked then dataPost.AddFile('file',imgfile.Text,''); //добавляем поле с файлом


    response.Text:= StringReplace(idHTTP1.Post('https://api.programm-school.ru/indypost1.php',dataPost),'<br>',#13#10,[rfReplaceAll]);

    datapost.Free;
end;

Итак, по порядку (хотя и есть комментарии):

Datapost – объект типа TIdMultiPartFormDataStream. Позволяет формировать структуру POST запроса состоящую из полей разного типа.

dataPost.AddFormField(‘title‘,title.Text,’utf-8‘).ContentTransfer := ‘8bit‘; – добавляет в DataPost поле с именем «title», значение из «title.Text», устанавливает кодировку передаваемых данных «utf-8»(параметр не обязательный, но без его явного указания кириллица передается знаками вопроса «?») и очень важный метод «ContentTransfer». Без этого метода на сервер улетают данные «абракадаброй».  Обратите внимание, имя поля(«title») на передающей стороне, должно соответствовать имени прописанному в скрипте: $_POST[‘title’].

Аналогично передаются данные в поле «content».

dataPost.AddFile(‘file‘,imgfile.Text,») – этой строкой мы формируем поток с данными из файла.

Все, данные сформированы, осталось их передать скрипту на сервере и получить ответ:

response.Text:= StringReplace(idHTTP1.Post(‘https://api.programm-school.ru/indypost1.php’,dataPost),'<br>’,#13#10,[rfReplaceAll]);

т.к. TMemo не понимает тэг переноса строки «<br>», мы воспользуемся функцией «StringReplace» для его замены на понятные символы переноса строки «#13#10».

По завершению всего очищаем память от объекта DataPost строкой:

datapost.Free;

Хотя в нашем примере это произойдет автоматически по окончании процедуры, но все ж…

Собственно результат работы программы на экране:

delphi_13.06.2016_15:19:48

Таким образом, мы можем отправить на сервер сколь угодно данных, файлов, обработать эти данные на сервере и сообщить приложению ответом, результат выполнения скрипта. Это может быть даже просто 0 или 1, что станет сигналом к дальнейшей реакции приложения.

Все. Всем удачи. Надеюсь, информация была полезной, и Вы найдете ей применение.

Готовый пример и скрипт Вы можете скачать отсюда.

Полный код модуля:

unit PostUnit;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, IdBaseComponent,
  IdComponent, IdTCPConnection, IdTCPClient, IdHTTP, IdMultipartFormData,
  Vcl.ExtDlgs;

type
  TForm1 = class(TForm)
    IdHTTP1: TIdHTTP;
    title: TEdit;
    content: TMemo;
    PostBut: TButton;
    response: TMemo;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    imgfile: TEdit;
    chkfile: TButton;
    Label4: TLabel;
    CheckBox1: TCheckBox;
    PictDialog: TOpenDialog;
    procedure PostButClick(Sender: TObject);
    procedure chkfileClick(Sender: TObject);
    procedure CheckBox1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.CheckBox1Click(Sender: TObject);
begin
//делаем активными или неактивными элемнты пути файла и кнопки диалога
imgfile.Enabled:=CheckBox1.Checked;
chkfile.Enabled:=CheckBox1.Checked;
end;

procedure TForm1.chkfileClick(Sender: TObject);
begin
//открываем диалог и вносим полный путь к файлу в imgfile(TEdit)
if PictDialog.Execute then  imgfile.Text:= PictDialog.FileName;

end;

procedure TForm1.PostButClick(Sender: TObject);
var dataPost:TIdMultiPartFormDataStream;

begin
   dataPost:=TIdMultiPartFormDataStream.Create;


    dataPost.AddFormField('title',title.Text,'utf-8').ContentTransfer := '8bit';
    dataPost.AddFormField('content',content.Text,'utf-8').ContentTransfer := '8bit';

  if  CheckBox1.Checked and (trim(imgfile.Text)='') then //проверка выбран файл или нет
      begin
            ShowMessage('Необходимо выбрать графический файл!');
            exit;
      end;

   if CheckBox1.Checked then dataPost.AddFile('file',imgfile.Text,''); //добавляем поле с файлом


    response.Text:= StringReplace(idHTTP1.Post('https://api.programm-school.ru/indypost1.php',dataPost),'<br>',#13#10,[rfReplaceAll]);

    datapost.Free;
end;

end.