Как-то относительно незаметно для меня прошло одно из нововведений 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:
- TNetHTTPClient — это ваш HTTP-клиент для работы с сервером
- 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хх — это тот случай, когда надо создавать исключение и останавливать работу, а просто сохраняет ответ и ожидает дальнейших действий.
Двигаемся далее. Рассмотрим как работают различные методы в 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; |
На что обратил сразу внимание:
- Частичный Get выделен как самостоятельный метод. Мелочь, а приятно — не придётся заморачиваться лишний раз с заголовками.
- Разработчики заранее озаботились тем, что различные 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 нам необходимо выполнить следующие обязательные условия:
- Определить поток для хранения результата (свойство ContentStream)
- Определить HTTP-метод (свойство MethodString)
- Выполнить метод 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 7 и Delphi 2005 с использованием СУБД MS SQL Server 2000, InterBase и Firebird. Приведена информация о теории построения реляционных баз данных и языке SQL. Освещены вопросы эксплуатации и администрирования СУБД.
|
||
Название: О чем не пишут в книгах по Delphi
Описание: Рассмотрены малоосвещенные вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные режимы их работы, особенности для протоколов TCP и UDP и др.
|
Спасибо, узнал много нового. Отличный блог, буду следить.
Vlad, добрый день!
Вовсю использую NetHTTPClient, но до сегодняшнего дня не было нужды использовать прокси. Никогда не использовал прокси в Delphi.
Прописал их в параметрах NetHTTPClient.ProxySettings := TProxySettings.Create(IP, Port);
При отправки Get запроса раза 3-4 сначала выдается ошибка, потом все норм.
Почему такое может происходить? И как проверить Proxy на доступность перед работой?