Глава 11. Интеграция с веб-сервисами
Ни одно мобильное приложение не существует изолированно. Вы можете создать автономное мобильное приложение на Delphi, но чаще всего разработчики хотят интегрироваться с сервисами для обеспечения доступа к данным и добавления социального измерения в пользовательский опыт. В этой главе мы рассмотрим различные технологии, доступные в Delphi для интеграции с мобильными бэкендами, работающими в облаке.
В этой главе будут рассмотрены следующие темы:
- Понимание веб-сервисов
- Создание нативного HTTP-клиента
- Использование XML SOAP веб-сервисов
- Интеграция с REST-сервисами
- Интеграция с облаком
- Использование AWS SDK для Delphi
Цель этой главы — научиться создавать мобильные HTTP-клиенты и подключаться к веб-сервисам и данным в облаке. Это очень обширная тема, поэтому мы сможем лишь затронуть её поверхностно.
Технические требования
Код в этой главе использует внешние веб-сервисы и облачные сервисы и требует наличия учётных записей в целевых системах. В большинстве случаев достаточно бесплатной учётной записи. Кроме того, последний демо-пример использует AWS SDK для Delphi, которая является дополнительной библиотекой.
Примеры из этой главы доступны по адресу: GitHub-репозиторий книги.
Понимание веб-сервисов
Не так давно сэр Тимоти Джон Бернерс-Ли изобрёл Всемирную паутину. В его видении системы управления информацией отдельные документы должны быть связаны между собой специальными унифицированными идентификаторами ресурсов (URL), и каждый, кто читает документ в веб-браузере, должен иметь возможность перейти непосредственно к связанному гипертекстовому документу. Он также разработал протокол передачи гипертекста (HTTP) и реализовал первые версии программ веб-сервера и веб-браузера.
HTTP — это простой протокол для обмена текстовыми документами через базовую инфраструктуру TCP/IP. Он очень масштабируем, потому что серверам и клиентам не нужно поддерживать активное сокет-соединение, и каждый HTTP-запрос независим от любого другого. Спецификация HTTP определяет различные типы запросов, которые клиент может отправить серверу, иногда называемые глаголами. Наиболее распространёнными являются GET, HEAD, POST, PUT и DELETE. Когда запрос успешен, сервер возвращает код состояния OK 200. Другой популярный код — 404, который указывает на ошибку «не найдено».
Первая популярная реализация веб-сервисов была основана на формате документов XML. XML-веб-сервисы определили набор XML-тегов для кодирования сообщений SOAP, которые обменивались между клиентами и серверами. Delphi 6 была первым инструментом разработчика на рынке, поддерживающим создание серверов и клиентов SOAP веб-сервисов. Эта поддержка всё ещё присутствует в текущих версиях Delphi, но со временем популярность SOAP снизилась. Вместо SOAP и XML теперь чаще используется JSON в качестве формата обмена и REST в качестве протокола связи.
Создание нативного HTTP-клиента
Пожалуй, самым важным сетевым протоколом связи является HTTP и его безопасная версия HTTPS. В кроссплатформенном мире Delphi существует HTTP-клиентская библиотека, которая обеспечивает единообразный доступ к реализациям HTTP-клиентов на разных платформах.
Вы можете работать с HTTP полностью в коде, используя типы из модулей System.Net.HttpClient и System.Net.URLClient, или полагаться на готовые компоненты из модуля System.Net.HttpClientComponents.
Давайте создадим простое приложение, которое позволит вводить URL в поле редактирования, загружать данные с помощью HTTP GET-запроса и отображать результат в memo:
- Создайте новый пустой мультиплатформенный проект Delphi, сохранив модуль главной формы как
uFormHTTP, а всё приложение какHTTPApp. - Измените свойство
Nameформы наFormHTTP. - Поместите компонент
TNetHTTPClientна форму. - Поместите компонент
TToolbarи компонентыTSpeedButtonиTEditна панель инструментов. - Переименуйте кнопку в
SpdbtnDownload, установитеStyleLookupвarrowdowntoolbutton, аAlignвRight. - Переименуйте элемент управления редактированием в
EdtURL, установите отступы по 8 пикселей со всех сторон иAlignвClient. - Поместите
TMemoна форму, переименуйте вMemoDataи выровняйте поClient.
Теперь дважды щёлкните на кнопке скорости и введите следующий код в обработчик события OnClick:
procedure TFormHTTP.SpdbtnDownloadClick(Sender: TObject);
var
Memstr: TMemoryStream;
Resp: IHTTPResponse;
begin
Memstr := TMemoryStream.Create;
try
Resp := NetHTTPClient1.Get(EdtURL.Text, Memstr);
if Resp.StatusCode = 200 then
MemoData.Lines.LoadFromStream(Memstr)
else
ShowMessage(Resp.StatusCode.ToString + ': ' + Resp.StatusText);
finally
Memstr.Free;
end;
end;
HTTP-клиентская библиотека работает как с протоколами HTTP, так и с HTTPS. Библиотека также может выполнять запросы асинхронно. Давайте модифицируем пример, чтобы сделать запрос асинхронным. Сначала установите свойство Asynchronous компонента HTTP-клиента в True.
Измените код обработчика OnClick:
procedure TFormHTTP.SpdbtnDownloadClick(Sender: TObject);
begin
NetHTTPClient1.Get(EdtURL.Text);
MemoData.Lines.Clear;
MemoData.Lines.Add('Downloading...');
end;
Теперь обработайте событие OnRequestCompleted компонента HTTP-клиента:
procedure TFormHTTP.NetHTTPClient1RequestCompleted(
const Sender: TObject; const AResponse: IHTTPResponse);
begin
if AResponse.StatusCode = 200 then
MemoData.Lines.LoadFromStream(AResponse.ContentStream)
else
ShowMessage(AResponse.StatusCode.ToString + ': ' + AResponse.StatusText);
end;
С этим изменением приложение останется отзывчивым, и пользовательский интерфейс сможет продолжать отрисовку экрана даже во время выполнения HTTP-запроса, потому что это происходит в другом потоке.
Использование XML SOAP веб-сервисов
Отправной точкой для реализации клиента SOAP веб-сервиса является документ WSDL, который определяет веб-методы, реализуемые данным веб-сервисом. Delphi предоставляет мастер импорта WSDL, который генерирует модуль Object Pascal с типами и методами, соответствующими функциональности SOAP веб-сервиса.
Давайте посмотрим на процесс интеграции с SOAP веб-сервисом на примере сервиса числовой обработки. Файл WSDL находится по адресу: NumberConversion WSDL.
После импорта WSDL мастер сгенерирует тип интерфейса NumberConversionSoapType:
type
NumberConversionSoapType = interface(IInvokable)
['{A2E4D9CB-C96F-0965-87F0-E78269B29A8C}']
function NumberToWords(const ubiNum: Int64): string; stdcall;
function NumberToDollars(const dNum: TXSDecimal): string; stdcall;
end;
Мастер также генерирует глобальную функцию:
function GetNumberConversionSoapType(
UseWSDL: Boolean = System.False;
Addr: string = '';
HTTPRIO: THTTPRIO = nil): NumberConversionSoapType;
В коде формы используем сгенерированную глобальную функцию для получения ссылки на интерфейс веб-сервиса:
procedure TFormNumbers.BtnConvertClick(Sender: TObject);
begin
var SNumber := GetNumberConversionSoapType.NumberToWords(
Trunc(NumberBox1.Value));
Memo1.Text := SNumber;
end;
В реальных сценариях модель SOAP может стать чрезвычайно сложной, и за последние годы SOAP был в большинстве случаев заменён на REST.
Интеграция с REST-сервисами
Архитектура REST для реализации веб-сервисов не так стандартизирована, как SOAP. Красота REST в том, что она полностью использует базовый протокол HTTP и просто использует заголовки запросов и ответов HTTP для структурной информации.
В REST больше свободы. Внутри REST-сервера есть различные ресурсы, идентифицируемые их URL-путями. Обычно к этим ресурсам или конечным точкам можно получить доступ с помощью различных HTTP-глаголов, таких как GET, POST, DELETE и PUT. Во многих случаях эти различные глаголы соответствуют различным операциям CRUD над базовым ресурсом.
Delphi предоставляет ряд компонентов для интеграции с REST-сервисами. Они доступны в палитре инструментов в категории REST Client и упоминаются под названием REST Client Library.
Для демо я бы хотел использовать сервис APILayer WeatherStack. Чтобы запустить это демо, вам нужно будет создать бесплатную учётную запись и получить API-ключ по адресу weatherstack.com.
После того как вы настроили REST Debugger и довольны результатами, вы можете скопировать конфигурацию в набор компонентов, готовых к размещению в вашем приложении Delphi, используя кнопку Copy Components.
Создайте новое мультиплатформенное приложение Delphi. Сохраните файл формы как WeatherClient_Form, а проект как WeatherClient. Измените имя формы на FormWeather.
Создайте новый модуль данных и добавьте его в проект. Измените имя модуля данных на DMWeather. В REST Debugger скопируйте настроенные компоненты и вставьте их на модуль данных. У вас должно быть три невизуальных компонента:
- Компонент
TRESTClientсBaseURL, установленным вhttp://api.weatherstack.com. - Компонент
TRESTRequestсо свойствомResource, установленным вcurrent, и двумя параметрами,access_keyиquery. - Компонент
TRESTResponseбез дополнительной конфигурации.
Добавьте в модуль данных компонент TFDMemTable для данных ответа и компонент TRESTResponseDataSetAdapter для сопоставления данных ответа с этой таблицей.
На главной форме добавьте кнопку, поле редактирования и метку. Назовите их соответственно BtnRefresh, EditLocation и LabelTemp. В обработчике события OnClick кнопки напишите следующий код:
procedure TFormWeather.BtnRefreshClick(Sender: TObject);
begin
DMWeather.RESTRequest1.Params[1].Value := EditLocation.Text;
DMWeather.RESTRequest1.Execute();
LabelTemp.Text := DMWeather.FDMemTable1.
FieldByName('temperature').AsString;
end;
android:usesCleartextTraffic="true".
Интеграция с облаком
Облачные вычисления — это довольно широкий термин, относящийся к глобальной тенденции перемещения внутренних или локальных серверов в полностью размещённое сервисное решение. Огромное преимущество облачных сервисов — это возможность очень быстро масштабировать их вверх, когда есть больший спрос, и масштабировать вниз, когда они не нужны.
Существуют три различных подхода к доступу к облачным сервисам из Delphi:
- Вы можете использовать библиотеку REST Client.
- Вы можете использовать фреймворк Cloud API, который Delphi предоставляет уже давно для доступа к нескольким сервисам AWS и Azure.
- Вы можете использовать AWS SDK для Delphi от Appercept, которая бесплатно доступна в GetIt для клиентов Delphi Enterprise.
Delphi Cloud API
Delphi Cloud API определена в модуле Data.Cloud.CloudAPI, со специализированными модулями с типами и классами, специфичными для работы с выбранными сервисами, доступными от Amazon и Azure, объявленными в Data.Cloud.AmazonAPI и Data.Cloud.AzureAPI.
| Amazon Web Services | Microsoft Azure | |
|---|---|---|
| Хранилище | Simple Storage Service (S3) | Blobs Service |
| Таблица | Simple Database Service | Table Service |
| Очередь | Simple Queue Service (SQS) | Queue Service |
Таблица 11.1: Сервисы AWS и Azure, доступные в Delphi Cloud API
Перенос приложения ToDo List в Azure
В этом разделе мы сосредоточимся на сервисе Azure Table. Это сервис хранения данных NoSQL, который вы можете использовать для обмена данными между несколькими устройствами и даже несколькими приложениями.
Каждая строка имеет два специальных поля, называемых RowKey и PartitionKey, которые используются как уникальные идентификаторы каждой строки. Учитывая важность этих двух полей, я объявил две константные строки для них:
const
XML_ROWKEY = 'RowKey';
XML_PARTITION = 'PartitionKey';
Код также имеет третью константу с именем таблицы:
const
tablename = 'todolist';
Найдите исходные файлы ToDoList из предыдущей главы и скопируйте их в новый каталог. Сохраните проект как ToDoListAzure.
Для загрузки конфигурации Azure лучше использовать внешний файл. Я создал файл azure.ini:
[azure]
AccountName=xyz
AccountKey=1234567890
Этот ini-файл загружается из домашнего пути при первом создании модуля данных:
procedure TDMToDo.DataModuleCreate(Sender: TObject);
var
IniFile: TMemIniFile;
begin
var IniFilename := GetHomePath + PathDelim + 'azure.ini';
if not FileExists(IniFilename) then
raise Exception.Create('Missing Azure configuration');
IniFile := TMemIniFile.Create(IniFilename);
try
AzureConnectionInfo1.AccountName :=
IniFile.ReadString('azure', 'AccountName', '');
AzureConnectionInfo1.AccountKey :=
IniFile.ReadString('azure', 'AccountKey', '');
finally
FreeAndNil(IniFile);
end;
end;
В том же обработчике события OnCreate нам нужно инициализировать объект TAzureTableService:
TableService := TAzureTableService.Create(AzureConnectionInfo1);
Загрузка списка строк в таблице базы данных в список объектов в памяти:
procedure TDMToDo.ToDoList(AList: TToDos);
var
Item: TToDo;
RowsList: TList<TCloudTableRow>;
begin
MaxId := 0;
if AList <> nil then
begin
AList.Clear;
RowsList := TableService.QueryEntities(tablename);
try
for var RowObj in RowsList do
begin
Item.Id := RowObj.GetColumn(XML_ROWKEY).Value.ToInteger;
Item.Title := RowObj.GetColumn('title').Value;
Item.Category := RowObj.GetColumn('category').Value;
AList.Add(Item);
if Item.Id > MaxId then
MaxId := Item.Id;
end;
finally
RowsList.Free;
end;
end;
end;
Локальная функция, вычисляющая ID для новой строки:
function TDMToDo.GetNewId: Integer;
begin
Inc(MaxId);
Result := MaxId;
end;
Создание новой строки:
function TDMToDo.ToDoCreate(AValue: TToDo): Integer;
begin
var Id := GetNewId;
var RowObj := TCloudTableRow.Create;
var ResponseInfo := TCloudResponseInfo.Create;
try
RowObj.SetColumn(XML_ROWKEY, Id.ToString);
RowObj.SetColumn(XML_PARTITION, Id.ToString);
RowObj.SetColumn('title', AValue.Title);
RowObj.SetColumn('category', AValue.Category);
TableService.InsertEntity(tablename, RowObj, ResponseInfo);
if ResponseInfo.StatusCode = 200 then
Result := Id
else
Result := -1;
finally
RowObj.Free;
ResponseInfo.Free;
end;
end;
Обновление строки:
function TDMToDo.ToDoUpdate(AValue: TToDo): Boolean;
begin
var RowObj := TCloudTableRow.Create;
var ResponseInfo := TCloudResponseInfo.Create;
try
RowObj.SetColumn(XML_ROWKEY, AValue.Id.ToString);
RowObj.SetColumn(XML_PARTITION, AValue.Id.ToString);
RowObj.SetColumn('title', AValue.Title);
RowObj.SetColumn('category', AValue.Category);
TableService.UpdateEntity(tablename, RowObj, ResponseInfo);
Result := ResponseInfo.StatusCode = 200;
finally
RowObj.Free;
ResponseInfo.Free;
end;
end;
Удаление строки:
function TDMToDo.ToDoDelete(Id: integer): Boolean;
begin
var RowObj := TCloudTableRow.Create;
var ResponseInfo := TCloudResponseInfo.Create;
try
RowObj.SetColumn(XML_ROWKEY, Id.ToString);
RowObj.SetColumn(XML_PARTITION, Id.ToString);
TableService.DeleteEntity(tablename, RowObj, ResponseInfo);
Result := ResponseInfo.StatusCode = 200;
finally
RowObj.Free;
ResponseInfo.Free;
end;
end;
Чтение одной строки:
function TDMToDo.ToDoRead(Id: Integer; out AValue: TToDo): Boolean;
begin
var ResponseInfo := TCloudResponseInfo.Create;
var RowObj := TableService.QueryEntity(
tablename, Id.ToString, Id.ToString, ResponseInfo);
if ResponseInfo.StatusCode = 200 then
begin
AValue.Title := RowObj.GetColumn('title').Value;
AValue.Category := RowObj.GetColumn('category').Value;
Result := True;
end
else
Result := False;
end;
Использование AWS SDK для Delphi
Если вы предпочитаете использовать Amazon Web Services (AWS), а не Azure, Delphi Cloud API предлагает аналогичные возможности и интеграцию. Однако для AWS существует другая, гораздо более расширенная клиентская библиотека — AWS SDK для Delphi от Appercept. Эта облачная клиентская библиотека бесплатно доступна в версии Enterprise Delphi и может быть загружена из GetIt.
Эта клиентская библиотека предлагает большое количество дополнительных сервисов AWS, включая: Cognito (пулы пользователей и идентичностей), Polly, Simple Email Service (SESV2), Simple Notification Service (SNS), Simple Queue Service (SQS), Simple Storage Service (S3), Textract, Amazon Translate, AWS Key Management Service (AWS KMS) и AWS Secrets Manager.
Одним из вариантов AWS SDK является хранение локальных данных учётной записи в файле учётных данных в папке .aws под главной папкой пользователя. Файл имеет формат INI-файла:
[default]
aws_access_key_id=***
aws_secret_access_key=***
Создайте новое мультиплатформенное приложение Delphi и сохраните модуль главной формы как S3SDKClient_MainForm, а проект как S3SDKClient. Сначала нам нужно добавить модуль AWS.S3 в раздел interface модуля формы.
Нам нужно инициализировать несколько объектов для использования со временем и между методами:
private
FS3Client: IS3Client;
LResponse: IS3ListObjectsV2Response;
Начальный клиентский интерфейс настраивается с программными параметрами запуска:
procedure TFormS3.FormCreate(Sender: TObject);
begin
var LOptions := TS3Options.Create;
LOptions.Region := 'us-east-1';
FS3Client := TS3Client.Create(LOptions);
end;
Когда нажата кнопка, мы можем использовать имя бакета из поля редактирования для инициализации объекта запроса:
procedure TFormS3.BtnListClick(Sender: TObject);
var
LRequest: IS3ListObjectsV2Request;
begin
LRequest := TS3ListObjectsV2Request.Create(EdBucketName.Text);
LResponse := FS3Client.ListObjectsV2(LRequest);
if LResponse.IsSuccessful then
for var Item in LResponse.Contents do
ListBox1.Items.Add(Item.Key + ' (' + Item.ContentType + ')');
end;
Последний шаг — обработать событие OnDoubleClick списка для загрузки выбранного элемента:
procedure TFormS3.ListBox1DblClick(Sender: TObject);
begin
var Item := LResponse.Contents[ListBox1.ItemIndex];
Item.DownloadFile(Item.Key);
Image1.Bitmap.LoadFromFile(Item.Key);
end;
Более обширные демо AWS SDK для Delphi доступны на GitHub по адресу: appercept/aws-sdk-delphi-samples.
Резюме
В этой главе мы подробно рассмотрели ряд различных альтернативных методов для создания клиентских приложений, использующих веб-сервисы. Мы изучили HTTP, SOAP и REST клиентские библиотеки, доступные в Delphi. Мы также изучили Delphi Cloud API и AWS SDK для Delphi.
В целом, эти методы очень важны, так как большинство приложений взаимодействуют с удалёнными данными и веб-сервисами в наши дни. Какой метод вы используете, будет зависеть от ваших конкретных потребностей.
В некоторых случаях, однако, вы можете захотеть построить как клиент, так и сам веб-сервис. Это тема, которую мы рассмотрим в следующих двух главах, начиная с основ и переходя к более продвинутым функциям.