Как отправить запрос HTTP POST в Delphi с помощью WinInet api

Я пытаюсь выполнить HTTP-запросы из Delphi с помощью функций WinInet.

До сих пор я:

function request:string;
var
  hNet,hURL,hRequest: HINTERNET;
begin
  hNet := InternetOpen(PChar('User Agent'),INTERNET_OPEN_TYPE_PRECONFIG or INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
  if Assigned(hNet) then 
  begin
  try
    hURL := InternetConnect(hNet,PChar('http://example.com'),INTERNET_DEFAULT_HTTP_PORT,nil,nil,INTERNET_SERVICE_HTTP,0,DWORD(0));
    if(hURL<>nil) then
      hRequest := HttpOpenRequest(hURL, 'POST', PChar('param=value'),'HTTP/1.0',PChar(''), nil, INTERNET_FLAG_RELOAD or INTERNET_FLAG_PRAGMA_NOCACHE,0);
    if(hRequest<>nil) then
      HttpSendRequest(hRequest, nil, 0, nil, 0);
    InternetCloseHandle(hNet);
  except
    on E : Exception do
      ShowMessage(E.ClassName+' error raised, with message : '+E.Message);
  end;
  end
end;

Но это ничего не делает (я обнюхиваю сетевой трафик http, чтобы увидеть, работает ли он). Я успешно использовал InternetOpenURL, но мне также нужно отправить запрос POST, и эта функция не делает этого.

Может ли кто-нибудь показать мне простой пример? В результате я хочу получить страницу ответа http в переменной var как строку.

Ответы

Ответ 1

Я получил всю часть url/filename, испорченную предыдущим кодом. Я использую этот от Jeff DeVore и отлично работает:

function request(const AUrl, AData: AnsiString; blnSSL: Boolean = True): AnsiString;
var
  aBuffer     : Array[0..4096] of Char;
  Header      : TStringStream;
  BufStream   : TMemoryStream;
  sMethod     : AnsiString;
  BytesRead   : Cardinal;
  pSession    : HINTERNET;
  pConnection : HINTERNET;
  pRequest    : HINTERNET;
  parsedURL   : TStringArray;
  port        : Integer;
  flags       : DWord;
begin
  ParsedUrl := ParseUrl(AUrl);

  Result := '';

  pSession := InternetOpen(nil, INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);

  if Assigned(pSession) then
  try
    if blnSSL then
      Port := INTERNET_DEFAULT_HTTPS_PORT
    else
      Port := INTERNET_DEFAULT_HTTP_PORT;
    pConnection := InternetConnect(pSession, PChar(ParsedUrl[0]), port, nil, nil, INTERNET_SERVICE_HTTP, 0, 0);

    if Assigned(pConnection) then
    try
      if (AData = '') then
        sMethod := 'GET'
      else
        sMethod := 'POST';

      if blnSSL then
        flags := INTERNET_FLAG_SECURE or INTERNET_FLAG_KEEP_CONNECTION
      else
        flags := INTERNET_SERVICE_HTTP;

      pRequest := HTTPOpenRequest(pConnection, PChar(sMethod), PChar(ParsedUrl[1]), nil, nil, nil, flags, 0);

      if Assigned(pRequest) then
      try
        Header := TStringStream.Create('');
        try
          with Header do
          begin
            WriteString('Host: ' + ParsedUrl[0] + sLineBreak);
            WriteString('User-Agent: Custom program 1.0'+SLineBreak);
            WriteString('Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'+SLineBreak);
            WriteString('Accept-Language: en-us,en;q=0.5' + SLineBreak);
            WriteString('Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7'+SLineBreak);
            WriteString('Keep-Alive: 300'+ SLineBreak);
            WriteString('Connection: keep-alive'+ SlineBreak+SLineBreak);
          end;

          HttpAddRequestHeaders(pRequest, PChar(Header.DataString), Length(Header.DataString), HTTP_ADDREQ_FLAG_ADD);

          if HTTPSendRequest(pRequest, nil, 0, Pointer(AData), Length(AData)) then
          begin
            BufStream := TMemoryStream.Create;
            try
              while InternetReadFile(pRequest, @aBuffer, SizeOf(aBuffer), BytesRead) do
              begin
                if (BytesRead = 0) then Break;
                BufStream.Write(aBuffer, BytesRead);
              end;

              aBuffer[0] := #0;
              BufStream.Write(aBuffer, 1);
              Result := PChar(BufStream.Memory);
            finally
              BufStream.Free;
            end;
          end;
        finally
          Header.Free;
        end;
      finally
        InternetCloseHandle(pRequest);
      end;
    finally
      InternetCloseHandle(pConnection);
    end;
  finally
    InternetCloseHandle(pSession);
  end;
end;

ParseUrl - это функция, которая разбивает URL-адрес в "hostname/filename", а TStringArray - это массив строк. Мне все же приходится пересматривать код завтра, но он выглядит отлично, и в моем сниффере я видел отправленные данные и заголовки.

Ответ 2

Лично я предпочитаю использовать библиотеку synapse для всех моих работ по TCP/IP. Например, простой HTTP-пост может быть закодирован как:

uses
  httpsend;

function testpost;
begin
  stm := tStringstream.create('param=value');
  try
    HttpPostBinary('http://example.com',Stm);
  finally
    stm.free;
  end;
end;

Библиотека хорошо написана и очень легко модифицируется в соответствии с вашими конкретными требованиями. Последняя версия subversion работает без проблем как для Delphi 2009, так и для Delphi 2010. Эта структура не основана на компонентах, а представляет собой ряд классов и процедур, которые хорошо подходят для многопоточной среды.

Ответ 3

Третий параметр (lpszObjectName) на HttpOpenRequest должен быть URL-адресом, который вы хотите запросить. Поэтому документация описывает пятый параметр (lpszReferer) как "указатель на строку с нулевым завершением, которая указывает URL-адрес документа, из которого был получен URL-адрес запроса (lpszObjectName)".

Опубликованные данные отправляются с HttpSendRequest; lpOptional параметр описывается следующим образом:

Указатель на буфер, содержащий любые необязательные данные, которые должны быть отправлены сразу после заголовков запросов. Этот параметр обычно используется для операций POST и PUT. Необязательными данными могут быть ресурсы или информация, отправляемые на сервер. Этот параметр может быть NULL, если нет дополнительных данных для отправки.

Второй параметр InternetOpen должен быть просто именем сервера; он не должен включать протокол. Протокол, который вы указываете с шестым параметром.

После того, как вы отправили запрос, вы можете прочитать ответ InternetReadFile и InternetQueryDataAvailable.

Не просто проверьте, возвращаются ли функции API нулю, а затем переходят к следующей строке. Если они не сработают, вызовите GetLastError, чтобы узнать, почему. Код, который вы опубликовали, не будет генерировать исключения, поэтому бесполезно их поймать. (И глупо "обрабатывать" их так, как вы это делаете. Не поймайте исключение, которое вы еще не знаете, как исправить. Пусть все остальное подходит к вызывающему абоненту или вызывающему абоненту и т.д..)