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

В последнее время (а это практически месяц) мне пришлось очень плотно работать с библиотекой Indy, в частности с несколькими её компонентами для работы с HTTP-протоколом. Использование другой библиотеки не обсуждался в принципе, т.к. переписывать весь код уже достаточно большого проекта только из-за того, что мне не нравится Indy никто бы и не стал. Да я и не просил. Опыт лишним не бывает.

Поэтому я с головой влез в дебри модулей последней версии Indy и начал активно разрабатывать свою часть проекта. И, кстати, могу сказать, что в большинстве случаев «глючность» Indy решается простым непринужденным выпрямлением рук и чтением мануалов по библиотеке. Indy не идеальная библиотека, есть и проблемы с её работой (с некоторыми встречался, про другие просто слышал), но в целом для выполнения повседневных задач типа отправить/получить запрос по http/https, провести base-аутентификацию и т.д. библиотека вполне подходит. Как знают мои постоянные читатели, несколько раз я упоминал в своем блоге на ряду с прочими компонентами для работы в Сети библиотеку ICS (Internet Component Suite) и даже некоторое время назад переписал модуль для API Twitter с Indy на ICS версий 5 (этого модуля в доступе нет) и 7. То бишь маленький, скромный, но тем не менее опыт использования ICS имеется.
И вот в процессе работы над текущим проектом у меня родилась мысль-идея — написать статью, которая поможет совсем уж неопытным в деле работы с HTTP-протоколом в Delphi программистам разобраться в различиях, преимуществах, особенностях и недостатках двух библиотек — Indy и ICS. Поэтому в этой статье я постараюсь обойтись без «тыканья палкой с гвоздями» в разработчиков библиотек (это незачем) и ограничиться только рассмотрением фактов. Итак начнем.

Первое и самое главное различие, которое необходимо запомнить и знать заключается в режимах работы. Indy использует блокирующий режим работы с сокетами, в то время как основным режимом ICS является неблокирующий (асинхронный) режим (хотя можно организовать и синхронную передачу данных). Именно по причине этого различия и начинаются разного рода толкования про плохую Indy и хорошую ICS. Сами по себе, что синхронный, что асинхронный режимы работы имеют как свои преимущества так и недостатки, просто надо ещё ДО начала работы над кодом программы определиться какой из режимов Вам больше подойдет. Например, частенько можно встретить высказывания от новичков про то, что дескать Indy «кривая» потому, что при выполнении длительных запросов программа «виснет».  Так как (без использования дополнительных компонентов) программе не виснуть на запросе, если режим работы синхронный? Indy в силу своей блокирующей природы поступает абсолютно правильно — не выходит из метода до тех пор, пока не доведет работу до логического завершения. Завершили — вышли. Если уж так хочется Indy и не хочется зависаний — используйте дополнительный компонент idAntiFreeze или работайте с потоками.

И вот, в силу своего главного различия, использование ICS после долгого использования Indy может вызвать некоторые затруднения в разработке.

Так как ICS использует асинхронный режим работы, то большая часть работы над программой будет основываться над обработкой тех или иных событий (про события компонента THTTPCli можно почитать здесь). Приведу простой пример — получения текста web-страницы.

Так Вы бы могли сделать, используя Indy:

  Memo1.Lines.Text:=IdHTTP1.Get('http://webdelphi.ru');//выполнили запрос
  ShowMessage('Страница скачана'); //вывели сообщение

И программа после скачивания страницы выдала бы Вам сообщение «Страница скачана». Это элементарный пример блокирующего режима работы — пока не скачали страницу ждем. Также само мы могли бы организовать и работу в ICS, но, если Вам необходим асинхронный режим, то этот же пример несколько усложняется, т.к. о факте завершения запроса нам необходимо будет ориентироваться по событиям компонента:

