Вроде бы всем хороши компоненты ICS — достаточно удобные в использовании, бесплатные (несмотря на «регистрацию»), поддерживают асинхронный режим работы и т.д. и т.п. Но вот со справочной информацией по работе с компонентами прямо-таки беда. Не то чтобы не было информации вообще — по элементарным операциям типа отправить/получить данные только в Рунете можно найти сотни статей, примеров и т.д., а по чуть более сложным впросам уже все. Приходится скакать с форума на форум в поисках крупиц информации.
Я решил немного упростить себе (и, возможно, всем остальным, кто используют компоненты ICS) жизнь и по мере сил и возможностей рассказывать в блоге все, что касается работы с компонентами ICS, чтобы вся доступная информация была под рукой. Начать решил с компонента HTTPCli, т.к. на данный момент именно он представляет для меня наибольший интерес.
Общая информация о компоненте HTTPCli
HTTPCli — компонент для организации работы с HTTP-протоколом на стороне клиента. Класс THTTPCli описан в модуле OverbyteIcsHttpProt.pas.
Поддерживает выполнение следующих запросов: Get, Post, Put, Head, Close, Abort;
Все запросы выполняются в двух режимах — синхронном и асихронном. Для выполнения асинхроной передачи данных используются методы вида xxxASync.
События компонента HTTPCli
OnStateChange
OnStateChange: TNotifyEvent
срабатывает в момент, когда компонент изменяет свое состояние. Срабатывает в первую очередь до всех остальных событий, а также в момент получения каких-либо данных (заголовков, тела ответа и т.д.).
Текущее состояние компонента содержится в его свойстве State и может принимать следующие значения:
THttpState = (httpReady, httpNotConnected, httpConnected, httpDnsLookup, httpDnsLookupDone, httpWaitingHeader, httpWaitingBody, httpBodyReceived,httpWaitingProxyConnect, httpClosing, httpAborting);
Состояние компонента (State) изменяется в зависимости от выполнения той ил иной команды. При успешном выполнении метода GET состояние может изменятся следующим образом:
httpNotConnected—>httpDnsLookup—>httpDnsLookupDone—>
httpConnected—>httpWaitingHeader—>httpWaitingBody—>
httpBodyReceived—>httpReady
OnSocketError
OnSocketError: TNotifyEvent
Срабатывает, когда происходит какая-либо ошибка сокета. Событие OnSocketError срабатывает как минимум один раз после того как компонент изменяет свое состояние в httpDnsLookupDone вне зависимости от того были ошибки или нет.
Чтобы получить код последней ошибки сокета можно использовать следующее свойство:
HttpCli1.CtrlSocket.LastError
LastError: integer — код последней ошибки.
OnSessionConnected
OnSessionConnected: TNotifyEvent
Срабатывает сразу после смены состояния объекта на httpConnected. После выполнения события компонент переходит в состояние httpWaitingHeader (ожидание заголовков запроса).
RequestHeaderBegin
RequestHeaderBegin: TNotifyEvent
Срабатывает до того как первая строка заголовков будет добавлена в запрос.
BeforeHeaderSend
BeforeHeaderSend(Sender: TObject; const Method: string; Headers: TStrings)
Срабатывает после RequestHeaderBegin, но до того момента как первый заголовок будет отправлен на сервер. При этом Method содержит строку, которая определяет тип запроса, а Headers — список основных заголовков, таких как, Content-Length, User-Agent и т.д.
Событие удобно использовать, когда необходимо добавить в запрос свои заголовки.
OnCommand
OnCommand(Sender: TObject; var S: string)
Срабатывает на выполнение каждой команды, например, OnCommand многократно срабатывает при добавлении заголовков в запрос. При этом S содержит строку команды, например, строку:
host: www.webdelphi.ru
OnRequestHeaderEnd: TNotifyEvent
Срабатывает после того как все заголовки будут успешно добавлены в запрос.
OnHeaderBegin
OnHeaderBegin: TNotifyEvent
Срабатывает до момента получения первого зоголовка от сервера.
OnHeaderData
OnHeaderData: TNotifyEvent
Срабатывает при получении каждого заголовка от сервера. Если необходимо получать и анализировать заголовки «на лету», то в обработчике этого события можно использовать свойство
HttpCli1.RcvdHeader:TStrings
в котором содержаться все заголовки, полученные от сервера.
OnHeaderEnd
OnHeaderEnd: TNotifyEvent
Срабатывает после получения всех заголовков от сервера, но до того как компонент перейдет в состояние httpWaitingBody (ожидание данных).
OnDocBegin
OnDocBegin: TNotifyEvent
Срабатывает до момента как будет получена первая часть данных документа.
OnDocData
OnDocData(Sender: TObject; Buffer: Pointer; Len: Integer)
Срабатывает при получении каждой части данных документа. Pointer содержит указатель на поток в который сохраняются принимаемые данные, Len — размер полученных данных в байтах.
OnDocEnd
OnDocEnd: TNotifyEvent
Срабатывает в момент, когда последние данные документа были получены, после смены компонентом состояния на httpBodyReceived, но до смены состояния на httpReady.
OnSessionClosed
OnSessionClosed: TNotifyEvent
Срабатывает после того как текущее соединение будет завершено.
Событие возникает после OnDocEnd, но до смены состояния события на httpReady.
RequestDone
RequestDone(Sender: TObject; RqType: THttpRequest; ErrCode: Word)
Завершающее событие. Срабатывает самым последним. При этом
RqType содержит тип выполненного запроса и может принимать одно из следующих значений:
THttpRequest = (httpABORT, httpGET, httpPOST, httpPUT, httpHEAD, httpCLOSE);
ErrCode содержит код ошибки, либо 0, если запрос выполнен успешно. Возможные коды ошибок:
httperrNoError = 0; httperrBusy = 1; httperrNoData = 2; httperrAborted = 3; httperrOverflow = 4; httperrVersion = 5; httperrInvalidAuthState = 6; httperrSslHandShake = 7;
Это, если можно так выразиться, основные события компонента, которые будут срабатывать при получении данных от сервера. Если при выполнении запроса сервер отправляет нам Cookies, то будет срабатывать ещё одно событие:
OnCookie(Sender: TObject; const Data: string; var Accept: Boolean);
Это событие будет срабатывать до момента как строка Cookie будет добавлена к заголовкам сервера, т.е. ДО события OnHeaderData и Data содержит текст Cookie без «set-cookie: «.
В общем случае, при выполнении GET-запроса события будут срабатывать в следующей последовательности:
OnStateChange —>…—> OnStateChange —> OnSocketError —> OnStateChange —> OnSessionConnected —> OnStateChange —> OnRequestHeaderBegin —> OnBeforeHeaderSend —> OnCommand —>…—> OnCommand —> OnRequestHeaderEnd —> OnCommand (добавление пустой строки) —> OnHeaderBegin —> OnCookie —> OnHeaderData —>…—> OnHeaderEnd —> OnStateChange —> OnDocBegin —> OnDocData —>…—> OnDocData —> OnStateChange —> OnDocEnd —> OnSessionClosed —> OnStateChange — OnRequestDone
В случае, если выполняется POST-запрос дополнительно к вышеуказанным событиям добавляются ещё как минимум три события:
OnSendBegin
OnSendBegin: TNotifyEvent
Выполняется до отправки данных на сервер, после того как сформированы заголовки сообщения (после OnCommand с пустой строкой).
OnSendData
OnSendData(Sender: TObject; Buffer: Pointer; Len: Integer);
Срабатывает после того как каждая часть сообщения была отправлена на сервер. Параметры (см. OnDocData).
OnSendEnd
OnSendEnd: TNotifyEvent
Срабатывает после того как все данные были отправлены на сервер и ожидается получение первого заголовка ответа.
Когда необходимо использовать события HTTPCli
Вообще, события компонента HTTPCli каждый использует тогда, когда это необходимо в конкретной ситуации и в конкретном случае. Но при работе c HTTPCli иногда могут возникать ситуации, когда без события никак нельзя обойтись.
Чтобы понять важность событий компонента рассмотрим такой пример: отправка заголовков авторизации («Authorization: «) при использовании на сервере OAuth.
Заголовок при таком виде авторизации должен во-первых содержать метод авторизации — «OAuth» и во-вторых, все необходимые параметры, такие как подпись (signature), oauth_nonce и т.д. и т.п.
Сами по себе компоненты ICS достаточно продвинутые и в работе того же HTTPCli можно без проблем использовать разные формы авторизации: Basic, Digest и т.д. При этом заголовок Authorization будет формироваться в процессе работы компонента и отправляться. Как быть с OAuth? Для авторизации нам придётся записывать заголовок вручную.
Однако, если Вы попробуете найти в свойствах компонента, что-то, наподобие Headers:TStrings, то ничего из этого не выйдет. Нет у HTTPCli свойства, отвечающего за хранение отправляемых заголовков. Есть только свойство RcvdHeader в котором содержать принятые заголовки от сервера. И именно в этом случае (добавлении заголовков) нам не обойтись без знания того, что у HTTPCli имеется событие OnBeforeHeaderSend, используя которое можно перед началом отправки «скормить» компоненту любой заголовок.
Например, так:
procedure TFormHTTP.HttpCli1BeforeHeaderSend(Sender: TObject; const Method: string; Headers: TStrings); begin ListBox2.Items.Add('Сработало событие BeforeHeaderSend'); Headers.Add('Authorization: OAuth ...') end;
В этом случае наш заголовок добавиться в конец списка и отправиться на сервер.
Аналогичные ситуации, когда нельзя игнорировать события, будут возникать в момент, когда Вы решите использовать асинхронные методы. Тогда без знания того в какой момент возникает то или иное событие построить что-либо работающее будет практически невозможно. Но об этом мы поговорим в следующий раз.
Спасибо.
ICS рулит, я в последнее время прямо-таки помешался на этой либе
Кстати, выражение «поддерживают асинхронный режим работы » не совсем верное.
Асинхронный режим — основной в ICS, а синхронный только эмулируется.
По-этому правильней писать «поддерживают синхронный режим работы наряду с асинхронным»
Дмитрий, спасибо за уточнение. Да, либа неплохая, сейчас перевожу библиотеку OAuth с Indy на ICS — тестовое приложение похудело примерно на 450 кило + немного по-шустрее работать стала.
А как обстоят дела у этой либы с NTLM Authentication?
Вроде бы поддерживает, но самому пока использовать не приходилось
Здравствуйте!
Вот проглю сейчас, пользуя эту либу…
Наткнулся на такую проблему: не могу удерживать ошибки..
Ошибки сокетов пробил. А вот такие как 404 и 403 (forbidden) жить мешают со своими окнами сообщений…
Как от них избавиться, не подскажете? Или только в сорце либы ковыряться?
Ну в Synapse эта проблема решается намного проще. А с ICS почему бы получение веб-страницы не завернуть в try..except..end и не обрабатывать HttpCli1.CtrlSocket.LastError? Не пробовали так делать?
Все-таки поковырял сорц и вбил вот такое:
[code]
if (FStatusCode >= 400) and (FStatusCode 401) and (FStatusCode 407) then
begin
DebugLog(loWSockErr, 'Error #' + inttostr(FStatusCode));
Abort;
end;
[/code]
OverbyteIcsHttpProt.pas: строка 3380 (в версии 7.03).
Вдруг, кому-то пригодится..
Юзаю этот логгер, но что-то пока не настроил его, так что об ошибке не оповещает. Зато, не мешает работать теперь эта ошибка)
Робота-индексера для поисковика клепаю, ссылок много, так что мне нужно было что бы только лог велся, и невзирая на ошибки, продолжалось индексирование.
Кто подскажет, как заставить THttpCli «разжимать» gzip контент?
Некоторые сайты отдают сжатый контент. Как быть?
Vlad, благодарю за подробное описание компонента. Остался пока один вопрос, который непонятно как реализовать. Допустим, программа запустилась, и что-то там в своём отдельном потоке TThread с помощью асинхронного THttpCli скачивает страничку сайта. Но пользователь вдруг сразу закрывает программу, а страничка ещё не скачалась. Как будет вести себя THttpCli? Т.е., в закрытии главной формы программы, пишем что-то типа: TThread.Terminated := True, но в самом деструкторе TThread я же ведь не могу взять и просто написать THttpCli.Free, пока он ещё не закончил свою работу, верно же ведь? Получается, что в теле TThread.Execute нужно писать цикл с проверками: 1. на Terminated; 2. а не закончил ли свою работу… Подробнее »
ICS THttpCli можно ли установить время жизни запроса.
Если работать через прокси то запрос может жить вечно!
HttpCli.close; по таймеру нежелательно.
Подскажите пожалуйста как решить проблему в самой компоненте?
Про ICS могу ошибаться, но посмотрите на конструктор компонента — там где-то должно задаваться врем отклика. Наример в Synapse у объекта Sock задается время 60000 — 1 минута, меняем на 1000 и сокет ждет ответ 1 секунду.