HTTP Client API в Delphi

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

 

Содержание  скрыть 

1 Введение

2 TNetHTTPClient

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

2.2 HTTP-методы в TNetHTTPClient

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

2.4 Класс TNetHTTPResponse – ответ

2.5 Заключение

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

Введение

Итак, что из себя представляет 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, тем более, что класс ответа как таковой имеется. Но об ответах – позже.

18+

  • ЕСТЬ ПРОТИВОПОКАЗАНИЯ. ПОСОВЕТУЙТЕСЬ С ВРАЧОМ

Ор­га­ни­зу­ем родыв го­ро­де Кан­кун!

  •  

Бес­плат­ный сер­вер WoW Sirus

  • Недо­ро­гойVPN

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 очень даже не плох. В качестве достоинств можно выделить:

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

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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值