HTML нараспашку

Автор: Виталий Белик

Однажды мой друг попросил меня быстро написать программу, которая помогла бы ему распарсить страницы из КЭШа поисковиков. Это смогло бы восполнить потери в базе его сайта, которую он «случайно» почистил. Я посмеялся и написал ему простейший парсер конкретно для Яндекса, Гугла, а потом подумал: «А что если попробовать сделать свою качалку страниц. А может даже и свой браузер… Как он будет выглядеть? Сложно ли это сделать?». Это заинтересовало меня – ответ «Гуглехрому» своими руками достаточно неплохая задача, верно?

Prehistory

Что же такое браузер? Википедия ответит на этот вопрос строчками «Веб-обозрева́тель, бра́узер (от англ. Web browser; вариант броузер – устаревшая и менее предпочтительная форма) – программное обеспечение для просмотра веб-сайтов, то есть для запроса веб-страниц (преимущественно из Сети), их обработки, вывода и перехода от одной страницы к другой». И будет совершенно права. Исконно браузерами называли инструментарий для отображения WEB сайтов у клиента. Но чтоб отобразить (я бы сказал – отрисовать) страницу браузер должен обладать двумя важными функциями: Загрузчиком страницы, и парсером страницы.

Загрузчик страницы должен быть достаточно умен, чтоб загружать контент (содержимое страницы в данном случае) по требованию браузера. Не секрет что HTML страницы содержат ссылки на картинки, мультимедиа и прочие файлы, которые необходимо клиенту открыть (проиграть, отобразить), и загрузчик просто таки обязан все эти вещи запросить из Интернета, создать файл, наполнить его контентом этой мультимедиа, и сообщить браузеру «Ей дружище, я закачал, можно отображать». Браузер согласно указаниям в HTML разметке выдает загруженное на экран в нужном месте, или в аудиоколонки, если это скажем песня.

* Комментарий автора.

Что касается парсинга, он вступает только после того, как загрузчик сделал свое черное дело, ну или, по крайней мере, по ходу загрузки. Все, думаю, уже давно заметили, что каждый браузер старается как можно скорее отобразить страницу, начиная с момента загрузки. Это достигается за счет того, что парсер отрабатывает теги не после того, как загрузчик отработает, полностью закачав страницу (даже без мультимедиа контента), а непосредственно в ходе самой закачки.

Допустим, схема может быть такой: Загрузчику браузер указывает, по сколько байт считывать страницу. Тот в цикле ее посасывает потихоньку, после каждой порции (пардон) отсоса, передает порцию парсеру, который быстренько (может быть даже в другом потоке) разбирает часть кода, определяя, насколько он цельный, насколько из того, что уже пришло можно понять что отображать. Если ему удается определить, что часть тегов уже закачались, они (теги) закрыты по правилам HTML, и представляют собой цельный объект, который о чем-то говорит точно и не однозначно, парсер просит браузер согласно этому тегу что-то отрисовать вне очереди. Браузер создает объект в памяти, наполняет его свойствами из этого тега, и отрисовывает этот объект и все последующие согласно их свойствам. Все замечали, что иногда бывает страница в начале закачки выглядит на весь экран, а потом резко сжимается – это как раз говорит о том, что тот объект, который теснит объекты еще загрузчиком не подгрузился, парсер еще не разобрал его, а браузер уже спешит отрисовать на экране страницу, наивно полагая, что он самый умный и неожиданностей не будет. Однако потом загрузчик закачивает какую-нить картинку, которая теснит своими размерами ее. Браузер, узрев «сие неподобие», о коем ему поведал парсер («Эй, брателло, зацени фотку. Только учти она размером метр на метр»), чешет в своей электронной шевелюре киберпальцами, создает такой объект «картинка» задавая ему размеры «Дофинта на Бог знает сколько» согласно поведанному ему парсером, и запускает цикл отрисовки заново, по всем объектам, уже имеющимся в его памяти. Здесь происходит масштабирование всех объектов, так чтоб вся страница вписалась в… Экран, окно? Что там, у браузера, в довольствии?

