Delphi-Help

Главная Статьи Интернет WinInet: как правильно скачать файл по протоколу HTTP

WinInet: как правильно скачать файл по протоколу HTTP

Оцените материал
(1 Голосовать)


WinInet: как правильно скачать файл по протоколу HTTP

Недавно столкнулся с необходимостью прочитать xml-файл с использованием только функций API. Данная статья призвана исправить небольшие упущения, допущенные в уже существующих статьях, и облегчить начальное изучение WinInet. Во многом она дублирует уже существующие статьи.

Функции WinINet API находятся в библиотеке wininet.dll. Заголовочные файлы для данных функций: WinInet для Delphi или Wininet.h для C++. Код в данной статье приведён на Delphi.

ПРЕДУПРЕЖДЕНИЕ

Поклонники C++ не должны забывать, что Delphi нечувствительна к регистрам символов имён функций, а вместо указателей в функцию могут быть переданы переменные по ссылке, что несколько изменяет вид прототипов функций. Индексация символов в строке в Delphi начинается с 1.

Инициализация подключения

Для начала нужно вызвать функцию InternetOpen. В зависимости от исполнения, функция имеет название InternetOpenW или InternetOpenA, что соответствует заданию строк в форматах Unicode или в ASCII соответственно.

function OpenInternet(Name: WideString): pointer;
begin
  result := InternetOpenW(@Name[1], INTERNET_OPEN_TYPE_PRECONFIG,
    nil, nil, 0);
end;

В данном коде создается функция-оболочка, которая по заданному имени агента пользователя (UA – User Agent) создаст специальный дескриптор. Агент пользователя – это ваше приложение, хотя вы можете ввести в переменную Name строку, например «Microsoft Internet Explorer». Второй параметр функции – тип доступа. Флаг INTERNET_OPEN_TYPE_PRECONFIG говорит о том, что данные о прокси-сервере (если таковой имеется) и другие установки берутся из реестра. В данной статье используется этот флаг. Далее следует строки, служащие для настройки доступа через прокси-сервер (при использовании флага INTERNET_OPEN_TYPE_PRECONFIG они игнорируются). Последний параметр – дополнительные настройки, задающие режим асинхронного доступа или чтения из кэша.

Создав новый дескриптор, можно использовать его в функциях InternetOpenUrl и InternetConnect. Закрывается дескриптор с помощью функции InternetCloseHandle. Используем полученный дескриптор в функции InternetConnect.

function Connect(hInternet : pointer): pointer;
begin
  result := nil;
  if hInternet = nil  then exit;
  
  result := InternetConnectW(hInternet, @SiteURL[1],
    INTERNET_DEFAULT_HTTP_PORT, 
    'anonymous', nil, INTERNET_SERVICE_HTTP, 0, 0);
end;

Функция также возвращает дескриптор. Первый параметр – дескриптор, полученный от InternetOpen. Второй параметр – URL сайта, например «korea.sionyx.ru». В имени сайта не должно быть символов «/» или ссылок на используемый протокол. Третий параметр – номер порта протокола, к которому мы хотим подключиться (в нашем случае это INTERNET_DEFAULT_HTTP_PORT, то есть порт 80). Следующие два параметра – строка с именем пользователя и паролем. Далее параметр, задающий используемый протокол (INTERNET_SERVICE_HTTP, то есть HTTP). Далее, флаги настройки ftp и указатель контекста, они нам не нужны.

Концом инициализации служит вызов функции HttpOpenRequest.

function NewRequest(fURL : WideString; hConnect: pointer): pointer;
begin
  result := nil;
  if hConnect = nil then exit;
 
  result := HTTPopenRequestW(hConnect, nil, @fURL[1],
                             nil, nil, nil, INTERNET_FLAG_PRAGMA_NOCACHE or INTERNET_FLAG_RELOAD, 0);
end;