procedure TForm2.BtnGetClick(Sender: TObject);
begin
  Memo1.Lines.Clear;
  RcvdStream:=TStringStream.Create;
  HttpCli1.URL:='http://webdelphi.ru';//назначили URL
  HttpCli1.RcvdStream:=RcvdStream; //назначили поток для сохранения данных
  HttpCli1.GetASync;//запустили асинхронное выполнения запроса
end;
 
procedure TForm2.HttpCli1RequestDone(Sender: TObject; RqType: THttpRequest;
  ErrCode: Word);
begin
//событие OnRequestDone - завершение запроса
  Memo2.Lines.LoadFromStrea(RcvdStream);
  ShowMessage('Страница скачана'); //вывели сообщение
  RcvdStream.Free
end;

Чувствуете различие? С Indy весь код занял две строки, а с ICS пришлось писать дополнительный обработчик события иначе мы б не узнали просто так о завершении запроса. Вот Вам и контраргумент в пользу Indy — при асинхронном режиме работы в ICS требуется иной подход к разработке (событийно-ориентированный), который часто требует больше времени. И этот вывод сделан только лишь на основании одного просто го примерчика.  Нет, мы, безусловно, можем также само воспользоваться событием OnWorkEnd у компонента Indy и вывести сообщение там, но зачем без лишней надобности городить огород — все равно результат выполнения запроса мы узнаем только после выхода из метода Get().

Второе также немаловажное различие между Indy и ICS заключается в обработке исключений.  В одном из последних постов в блоге я говорил о том, что меня жутко бесит принцип обработки исключений в Indy. Меня это способ и сейчас не радует, но делать нечего, если решили остановить свой выбор на Indy — привыкайте, что и на ответ сервера «200 Ok» Вам могут просигналить об исключении. Опять же небольшой, но показательный пример. Довольно частой при работе с HTTP является проблема остановки выполнения запроса в любой момент времени, например, на середине работы или после получения определенных данных. Про решение этой задачи в Synapse я рассказывал неоднократно, теперь примеры в Indy и ICS.

Как принудительно прервать выполнение запроса в Indy? Для этого надо как минимум:

  1. Воспользоваться обработкой сообщений
  2. Уметь работать с исключениями Indy и учится их правильно отлавливать.
Рассмотрим такой пример — будем разрывать прерывать закачку документа после того как общий объем скаченных данных превысит 1 кб, то есть практически в самом начале.
Для получения информации о ходе выполнения запроса в Indy у компонента idHTTP имеется сразу три события:
  • OnWorkBegin — сообщает о начале работы
  • OnWork — сообщает информацию о ходе выполнения запроса
  • OnWorkEnd — сообщает о завершении работы.
С помощью трех этих событий можно легко организовать, например, заполнение ProgressBar’а по мере выполнения запроса. А можно и решить нашу задачу. Так как разрывать соединение нам придется уже после начала работы, но до её завершения, то логично воспользоваться обработчиком OnWork. Создадим небольшой проект в Delphi, бросим на главную форму компонент idHTTP, Memo для вывода скаченной части документа и кнопку для запуска процесса выполнения запроса.  В uses подключим модуль IdException для обработки исключений Indy. Для OnClick кнопки напишем такой обработчик:
procedure TForm2.Button1Click(Sender: TObject);
begin
   Memo1.Lines.Clear;
   try
      Memo1.Lines.Text:=IdHTTP1.Get('http://webdelphi.ru');
   except
     on E: EIdConnClosedGracefully do
       ShowMessage('Сработало исключение EIdConnClosedGracefully');
     on E:EIdException do
       ShowMessage('Сработало другое исключение Indy (EIdException)');
   end;
end;