В общем-то, нельзя сказать, что все сложно. Не беря во внимание скрипты, и прочие «прибамбасы» современных браузеров. Но отрисовать форматировано, красиво, текст на экране – главная задача, испокон веков приписываемая HTML.

Сразу оговоримся – дебри «фтопку». Пусть тепло дают, а наша задача – сделать так, как это делали наши деды и отцы. Наипростейшее, способное пролить некий свет на темное царство Интернет-клиентов (Наипростейшее что? Из предыдущего предложения не ясно и из этого тоже).

Заметили, как я уже говорил, написать это стоит на разных языках. Для кругозора, для интереса, предлагаю выбрать четыре языка для сравнения: Делфи, Си (для красоты возьмем «современные» плюсы, состыкованные с .NET). Дельфи и С++ это только два языка, а где еще оставшиеся?

Loading… 90% remained

Прежде всего, предлагаю порыться по «сусекам», выудив на кухонный стол все, чем можно воспользоваться для «кашеварства». Давайте посмотрим, чем наши инструменты смогут оперировать, какие гвозди будем забивать. Начнем с Делфи. Он имеет ряд преимуществ (на первый взгляд) в виде наличия компонентов быстрого проектирования. Не зря сегодняшние версии Делфи имеют аббревиатуру RAD Studio – Rapid Developing Studio – Студия быстрой разработки. Кинул на форму компоненты и запускай. Все программа готова. Это как если бы автомобили продавали с конвейерами – набрал тип авто, жмакнул кнопку – получай какое (имеется ввиду авто) захотел.

Прикольно было бы свои машины штамповать. Имеешь набор стандартных крыш, кузовов, колес. Вот, кстати, аналогия с Need for Speed – Там ты можешь одну и ту же машину «отрапидить» разными компонентами-запчастями буквально одним кликом. Не нужно перебирать вручную движок, свечи подбирать, покупать пневмопистолеты для систем окраски, нанимать художника для граффити. Все уже вывалено на палитре компонентов, достаточно только протянуть руку и взять это счастье. В случае с Делфи нам предоставлена их (ну или почти их) знаменитая библиотека INDY (Которая почему-то расшифровывается как Internet Direct), упавшая на хвост борландовцам в свое время, и продолжающая развиваться по сей день. Вообще можно сказать, что эта библиотечка для начала достаточно удобна, авторы постарались втиснуть в нее все, что только душа захотела, в результате чего она стала похожа на монстра типа этого: 

Говорят, что это пишушая ручка. Как бы там ни было, INDY в целом способна (или способно? Какого оно пола-то?) решить проблемы с загрузкой страницы сайта, и принадлежащих ей файлов мультимедиа контента. Для этого достаточно бросить на форму компонент IdHTTP и его метод Get, которому нужно передать адрес URL, вернет в качестве строки весь код HTML.

Хы. Все, загрузчик написан. Вот вам преимущества Rapid- технологий. А заодно ничего лишнего, капающего в глаза жуткими изворотами многотонного кода – одна строчка и можно парсить текст из Мемо.

Однако преимущества таких компонентов имеют свойство резко заканчиваться на границе рапидных проектов. Далее идет пустыня, где ветра багов сметают песок с дюн надежности компонентов, унося его в далекие места. Никто не спорит что, используя эти компоненты можно написать рабочую программу, но чего это может стоить от задачи к задаче? Может быть тогда уже проще будет воспользоваться компонентом TWebBrowser, закачать с его (точнее с помощью ядра Internet Explorer, коим он вовсю пользует) помощью страницу. Им же и распарсить. И отрисовывать ничего не надо – все уже готово.

Но так не интересно. Студенты не ищут легких путей. Изобретатели и энтузиасты, впрочем, тоже.