Данная функция создаёт HTTP-запрос на получение конкретного файла и возвращает дескриптор HTTP-запроса. Первый параметр – дескриптор, возвращённый функцией InternetConnect. Второй параметр – строка с именем метода доступа к ресурсу, для чтения строка должна быть “GET”. Нулевой указатель в данном случае будет интерпретирован как “GET”. Следующий параметр – имя файла, к которому осуществляется доступ (без имени сервера, например, abc.htm). За ним идет необязательная строка, указывающая версию HTTP. Далее – имя документа, из которого была получена ссылка, тоже не указываем. Следующий параметр – массив строк. Он задаёт тип документов, который может принимать UA, то есть наша программа. В данном случае мы её не указываем. Это подразумевает указание одной строки «text/*», то есть приём любого текста (а не графики, например). Для информации о допустимых типах документов см. http://www.iana.org/assignments/media-types/. Предпоследний параметр – опции получения файла (некоторые значения флагов указаны в таблице), последний – контекст.

  • INTERNET_FLAG_CACHE_IF_NET_FAIL Получает ресурс из кэша, если он недоступен в Internet.
  • INTERNET_FLAG_KEEP_CONNECTION Флаг сохранения настроек авторизации на протяжении подключения.
  • INTERNET_FLAG_NEED_FILE Кэширует файл или создаёт временный файл для получаемого ресурса.
  • INTERNET_FLAG_PRAGMA_NOCACHE Считывает данные с нужного сервера, минуя кэш прокси-сервера.
  • INTERNET_FLAG_RELOAD Считывает данные с сервера, а не из кэша системы.

Подключение к ресурсу осуществляется с помощью функции HttpSendRequest.

function SendRequest(hRequest: pointer): boolean;
begin
  result := false;
  if hRequest = nil then exit;
 
  result := HTTPsendRequestW(hRequest, nil, 0, nil, 0);
end;

Первый параметр – дескриптор, полученный из функции HttpOpenRequest. Второй параметр содержит дополнительные заголовки, которых у нас нет. Третий параметр указывает длину этих заголовков. Далее два параметра – дополнительные данные, посылаемые серверу, и их размер. Данные параметры могут быть использованы при применении методов «POST» и «PUT» и нас не интересуют.

Если функция отработала успешно, она возвращает «true», иначе «false».

Для скачивания нам потребуется функция InternetQueryDataAvailable, про которую забыли упомянуть в вышеназванных статьях.

function DataAvailable(hRequest: pointer; out Size : cardinal): boolean;
begin
  result := wininet.InternetQueryDataAvailable(hRequest, Size, 0, 0);
end;

Данная функция принимает дескриптор HTTP-запроса и возвращает во второй параметр количество данных, доступных для скачивания немедленным вызовом функции InternetReadFile. Если функция отработала успешно, она возвращает «true». Если на момент вызова функции данные недоступны, она ждёт. Перед повторным вызовом функции нужно считать указанное количество данных с помощью функции InternetReadFile.

function  ReadURL(fURL : WideString): WideString;
var
  hInternet  : pointer;
  hConnect   : pointer;
  hRequest   : pointer;
 
  Size       : cardinal;
  B          : boolean;
  Buff       : ANSIString;
  ReadedSize : cardinal;
  I, L       : cardinal;
begin
  result := '';
 
  hInternet := OpenInternet('FDSC Informer');
  hConnect  := Connect(hInternet);
  hRequest  := NewRequest(fURL, hConnect);
  if NOT SendRequest(hRequest) then
  begin
    if hRequest  <> nil then CloseURL(hRequest);
    if hConnect  <> nil then CloseURL(hConnect);
    if hInternet <> nil then CloseURL(hInternet);
    exit;
  end;
 
  SetLength(Buff, 1);
  B := false;
 
  I := 1;
  while true do
  begin
    DataAvailable(hRequest, L);
    if L = 0 then break;
    SetLength(Buff, I + L);
    B := InternetReadFile(hRequest, @Buff[I], L, ReadedSize);
if NOT B then break;
inc(I, ReadedSize);
  end;
 
  Buff[I] := #0;
 
  CloseURL(hRequest);
  CloseURL(hConnect);
  CloseURL(hInternet);
  if B then
  begin
    result := WideString(Buff);
  end else result := '';
end;

В данном листинге показан весь процесс инициализации и чтения. Само чтение осуществляется в цикле. Поскольку нам неизвестен размер получаемого файла, размер буфера Buff увеличивается на каждой итерации с копированием уже полученного содержимого. Функция InternetReadFile получает дескриптор запроса, указатель на начало буфера, указатель на количество получаемых данных и, наконец, указатель (в Delphi – параметр типа var) на переменную, принимающую фактическое значение числа считанных байт данных.

Получение размера принимаемых данных

Цикл чтения можно оптимизировать, если узнать размер получаемого файла. Как это сделать? Для этого существует функция HttpQueryInfo.

function HttpQueryInfoW(hRequest: HINTERNET; dwInfoLevel: DWORD;
  lpvBuffer: Pointer; var lpdwBufferLength: DWORD;
  lpdwReserved: pointer): BOOL; stdcall;
    external 'wininet.dll' name 'HttpQueryInfoW';
 
function SizeQuery(hRequest: pointer; out Size : cardinal): boolean;
var
  RSize : cardinal;
begin
  RSize := 4;
  result := HttpQueryInfoW(hRequest, 
    HTTP_QUERY_CONTENT_LENGTH or HTTP_QUERY_FLAG_NUMBER,
    @Size, RSize, nil);
end;

Первый параметр – дескриптор запроса. Далее указываем флаг HTTP_QUERY_CONTENT_LENGTH, который служит для получения информации о размере получаемых данных и HTTP_QUERY_FLAG_NUMBER, который указывает, что размер нужно получить в целочисленную переменную, а не в строку. Следующий параметр – указатель на буфер получаемой информации, в данном случае это целочисленная переменная. Далее – размер буфера (4 байта). Последний параметр нас не интересует.

ПРЕДУПРЕЖДЕНИЕ

Не все серверы поддерживают запросы о длине получаемого файла. В случае ошибки функция вернёт false, а функция GetLastError значение ERROR_HTTP_HEADER_NOT_FOUND.

С использованием функции HttpQueryInfoW код функции ReadURL можно модифицировать

function SizeQuery(hRequest: pointer; out Size : cardinal): boolean;
var
  RSize  : cardinal;
  p : pointer;
begin
  RSize  := 4;
  result := HttpQueryInfoW(hRequest, 
    HTTP_QUERY_CONTENT_LENGTH or HTTP_QUERY_FLAG_NUMBER,
    @Size, RSize, nil);
  if NOT result then Size := 0;
end;
 
function  ReadURL(fURL : WideString): WideString;
 
  // ...
  SizeQuery(hRequest, Size);
 
  inc(Size);
  SetLength(Buff, Size);
 
  B := false;
 
  I := 1;
  while true do
  begin
    DataAvailable(hRequest, L);
 
    if L = 0 then
      break;
 
    if (I + L) > Size then
      SetLength(Buff, I + L);
 
    B := InternetReadFile(hRequest, @Buff[I], L, ReadedSize);
 
    if NOT B then
      break;
 
    inc(I, ReadedSize);
  end;
  // ...

Для получения типа содержимого файла вызов функции HttpQueryInfoW должен выглядеть так:

  RSize := 0;
  result := HttpQueryInfoW(hRequest, HTTP_QUERY_CONTENT_TYPE,
                   p, RSize, nil);
  p := pointer(LocalAlloc(LPTR, RSize));
  result := HttpQueryInfoW(hRequest, HTTP_QUERY_CONTENT_TYPE,
                   p, RSize, nil);
 
  // Вывод результата в диалоговом окне
  MessageBoxW(hdlg, p, 'content type of the resource', 
    MB_OK or MB_ICONINFORMATION);

Например, можно получить строку «text/html; charset=ISO-8859-1» или «text/xml».

Заключение

В статье описан простейший процесс получения файла через протокол HTTP с использованием функций WinINet API.

Прочитано 7038 раз
Другие материалы в этой категории: Создание Web-приложений в среде Delphi »

Авторизация



Счетчики