如何异步使用“WinHttp.WinHttpRequest.5.1”?
Posted
技术标签:
【中文标题】如何异步使用“WinHttp.WinHttpRequest.5.1”?【英文标题】:How to use "WinHttp.WinHttpRequest.5.1" asynchronously? 【发布时间】:2012-01-22 17:57:47 【问题描述】:代码:
var
WinHttpReq: OleVariant;
procedure TForm1.Button1Click(Sender: TObject);
begin
WinHttpReq := CreateOleObject('WinHttp.WinHttpRequest.5.1');
WinHttpReq.Open('GET', 'http://***.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 的一个非常好的实现(附源代码)。
【问题讨论】:
【参考方案1】:正如 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://***.com');
end;
end.
【讨论】:
非常好的代码 TLama。但是,我想实现OnResponseDataAvailable
、OnError
等。顺便说一句,如果代码在主线程中运行,我们是否需要CoInitialize
?
如果你使用COM后期绑定来实现事件处理是相当complicated
。是的,您必须调用CoInitialize
,因为我的示例中的THTTPRequest
是工作线程(TThread
后代),而不是主要线程(尽管它们在同一个单元中,但假设TForm1
是主线程,每个THTTPRequest
是单独的工作线程)。而且你必须为每个线程初始化 COM 库。
我的意思是,即使我不使用线程(如我最初发布的代码),我是否需要调用 CoInitialize?我在 EDIT 部分添加了一个非常好的演示,它展示了如何使用 WinHTTPRequest
接收事件。
不,对于主线程,您不必这样做。作为证明,您可以检查CoInitialize
的返回值,如果返回S_FALSE
,则COM 库已经初始化。它返回;)【参考方案2】:
IWinHttpRequest 非常原始。 注意 Open() 中指定的异步模式!
如果您认为可以使用 get_ResponseStream() 返回的 IStream 下载大文件,并在数据到达时将数据以小块的形式写回文件,那您就错了。
无论您使用同步还是异步模式:IWinHttpRequest 总是将整个服务器响应加载到内存中,并且 get_ResponseStream() 返回 E_PENDING,直到整个下载都存储在内存中。
此界面仅适用于小文件。
【讨论】:
感谢您指出这一点。还有其他建议的替代品吗? 下载大文件的另一种方法是在 WinInet.dll 中:参见 HttpOpenRequest() 等。【参考方案3】:我建议您了解 TThread 对象。创建一个继承自 TThread 的新类,重写 Execute 方法,调用 CoInitialize(以启用 COM)并执行 WinHTTPRequest 代码。请求完成后,使用 Synchronize 将结果传递回前台线程。此外,您应该能够在 Execute 方法的 try/except 子句中捕获异常。
另一个选项是切换到具有异步布尔属性的 IXMLHTTPRequest 对象。使用后期绑定捕获事件可能非常困难,但您可以定期检查状态属性。
【讨论】:
以上是关于如何异步使用“WinHttp.WinHttpRequest.5.1”?的主要内容,如果未能解决你的问题,请参考以下文章