А коль скоро так – предлагаю взять на заметку эту святую простоту, может, где и пригодится.

Что еще Делфи может предложить. Из сторонних компонент скажем библиотеку Synapse Ararat, которую можно скачать с портала [1]. Почему бы и нет, тем паче отзывы не самые плохие. Можно скачать их (на момент написания статьи) Stable версию: «This is latest full release of Synapse, including demos and documentation. If you found some bugs here, see to SynaSnap development snapshot first» 2009-10-09 release no.39. Скачать труда не составляет. Возможно, даже проинсталлировать в палитру компонентов. Или по-простому, по-деревенскому ручками подключить (все это описано в [2] симпатичной на вид мадамой-автором статьи). Ничего сложного – так же одним единственным методом можно получить контент страницы

Всего-то четыре строчки и никакой мороки… Сказал я себе и жмакнул F9, он же пуск и… Жестоко обломался ибо мой антивирус сообщил что созданный экзешник инфицирован

Ой, держите меня… Я «пацталом» (извиняюсь за не литературщину). Я поинтересовался, что это за вирус, хоть приблизительно, нашел примерно такое: «Вредоносная программа, предназначенная для несанкционированного пользователем обращения к интернет-ресурсам (обычно, к веб-страницам). Достигается это либо посылкой соответствующих команд браузеру, либо заменой системных файлов, в которых указаны «стандартные» адреса интернет-ресурсов (например, файл hosts в MS Windows)…».

Мда… И это при том, что Synapse распространяется в виде исходников. Еще один «Nine Inch Nail» в крышку гроба сторонних компонентов и библиотек. Не обижайтесь фаны Синапса, но я однозначно понял, что эта библиотечка не стоит того чтобы, потом думать, а не выскочит ли у клиента антивирус из своей конуры и не покусает ли мои программы, использующие эту библиотеку.

Ладно, уж… Чего нам еще предлагает Делфи? Стандартные ClienSocket. Например, на просторах Интернета есть такая статейка [3]. Как вариант, можно взять на заметку. Однако скажу что, слегка попробовав этот пример, нарвался на ошибку

Эта ошибка происходит из-за того, что я в свойство Address передаю имя сайта в www форме:

А нужно передавать IP (я в коде его закомментировал). Если прописать IP программа отработает, но опять таки, придет страница с заголовком, неочищенная, да еще не факт, что целиком.

Не лучший способ, согласитесь. Вроде и можно, но… Зубной щеткой пол мыть, короче говоря. Да и IP передавать не удобно. Значит, этот способ загрузки тоже не очень подходит. Тем более, что эти компоненты уже успели устареть, и сегодняшнее Дельфи предпочитает их держать про запас, но не в активе. Что же остается?

А давайте посмотрим, что нам предложит… WinAPI. Да-да. Именно она (или он, или оно?) (API –программный интерфейс, значит он). Ведь все равно, как ни крути, все эти злосчастные двуличные компоненты используют функционал операционки (в данном случае, Windows). Ну, так, а что нам мешает их использовать? Тем более ту же схему (с функциями операционки) можно провернуть на любом языке. На Си, Обероне, Яве… будем надеться, что и в C# есть свои классы, которые можно попользовать для этого (как и в Яве, заявлена попытка на кроссплатформенность, поэтому с некоторой осторожностью).

Loading… 80% remained

Сразу вырисовывается задача – выяснить, какие WinAPI функции помогут нам в решении проблемы закачки сайта, да еще так их подобрать нужно, чтоб одинаковая схема сработала во всех языках.

Сразу скажу, что основной костяк функционала загрузчика живет в библиотеке Wininet.dll. Она есть во всех Виндовсах, начиная с 2000-го, предполагаю, что у вас не 98, потому именно ее и будем использовать. Можно сказать на правах рекламы.

