уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

Как-то относительно незаметно для меня прошло одно из нововведений Delphi — HTTP Client API. Этот API появился, оказывается, ещё в Delphi XE8 и, даже, получил развитие в Delphi 10.1 Berlin. Вполне возможно, что этот API прошел мимо меня по той простой причине, что для наибольшего количества приложений Delphi, использующих в работе HTTP-протокол я использовал библиотеку Synapse и других альтернатив особо не рассматривал. Однако, попался сегодня на глаза пример асинхронной загрузки файла с использованием HTTP Client API и я решил, что пора бы эту возможность Delphi изучить более подробно — вдруг окажется, что этот API совсем не плох и можно будет его использовать для работы.

Введение

Итак, что из себя представляет HTTP Client API в Delphi 10.3 Rio (предыдущие версии не рассматриваю по той причине, что они у меня не установлены). В папке %InstallDir%\source\rtl\net\ обнаружены следующие модули, относящиеся непосредственно к работе HTTP-протоколом:

  • System.Net.FileClient.pas
  • System.Net.HttpClient.pas
  • System.Net.HttpClient.Android.pas
  • System.Net.HttpClient.Mac.pas
  • System.Net.HttpClient.Win.pas
  • System.Net.HttpClientComponent.pas
  • System.Net.Mime.pas
  • System.Net.Socket.pas
  • System.Net.URLClient.pas
  • System.NetConsts.pas

Наличие файлов типа System.Net.HttpClient.ХХХХХХ.pas уже как бы намекает на то, что HTTP Client API можно свободно использовать в разных операционных системах, что хорошо.

В модуле System.Net.HttpClientComponent.pas содержатся два компонента, которые Вы можете обнаружить также в палитре компонентов на вкладке Net:

  1. TNetHTTPClient — это ваш HTTP-клиент для работы с сервером
  2. TNetHTTPRequest — ваш запрос к HTTP-серверу

Возник сразу вопрос: почему не сделали тогда и третий компонент, например, TNetHTTPResponse (ответ сервера), по аналогии с тем, что сделано в REST Clietn Library, тем более, что класс ответа как таковой имеется. Но об ответах — позже.

TNetHTTPClient

TNetHTTPClient в плане построения напоминает аналогичный компонент от Indy, то есть каждый отдельный HTTP-метод (GET, POST, PUT и т.д.) представляет собой отдельный метод компонента и, соответственно, некоторые методы перезагружны (помечены как overload).

Попробуем воспользоваться HTTP-клиентом в Delphi, отправить какой-нибудь запрос и получить ответ. На форме тестового приложения я поместил TMemo для вывода результата, одну кнопку и компонент TNetHTTPClient. Обработчик метода OnClick кнопки сделал следующим:

procedure TForm4.Button1Click(Sender: TObject);
begin
Memo1.Lines.LoadFromStream(NetHTTPClient1.Get('http://webdelphi.ru').ContentStream);
end;

Результат не заставил себя долго ждать:

Этот пример примечателен сам по себе по следующим причинам:

  • во-первых, TNetHTTPClient сделал правильный редирект на необходимый адрес, так как блог webdelphi в настоящее время доступен по https и запрос на адрес http://webdelphi.ru возвращает код 301 (Moved Permanently).
  • во-вторых, так как мне необходимо было только вывести содержимое главной страницы в Memo — я не создал ни одной лишней переменной в коде.

В принципе, возможности как по перенаправлению на необходимый адрес, так и работа с контентом без лишних манипуляций с переменными имеется и в той же Indy, однако, работа TNetHTTPClient организована несколько иначе. Рассмотрим по прядку.

Управление перенаправлением в TNetHTTPClient 

У компонента определено следующее свойство:

property RedirectsWithGET: THTTPRedirectsWithGET read GetRedirectsWithGET write SetRedirectsWithGET default CHTTPDefRedirectsWithGET;
 THTTPRedirectWithGET = (Post301, Post302, Post303, Post307, Post308,
                          Put301, Put302, Put303, Put307, Put308,
                          Delete301, Delete302, Delete303, Delete307, Delete308);

