Вроде бы всем хороши компоненты 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
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;
В этом случае наш заголовок добавиться в конец списка и отправиться на сервер.
Аналогичные ситуации, когда нельзя игнорировать события, будут возникать в момент, когда Вы решите использовать асинхронные методы. Тогда без знания того в какой момент возникает то или иное событие построить что-либо работающее будет практически невозможно. Но об этом мы поговорим в следующий раз.
Представьте себе ситуацию: Вам надо позвонить клиенту или заказчику, а труба не фурычит. Что делать? Срочный ремонт сотовых телефонов будет единственным Вашим спасением. Ремонтируют всё от Fly до IPhone.
--------------------------
| Делись! | Загружай! | Плюсуй! |
| | |









24 мая 2010 в 3:06 пп
Спасибо.
ICS рулит, я в последнее время прямо-таки помешался на этой либе
24 мая 2010 в 3:13 пп
Кстати, выражение «поддерживают асинхронный режим работы » не совсем верное.
Асинхронный режим — основной в ICS, а синхронный только эмулируется.
По-этому правильней писать «поддерживают синхронный режим работы наряду с асинхронным»
24 мая 2010 в 3:45 пп
Дмитрий, спасибо за уточнение. Да, либа неплохая, сейчас перевожу библиотеку OAuth с Indy на ICS — тестовое приложение похудело примерно на 450 кило + немного по-шустрее работать стала.
26 мая 2010 в 4:19 пп
А как обстоят дела у этой либы с NTLM Authentication?
26 мая 2010 в 7:02 пп
Вроде бы поддерживает, но самому пока использовать не приходилось
30 Июл 2010 в 7:31 дп
Здравствуйте!
Вот проглю сейчас, пользуя эту либу…
Наткнулся на такую проблему: не могу удерживать ошибки..
Ошибки сокетов пробил. А вот такие как 404 и 403 (forbidden) жить мешают со своими окнами сообщений…
Как от них избавиться, не подскажете? Или только в сорце либы ковыряться?
30 Июл 2010 в 11:56 дп
Ну в Synapse эта проблема решается намного проще. А с ICS почему бы получение веб-страницы не завернуть в try..except..end и не обрабатывать HttpCli1.CtrlSocket.LastError? Не пробовали так делать?
31 Июл 2010 в 8:18 пп
Все-таки поковырял сорц и вбил вот такое:
if (FStatusCode >= 400) and (FStatusCode 401) and (FStatusCode 407) then
begin
DebugLog(loWSockErr, 'Error #' + inttostr(FStatusCode));
Abort;
end;
OverbyteIcsHttpProt.pas: строка 3380 (в версии 7.03).
Вдруг, кому-то пригодится..
Юзаю этот логгер, но что-то пока не настроил его, так что об ошибке не оповещает. Зато, не мешает работать теперь эта ошибка)
Робота-индексера для поисковика клепаю, ссылок много, так что мне нужно было что бы только лог велся, и невзирая на ошибки, продолжалось индексирование.
10 Сен 2010 в 6:36 пп
Кто подскажет, как заставить THttpCli «разжимать» gzip контент?
Некоторые сайты отдают сжатый контент. Как быть?
31 мая 2011 в 1:16 пп
Vlad, благодарю за подробное описание компонента.
Остался пока один вопрос, который непонятно как реализовать.
Допустим, программа запустилась, и что-то там в своём отдельном потоке TThread с помощью асинхронного THttpCli скачивает страничку сайта. Но пользователь вдруг сразу закрывает программу, а страничка ещё не скачалась. Как будет вести себя THttpCli?
Т.е., в закрытии главной формы программы, пишем что-то типа: TThread.Terminated := True, но в самом деструкторе TThread я же ведь не могу взять и просто написать THttpCli.Free, пока он ещё не закончил свою работу, верно же ведь? Получается, что в теле TThread.Execute нужно писать цикл с проверками: 1. на Terminated; 2. а не закончил ли свою работу THttpCli? А если страничка будет скачиваться 5 или 10 секунд? Тогда программа будет ждать эти 5-10 секунд, чтобы потом закрыться? Но как-то это не очень красиво…
13 Июн 2011 в 5:55 дп
ICS THttpCli можно ли установить время жизни запроса.
Если работать через прокси то запрос может жить вечно!
HttpCli.close; по таймеру нежелательно.
Подскажите пожалуйста как решить проблему в самой компоненте?
13 Июн 2011 в 5:45 пп
Про ICS могу ошибаться, но посмотрите на конструктор компонента — там где-то должно задаваться врем отклика. Наример в Synapse у объекта Sock задается время 60000 — 1 минута, меняем на 1000 и сокет ждет ответ 1 секунду.