Для изучения ее функционала можно «забуриться» в MSDN [4] (к слову, одна из самых неудобных и непонятных справок). Эта страница существовала на момент выхода статьи, не думаю, что ее скоро отменят. Там описан функционал для работы с Интернетом. Весьма внушительная библиотечка надо сказать.

Несмотря на все ее «разнообразие» нам понадобятся далеко не все функции, а только некоторые из них:

InternetOpen – Инициирует работу приложения с Интернетом. Здесь задается имя Агента НТТР протокола**. Фактически – это название нашего браузера. Туда можно впихнуть любое имя, подружки, название города, любимого киногероя, просто набор символов ничего не означающих.

** Комментарий автора.

«Первоначально протокол HTTP разрабатывался для доступа к гипертекстовым документам Всемирной паутины. Поэтому основными реализациями клиентов являются браузеры (агенты пользователя). Популярные браузеры (в алфавитном порядке): Epiphany, Google Chrome, Internet Explorer, Konqueror, Mozilla Firefox, Opera, Safari» – говорит нам Википедия.

В эту же функцию передается тип соединения, в нашем случае INTERNET_OPEN_TYPE_PRECONFIG, который заставляет функцию получать из реестра винды базовые настройки Интернета (если не ошибаюсь, речь идет о настройках Internet Explorer, которые считаются для Windows основными). Либо же остальные флаги (читайте MSDN) с указанием прокси сервера (если пишете раздельно пишите раздельно везде). Прикинемся карандашами, и будем считать, что прокси-серверов у нас либо нет, либо в IE все настроено как надо;

Получив хендл сессии предыдущей функции, если он верный, можно открывать страницу. За это ответит функция InternetOpenURL, которой мы передадим адрес сайта строкой. Остальные параметры не особо важны, их можно потом покрутить. Однако я предлагаю обратить внимание на параметр флага, в который стоит таки подставить константу INTERNET_FLAG_RELOAD, которая заставит винду считать страницу с сервера по-любому. Страница то может быть уже кем-то считана на нашей машине, и хранится у нас локально в КЭШе. Нам это не с руки, потому заставим эту страницу перечитаться, игнорируя кэш;

Соединение открыто, сессия тоже. Предыдущая функция дала хендл файла. Теперь можно с ним поступать точно так же как с обычными файлами в виндовсе – считывать поблочно в переменную массива… байтов, символов… Думаю уместнее все-таки байтов или ANSI символов. Чем и займется функция InternetReadFile. Она чертовски похожа на аналог, работающий с файлами. Здесь тоже нужно указать хендл (в нашем случае то, что вернула InternetOpenURL), буфер – массив байт. Куда будет считываться поблочно страница. ПОБЛОЧНО. Что дает возможность контролировать каждое считывание, отображая ход процесса в ProgressBar например. А так же можно парсить прямиком в ходе считывания, что уже, согласитесь. Неплохо;

Считали? Не забудем закрыть за собой файл, убрав мусор. Чем и займутся функция InternetCloseHandle для файла и для сессии.

Альтернатива этой схеме – функции из той же библиотеки HttpOpenRequest, HttpSendRequest. Всего-то разницы, но они специально предназначены для работы с http.

Я предлагаю так – В Делфи мы воспользуемся функциями InternetOpenURL, а в Си для разнообразия HttpOpenRequest. Какая разница? Есть разница, но пока что в целом значения этому придавать не будем.

Все. Это основная схема загрузки. Чистый WinAPI без «прибабахов» и жестоких проделок с компонентами. Предлагаю, кстати, заметить на будущее***, как дело обстоит в C#:

*** Комментарий автора.

Тут Микрософт тоже постарался позаботиться об удобстве загрузок. Если набрать в поисковике запрос «C# Закачка WEB страниц», то можно легко увидеть, что за все это безобразное шастанье по всемирной паутине в .NET отвечает пространство имен System.NET. Что ж. Давайте заглянем в его описание в MSDN, набрав в поиске MSDN’а эту фразу. Получим выход на страницу [5]. Ух, ты. Она на русском, это гут. По крайней мере, была на русском и в частности вообще была на момент написания статьи.