Это свойство позволяет нам настроить различное поведение компонента в зависимости от используемого HTTP-метода и возвращаемого кода статуса. Например, мы можем делать перенаправление, если при использовании метода POST сервер вернул нам код 301 и не перенаправлять пользователя на другой адрес, если сервер вернет код 302 и так далее.

Также определены и такие свойства как:

property HandleRedirects: Boolean read GetHandleRedirects write SetHandleRedirects;
property MaxRedirects: Integer read GetMaxRedirects write SetMaxRedirects default 5;

Соответственно, свойство HandleRedirects позволяет или запрещает делать автоматические перенаправления, а MaxRedirects определяет максимально возможное количество перенаправлений пользователя.

В отличие от Indy, которая норовит по каждому поводу выкинуть какое-нибудь исключение, TNetHTTPClient работает «по-тихому», как Synapse, то есть не считает, что коды 3хх — это тот случай, когда надо создавать исключение и останавливать работу, а просто сохраняет ответ и ожидает дальнейших действий.

Что ещё примечательно, так это, что Indy на примере, представленном выше, вообще не правильно сработала — при отключенном перенаправлении выдала код статуса 403, хотя в реальности сервер возвращает именно 301 код редиректа. Так что, 10 очков Гриффиндору HTTP Client API.

Двигаемся далее. Рассмотрим как работают различные методы в TNetHTTPClient

HTTP-методы в TNetHTTPClient

Компонент поддерживает следующие стандартные HTTP-методы:

HTTP-метод Реализация в TNetHTTPClient
DELETE
function Delete(const AURL: string; const AResponseContent: TStream = nil; const AHeaders: TNetHeaders = nil): IHTTPResponse;
OPTIONS
function Options(const AURL: string; const AResponseContent: TStream = nil; const AHeaders: TNetHeaders = nil): IHTTPResponse;
GET
function Get(const AURL: string; const AResponseContent: TStream = nil; const AHeaders: TNetHeaders = nil): IHTTPResponse;
function GetRange(const AURL: string; AStart: Int64; AnEnd: Int64 = -1; const AResponseContent: TStream = nil;
const AHeaders: TNetHeaders = nil): IHTTPResponse;
TRACE
function Trace(const AURL: string; const AResponseContent: TStream = nil; const AHeaders: TNetHeaders = nil): IHTTPResponse;
HEAD
function Head(const AURL: string; const AHeaders: TNetHeaders = nil): IHTTPResponse;
POST
function Post(const AURL: string; const ASourceFile: string; const AResponseContent: TStream = nil;
const AHeaders: TNetHeaders = nil): IHTTPResponse; overload;
 
function Post(const AURL: string; const ASource: TStrings; const AResponseContent: TStream = nil;
const AEncoding: TEncoding = nil; const AHeaders: TNetHeaders = nil): IHTTPResponse; overload;
 
function Post(const AURL: string; const ASource: TStream; const AResponseContent: TStream = nil;
const AHeaders: TNetHeaders = nil): IHTTPResponse; overload;
 
function Post(const AURL: string; const ASource: TMultipartFormData; const AResponseContent: TStream = nil;
const AHeaders: TNetHeaders = nil): IHTTPResponse; overload;
PUT
function Put(const AURL: string; const ASourceFile: string; const AResponseContent: TStream = nil;
const AHeaders: TNetHeaders = nil): IHTTPResponse; overload;
 
function Put(const AURL: string; const ASource: TStrings; const AResponseContent: TStream = nil;
const AEncoding: TEncoding = nil; const AHeaders: TNetHeaders = nil): IHTTPResponse; overload;
 
function Put(const AURL: string; const ASource: TStream = nil; const AResponseContent: TStream = nil;
const AHeaders: TNetHeaders = nil): IHTTPResponse; overload;
 
function Put(const AURL: string; const ASource: TMultipartFormData; const AResponseContent: TStream = nil;
const AHeaders: TNetHeaders = nil): IHTTPResponse; overload;
MERGE
function Merge(const AURL: string; const ASource: TStream; const AHeaders: TNetHeaders = nil): IHTTPResponse;
 
function MergeAlternative(const AURL: string; const ASource: TStream;
const AHeaders: TNetHeaders = nil): IHTTPResponse;
PATCH
function Patch(const AURL: string; const ASource: TStream = nil; const AResponseContent: TStream = nil;
const AHeaders: TNetHeaders = nil): IHTTPResponse; overload;
 
