如何异步使用“WinHttp.WinHttpRequest.5.1”?

4

代码:

var
  WinHttpReq: OleVariant;

procedure TForm1.Button1Click(Sender: TObject);    
begin
  WinHttpReq := CreateOleObject('WinHttp.WinHttpRequest.5.1');
  WinHttpReq.Open('GET', 'http://stackoverflow.com', TRUE); // asynchronously
  WinHttpReq.setRequestHeader('User-Agent', 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0');
  WinHttpReq.Send();
  // HOW to set a callback procedure here and get the response?
end;

注意:我不想导入mshttp.dll并使用TLB。我想通过后期绑定来使用它。如果有异常,我也希望能够处理它们。
编辑: 我接受TLama的答案,因为它给了我一个很好的替代方案。而且它还有一个很好的源代码示例。
这里有一个非常好的 WinHTTPRequest Wrapper with IConnectionPoint for Events 的实现(附有源代码)。
3个回答

3
如Stijn在他的答案中所说,为了防止您的程序出现卡顿,请使用线程。 IWinHttpRequest.Open 也具有异步配置能力,但很难捕获事件,并且IWinHttpRequest.WaitForResponse即使如此也会使您的程序陷入困境。
下面是一个简单的示例,演示如何将响应文本放入表单的记事本框中。请注意,以下示例使用同步模式,并且您还可以使用IWinHttpRequest.SetTimeouts来修改超时值。如果您要像在问题中那样使用异步模式,则必须使用IWinHttpRequest.WaitForResponse方法等待结果。
///////////////////////////////////////////////////////////////////////////////
/////   WinHttpRequest threading demo unit   //////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

unit WinHttpRequestUnit;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ActiveX, ComObj, StdCtrls;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

///////////////////////////////////////////////////////////////////////////////
/////   THTTPRequest - TThread descendant for single request   ////////////////
///////////////////////////////////////////////////////////////////////////////

type
  THTTPRequest = class(TThread)
  private
    FRequestURL: string;
    FResponseText: string;
    procedure Execute; override;
    procedure SynchronizeResult;
  public
    constructor Create(const RequestURL: string);
    destructor Destroy; override;
  end;

///////////////////////////////////////////////////////////////////////////////
/////   THTTPRequest.Create - thread constructor   ////////////////////////////
///////////////////////////////////////////////////////////////////////////////

// RequestURL - the requested URL

constructor THTTPRequest.Create(const RequestURL: string);
begin
  // create and start the thread after create
  inherited Create(False);
  // free the thread after THTTPRequest.Execute returns
  FreeOnTerminate := True;
  // store the passed parameter into the field for future use
  FRequestURL := RequestURL;
end;

///////////////////////////////////////////////////////////////////////////////
/////   THTTPRequest.Destroy - thread destructor   ////////////////////////////
///////////////////////////////////////////////////////////////////////////////

destructor THTTPRequest.Destroy;
begin
  inherited;
end;

///////////////////////////////////////////////////////////////////////////////
/////   THTTPRequest.Execute - thread body   //////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

procedure THTTPRequest.Execute;
var
  Request: OleVariant;
begin
  // COM library initialization for the current thread
  CoInitialize(nil);
  try
    // create the WinHttpRequest object instance
    Request := CreateOleObject('WinHttp.WinHttpRequest.5.1');
    // open HTTP connection with GET method in synchronous mode
    Request.Open('GET', FRequestURL, False);
    // set the User-Agent header value
    Request.SetRequestHeader('User-Agent', 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0');
    // sends the HTTP request to the server, the Send method does not return
    // until WinHTTP completely receives the response (synchronous mode)
    Request.Send;
    // store the response into the field for synchronization
    FResponseText := Request.ResponseText;
    // execute the SynchronizeResult method within the main thread context
    Synchronize(SynchronizeResult);
  finally
    // release the WinHttpRequest object instance
    Request := Unassigned;
    // uninitialize COM library with all resources
    CoUninitialize;
  end;
end;

///////////////////////////////////////////////////////////////////////////////
/////   THTTPRequest.SynchronizeResult - synchronization method   /////////////
///////////////////////////////////////////////////////////////////////////////

procedure THTTPRequest.SynchronizeResult;
begin
  // because of calling this method through Synchronize it is safe to access
  // the VCL controls from the main thread here, so let's fill the memo text
  // with the HTTP response stored before
  Form1.Memo1.Lines.Text := FResponseText;
end;

///////////////////////////////////////////////////////////////////////////////
/////   TForm1.Button1Click - button click event   ////////////////////////////
///////////////////////////////////////////////////////////////////////////////

// Sender - object which invoked the event

procedure TForm1.Button1Click(Sender: TObject);
begin
  // because the thread will be destroyed immediately after the Execute method
  // finishes (it's because FreeOnTerminate is set to True) and because we are
  // not reading any values from the thread (it fills the memo box with the
  // response for us in SynchronizeResult method) we don't need to store its
  // object instance anywhere as well as we don't need to care about freeing it
  THTTPRequest.Create('http://stackoverflow.com');
end;

end.

非常好的代码TLama。但是,我希望实现OnResponseDataAvailableOnError等等。顺便问一下,如果代码在主线程中运行,我们需要CoInitialize吗? - kobik
如果你使用COM延迟绑定技术,实现事件处理会相当复杂。是的,你必须调用CoInitialize函数,因为我例子中的THTTPRequest是工作线程(TThread的派生类),而不是主线程(尽管它们在同一个单元中,但可以认为TForm1是主线程,每个THTTPRequest都是独立的工作线程)。你需要为每个线程初始化COM库。 - TLama
我想表达的是,即使我不使用线程(就像我最初发布的代码一样),我是否需要调用CoInitialize?我在“编辑”部分添加了一个非常好的演示,展示了如何使用WinHTTPRequest接收事件。 - kobik
1
不需要,对于主线程你不必这样做。作为证明,您可以检查CoInitialize的返回值,如果返回S_FALSE,则COM库已经初始化。然后它会返回;) - TLama

2

IWinHttpRequest非常原始。

注意在Open()中指定Async模式的限制!

如果你认为可以使用通过get_ResponseStream()返回的IStream下载大文件,并在数据到达时将数据以小块写回文件,那么你就错了。

无论你使用同步模式还是异步模式:IWinHttpRequest总是将整个服务器响应加载到内存中,并且get_ResponseStream()会返回E_PENDING,直到整个下载过程已经存储在内存中。

此接口仅适用于小文件。


谢谢您指出这个问题。还有其他建议的替代方案吗? - kobik
一个下载大文件的替代方案是使用WinInet.dll:请参见HttpOpenRequest()等。 - Elmue

1
我建议您学习TThread对象。创建一个继承自TThread的新类,重写Execute方法,调用CoInitialize(启用COM)并执行WinHTTPRequest代码。当请求完成时,使用Synchronize将结果传递回前台线程。此外,您应该能够在Execute方法中使用try/except子句捕获异常。
另一个选择是切换到IXMLHTTPRequest对象,它具有async布尔属性。使用late-binding捕获事件可能非常困难,但您可以定期检查state属性。

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