Loading… 70% remained

Итак, думаю направление для движения у нас уже определено, пора сделать первый шаг на пути к истине. И начнем мы шагать, обувшись в ботинки с названием Delphi. Наша задача сейчас – описать класс, способный по переданному ему URL загружать основной контент WEB страницы.

Чем же стоит наделить наш класс, чтоб загрузка происходила максимально удобно? Прежде всего, она должна быть надежной, ошибки должны (вернее желательно) обрабатываться. Если сессия не открыта, если файла такого нет, стоит предупредить человека что возникло исключение, но не по-борландовски, не назойливо – окошко с сообщением о конце мира, на котором поставлен белый крест в красном круге бесит невероятно, после чего программа любит просто закрываться. Нет, нужно просто описать событие, где будет обрабатываться ошибка. Захочет программист – опишет обработчик, не захочет, получит по шапке, и уронит свою репутацию. Но такую возможность, сохранить свою репу, ему нужно дать. Так же стоит прикрутить событие, срабатывающее при получении очередной порции контента. Парсер при этом может отработать эту часть, если это возможно, а браузер отобразить. Напоследок, не забыть событие окончания загрузки.

Также предлагаю изначально избавить загрузчик от замораживания приложения, и поместить его в дополнительный поток. Для чего стоит описать класс типа TThread, в котором загрузчик пусть и отрабатывает. Это придаст ему некой независимости. Скажите мне, вы хоть раз видели браузер, который, пока вся страница не загрузится, висит аки ковбой на виселице под палящим техасским солнцем? Я не видел таких браузеров. Может быть, просто не застал, по крайней мере, сейчас их вряд ли можно найти. Сейчас все поставлено на параллельность, каждый уважающий себя программист старается привинтить механизм сопроцессор, представляемый ему операционкой, если конечно она на это способна, и максимально с выгодой для себя и пользователя использовать его. Поскольку я использую Windows, сопроцессы стоит учесть, более того не придется писать много кода. К счастью микросовтовцы не наворачивали этот «механайз» тяжелым интерфейсом с кучей функционала. В Делфи я покажу, как использовать его VCL, а точнее класс (потомок класса) TThread. В Си же можно поискать аналогичный набор классов для организации сопроцессов (или доппотоков, кому какой термин нравится больше).

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

 
THTTPLoader=class
    private
      //Сопроцесс загрузки
      LoaderThread:TThread;

      //Строки адреса и содержимого WEB страницы
      FURL,FContent: String;
      FOnDoneLoad: TNotifyEvent;
      FOnDownloadPortion: TNotifyEvent;
      FOnError: TNotifyEvent;
      function GetContent: String;
      procedure SetURL(const Value: String);
      procedure SetOnDoneLoad(const Value: TNotifyEvent);
      procedure SetOnDownloadPortion(const Value: TNotifyEvent);
      procedure SetOnError(const Value: TNotifyEvent);
    Public
     // Строки ошибки и порции очередной закачки 
     ErrorString,Portion:String;
     // Для мобильности код ошибки
     ErrorCode:Integer;
     // и размер очередной порции, скачанной на очередной итерации загрузки 
     PortionSize:Cardinal;
     Property Content:String read GetContent;
   Published
     Property URL:String read FURL write SetURL;

     // События, возникающие по окончанию загрузки
     Property OnDoneLoad:TNotifyEvent read FOnDoneLoad write SetOnDoneLoad;

     // по загрузке очередной порции
     Property OnDownloadPortion:TNotifyEvent read FOnDownloadPortion write SetOnDownloadPortion;

     // при ошибке
     Property OnError:TNotifyEvent read FOnError write SetOnError;
  end;