Здесь мы как и в предыдущем примере Indy пробуем запросить контент страницы блога, выполнив GET-запрос. Если запрос пройдет успешно — в Memo выведется весь текст со страницы. Если же закачка будет прервана по какой-либо причине, то мы попытаемся знать какое из исключений Indy сработало. В качестве примера я проверяю было ли исключение принудительного закрытия соединение (EIdConnClosedGracefully), которое относится к «тихим» исключениям Indy (к ним относятся все исключения порожденные от EIdSilentException) или же сработало какое-либо другое исключение.
Теперь обратимся к обработчику OnWork компонента idHTTP. В чем будет заключаться код обработчика? Во-первых в обработчике OnWork мы должны определить надо ли разрывать соединение или нет. Для этого у нас есть значение — 1024 байта или более полученной информации. Во-вторых, не забывая о том, что Indy работает в блокирующем режиме, здесь же в обработчике нам необходимо как-то сохранить уже полученные данные.
Пишем такой обработчик:

procedure TForm2.IdHTTP1Work(ASender: TObject; AWorkMode: TWorkMode;
  AWorkCount: Int64);
var S: TStringStream;
begin
  if AWorkCount>1024 then
    begin
      S:=TStringStream.Create;
      try
        S.LoadFromStream(TIdHTTP(ASender).Response.ContentStream);//получили текст
        Memo1.Lines.LoadFromStream(S);//вывели в Memo
      finally
        TIdHTTP(ASender).Socket.Close;//в любом случае закрыли соединение
        S.Free;
      end;
    end;
end;

Здесь я воспользовался методом Close сокета (Socket) и принудительно оборвал передачу данных. Также я мог воспользоваться методом CloseFraceFully того же сокета или методом Disconnect объекта idHTTP — результат был бы тот же, различие только в работе методов (можете на досуге покопаться в их коде).
Теперь можете запустить программу и проверить работу. При закрытии сокета будет срабатывать исключение EIdConnClosedGracefully о чем будет выводится соответствующее сообщение, а в Memo выводится текст размер которого будет чуть превышать размер в 1 Kb.

Как принудительно прервать выполнение запроса в ICS?
Для ICS работа остается прежней — необходимо только ещё одно обработать событие OnDocData, которое сигнализирует нам о том, что получена очередная порция данных. Вначале пишем обработчик OnClick кнопки:

procedure TForm2.Button2Click(Sender: TObject);
begin
  HttpCli1.URL:='http://webdelphi.ru';
  HttpCli1.RcvdStream:=RcvdStream;
  {начали асинхронное выполнение запроса}
  HttpCli1.GetASync;
end;

Обработчик события OnRequestDone:

procedure TForm2.HttpCli1RequestDone(Sender: TObject; RqType: THttpRequest;
  ErrCode: Word);
begin
  {выводим полученный текст в Memo}
  Memo2.Lines.LoadFromStream(RcvdStream);
end;

Следующий обработчик — решение нашей задачи. В обработчике OnDocData:

procedure TForm2.HttpCli1DocData(Sender: TObject; Buffer: Pointer;
  Len: Integer);
begin
  if Len>1024 then
    HttpCli1.CtrlSocket.Close;
end;

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

  1. писать обработчик OnWork и принудительно обрывать соединение
  2. обрабатывать исключения
  3. лезть в свойства компонента и «выуживать» оттуда поток с данными, чтобы не потерять их.
в ICS по сравнению с предыдущем примером работы с HTTPCli нам оказалось достаточным написать всего лишь пару строк в обработчике события и при этом мы в принципе не открыли для себя ничего особенно нового в плане работы с библиотекой — ICS и так «заточена» под обработку событий.
В целом можно сказать, что все особенности работы Indy и ICS заключаются именно в различных режимах работы. После Indy ICS может показаться с одной стороны более «продвинутой», а с другой — более громоздкой в плане написания кода программы, т.к. придется достаточно плотно заниматься написанием различных обработчиков событий и тем самым наращивать как объем так и сложность исходника, но, подчеркну ещё раз — что Indy, что ICS можно одинаково эффективно использовать под использование повседневных рутинных операций при работе с HTTP-протоколом. А недостатков, тонкостей, сложностей хватает в обоих этих достаточно больших (я бы даже сказал монстроидальных после использования Synapse) библиотеках вне зависимости от Ваших или моих предпочтений и так или иначе при рассмотрении более-менее сложных примеров работы нам придется с ними сталкиваться. Но недостатки библиотек — это уже совсем другая история о которой поговорим позже.

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

