2011-12-17 2 views
4

Код:Как использовать «WinHttp.WinHttpRequest.5.1» асинхронно?

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. Я хочу использовать его через позднюю привязку. Я также хотел бы обрабатывать исключения, если они есть.

EDIT: Я принимаю ответ TLama, потому что он дает мне хорошую альтернативу тому, что я изначально задавал. плюс у него есть хороший пример источника.

Вот очень хорошая реализация WinHTTPRequest Wrapper with IConnectionPoint for Events (исходный код прилагается).

ответ

3

Как сказал в своем ответе Стийн, чтобы предотвратить отставание вашей программы, используйте потоки. 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. 
+0

очень красивый код TLama. но я прыгал реализовать «OnResponseDataAvailable», «OnError» и т. д. Кстати, нам нужно «CoInitialize», если код работает в основном потоке? – kobik

+0

Это довольно ['сложный'] (http://www.techvanguards.com/com/concepts/events.asp) для реализации обработки событий, если вы используете COM-привязку позднее. И да, вы должны вызвать ['CoInitialize'] (http://msdn.microsoft.com/en-us/library/windows/desktop/ms678543%28v=vs.85%29.aspx), потому что' THTTPRequest 'из моего примера - рабочий поток (« потомок TThread »), а не основной (они находятся в одной и той же единице, но, скажем,« TForm1 »является основным потоком, и каждый« THTTPRequest »является отдельным рабочим потоком) , И вы должны инициализировать библиотеку COM для каждого потока. – TLama

+0

Что я имел в виду, мне нужно вызвать CoInitialize, даже если я НЕ использую потоки (например, код, который я опубликовал изначально)?Я добавил очень приятную демонстрацию в разделе EDIT, которая показывает, как потопить события с помощью 'WinHTTPRequest'. – kobik

1

Я предлагаю вам узнать об объекте TThread. Создайте новый класс, наследующий от TThread, переопределите метод Execute, вызовите CoInitialize (чтобы включить COM) и выполните код WinHTTPRequest. Когда запрос будет выполнен, используйте Synchronize, чтобы передать результат обратно в поток переднего плана. Также вы должны уловить исключения в предложении try/except в методе Execute.

Другим вариантом является переход на объект IXMLHTTPRequest, который имеет свойство aync boolean. Захват событий с поздним связыванием может быть довольно сложным, но вы можете регулярно проверять свойство состояния.

2

IWinHttpRequest довольно примитивен. Предостережение с режимом Async, указанным в Open()!

Если вы считаете, что можете загрузить большой файл, используя IStream, который возвращается get_ResponseStream(), и записывайте данные обратно в файл небольшими кусками по мере их поступления, вы ошибаетесь.

Независимо от того, используете ли вы режим Sync или Async: IWinHttpRequest всегда загружает весь ответ сервера в память, а get_ResponseStream() возвращает E_PENDING, пока загрузка ENTIRE не будет сохранена в памяти.

Этот интерфейс был разработан только для небольших файлов.

+0

Спасибо, что указали это. любые другие предложенные альтернативы, может быть? – kobik

+0

Альтернатива для загрузки больших файлов находится в WinInet.dll: см. HttpOpenRequest() и т. Д. – Elmue