Стандартными средствами Делфи описывается типичный набор – свойства, методы класса. При присваивании свойству URL строки адреса будет инициироваться сопроцесс, в котором пройдет загрузка страницы. Загрузка будет происходить по частям в цикле, каждая загруженная часть может быть обработана в событии OnDownloadPortion, после чего пойдет загрузка следующей порции. Размер порции загрузчик определит сам. После окончания загрузки сработает событие окончания загрузки, дабы уведомить программу о том, что весь контент предоставлен в ее распоряжение. Естественно загрузчик имеет свойство, указывающее на объект сопроцесса LoaderThread, класс которого описан в следующем листинге. Он достаточно прост, не содержит ничего лишнего. Я предлагаю поместить его в коде после описания класса загрузчика. Так будет удобнее приводить типы, ведь основная работа все-таки придется на долю этого потока:

 
TThreal_Loader = class(TThread)
  private
   Loader:THTTPLoader;
    { Private declarations }
  protected
    procedure Execute; override;
  end;

Тут и описывать собственно нечего. Свойство Loader будет знать своего работодателя, который этот сопроцесс вызывает, и будет активно в ходе закачки передавать ему закачанные данные, и вызывать обработчики его событий (если они назначены программистом в основной программе). Обратите внимание – Дельфи позволило мне описать это свойство как THHPLoader, потому что я поместил этот класс выше, таким образом, поток видит его. Если бы я поместил описание интерфейса потока перед этим классом в модуле, мне бы пришлось приводить типы. Например, для помещения в свойство FContent мне бы пришлось писать что-то вроде THTTPLoader(Loader).FContent в то время как сейчас я просто напишу Loader.FContent поскольку тип Loader’a уже известен компилятору при анализе этой части. Меньше букв писать – читабельнее программа получается, хороший программист старается это учитывать, и помещает описания классов так, чтоб они максимально эффективно могли ссылаться друг на друга. Выше всех класс, которому не обязательно важно работать с полями других классов, ниже классы, которые интенсивно работают с полями вышестоящего класса. Жаль что Делфи (та и Паскаль – уважай язык, хоть он и старый, Фортран же пишут с большой буквы), соблюдая строгость, не хотят находить тип класса в любом месте программы, а смотрят только в начале перед использованием типа. Ну да ладно, видимо, причины, соблюдать такую строгость, у разработчиков были. Как бы там ни было, сопроцесс знает своего работодателя в лицо, и преспокойненько с ним работает на «ты».

Само тело работодателя THTTPLoader хиленькое. Мы не будет его накачивать стероидами, пусть будет стройный и маленький. От него особых телодвижений не потребуется – он выступает в роли посредника:

 
{ THTTPLoader }

  function THTTPLoader.GetContent: String;
  begin
    Result:=FContent;
  end;

  procedure THTTPLoader.SetOnDoneLoad(const Value: TNotifyEvent);
  begin
    FOnDoneLoad := Value;
  end;

  procedure THTTPLoader.SetOnDownloadPortion(const Value: TNotifyEvent);
  begin
    FOnDownloadPortion := Value;
  end;

  procedure THTTPLoader.SetOnError(const Value: TNotifyEvent);
  begin
    FOnError := Value;
  end;

  procedure THTTPLoader.SetURL(const Value: String);
  begin
    FURL := Value;
    LoaderThread:=TThreal_Loader.Create(true);
    with TThreal_Loader(LoaderThread) do begin
     Loader:=self;
     FreeOnTerminate:=true;
     Resume;
    end;
  end;

