如何使用WinInet API在Delphi中发送HTTP POST请求

15

我正在尝试使用WinInet函数从Delphi进行HTTP请求。

到目前为止,我已经:

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响应页面作为字符串保存在变量中。

3个回答

9

之前的代码中,我把所有的url/文件名部分都搞错了。现在我正在使用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分解为“主机名/文件名”,TStringArray是一个字符串数组。明天我还需要再次审查代码,不过看起来很好,并且在我的嗅探器中,我看到了发送的POST数据和标头。


3
我已经为你找到了代码的来源,这样你就可以引用原作者了。我对代码进行了修改,删除了设计不合理的布尔值case语句,并更改了引荐者,使其不再虚假地声称是Firefox 3.0.10。 - Rob Kennedy
3
我找不到ParseURL所在的单元,或者它可能是一种自定义程序。这里的代码:http://delphi.pastebin.com/f1ea3a752有些不同,没有使用ParseURL。 - lkessler
你如何向POST请求添加自定义头部值? - hikari

2

个人而言,我更喜欢使用synapse库来处理所有的TCP/IP工作。例如,一个简单的HTTP post可以编写为:

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中都能运行正常,这个框架不是基于组件的,而是一系列类和过程,适合在多线程环境中使用。


1

HttpOpenRequest 的第三个参数(lpszObjectName)应该是您要请求的URL。这就是为什么文档将第五个参数(lpszReferer)描述为“指向以空字符结尾的字符串的指针,该字符串指定获取请求中URL(lpszObjectName)的文档的URL。”

发布的数据将随HttpSendRequest一起发送;lpOptional参数的描述如下:

指向包含任何可选数据的缓冲区,以便在请求标头之后立即发送。此参数通常用于POST和PUT操作。可选数据可以是要发布到服务器的资源或信息。如果没有可选数据要发送,则此参数可以为NULL。

InternetOpen 的第二个参数应该只是服务器名称;它不应该包括协议。您可以使用第六个参数指定协议。

发送请求后,您可以使用 InternetReadFileInternetQueryDataAvailable 读取响应。

不要仅检查API函数是否返回零,然后继续下一行。如果它们失败,请调用 GetLastError 查找原因。您发布的代码不会引发异常,因此捕获任何异常都是徒劳的。(而且按照您当前的处理方式“处理”它们是愚蠢的。不要捕获您不知道如何修复的异常。让其他所有内容传递给调用者,或者调用者的调用者等等。)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接