Сегодня решил дописать свой 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. В процессе работы нам может потребоваться:
- Получение какой-либо конкретной информации, например, код ответа сервера.
- Получение всей информации — всех заголовков.
Соответственно, в первом случае, будет излишним взводить HTTP_QUERY_RAW_HEADERS_CRLF (код статуса занимает 6-8 байт, а заголовки могу насчитывать килобайты информации). Следовательно в качестве входных параметров функции будут выступать:
- Хэндл (hRequest)
- Флаг (dwInfoLevel) тип параметра — integer.
Теперь по самому алгоритму. Будем делать так:
- Задаем буферу минимальный размер, которого будет достаточно, чтобы прочитать код статуса (сама функция возвращает минимальное значение 8 байт)
- Пробуем выполнить функцию HttpQueryInfo
- Если возвращается ERROR_INSUFFICIENT_BUFFER,
то изменяем размер буфера и повторно вызываем HttpQueryInfo. - Если возвращается код ошибки отличный от 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 раза и читаем сразу все. Можно дописать приведенную выше процедуру и получать, например, данные по установленным кукам.
Спасибо автору. Интересная статья, попробую в свободное время
А как в WinInet куки чистить? на родном сайте http://msdn.microsoft.com/en-us/library/aa385473(v=vs.85).aspx нашёл функцию «InternetClearAllPerSiteCookieDecisions» но подключить её не получилось. Я уже изучался, праграмма есть для регистрации, но регатся могу только один раз. Куки остаются и я как бы всегда авторизированный(((( Спасает только переоткрытие программы.
Спасибо, очень помогло, а то с утра как споткнулся об эту функцию, так до обеда голову ломал, пытаясь все тонкости понять.
По-моему, в коде
if GetLastError=ERROR_INSUFFICIENT_BUFFER then //увеличиваем буффер
begin
SetLength(code,size);
size:=Length(code);
нужно «SetLength(code,size+8)» вместо «SetLength(code,size)».