| | | | |
Полный текст материала
Другие публикации автора: Петр Смирнов
Цитата или краткий комментарий: «... Для Delphi существует немало компонентов, предназначенных для работы со стандартным HTTP протоколом. Они умеют очень многое кроме простой загрузки с HTTP/HTTPS. Но во многих программах требуется всего лишь использовать загрузку и парсинг страниц и файлов с HTTP/HTTPS серверов. И для них вполне хорошим решением будет использование функций работы с HTTP под названием WinInet. ...» |
Важно:- Страница предназначена для обсуждения материала, его содержания, полезности, соответствия действительности и так далее. Смысл не в разборке, а в приближении к истине :о) и пользе для всех.
- Любые другие сообщения или вопросы, а так же личные эмоции в адрес авторов и полемика, не относящаяся к теме обсуждаемого материала, будут удаляться без предупреждения авторов, дабы не мешать жителям нормально общаться.
- При голосовании учитывайте уровень, на который расчитан материал. "Интересность и полезность" имеет смысл оценивать относительно того, кому именно предназначался материал.
- Размер одного сообщений не должен превышать 5К. Если Вам нужно сказать больше, сделайте это за два раза. Или, что в данной ситуации правильнее, напишите свою статью.
Всегда легче осудить сделанное, нежели сделать самому. Поэтому, пожалуйста, соблюдайте правила Королевства и уважайте друг друга.
Добавить свое мнение.
Всего проголосовали: 0 | Всего проголосовали: 0 |
Отслеживать это обсуждение
Всего сообщений: 907-06-2014 14:03сообщение от автора материала Приходится прибегать к рекламным трюкам для того, чтобы расширить зону тестирования в поисках истины. Благодаря »вопрос КС №82590« и рыцарю %url[Oleg_Guz]/asp/users.asp?ID=25320 удалось выяснить, что есть проблемы при компиляции на XE5, хотя это всё связано с тем, что "новые" версии Delphi по умолчанию трактуют string как WideString а PChar как PWideChar, тогда как "старые" версии - как AnisString/PAnciChar соответственно. В очередном обновлении я внёс изменения, которые должны устранить проблему, но пока выкладывать третью версию компонента не буду - слишком незначительные изменения, подожду, когда побольше багов накопится. |
|
05-06-2014 11:34Пусть будет и внешняя штука, по которой можно определить прогресс
Я скромно умолчал о недостатках такого подхода:)
Нужно очень аккуратно освобождать поток, к которому могут быть обращения извне.
С уважением. |
|
04-06-2014 15:31сообщение от автора материала >>> Использование OnProgress упрощает работу
В таком случае поступаю очень просто. Пусть будет и внешняя штука, по которой можно определить прогресс, пусть будет и OnProgress. Кому что понравится. Поскольку у меня основная масса утилит - консоль, куда надо выводить прогресс по типу "вернись в начало строки и нарисуй полоску", то мне от прогресса деваться некуда. Ну а в VCL приложении получатся варианты...
>>> Упомянутое вами ограничение размера файла 2 гб
Уже переделал под WinAPI. Так что единственное место, где может возникнуть исключение - это где угодно, но критическое, типа Access Violation. Пока в раздумьях... убить-неубить... вот в чём вопрос. Вроде как желательно пользователя оповестить о том, что поток всё-таки помер, пусть и от критической ошибки. Потому что в большинстве случаев при этом обработчик ошибок спасает качаемый URL и втыкает его в конец очереди (изменяя счётчик ошибок, но это детали реализации).
>>> не проверяете результат InternetCrackURL (он вообще тут нужен?)
Сам вызов то нужен. Потому что как минимум, мне требуется подключение к нестандартному HTTPS порту (в моём случае - 5005), а указать его прямо в URL - милое дело. InternetCanonicalizeUrl - посмотрю, что за зверь, не встречался ранее, если будет заметна польза, добавлю. Ну и проверку ошибок при InternetCrackURL тоже запилю.
>>> Нет, это на вашей совести :) Можно ввести понятие серьезности ошибок
Ну у меня любая ошибка приводит к выходу из потока. Так что... не думаю, что нужны вложенные ошибки, или приоритет. Слишком всё прямолинейно.
>>> Сдается мне его можно оставить после InternetReadFile (кстати, там его нет), после записи в файл
Проверка стоит в until(fTerminater). После InternetReadFile идут: запись в файл и вызов прогресса. Вроде как все операции близки к мгновенным. А после вызова прогресса (где, кстати, и есть максимальная вероятность взведения fTerminated) - проверка условием цикла. Так что вроде бы всё достаточно логично. А вообще, практика показала, что основное время тратится на HttpSendRequest (потому почти сразу после него - проверка), это (при появлении окон антивируса, а то и при просто загруженном процессоре или сети, например торрент качает что-то) занимает аж под десять-пятнадцать секунд. Так что вероятность нажать тут отмену более чем ненулевая. И HttpQueryInfo иногда подтормаживают, хотя тут не более секунды... так что там тоже стоит проверка. В начале процедуры уберу проверку на завершение - вряд ли имеет смысл стартовать поток только для того, чтобы его немедленно прибить. |
|
04-06-2014 15:19>>>>Особенность обработки с использованием кодов ошибок.
>>Нет, это не так. И в таком коде можно отделить код обработки ошибок от кода бизнес-логики.
Да, прошу прощения, полностью отделить код обработки ошибок как при использовании исключений, конечно, не получится.
|
|
04-06-2014 13:00>>все библиотеки (индейка, синапс, ICS) они все поддерживают OnProgress...
Использование OnProgress упрощает работу (а если выполнение происходит в основном потоке - по другому не получится). Но если выполнение происходит в отдельном потоке - есть варианты. Предложенный мной а) устраняет прямую зависимость времени отклика от размера буфера (и любых завязок на то, что происходит в Thread.Execute) и б) устраняет привязку к единственному обработчику прогресса (проще говоря реализует схему один писатель-много читателей).
Смысл такой (что не призываю так делать - просто мысли вслух):
Представьте что вы - начальник, у вас в подчинении рабочий, которому предписано выкопать канаву от забора до забора.
В случае OnProgress, ситуация выглядит следующим образом: прокопав каждые 5 метров он бежит к вам и сообщает, сколько работы сделано. Если начальников много (прописано в его OnProgress) - он обегает их всех - и в это время не работает. Если он отлучился по нужде (что-то "подтормаживает") - вы ждете его, не имея возможности узнать сколько работы проделано.
В другом варианте - он делает свою работу ни на что не отвлекаясь, все кому нужно могут прийти к нему и посмотреть (через критические секции), сколько работы сделано. Это требует выполнения работы в отдельном потоке, зато рабочий может не заботиться о всех этих наблюдателях. Естественно он может/должен сообщить о завершении работы и прервать ее при поступлении соответствующей команды.
То есть работа по-прежнему выполняется кусками, но посмотреть результаты можно в любой момент без всяких обработчиков событий, не прерывая основного процесса (под процессом здесь я понимаю последовательность действий, направленную на решение поставленной задачи, а не процесс, создаваемый CreateProcess()).
Такой вариант конечно сложнее, но главным образом из-за необходимости отдельного потока. Если же он и так уже есть...
>>Они уже протестированы Microsoft
Нет, речь о поведении которое может быть правильным с точки зрения Microsoft, но нежелательным для разработчика/пользователя - про всплывающее окно антивируса вы рассказывали, вопрос - может ли возникнуть необходимость подъема прав доступа, например.
>>try..except... думаю - убрать его нафиг оттудова
Не спешите. Упомянутое вами ограничение размера файла 2 гб - где сработает? Если (я не проверял) на BlockWrite, то: "...If the $I+ compiler directive is in effect, errors raise an EInOutError exception." - нужно как-то отработать. Аналогичным образом нужно локализовать все возможные места генерации исключений (имеют тенденцию возникать в самых неожиданных местах).
>> Зачем делать больше?
Затем, что схожие ошибки должны обрабатываться аналогичным образом. Если кто-то захочет добавить код перед замыкающим finally - возможность нахождения в состоянии ошибки для него будет неочевидной.
>>Особенность обработки с использованием кодов ошибок.
Нет, это не так. И в таком коде можно отделить код обработки ошибок от кода бизнес-логики.
К примеру, можно выделить в отдельную процедуру код с инициализацией UrlComp. Кстати вы не проверяете результат InternetCrackURL (он вообще тут нужен?), и еще: если не можете гарантировать корректность переданной схемы рекомендуется предварительно вызывать InternetCanonicalizeUrl c ICU_BROWSER_MODE.
Вообще все эти подробности с InternetCrackURL смело можно вынести за пределы многопоточного кода - подключения они не требуют, а код засоряют.
Думаю и дальше по коду можно какие-то части оформить по аккуратнее.
>>Вот тут... как говорится, всё знает великий Хрен.
Нет, это на вашей совести :) Можно ввести понятие серьезности ошибок, можно передавать обе, можно вообще забить - и не самый плохой подход, между прочим...
>>Обработчик пользовательского завершения просто воткнут везде, где напрашивался...
А это правильно? Нужно оценить расход времени на разных шагах программы и оставить там, где отжирается основное время. Сдается мне его можно оставить после InternetReadFile (кстати, там его нет), после записи в файл (тоже нет, а операция недетерминированная - в моем понимании), после вызова обработчиков событий (вот тут уж действительно пользователь может вставить хрен знает что - недостаток схемы с OnProgress). А то так поставить только один раз, в цикле сразу после слова repeat. Код до repeat, по-моему, копеешный - но могу ошибаться.
>>Собственно, для этого я и выложил его на Королевство.
Ну и хорошо :)
С уважением.
|
|
04-06-2014 10:23сообщение от автора материала >>> как связано ограничение размера принимаемого файла и возможность скачать первые 4 кб
Просто я могу дополнительно ограничить размер. Физически размер ограничен 2 Гб (паскалевские файлы), но при необходимости, настроив MaxSize я могу заставить поток прервать закачку раньше. Штатным методом, не вызывая Abort в OnProgress. Ну, мне показалось это более прямым методом проверки файл на корректное существование.
>>> Почему не штатный FileStream
Много зависимостей с собой тянет. Classes - очень тяжёлый модуль. А при включённом консольком режиме он не используется. SysUtils в общем то тоже нелёгкий, но попытка отказаться от него привела к тяжёлым последствиям в виде реализации многих стандартных функций самопальными заглушками... так что пусть поторчит, пока не придумаю решения получше.
>>> Я думаю ошибочна сама концепция зависимости реакции UI
Простите, но все библиотеки (индейка, синапс, ICS) они все поддерживают OnProgress. Только называется он по-разному, но суть именно такова. Они все ошибаются?
>>> тестирования программы для разных вариантов прав доступа пользователя
Вот в этом прелесть использования стандартных, а не самопальных вещей. Они уже протестированы Microsoft и несколькими миллионами пользователей. Есть подозрение, что косяков там не так уж и много осталось...
>>> так или иначе имеется секция try..excep
Собственно, она там ловит только совсем уж критические ошибки. Привёрнута к уже почти полностью рабочему коду для страховки от, как говорится, "случайного AccessViolation". Теперь уже думаю - убрать его нафиг оттудова. Не так уж часто эти AV случаются... во всяком случае, в нормально работающей программе.
>>> В строке 941 должен быть Exit?
Ага, должен. Только вот "беда" - если он сработает, произойдёт падение в секцию finally. А если его не будет... то произойдёт тоже падение в секцию finally. Разница только в том, что в первом случае будет попытка развёртывания стека, а во втором - нет. Зачем делать больше?
>>> вся процедура состоит из обработки ошибок
Особенность обработки с использованием кодов ошибок.
>>> Должно ли условие fTerminated перекрывать сообщение об ошибке
Вот тут... как говорится, всё знает великий Хрен. Обработчик пользовательского завершения просто воткнут везде, где напрашивался... и всё. Вероятность его срабатывания ненулевая разве что в процессе скачивания.
>>> я объясняю, почему я бы не стал его использовать в нынешнем виде
Собственно, для этого я и выложил его на Королевство. Ну согласитесь сами, ну не задавать же вопрос: "Здравствуйте, я тут написал компонент. Он работает, но что в нём можно улучшить?" Ну согласитесь, вопрос такого плана звучит ГЛУПО. А написать статью и неявно "попросить" внести предложения по улучшению... на мой взгляд уже более "тонко". Чем я и пользуюсь. Жаль только, что это первая моя статья, к которой есть конструктивные комментарии, в предыдущих статьях толку от публикации было мало и компоненты так и остались в первозданном виде... |
|
04-06-2014 04:19Почему у меня все время отваливается авторизация на сайте?
По порядку:
>>Кстати, мне на практике такие сервера не встречались. Везёт?
Ну вот пример из последних: http://examples.oreilly.com/9781565920583/CDROM/GFF/GRAPHICS/BOOK/ и на этом сайте еще много подобных якобы "битых" страниц. Соответственно, если проходить страницы рекурсивно вашим компонентом, все ссылки на ней тоже будут потеряны.
Да и вообще в некоторых случаях сервер просто не может определить размер - можно поискать RFC. Кстати вы обращали внимание, что IE при загрузке файла тоже не всегда показывает прогресс?
>>Во-первых, есть свойство MaxSize..., которое ограничивает размер скачиваемого файла. Весьма удобная вещь, поскольку позволяет скачивать файл не полностью, а только первые 4 кб (в моём случае)...
Вот тут не понял: как связано ограничение размера принимаемого файла и возможность скачать первые 4 кб?
В любом случае, дырявых абстракций (неявное указание доступного размера файла) лучше избегать.
>>Возможно, со временем переделаю под WinAPI CreateFile/WriteFile
Почему не штатный FileStream?
>>А вроде как именно им пользуется Internet Explorer?
Без понятия. Повторюсь, использование WinInet для частных задач не возбраняется. Проблемы могут начаться, если кто-то решит его использовать по-другому.
>>Да, можно увеличить размер буфера. Но тогда, соответственно, реже будет срабатывать OnProgress...
Я думаю ошибочна сама концепция зависимости реакции UI (да-да, OnProgress/OnProgressProc - это именно UI) от хода подобного процесса ошибочна. Каждый должен заниматься своим делом - процесс загружает файл с таким буфером как хочет, UI смотрит на него с такой частотой какой хочет и обновляет прогресс бар... Можно придумать вариант, раз уж вы все равно используете отдельный поток.
>>Ага, это раздражает безумно всплыванием запроса от антивируса
Более того, это означает необходимость тестирования программы для разных вариантов прав доступа пользователя, а с учетом разных версий/редакций ОС это может превратиться в кошмар. (если кто-то скажет, что это просто - я его ударю).
>>Бинарный размер слабо зависит от количества строчек кода
Речь не об этом. Если у меня простая консольная программа, которая стучится на сайт, скачивает с него файл и грузит его в базу (один dpr-файл в сотню строк), я скорее использую что-то вроде http://www.cryer.co.uk/brian/delphi/wininet/example_download_file_http.htm
, чем буду подключать ваш или подобный модуль.
Для проекта за который платят деньги - возьму что-нибудь более надежное.
>>Про достоинства Вы забыли
Ну да, плавно перетекающие в недостатки. Что для одного баг - для другого фича:)
Конечно, нужно выбирать компромисс. Я собственно об этом и говорю.
>>Секасом с индейкой не приходилось заниматься?
>>А обработка ошибок...
Про секас...
Если у меня возникнет ошибка, мне придется разбираться с этим кодом, так что простите...
У вас отсутствует единая стратегия обработки ошибок. Посмотрим на RunThread(). Я не имею ничего против кодов возврата, но у вас так или иначе имеется секция try..except. Т.е. часть ошибок возвращают код, часть ловится как исключения.
Постоянно используются конструкции вида:
if not что-то then begin
fErrorMessage:='xxxxx';
fErrorCode:=GetLastError;
exit;
end;
В строке 941 должен быть Exit? Если везде (в тринадцати предыдущих случаях) он присутствует почему он пропущен тут? Что произойдет если этот код выполнится? Должно ли условие fTerminated перекрывать сообщение об ошибке 'File size differs from stream received'?
Понятно, что можно разобраться, но можно было сделать попроще. Ощущение, что вся процедура состоит из обработки ошибок - сложно найти места где делается что-то полезное. Кстати, вообще, много дублирующегося кода.
Поймите меня правильно, я не говорю, что компонент работает некорректно, я объясняю, почему я бы не стал его использовать в нынешнем виде.
Извините, если что-то сказал не так.
С уважением.
|
|
04-06-2014 01:01сообщение от автора материала >>> Сервер, вообще говоря, не обязан возвращать content-length и ситуация это не такая уж редкая
Ничего страшного в этом нет, можно просто отключить BinaryData. Как сказано в описании, это приведёт к неправильной работе OnProgress (поскольку какой прогресс может быть, если нет общего размера). Кстати, мне на практике такие сервера не встречались. Везёт? Кстати, заметил в статье ошибку - там тип BinaryData указан как String, а на самом деле он boolean. Со временем поправлю.
>>> вы ограничили размер файла 4 гигабайтами
Нет, 2 гигабайтами. Там не только в разборе проблема. Во-первых, есть свойство MaxSize (про которое я, кстати, забыл указать, тоже со временем поправлю), которое ограничивает размер скачиваемого файла. Весьма удобная вещь, поскольку позволяет скачивать файл не полностью, а только первые 4 кб (в моём случае), чтобы определить - возвращает сервер реальный файл (ладно, данные, похожие на реальный файл), или HTML файлик с описанием ошибки. Оно как раз cardinal, что ограничивает размер. Во-вторых, свойство fFileSize:integer, что как раз ограничивает 2 Гб размер файла. Можно поправить на int64, но это приведёт к другой проблеме. Паскалевские файлы (используемые в модуле) от рождения не умеют работать с файлами более 2 Гб. Возможно, со временем переделаю под WinAPI CreateFile/WriteFile, но это со временем...
>>> медленный
А вроде как именно им пользуется Internet Explorer? Если не так - скажите. А если так - то ничего не медленный, а более чем нормальный. Ещё не видел ни одной линии связи, чтобы простая скачивалка осликом тормозила.
>>> усугубляется тем, что скачивается по чайной ложке
Да, можно увеличить размер буфера. Но тогда, соответственно, реже будет срабатывать OnProgress. Скорость скачивания с серверов, с которыми я работаю и так невысока и мне важнее получать стабильные OnProgress. Наилучшим решением, вынесенным из Вашего комментария станет такое - настройка размера буфера. По умолчанию поставить 64 килобайта, но если нужно будет получать более стабильный OnProgress, можно будет убавить. Переделок не так уж много. За идею спасибо. Пусть и подсказанную косвенно.
>>> нельзя использовать как сервис
Никогда не писал сервисы, так что не могу комментировать.
>>> запускать от имени системной учетной записи (лезет в реестр пользователя)
Ага, это раздражает безумно всплыванием запроса от антивируса. Но увы... как иначе прочитать настройки Internet Explorer.
>>> тем более, устанавливать компонент
А компонентом он остался только от своего родителя. Там есть помарка про директиву условной компиляции CONSOLE. Нормального способа получить настройку, консольное ли приложение, или нет, я не нашёл, пришлось использовать такой костыль. При этом отключается возможность работы как компонента. Так что лишних зависимостей при этом не будет.
>>> модуль в 1000+ строк
Бинарный размер слабо зависит от количества строчек кода. Одна строчка Uses ActiveX; увеличит размер модуля больше, чем тысяча строчек разбора строки конечным автоматом.
>>> недостатки этой библиотеки хорошо известны
Про достоинства Вы забыли. Три киллер-фичи, которые просто убивают любой другой компонент:
1. Синхронная с Осликом авторизация на сайтах, то есть пользователь, потенциально не доверяющий программе, может просто авторизоваться в Ослике и не сообщать пароль программе.
2. Безнастроечная работа в корпоративной сети, где есть прокси с NTLM авторизацией - ни одно из решений не смогло предоставить ничего подобного, кроме WinInet. Всегда приходилось руками прописывать прокси, порт, пользователя, пароль. А WinInet входит в интернет (если он включён на прокси) сразу. Это настолько важно и удобно, что про остальные компоненты можно просто забыть! Насчёт WinHttp надо посмотреть, но раз он "не лезет к реестр пользователя", то скорее всего, там с этим тоже всё плохо. Так что тоже не вариант.
3. HTTPS. Никаких лишних библиотек. Секасом с индейкой не приходилось заниматься? "Я хочу только конкрентую либу OpenSSL и ни с чем другим работать не буду". Другие компоненты тоже так или иначе капризничают при работе. А некоторое неудобство при работе с WinInet возникло только при использовании клиентского сертификата... пока проблема не решена, если кто-нибудь знает, как автоматически выбрать клиентский сертификат из WinInet - милости прошу подсказать решение.
>>> Замечание по коду: бизнес-логика, обработка ошибок, UI перемешаны
UI я что-то не заметил в компоненте. А обработка ошибок обусловлена кодами возврата... преобразовывать их в исключения, чтобы сразу поймать (а дело происходит в потоке) - это плохая практика ИМХО. Так что другого решения, кроме как ифом проверять возвращаемый результат, на месте формировать сообщение об ошибке и отваливаться - увы, больше ничего придумать не могу. Если есть идеи, прошу предложить. Переделаю :-)
В любом случае, спасибо за комментарий. |
|
03-06-2014 13:40Добрый день!
Сервер, вообще говоря, не обязан возвращать content-length и ситуация это не такая уж редкая. Или может вернуть некорректное значение - все на его совести. Понятно, что данный компонент выдаст в этом случае ошибку, хотя файл можно вполне себе нормально скачать (см. например http://www.webdelphi.ru/2014/01/kniga-o-synapse-glava-1-rabota-s-http-v-synapse/).
Кроме того вы ограничили размер файла 4 гигабайтами - если файл больше, вероятно (я не проверял) выдаст ошибку HTTPGET_BADRESPONSE, хотя сервер тут будет совершенно ни при чем.
Если говорить о WinInet, то недостатки этой библиотеки хорошо известны: медленный (у вас усугубляется тем, что скачивается по чайной ложке), нельзя использовать как сервис, запускать от имени системной учетной записи (лезет в реестр пользователя) и т. д. И хотя для собственных нужд, утилит и т. п. его (WinInet) вполне возможно, то подключать к небольшому проекту модуль в 1000+ строк или, тем более, устанавливать компонент я бы не стал.
Что касается серьезных проектов, лучше рассмотреть альтернативы. Если не хочется использовать сторонние библиотеки можно вместо WinInet попробовать WinHttp - та же функциональность без указанных недостатков. (как тут: http://blog.synopse.info/post/2011/07/04/WinINet-vs-WinHTTP).
Замечание по коду: бизнес-логика, обработка ошибок, UI перемешаны - сложно читать.
С уважением. |
|
|
|