function PatchAlternative(const AURL: string; const ASource: TStream = nil; const AResponseContent: TStream = nil;
const AHeaders: TNetHeaders = nil): IHTTPResponse; overload;

На что обратил сразу внимание:

  1. Частичный Get выделен как самостоятельный метод. Мелочь, а приятно — не придётся заморачиваться лишний раз с заголовками.
  2. Разработчики заранее озаботились тем, что различные API онлайн-сервисов могут по разному работать с методами PATCH и MERGE. Поэтому в методах PatchAlternative и MergeAlternative используется обычный метод PUT с добавлением к запросу заголовка x-method-override.

Также, определен ещё один метод, позволяющий использовать запросы как в Synapse:

function Execute(const ARequest: IHTTPRequest; const AContentStream: TStream = nil): IHTTPResponse; overload;
 
function Execute(const ARequestMethod: string; const AURI: TURI; const ASourceStream: TStream = nil;
const AContentStream: TStream = nil; const AHeaders: TNetHeaders = nil): IHTTPResponse; overload;

В первом случае мы должны передать в метод Execute интерфейс IHTTPRequest, содержащий информацию по запросу. Во втором случае мы передаем в метод строку с HTTP-методом, URL и, если необходимо, то определяем дополнительные параметры — поток для хранения результата и заголовки.

Если переписать пример выше на использование метода Execute, то получим следующий код:

Memo2.Lines.LoadFromStream(NetHTTPClient1.Execute('get', TURI.Create('http://webdelphi.ru')).ContentStream);

Наличие такого метода у TNetHTTPClient, с одной стороны, позволяет использовать компонент так, как мы привыкли в Synapse, а, с другой стороны (что более важно), реализовывать работу нашего клиента с использованием любых других методов, поддерживаемых сервером, например, реализовать свой собственные метод обмена данными со своим сервером.

В принципе, перечень поддерживаемых HTTP-методов достаточный, чтобы реализовать любую работу с HTTP, за это — еще один зачет HTTP Client API.

На этом шаге мы вплотную приблизились к рассмотрению запросов и ответов к серверу, а именно их реализации в HTTP Client API. Начнем с запросов.

Компонент TNetHTTPRequest — запрос

Компонент TNetHTTPRequest предназначен для обработки HTTP-запросов. Для его работы необходимо определить клиент, то есть задать свойство компонента:

property Client: TNetHTTPClient read GetClient write SetClient;

Компонент реализует те же HTTP-методы, что и TNetHTTPClient: GET, POST, PUT и так далее. Вместе с этим, TNetHTTPRequest расширяет работу TNetHTTPClient. Например, используя этот компонент можно реализовать загрузку страницы блога вот так:

var MS: TMemoryStream;
begin
  MS:=TMemoryStream.Create;
 
  NetHTTPRequest1.ContentStream:=MS;
  NetHTTPRequest1.MethodString:='get';
  NetHTTPRequest1.URL:='http://webdelphi.ru';
  NetHTTPRequest1.Execute();
  try
    Memo1.Lines.LoadFromStream(NetHTTPRequest1.ContentStream);
  finally
    FreeAndNil(MS)
  end;
end;

Таким образом, чтобы выполнить запрос к серверу с использованием TNetHTTPRequest нам необходимо выполнить следующие обязательные условия:

  1. Определить поток для хранения результата (свойство ContentStream)
  2. Определить HTTP-метод (свойство MethodString)
  3. Выполнить метод Execute

Второй вариант — использовать методы Get, Post, Put, Head и другие, как это делалось при использовании TNetHTTPClient.

Класс TNetHTTPResponse — ответ

Этот класс реализует интерфейс IHTTPResponse (тот самый, который возвращается в результате выполнения методов клиента TNetHTTPClient).

Используя этот класс и реализуемый им интерфейс мы можем получить всю информацию по ответу сервера: код статуса, куки, заголовки, тело ответа и так далее.

Перепишем пример таким образом, чтобы получить максимум информации об ответе. Для этого я добавил на форму ещё несколько Memo:

Обработчик OnClick сделаем таким:

