Версия для печати


Компонент THTTPGet
http://www.delphikingdom.com/asp/viewitem.asp?catalogID=1449

Петр Смирнов
дата публикации 01-06-2014 10:48

Для Delphi существует немало компонентов, предназначенных для работы со стандартным HTTP протоколом:

Эти компоненты умеют очень многое, не только простую загрузку с HTTP/HTTPS, но и работу с FTP, SMTP, POP3 и рядом других протоколов. Но во многих программах требуется всего лишь использовать загрузку и парсинг страниц и файлов с HTTP/HTTPS серверов. И поддержка "лишних" функций совершенно не планируется (я, конечно, понимаю фанатов, которые будут пытаться реализовать абсолютно абстрагированную от источника данных программу, но не стоит упорствовать, поверьте) то вполне хорошим решением будет использование функций работы с HTTP (которыми, кстати, пользуется сам WebBrowser) под названием WinInet.

Документацию по WinInet можно найти как на языке оригинала — MSDN, так и на русском. Разумеется, и там и там описания будут даны на С++. Если поискать, то можно найти десятки несвязных примеров использования WinInet в проектах Delphi. Они оформлены просто как куски кода, а потому об их реиспользовании даже не идёт речь. Порывшись на Torry, мне удалось найти немало прилично оформленных модулей, упрощающих использование WinInet. Откинув все коммерческие и shareware (странно платить за то, что уже оплачено покупкой Windows и Delphi и то, что ожидается в комплекте с этими продуктами), особенно хочется отметить оформленные в виде компонентов:

Как я уже говорил, платные варианты никак не устраивают. А простота указанного HTTPGet подкупала. Потому я решил использовать именно его. Тем более, что никаких требований, кроме оставления в покое комментария в начале модуля, использование компонента не предполагало. Поэтому я начал активно использовать этот компонент в своих программах и использовал его с 2008 года.

Разумеется, через некоторое время мне стало "не хватать" тех возможностей, которые предлагал мне компонент "из коробки". А поскольку исходные коды были доступны, то я принялся дорабатывать его под свои нужды. В первую очередь, это коснулось того, что компонент получил возможность отправлять POST запросы, а также добавлять дополнительные поля в запрос. Компонент перестал виснуть при работе из консольного приложения. Ну и ещё несколько косметических изменений. Именно доработанный компонент я и предлагаю Вашему вниманию. Ничего необычного он не предлагает, но для меня его использование оказалось проще, чем использование навороченных Indy или Synapse.

Достоинства

Недостатки

Свойства

В таблице используются обозначения:

