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

Сегодня решил дописать свой THTTPSender, который использует в работе WinInet.  Первое, что необходимо было сделать — это научиться правильно читать код ответа сервера для того, чтобы продолжить работу над Google Data API. При этом важно было не только «поймать» код 200 или 400, но и определять моменты, когда сервер выдает коды 301 или 302.  Обычно при работе с WinInet этот момент (определение кодов перенаправления) опускается, т.к. чаще всего удобно, чтобы перенаправление происходило автоматически. Сегодня разберемся как отлавливать все коды статуса и читать заголовки сервера.Для того, чтобы получить какую либо служебную информацию от сервера мы можем воспользоваться методом HttpQueryInfo. Рассмотрим входные параметры функции:

  • hRequest — Handle, который мы получаем при выполнении HttpOpenRequest или InternetOpenUrl.
  • dwInfoLevel — комбинация флагов, каждый из которых указывает, какая информация нам необходима
  • lpvBuffer — указатель на буфер в который будет происходить запись данных.
  • lpdwBufferLength — размер буфера
    lpdwIndex — индекс заголовка

Сама функция HttpQueryInfo работает следующим образом: при выполнении происходит попытка чтения в буфер запрошенной информации. Если размер буфера оказывается мал, то вызывается исключение ERROR_INSUFFICIENT_BUFFER и переменная lpdwBufferLength будет содержать необходимую размерность буфера для приёма всех данных. В случае, если мы запрашиваем информацию, которую сервер вернуть не в состоянии, вызывается исключение ERROR_HTTP_HEADER_NOT_FOUND.

Немного поэкспериментировав с HttpQueryInfo обнаружил следующее: по документации MSDN для успешного выполнения функции необходимо указывать хэндл, который мы берем из HttpOpenRequest или InternetOpenUrl. Если использовать InternetOpenUrl, то  так и есть — функция отрабатывает как надо и возвращает результат. Если я использую HttpOpenRequest, то функция отказывается возвращать данные (при этом GetLastError ничего не выдает), но срабатывает после выполнения метода HttpSendRequest. Не думаю, что это глюк, но учитывать этот момент стоит.

Теперь, что касается исключение ERROR_HTTP_HEADER_NOT_FOUND. На это исключение можно довольно легко налететь. Например, мы запрашиваем информацию о последнем изменении страницы. Для этого мы устанавливаем флаг HTTP_QUERY_LAST_MODIFIED и пробуем выполнить метод. Если в заголовках сервера нет заголовка Last-Modified, то функция вернет False и в результате выполнения GetLastError мы получим исключение ERROR_HTTP_HEADER_NOT_FOUND. Поэтому следует очень осторожно пользоваться методом, когда есть вероятность того, что в заголовках нет необходимой информации.

Чтобы получать всю информацию от сервера, которую он выдает, я пользуюсь флагом HTTP_QUERY_RAW_HEADERS_CRLF, при этом возвращается строка, содержащая все заголовки сервера и каждый заголовок разделен символами CR/LF (#10#13), что удобно, например, для использования результата в списках TStringList.

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

  1. Получение какой-либо конкретной информации, например, код ответа сервера.
  2. Получение всей информации — всех заголовков.

Соответственно, в первом случае, будет излишним взводить HTTP_QUERY_RAW_HEADERS_CRLF (код статуса занимает 6-8 байт, а заголовки могу насчитывать килобайты информации). Следовательно в качестве входных параметров функции будут выступать:

  1. Хэндл (hRequest)
  2. Флаг (dwInfoLevel) тип параметра — integer.

Теперь по самому алгоритму. Будем делать так:

  1. Задаем буферу минимальный размер, которого будет достаточно, чтобы прочитать код статуса (сама функция возвращает минимальное значение 8 байт)
  2. Пробуем выполнить функцию HttpQueryInfo
  3. Если возвращается ERROR_INSUFFICIENT_BUFFER,
    то изменяем размер буфера и повторно вызываем HttpQueryInfo.
  4. Если возвращается код ошибки отличный от 122 (ERROR_INSUFFICIENT_BUFFER), то завершаем работу без повторного вызова HttpQueryInfo.

Все вышесказанное в Delphi выглядит следующим образом:

function THTTPSender.GetQueryInfo(hRequest: Pointer; Flag: integer): string;
var code: String;
    size,index:Cardinal;
begin
  SetLength(code,8);//достаточная длина для чтения статус-кода
  size:=Length(code);
  index:=0;
  if HttpQueryInfo(hRequest, Flag ,PChar(code),size,index)then
    Result:=Code
  else
    if GetLastError=ERROR_INSUFFICIENT_BUFFER then //увеличиваем буфер
      begin
        SetLength(code,size);
        size:=Length(code);
        if HttpQueryInfo(hRequest,Flag,PChar(code),size,index) then
          Result:=code;
      end
  else
    begin
      FErrorCode:=GetLastError;
      Result:='';
    end;
end;

Теперь используем эту функцию для чтения и сохранения заголовков сервера.  Для получения всех заголовков сервера необходимо сделать следующий вызов:

GetQueryInfo(hRequest, HTTP_QUERY_RAW_HEADERS_CRLF)

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

  • данные по протоколу
  • код статуса сервера
  • ответ сервера (текст)
  • если код статуса 301 или 302 — получить адрес перенаправления (заголовок Location).

Однако здесь следует учесть один момент. Заключается он в следующем:

чтобы отлавливать коды 301 и 302 при вызове метода HttpOpenRequest обязательно надо взводить флаг INTERNET_FLAG_NO_AUTO_REDIRECT, который останавливает автоперенаправление на адрес.

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

procedure THTTPSender.ParseHeaders(HeasersStr: string);
var i:integer;
    s: string;
begin
  if not Assigned(FHeaders)then FHeaders:=TStringList.Create;
  FHeaders.Clear;
  FHeaders.Text:=HeasersStr;
  FHeaders.Delete(FHeaders.Count-1); //последний элемент содержит всегда CRLF
 
  if FHeaders.Count>0 then
    begin
//читаем данные о протоколе, статус-коде и ответе сервера
      S:=FHeaders[0];
      FProtocol:=copy(s,1,pos(' ',s)-1);
      Delete(s,1,Length(FProtocol)+1);
      FProtocol:=copy(FProtocol,1,pos('/',FProtocol)-1);
      FResponseCode:=StrToInt(copy(s,1,pos(' ',s)-1));
      Delete(s,1,Length(IntToStr(FResponseCode))+1);
      FResponseText:=Trim(s);
//если было перенаправление, то читаем адрес
     if (ResponseCode=HTTP_STATUS_MOVED)or(ResponseCode=HTTP_STATUS_REDIRECT) then
       for I := 0 to FHeaders.Count - 1 do
         begin
           if pos('location:',lowercase(FHeaders[i]))>0 then
             begin
               FLocation:=LowerCase(FProtocol)+'//'+FDomain+'/'+Trim(copy(FHeaders[i],10,length(FHeaders[i])-9));
               break;
             end;
         end;
  end;
end;

Таким образом, вместо того, чтобы вызывать HttpQueryInfo
несколько раз для получения необходимой информации, мы вызываем метод всего 1-2 раза и читаем сразу все. Можно дописать приведенную выше процедуру и получать, например, данные по установленным кукам.

0 0 голоса
Рейтинг статьи
уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
Подписаться
Уведомить о
4 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии
Павел
Павел
16/02/2010 12:59

Спасибо автору. Интересная статья, попробую в свободное время

Андрей
Андрей
10/01/2011 19:21

А как в WinInet куки чистить? на родном сайте http://msdn.microsoft.com/en-us/library/aa385473(v=vs.85).aspx нашёл функцию «InternetClearAllPerSiteCookieDecisions» но подключить её не получилось. Я уже изучался, праграмма есть для регистрации, но регатся могу только один раз. Куки остаются и я как бы всегда авторизированный(((( Спасает только переоткрытие программы.

Слава
Слава
02/03/2012 18:26

Спасибо, очень помогло, а то с утра как споткнулся об эту функцию, так до обеда голову ломал, пытаясь все тонкости понять.

Слава
Слава
02/03/2012 18:51

По-моему, в коде
if GetLastError=ERROR_INSUFFICIENT_BUFFER then //увеличиваем буффер
begin
    SetLength(code,size);
    size:=Length(code);
 нужно «SetLength(code,size+8)» вместо «SetLength(code,size)».