Описание: Рассмотрены практические вопросы по разработке клиент-серверных приложений в среде Delphi 7 и Delphi 2005 с использованием СУБД MS SQL Server 2000, InterBase и Firebird. Приведена информация о теории построения реляционных баз данных и языке SQL. Освещены вопросы эксплуатации и администрирования СУБД.
купить книгу delphi на ЛитРес
Описание: Рассмотрены малоосвещенные вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные режимы их работы, особенности для протоколов TCP и UDP и др.
купить книгу delphi на ЛитРес
5 1 голос
Рейтинг статьи
уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
Подписаться
Уведомить о
15 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии
arankar
arankar
05/08/2011 10:01

Здравствуйте! Можете помочь с компонентом TIdTCPServer? При остановке сервера если есть подключенные клиенты программа зависает. Как принудительно отключить все клиенты до остановки сервера? 

arankar
arankar
05/08/2011 10:14

кстати у меня Indy 10.5.8.0 и Delphi2009

DeepBlack
DeepBlack
05/08/2011 15:09

ICS никому не советую, глючна до безобразия. Лучше пользоваться другими аналогами, тем более бесплатными. Indy — прошлый век, и использовать лучше когда нужно написать что-то свое и маленькое.

Chrome~
Chrome~
07/08/2011 04:12

Не верю, что ICS глючная. Покажи реальный пример, когда она неправильно работает.

DeepBlack
DeepBlack
10/08/2011 15:05

Попробуйте на ICS закачку файлов по http написать, и Вы поймете о чем я говорю. Файлы закачиваются не до конца, а этому конвееру пофиг, при этом даже event не срабатывает. Или пытается создать соединение на закачку и пропускает его по TTL, при этом TTL запроса редактировать насколько я помню нельзя (точнее можно, но эффекта 0), в любом случае при запросе на соединение приложение виснет. При этом не понятно почему запрос подвисает, на другом компоненте подобных глюков не наблюдается. А глюки с FTP, я вообще молчу. Upload по FTP становится просто карой небесной. В общем если нравится писать кучу заплаток для… Подробнее »

DeepBlack
DeepBlack
10/08/2011 16:51

Речь шла о CIS — Clever Internet Suite. К ICS претензий не имею… Перехожу на RAD, голова кипит.

DeepBlack
DeepBlack
11/08/2011 12:10

Перестановка букв, а какая разница!)))

arankar
arankar
17/08/2011 09:08

Спасибо!  CloseGracefully закрывает соединение но не всегда, может у меня код не правильный
[code]
var
   List: TList;
   Context: TIdContext;
   i: integer;
 begin
      list:= idtcpsrvrServer.Contexts.LockList;
      try
        for i := 0 to list.Count - 1 do
        begin
          context := list[i];
          context.Connection.Socket.CloseGracefully;
        end;
      finally
        idtcpsrvrServer.Contexts.UnlockList;
      end;
[/code].
 

Anfall
Anfall
20/08/2011 00:08

Спасибо. Полезная статья.
А как правильно с этой либой останаливать соединение?
http.abort или http.close ?
 

Дима
Дима
14/04/2012 15:24

Добрый день не плучаеться установить ICS, при установке пакета (C:\Users\Lahmadgun\Desktop\My Lib\ICS\Delphi\Vc32\OverbyteIcsD2010Design.dproj) вылазит собщение: «Can’t load package Delphi\Vc32\OverbyteIcsD2010Design.bpl», несмотря на то что я прописал пути.
 

Вячеслав
Вячеслав
01/09/2012 05:31

День добрый!
Почитал Ваши статьи и написал парсер позиций сайта. Все работает за исключением одного: Яндекс очень скоро выдает капчу. Юзера меняю, анонимные прокси применяю, задержку 3 секунды делаю. Ничего не помогает. Может Яндекс еще и MAC-адрес проверяет? Что можете посоветовать?