Собственно говоря, его сердце (а так же печень, легкие и пр. органы) помещено в единственную важную процедуру – обработчик свойства URL SetURL. Здесь создается сопроцесс (на всякий случай я переменную на него предлагаю сделать полем, вдруг нашему хилячку понадобится сопроцесс этот, контролировать), который знакомится с загрузчиком, посредством свойства Loader, задается указание «уволиться» после окончания загрузки (а больше он не понадобится) и запускается машинка загрузки. Тут и начинается самое интересное:

 
procedure TThreal_Loader.Execute;
  var hSession,hFile:HINTERNET; s:string; Size,nbr:Cardinal;
  begin
    inherited;
    Loader.FContent:='';  Loader.ErrorString:='';
    //Откроем сессию.  Наш агент называется красивым женским именем. :)
    hSession := InternetOpen('Анютка',INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
    // Если сессия открыта, хендл сесии создан
    if hSession<>nil then begin
      // Запросим файл с указанием всенепременно загрузить его с сервера
      hFile := InternetOpenURL(hSession, PChar(Loader.FURL), nil, 0,INTERNET_FLAG_RELOAD, 0);
      // Если запрос удался
      if hFile<>nil then begin
        // выясним сколько нужно памяти для первой порции
        InternetQueryDataAvailable(hFile,Size,0,0);
        // Если конечно сервер нас не пошлет, сказав, что порции не будет
        if Size<>0 then begin
          // Но пока он нам их отсыпает
          while Size<>0 do begin
            // мы с благодарностью протягиваем ладошки
            SetLength(s,Size);
            // и получаем очередную жменьку
            if not InternetReadFile(hFile,@s[1],Size,nbr) then break;
            // которую отдаем загрузчику на съедение со всей кожурой, и размерами
            Loader.FContent:=Loader.FContent+s;
            Loader.Portion:=s;
            Loader.PortionSize:=nbr;
            // о чем уведомляем основную программу - "Смотрите, а он семки хавает"
            if Assigned(Loader.FOnDownloadPortion) then Loader.FOnDownloadPortion(Loader);
            // не забыв о вежливости, приостанавливаемся на некоторое время
            // чтоб не загружать процессор, дав ему возможность поработать с другими
            // запущенными приложениями без напряга
            sleep(10);
            // После чего получаем размер очередной порции и опять в ту же степь
            InternetQueryDataAvailable(hFile,Size,0,0);
          end;
        end else begin
         //Если Размер файла по каким то причинам нулевой
         Loader.ErrorString:='Запрашиваемый файл пуст';  Loader.ErrorCode:=1;
         if Assigned(Loader.FOnError) then Loader.FOnError(Loader);
        end;
        // в конце загрузки не забываем закрыть хендл...
        InternetCloseHandle(hFile);
      end else begin
       //Если файл не открывается сгенерируем ошибку для обработки
       Loader.ErrorString:='Не могу открыть файл';  Loader.ErrorCode:=2;
       if Assigned(Loader.FOnError) then Loader.FOnError(Loader);
      end;
      // ...и освободить сессию
      InternetCloseHandle(hSession);
    end else begin
     //Если сессия не открывается сгенерируем ошибку для обработки
     Loader.ErrorString:='Не могу открыть сессию'; Loader.ErrorCode:=3;
     if Assigned(Loader.FOnError) then Loader.FOnError(Loader);
    end;
    // ну и по джентльменски уведомим программу об окончании загрузки
    if Assigned(Loader.FOnDoneLoad) then Loader.FOnDoneLoad(Loader);
  end;

Обратите внимание – функции получения контента из Интернета жутко смахивают на функции считывания файла. Для винды Интернет – это та же сеть, с теми же файлами. А адреса порталов – «расшарки», с которых можно что-то скачать, скопировать. Так что принципы везде одни и те же, не нужно что-то новое отличное от привычного изучать. Это хорошо, зачет Микрософту за это, могли ведь написать какое-нибудь страшилище, но не написали (но не скопировали, будет точнее).

Можно еще обратить внимание, что я не все ошибки (не на каждую функцию) обрабатываю. Впрочем для минимума такой бдительности думаю хватит. Надеюсь всем понятно, что стоящим решением будет выделить его в отдельный модуль? Если нет, то рекомендую именно это. Для каждого свой Хаус.

На данном этапе появляется возможность попробовать монстра в действии, для чего создадим в Делфи проект с формой, на которую можно поместить скажем… Мемо. И кнопку, по жмаку которой будет загружаться страница. Это основы Делфи, я не буду рассказывать, как это сделать, если не знаете, почитайте один из самоучителей по Делфи, или просто книгу, о том, как работать в дизайнере. Тут же я уточню, что модуль с загрузчиком подключим в раздел uses и для формы создадим свойство-переменную private l:THTTPLoader; Надеюсь, никого не смущает, что я поместил переменную загрузчика в приватную секцию формы? Он то и нужен только форме, в крайнем случае, можно определить его место среди глобальных переменных, но уверен, что многие программисты начнут плеваться на такое местоположение. В общем, не суть важно (сейчас) куда его поместить. Пусть будет в приватной комнате. Теперь на форму кинем Мемо, растянем его на все форму, выставим (для красоты) полосы прокрутки, и кнопку. Куда-нибудь в угол. Можно меню вместо кнопки. В любом случае по клику кнопки (или менюшки) нужно будет выполнять процесс загрузки. Но для начала не забудем корректно создать и освободить компонент:

 
procedure TForm1.FormCreate(Sender: TObject);
  begin
   l:=THTTPLoader.Create;
   l.OnDoneLoad:=OnTerm;
   l.OnDownloadPortion:=OnPortion;
   l.OnError:=OnError;
  end;

  procedure TForm1.FormDestroy(Sender: TObject);
  begin
   l.Free;
  end;

L – переменка загрузчика, ее создаем с формой, и с формой же освобождаем. Загрузчику указываем обработчики его событий. В них отразим ход, так сказать, загрузки.

И саму соль – клик кнопки, и «напряг» загрузчика:

 
procedure TForm1.Button1Click(Sender: TObject);
  begin
  L.URL:='http://www.ya.ru';
  end;

Здесь загрузчику передается адрес сайта, с которого нужно скачать что-то. Будем для простоты полагаться на автоматику портала – получать страницу по умолчанию. Для демонстрации возможностей этого хватит. Чтобы окончательно ничего не забыть опишем обработчики событий загрузчика:

 
procedure TForm1.OnPortion(Sender: TObject);
  begin
    Memo1.Lines.Add('Загружено '+IntToStr(THTTPLoader(sender).PortionSize));
  end;

  procedure TForm1.OnTerm(Sender: TObject);
  begin
   Memo1.Lines.Add(THTTPLoader(sender).Content);
  end;

  procedure TForm1.OnError(Sender: TObject);
  begin
    Caption:=THTTPLoader(sender).ErrorString;
  end;

Не будем особо оригинальничать:

OnPortion – Обработчик для каждой загруженной порции. Заставим его в Мемо отражать статистику загрузки;

OnTerm – Обработчик окончания загрузки. Здесь просто впишем в мемо весь полученный контент страницы;

OnError сработает если произойдет какая-то ошибка. Будем надеяться, что не произойдет.

Теперь все счастье у нас в руках, и можно выпускать его на волю. Жмем F9, и кнопку.

Во! То, что надо. В работе с Интернетом правила файрволла учитывать обязательно. Обратите внимание – в Мемо прошла статистика о том, какие порции были загружены, по сколько байт, после чего прям таки сама страница во всей ее наготе расположилась (или разложилась) в Мемо.

На данный момент можно сказать, что в Дельфи удалось осуществить загрузчик, и между прочим малой кровью. Без зависимости от компонентов, что уже обнадеживает на масштабируемость проекта в будущем, если понадобится перемести его на, скажем, новую версию Дельфи. Это хорошо. Это плюс, и достаточно ощутимый. Несмотря на то, что я писал программу на D6 (D2001) она вполне откомпилируется в последующих версиях. Ах да… Я не указал, какую версию Делфи будем использовать. Будет лучше, если проект вообще не будет зависеть от версий.

Вы можете ответить или разместить запись на вашем сайте.

Ответить

Powered by Procoder