ВидимостьНазваниеТипУмолчаниеДоступОписание (оригинальное описание)
Отладочные свойства
publicRawHeadersstringпустоRO "Сырые" заголовки, без разбора, пришедшие от сервера. Поскольку запрос этого параметра занимает некоторое время, он по умолчанию отключён. Включается с помощью параметра DebugMode. При выключенном DebugMode всегда возвращает пустую строку
publicDebugModebooleanfalseRW Включает режим получения "сырых" заголовков. Это чуточку замедляет работу компонента. К тому же, сырые заголовки практически никогда не требуются (мне потребовались только раз в жизни, для чего я и ввёл этот параметр).
Конфигурация компонента
publishedTimeoutinteger30000RW Используется при выполнении запросов с ожиданием потока. Если время работы потока превысит указанное значение — то поток будет аварийно остановлен, а сам процесс загрузки остановится с этой ошибкой. При работе без ожидания потока, либо в полностью синхронном режиме таймаут не используется.
publishedWaitThreadbooleanfalseRW Если включено, то попытка выполнения асинхронных функций GetFile и GetString остановит выполнение процедуры, дожидаясь окончания загрузки. При этом можно не пользоваться событиями, а вычитывать данные прямо из ResultString, или скачанного файла. Для консольного варианта исполнения, свойство включено. Но в консольной программе имеет смысл вообще использовать синхронные GetFileSync и GetStringSync. Они не порождают ненужных потоков.
publishedKeepAlivebooleantrueRW Добавляет заголовок Keep-Alive в отправляемые на сервер данные. Поскольку протокол HTTP 1.1, используемый в компоненте, подразумевает, что соединение будет "постоянным", а большинство серверов это поддерживают, я вообще не вижу смысла отключать этот параметр. Правда, есть такая "особенность" этого постоянного соединения, что иногда возникает ошибка "Соединение с сервером было сброшено". Отключение Keep-Alive помогает этого избежать, но значительно замедляет соединение — так, для пачки файлов размером 300-400 байт, время на получение удваивается.
publishedConnectionTypeTConnectionTypectAutoRW Устанавливает тип используемого интернет-соединения:
ctDirect — использует прямое подключение, без прокси
ctAuto — использует настройки соединения как у Internet Explorer
ctAutoNoINS — использует настройки соединения как у Internet Explorer, но пропускает загрузочные Microsoft JScript и скрипты настройки интернета (это перевод официальной страницы, он мне кажется кривым, у кого есть идеи лучше— предлагайте).
ctProxy — принудительное использование прокси (см. свойства, начинающиеся на Proxy...)
publishedProxyBypassstringпустоRW Перечисляет список серверов, или IP адресов, обращения к которым не должно производиться через прокси-сервер даже если ConnectionType=ctProxy. Этот список может содержать маски. Обращение к адресам localhost, loopback, 127.0.0.1, ::1 всегда выполняется напрямую, так как большинство прокси-серверов неправильно обрабатывают обращения с таким адресом.
PS: В документации не указано, как именно нужно перечислять список серверов в данном списке: через пробел, запятую, точку с запятой и т.д. Эксперимент показал, что список можно писать через запятую.
publishedProxystringпустоRW Указывает прокси-сервер для соединения ctProxy. Практика показала, что для ctAuto и ctAutoNoINS прокси не всегда корректно цепляется, так что есть подозрение, что прокси при его наличии надо указывать всегда.
publishedProxyPortinteger8080RW Порт для прокси сервера
publishedProxyUserstringпустоRW Если прокси требует авторизацию, укажите здесь имя пользователя. Если используется NTLM аутентификация, то ничего указывать не надо — всё и так будет работать. Это ключевой момент, крайне важный для меня. С NTLM аутентификацией многие другие компоненты адекватно не работает. Если имя пользователя не указано, аутентификация не импользуется.
publishedProxyPasswordstringпустоRW Соответственно, пароль для того пользователя, который используется для авторизации на прокси. Несмотря на то, что свойство я сделал published, я считаю достаточно странным хранить здесь пароль, лучше всё-таки воспользоваться защищённым хранилищем.
publishedUserNamestringпустоRW Имя пользователя, используемого HTTP аутентификации. Никакой связи с прокси-аутентификацией! Если не указан — HTTP аутентификация не производится.
Оригинальное описание:
Set this properties if you trying to get data from password protected directories.
publishedPasswordstringпустоПароль пользователя, используемый для HTTP аутентификации.
publishedAgentstringMozilla/4.0...RW Идентификатор клиента пользователя. Нужен крайне редко, по умолчанию в 99% справляется
Оригинальное описание:
User Agent
publishedRefererstringпустоRW Здесь хранится URL предыдущей загруженной страницы.
Оригинальное описание:
Additional data about referer document
publishedAddHeaderstringпустоRW Дополнительные заголовки, которые можно отправить с запросом. Пример: X-Requested-With: XMLHttpRequest (имитация запроса от XMLHttpRequest) или DNT: 1 (просьба сайт не сохранять информацию о пользователе... впрочем, все сайты эту просьбу игнорируют).
publishedAcceptTypesstring*/*RW Какие типы (в соответстии с RFC) хочет принимать приложение. По умолчанию — всё, что отправит сервер.
publishedBinaryDatastringtrueRW Трактует передаваемые сервером данные как двоичный поток. На самом деле, просто пытается узнать размер данных ДО их реального получения. Многие сервера генерируют такой заголовок и для HTML страниц и даже для динамически генерируемых страниц. Если эта настройка отключена, будет неправильно работать OnProgress. Необходимости в отключении этой настройки я не вижу.
Оригинальное описание:
This setting specifies which type of data will taken from the web. If you set this property TRUE then component will determinee the size of files *before* getting them from the web.
If this property is FALSE then as we do not knows the file size the OnProgress event will doesn't work. Also please remember that is you set this property as TRUE you will not capable to get from the web ASCII data and ofter got OnError event.
publishedUseCachebooleanfalseRW Указывает, что запрашиваемые данные можно взять из кеша Internet Explorer.
Оригинальное описание:
Get file from the Internet Explorer's cache if requested file is cached.
publishedPostQuerystringпустоRW Выполняет POST запрос с указанными данными. Разумеется, данные должны быть правильно URLEncodнуты, иначе всё может закончиться плачевно. Заголовок application/x-www-form-urlencoded добавляется автоматически.
publishedFileNamestringпустоRW Указывает путь к локальному файлу, куда будут сохранены данные, если был выполнен запрос с использованием GetFileSync или GetFile. Если это свойство не задать, попытка закачки с помощью GetFile или GetFileSync не выполнится.
Оригинальное описание:
Path to local file to store the data taken from the web
publishedURLstringпустоRW URL, который надо скачать. Не должен быть пустым при выполнении любого метода.
Оригинальное описание:
The url to file or document
publishedTaginteger0RW Классика — просто числовое поле, где Вы можете хранить что угодно.
Полученная информация
publicReadybooleantrueRO Показывает готовность компонента к новой загрузке. Если false — значит, компонент производит загрузку. Обычно, не имеет смысла проверять эту переменную — по окончанию работы автоматически сработает либо OnError, либо OnDoneFile или OnDoneString.
publicResultStringstringпустоRO После выполнения методов GetString, GetStringSync, RunSync(false), RunAsync(false) содержит полученные с сервера данные в виде строки. Во всех остальных случаях — пустая строка. Передаётся в качестве параметра в обработчики OnDoneString, OnDoneStringProc, потому данное свойство полезно только в случае синхронных запросов без использования специализированного обработчика.
publicErrorMessagestringпустоRO Содержит описание, отданное операционной системой при возникновении любых ошибок. Также эта строка передаётся в обработчик OnError, OnErrorProc, потому данное свойсво полезно только в случае синхронных запросов без использования специализированного обработчика. Для унификации, все сообщения англоязычные.
publicErrorCodeinteger0RO Содержит код ошибки, возвращаемые операционной системой, или одно из предопределённых в модуле значений:
HTTPGET_TIMEOUT — время работы потока загрузки превысило значение, указанное в поле Timeout
HTTPGET_NOTSTARTED — ошибка возникает, если не удалось стартовать поток (хендл от BeginThread=0). Мне никогда не встречалась
HTTPGET_FILENOTCREATED — GetFile или GetFileSync не смогли создать файл, указанный в свойсте FileName. Скорее всего, файл уже существует, или папка, куда вы хотите загрузить файл, недоступна для записи текущему пользователю.
HTTPGET_USERREQUEST — поток был завершён процедурой Abort
HTTPGET_BADRESPONSE — одно или несколько полей, переданных сервером, не опознаны. Например, код HTTP запроса (обычно — 200) принял нечисловое значение. Ни с одним из серверов, с которыми я работал, такой ошибки не появлялось.
Это значение передаётся в обработчик OnError, OnErrorProc, потому данное свойсво полезно только в случае синхронных запросов без использования специализированного обработчика.
publicCookiestringпустоRO Осталось в наследство от старого компонента. Отдаёт полученные куки. Вызывает метод GetCookie('');
Оригинальное описание:
Cookies, that you want to send to host. After processing, there will be cookies, sent by host. Now, Expires, Domain, Path and secure cookie options didn't stored.
publicContentDispositionstringпустоRO Возвращаемая сервером строка, описывающая, какое имя и содержимое у присланного файла. Сам компонент игнорирует данное свойство, то есть ВСЕГДА сохраняет файл по тому имени, которое задано в свойстве FileName, так что после закачки приложение самостоятельно (если в этом возникает необходимость) должно проверить это свойство и переименовать закачанный файл в соответствии с указаниями от сервера. Строка не парсится. Обычно, она имеет вид attachment; filename="fname.ext" и легко разбирается на части регулярным выражением. Также в ответе может присутствовать размер файла — выведите отладочное сообщение при написании программы, чтобы понять, какой реально вид у этого заголовка для сервера, с которым Вы работаете.
publicResponseCodecardinal200RO Возвращённый в соответствии с RFC результат

Свойства

Здесь перечислены две группы свойств. Они абсолютно идентичны и вызываются одновременно, если заданы и то и другое. Но для VCL компонентов привычнее писать обработчики в объектно-ориентированном виде (то бишь при работе в дизайнере компонентов). Для консольного приложения (кому как, ИМХО) более привычны обычные процедуры, а то и вообще синхронный режим работы.

НазваниеПрототипОписание
Свойства, предназначенные для VCL приложений (published)
OnProgressprocedure(Sender: TObject; TotalSize, Readed: Integer) of object; Выполняется при закачке очередной порции данных.
Sender — THTTPGet, отправивший сообщение. Крайне полезно в многопоточной конфигурации.
TotalSize — сколько данных предстоит получить. При выключенном BinaryData будет равен всегда 0.
Readed — сколько данных уже получено
OnDoneFileprocedure(Sender: TObject; const FileName: String; FileSize: Integer) of object; Выполняется по окончанию загрузки файла. Файл при этом уже отпущен компонентом и может быть использован (например, переименован в соответствии с командой от сервера).
Sender — THTTPGet, отправивший сообщение. Крайне полезно в многопоточной конфигурации.
FileName — то же, что и FileName компонента
FileSize — размер принятого файла
OnDoneStringprocedure(Sender: TObject; const Result: String) of object; Выполняется по окончанию загрузки строки.
Sender — THTTPGet, отправивший сообщение. Крайне полезно в многопоточной конфигурации.
Result — то же, что ResultString компонента.
OnErrorprocedure(Sender: TObject; const Error: String; Code: integer) of object; Выполняется при ошибке. Если это событие произошло, то ни OnDoneFile ни OnDoneString выполнены не будут. При ошибке, можно попытаться интерпретировать содержимое ResultString или недокачанного файла (автоматически он не удаляется, его надо удалять руками).
Sender — THTTPGet, отправивший сообщение. Крайне полезно в многопоточной конфигурации. Внимательно следите, не вызвана ли ошибка отменой закачки по команде пользователя, чтобы случайно не стартовать новую закачку!
Error — сообщение об ошибке на русском языке в кодировке CP-1251 (для VCL)
Code — код ошибки. Требуется, если необходимо локализовать приложение
Свойства, предназначенные для консольных приложений (public)
OnProgressProcprocedure(Sender: TObject; TotalSize, Readed: Integer) Выполняется при закачке очередной порции данных. Полный аналог OnProgress, но является обычной процедурой.
OnDoneFileProcprocedure(Sender: TObject; const FileName: String; FileSize: Integer); Выполняется по окончанию загрузки файла. Полный аналог OnDoneFileProc, но является обычной процедурой.
OnDoneStringProcprocedure(Sender: TObject; const Result: String); Выполняется по окончанию загрузки строки. Полный аналог OnDoneString, но является обычной процедурой.
OnErrorProcprocedure(Sender: TObject; const Error: String; Code: integer); Выполняется при ошибке. Полный аналог OnError, но является обычной процедурой, а также сообщение об ошибке поступает в OEM кодировке, а не в ANSI (для непосредственного вывода на консоль).

Методы

Методы для закачки имеют два вида: синхронный (выполняется без порождения отдельного потока) и асинхронный (выполняется в отдельном потоке). Какой именно метод выбрать — зависит от ситуации, но в асинхронной по природе VCL стоит внимательнее приглядеться к асинхронной реализации, а в консоли пользоваться в основном синхронной. Разумеется, это только предположения, в реальной жизни всё может быть несколько сложнее.

ПрототипОписание
constructor Create(aOwner: TComponent=nil); Создаёт объект. Для консольного варианта можно явно не указывать несуществующего владельца.
destructor Destroy; Без комментариев
function SetCookie(const Name,Value:string):boolean; Устанавливает в Internet Explorer указанную куку
function GetCookie(const Name:string):string; Читает указанную куку из Internet Explorer. Если указать пустое имя — будут прочитаны все куки для соответствующего URL.
procedure Abort; Отменяет асинхронную загрузку. Это приводит к появлению ошибки!
Оригинальное описание:
Stop the current session
Асинхронные методы — выполняются в отдельном потоке
function GetFile:boolean; Для совместимости со приложениями, заточенными на оригинального THTTPGet оставлена старая GetFile. На самом деле, она просто вызывает RunAsync(true);
Оригинальное описание:
Get the file from the web specified in the URL property and store it to the file specified in the FileName property
function GetString:boolean; Для совместимости с приложениями, заточенными на использование оригинального THTTPGet оставлен старый GetString. На самом деле, просто вызывает RunAsync(false);
Оригинальное описание:
Get the data from web and return it as usual String. You can receive this string hooking the OnDoneString event.
function RunASync(const ToFile:boolean):boolean; Загружает данные в отдельном потоке. Если установлен параметр WaitThread, то выход из функции будет осуществлён только после окончания загрузки данных, в этом случае возаращаемое значение true говорит о том, что закачка завершена успешно (используйте ResultString или файл с именем FileName, чтобы получить запрашиваемую информацию), а false — что произошла некоторая ошибка (используйте ErrorCode и ErrorMessage для уточнения).
Если ToFile установлен, то закачка производится в файл с указанным именем, в противном случае — в ResultString
Если WaitThread сброшен, то выход из функции производится немедленно, при этом возвращаемый результат true говорит лишь о том, что поток был успешно запущен, а false — что поток запущен не был. Наиболее вероятная проблема в данном случае — потом ранее был запущен и сейчас ведёт скачивание другой информации.
Учтите, что использовать одновременно синхронные и асинхронные обращения нельзя, работать так они не будут. Выберите заранее тот режим, который Вам нужен. Лично я считаю, что синхронные методы надо использовать в консольных приложениях, а асинхронные — в VCL приложениях.
Синхронные методы — выполняются в вызывающем потоке
function GetFileSync:boolean; Аналог GetFile со включённым WaitThread, но выполняется без выделения в отдельный поток. На самом деле, просто вызывает RunSync(true);
function GetStringSync:string; Аналог GetString со включённым WaitThread, но выполняется без выделения в отдельный поток. На самом деле, просто вызывает RunSync(false);
function RunSync(const ToFile:boolean):boolean; Загружает данные в том потоке, из которого производится вызов, без порождения отдельного потока. Это приводит к тому, что не работает свойство Timeout, защищающее от зависания, не в полной мере отрабатывает Abort.
Если ToFile установлен, то закачка производится в файл с указанным именем, в противном случае — в ResultString
Всестороннего тестирования синхронных методов в VCL приложениях не проводилось. Есть подозрение, что при использовании в главном потоке, будут проблемы (поток останется замороженным на всё время работы компонента). Также при использовании обработчиков событий, могут появляться проблемы, связанные с тем, что вызовы обработчиков выболняются без использования Synchronize, что (к сожалению, лишь периодически) будет приводить к различным проблемам при использовании VCL компонентов из таковых обработчиков.

Установка компонента

На самом деле, ничего не мешает использовать этот компонент как простой хелпер-класс вроде TStringList, создавая его прямо в процедуре/методе и тут же его разрушая. Но для адекватного использования асинхронного метода, всё-таки лучше воспользоваться средствами, предлагаемыми Delphi. Для этого откройте (или создайте) любую библиотеку компонентов (обычно я не мудрствую лукаво и использую стандартную dclusr.dpk), добавить туда .pas файл и нажать кнопку "Install". Если всё нормально, компонент будет установлен в среду и будет доступен на вкладке UtilMind (по историческим причинам). Как пользоваться палитрой компонентов, я надеюсь, учить Вас не нужно.

Примеры использования

Сами исполняемые файлы я не прикладывал. Причина описана выше в разделе Недостатки.

  1. Скрытое тестовое приложение. Единственный файл — TestHidden.dpr. Всё, что делает — загружает главную страницу нашего сайта DelphiKingdom. В конце выводится два MessageBox, показывающие конец загрузки данных и конец работы программы. Особенности поведения скомпилированной версии этой программы описаны в разделе Недостатки.
  2. Консольное тестовое приложение. Единственный файл — TestConsole.dpr. Всё, что делает — загружает главную страницу нашего сайта DelphiKingdom. В процессе выводится прогресс выполнения работы, в конце запрашивается нажатие "Ввод". Данные сохраняются в файл index.htm (по умолчанию). В зависимости от того, был ли залогинен пользователь в Internet Explorer, получим либо незалогиненную версию сайта, либо уже залогиненную. Особенности поведения скомпилированной версии этой программы описаны в разделе Недостатки.
  3. Стандартное VCL приложение. Три файла: проекта TestUpdater.dpr, формы TestUpdate.dfm и исходник формы TestUpdate.pas. Для успешной компиляции, требуется установить компонент в среду Delphi. Что делает эта программа? Есть такая игра — OSU!. Я в неё никогда не играл, но по просьбе одного человека набросал программу для скачивания файлов с этого ресурса. Суть заключается в том, что качать можно только в том случае, если пользователь залогинен на сайте. Так что программа имеет две кнопки и два поля ввода:

Постскриптум

Надеюсь, что компонент Вам понравится. Плюсы и минусы перечислены, примеры использования есть, описания свойств и методов имеются. Что же ещё я забыл? Ах да! Самое главное! Архив с компонентом и примерами прилагается к статье.

Я решил не раскладывать всё по папкам... и так всё достаточно очевидно. Пишите в комментариях, какие изменения стоит внести в компонент. Надеюсь на конструктивную критику. Всегда Ваш

Python aka SmiSoft.



К материалу прилагаются файлы: