Всем привет!
При разработки очередного 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, набрасываем следующий макет формы:
В макете используются следующие компоненты: Label, Button(2шт.), Edit(2шт.), Memo(2шт.), CheckBox, OpenDialog, IdHTTP. Задайте следующим компонентам имена (свойство “Name”):
- Edit(заголовок) – Name= title;
- Edit(путь к файлу) – Name = imgfile;
- Memo(Содержание) – Name = content;
- Memo(Результат) – Name = response;
- Button(…) – Name = chkfile;
- Button(POST) – Name = PostBut;
- 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;
Хотя в нашем примере это произойдет автоматически по окончании процедуры, но все ж…
Собственно результат работы программы на экране:
Таким образом, мы можем отправить на сервер сколь угодно данных, файлов, обработать эти данные на сервере и сообщить приложению ответом, результат выполнения скрипта. Это может быть даже просто 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.