procedure TForm4.Button1Click(Sender: TObject);
var MS: TMemoryStream;
    Resp: IHTTPResponse;
    Header: TNameValuePair;
    Cookie: TCookie;
begin
  memHeaders.Lines.Clear;
  memCookies.Lines.Clear;
  memContent.Lines.Clear;
 
  MS:=TMemoryStream.Create;
 
  NetHTTPRequest1.ContentStream:=MS;
  NetHTTPRequest1.MethodString:='get';
  NetHTTPRequest1.URL:=edURL.Text;
  Resp:=NetHTTPRequest1.Execute();
  try
   //загружаем контент
    memContent.Lines.LoadFromStream(NetHTTPRequest1.ContentStream);
   //выводим заголовки
    for Header in Resp.Headers do
      MemHeaders.Lines.Add(Header.Name+': '+Header.Value);
   //выводим куки
    for Cookie in Resp.Cookies do
      memCookies.Lines.Add(Cookie.ToString);
   //выводим версию HTTP
    case Resp.Version of
      THTTPProtocolVersion.UNKNOWN_HTTP: lbHttpVer.Caption:='n/a';
      THTTPProtocolVersion.HTTP_1_0: lbHttpVer.Caption:='1.0';
      THTTPProtocolVersion.HTTP_1_1: lbHttpVer.Caption:='1.1';
      THTTPProtocolVersion.HTTP_2_0: lbHttpVer.Caption:='2.0';
    end;
   //выводим код статуса
    lbStatusCode.Caption:=Resp.StatusCode.ToString;
  finally
    FreeAndNil(MS)
  end;
end;

Также TNetHTTPResponse содержит следующие полезные методы:

//Представляет поток ContentStream в виде строки с заданной кодировкой
function ContentAsString(const AnEncoding: TEncoding = nil): string; override;
//проверяет содержит ли список заголовков заголовок с именем Name
function ContainsHeader(const AName: string): Boolean; virtual;
//возвращает значение заголовка с именем Name
function GetHeaderValue(const AName: string): string; virtual;

Заключение

В целом, поверхностный обзор HTTP Client API в Delphi мне показал, что этот относительно новый фреймворк для работы с протоколом HTTP в Delphi очень даже не плох. В качестве достоинств можно выделить:

  • возможность управлять механизмом редиректов
  • возможность использовать различные способы выполнения запросов к серверу
  • достаточно удобная реализация работы с куками и заголовками
  • кроссплатформенность.

За пределами этого обзора, конечно, остались такие интересные вопросы как использование прокси, сжатия, асинхронная работа, загрузка файлов в синхронном и асинхронном режимах и так далее, но для первого раза, думаю, пока хватит. Далее рассмотрим и эти вопросы.

Книжная полка

Описание Подробно рассматривается библиотека FM, позволяющая создавать полнофункциональное программное обеспечение для операционных систем Windows и OS X, а также для смартфонов и планшетных компьютеров, работающих под управлением Android и iOS
купить книгу delphi на ЛитРес
Описание: Рассмотрены практические вопросы по разработке клиент-серверных приложений в среде Delphi 7 и Delphi 2005 с использованием СУБД MS SQL Server 2000, InterBase и Firebird. Приведена информация о теории построения реляционных баз данных и языке SQL. Освещены вопросы эксплуатации и администрирования СУБД.
купить книгу delphi на ЛитРес
Описание: Рассмотрены малоосвещенные вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные режимы их работы, особенности для протоколов TCP и UDP и др.
купить книгу delphi на ЛитРес
5 5 голоса
Рейтинг статьи
уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
Подписаться
Уведомить о
2 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии
OldUncle
OldUncle
01/03/2019 06:36

Спасибо, узнал много нового. Отличный блог, буду следить.

Dimon_MDV
Dimon_MDV
18/11/2019 17:45

Vlad, добрый день!
Вовсю использую NetHTTPClient, но до сегодняшнего дня не было нужды использовать прокси. Никогда не использовал прокси в Delphi.
Прописал их в параметрах NetHTTPClient.ProxySettings := TProxySettings.Create(IP, Port);
При отправки Get запроса раза 3-4 сначала выдается ошибка, потом все норм.
Почему такое может происходить? И как проверить Proxy на доступность перед работой?