本文会演示如何将 IXMLHTTPRequest2 和 IXMLHTTPRequest2Callback 接口与任务结合使用,以将 HTTP GET 和 POST 请求发送至通用 Windows 平台 (UWP) 应用中的 Web 服务。 通过将 IXMLHTTPRequest2 与任务组合在一起,你可以编写通过其他任务编写的代码。 例如,可以使用下载任务作为任务链的一部分。 工作取消时,下载任务也会响应。
还可以使用 C++ REST SDK 从使用 C++ 应用的 UWP 应用或从桌面 C++ 应用来执行 HTTP 请求。
本文档首先演示如何创建 HttpRequest 及其支持类。 然后,演示如何从使用 C++ 和 XAML 的 UWP 应用使用此类。
IXMLHTTPRequest2 和 IXMLHTTPRequest2Callback 是建议在 UWP 应用中使用的接口。 还可以调整此示例,以用于桌面应用程序。
先决条件
Visual Studio 2017 及更高版本中的 UWP 支持是可选的。 若要安装它,请从 Windows“开始”菜单打开 Visual Studio 安装程序,并选择正在使用的 Visual Studio 版本。 单击“修改”按钮,确保选中“UWP 开发”磁贴。 在“可选组件”下,确保选中“C++ UWP 工具”。 对于 Visual Studio 2017,请使用 v141;对于 Visual Studio 2019,请使用 v142。
定义 HttpRequest、HttpRequestBuffersCallback 和 HttpRequestStringCallback 类
当您使用 IXMLHTTPRequest2 接口通过 HTTP 创建 Web 请求时,可以实现 IXMLHTTPRequest2Callback 接口来接收服务器响应并对其他事件做出响应。 此示例定义了 HttpRequest 类来创建 Web 请求,并定义了 HttpRequestBuffersCallback 和 HttpRequestStringCallback 类来处理响应。 HttpRequestBuffersCallback 和 HttpRequestStringCallback 类支持 HttpRequest 类;在应用程序代码中您只能使用 HttpRequest 类。
GetAsync 类的 PostAsync 和 HttpRequest 方法可以使您分别启动 HTTP GET 和 POST 操作。 这些方法使用 HttpRequestStringCallback 类以便将服务器响应作为字符串读取。 SendAsync 和 ReadAsync 方法使您能够对区块中的大型内容进行流式处理。 这些方法均返回 concurrency::task 以表示操作。 GetAsync 和 PostAsync 方法会产生 task<std::wstring> 值,其中 wstring 部分表示服务器的响应。 SendAsync 和 ReadAsync 方法将产生 task<void> 值;当发送和读取操作完成后这些任务将会完成。
因为 IXMLHTTPRequest2 接口是异步操作的,本示例使用 concurrency::task_completion_event 来创建一个任务,该任务将在回调对象完成或取消下载操作后完成。 HttpRequest 类从此任务创建基于任务的延续以设置最终结果。 HttpRequest 类使用基于任务的延续来确保,即使在前面的任务产生错误或取消的情况下,延续任务也会运行。 有关基于任务的延续的详细信息,请参阅任务并行
若要支持取消,HttpRequest、HttpRequestBuffersCallback 和 HttpRequestStringCallback 类将使用取消标记。 HttpRequestBuffersCallback 和 HttpRequestStringCallback 类使用 concurrency::cancellation_token::register_callback 方法使任务完成事件能够响应取消。
定义 HttpRequest 类
1. 在主菜单中,选择“文件”>“新建”>“项目”。
2. 使用 C++“空白应用(通用 Windows)”模板来创建空白 XAML 应用项目。 此示例将项目命名为 UsingIXMLHTTPRequest2。
3. 在项目中添加一个名为 HttpRequest.h 的标头文件和一个名为 HttpRequest.cpp 的源文件。
4. 在 pch.h 中,添加此代码:
#include <ppltasks.h>
#include <string>
#include <sstream>
#include <wrl.h>
#include <msxml6.h>
5. 在 HttpRequest.h 中,添加此代码:
我们会创建两个类,分别应对不同的情况,第一个类定义如下:
#pragma once
#include "pch.h"
inline void CheckHResult(HRESULT hResult)
{
if (hResult == E_ABORT)
{
concurrency::cancel_current_task();
}
else if (FAILED(hResult))
{
throw Platform::Exception::CreateException(hResult);
}
}
namespace Web
{
namespace Details
{
// 当响应需要部分缓冲区时,使用IXMLHTTPRequest2Callback的实现。
// 当只需要完整的响应时,请改用HttpRequestStringCallback。
class HttpRequestBuffersCallback
: public Microsoft::WRL::RuntimeClass<
Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
IXMLHTTPRequest2Callback,
Microsoft::WRL::FtmBase>
{
public:
HttpRequestBuffersCallback(IXMLHTTPRequest2* httpRequest,
concurrency::cancellation_token ct = concurrency::cancellation_token::none()) :
request(httpRequest), cancellationToken(ct), responseReceived(false), dataHResult(S_OK), statusCode(200)
{
// Register a callback function that aborts the HTTP operation when
// the cancellation token is canceled.
if (cancellationToken != concurrency::cancellation_token::none())
{
registrationToken = cancellationToken.register_callback([this]()
{
if (request != nullptr)
{
request->Abort();
}
});
}
dataEvent = concurrency::task_completion_event<void>();
}
// Called when the HTTP request is being redirected to a new URL.
IFACEMETHODIMP OnRedirect(IXMLHTTPRequest2*, PCWSTR)
{
return S_OK;
}
// Called when HTTP headers have been received and processed.
IFACEMETHODIMP OnHeadersAvailable(IXMLHTTPRequest2*, DWORD statusCode, PCWSTR reasonPhrase)
{
HRESULT hr = S_OK;
// We must not propagate exceptions back to IXHR2.
try
{
this->statusCode = statusCode;
this->reasonPhrase = reasonPhrase;
concurrency::critical_section::scoped_lock lock(dataEventLock);
dataEvent.set();
}
catch (std::bad_alloc&)
{
hr = E_OUTOFMEMORY;
}
return hr;
}
// Called when a portion of the entity body has been received.
IFACEMETHODIMP OnDataAvailable(IXMLHTTPRequest2*, ISequentialStream* stream)
{
HRESULT hr = S_OK;
// We must not propagate exceptions back to IXHR2.
try
{
// Store a reference on the stream so it can be accessed by the task.
dataStream = stream;
// The work must be done as fast as possible, and must not block this thread,
// for example, waiting on another event object. Here we simply set an event
// that can be processed by another thread.
concurrency::critical_section::scoped_lock lock(dataEventLock);
dataEvent.set();
}
catch (std::bad_alloc&)
{
hr = E_OUTOFMEMORY;
}
return hr;
}
// Called when the entire entity response has been received.
IFACEMETHODIMP OnResponseReceived(IXMLHTTPRequest2* xhr, ISequentialStream* responseStream)
{
responseReceived = true;
return OnDataAvailable(xhr, responseStream);
}
// Called when an error occurs during the HTTP request.
IFACEMETHODIMP OnError(IXMLHTTPRequest2*, HRESULT hrError)
{
HRESULT hr = S_OK;
// We must not propagate exceptions back to IXHR2.
try
{
concurrency::critical_section::scoped_lock lock(dataEventLock);
dataHResult = hrError;
dataEvent.set();
}
catch (std::bad_alloc&)
{
hr = E_OUTOFMEMORY;
}
return hr;
}
// Create a task that completes when data is available, in an exception-safe way.
concurrency::task<void> CreateDataTask();
HRESULT GetError() const
{
return dataHResult;
}
int GetStatusCode() const
{
return statusCode;
}
std::wstring const& GetReasonPhrase() const
{
return reasonPhrase;
}
bool IsResponseReceived() const
{
return responseReceived;
}
// Copy bytes from the sequential stream into the buffer provided until
// we reach the end of one or the other.
unsigned int ReadData(
_Out_writes_(outputBufferSize) byte* outputBuffer,
unsigned int outputBufferSize);
private:
~HttpRequestBuffersCallback()
{
// Unregister the callback.
if (cancellationToken != concurrency::cancellation_token::none())
{
cancellationToken.deregister_callback(registrationToken);
}
}
// Signals that the download operation was canceled.
concurrency::cancellation_token cancellationToken;
// Used to unregister the cancellation token callback.
concurrency::cancellation_token_registration registrationToken;
// The IXMLHTTPRequest2 that processes the HTTP request.
Microsoft::WRL::ComPtr<IXMLHTTPRequest2> request;
// Task completion event that is set when data is available or error is triggered.
concurrency::task_completion_event<void> dataEvent;
concurrency::critical_section dataEventLock;
// We cannot store the error obtained from IXHR2 in the dataEvent since any value there is first-writer-wins,
// whereas we want a subsequent error to override an initial success.
HRESULT dataHResult;
// Referenced pointer to the data stream.
Microsoft::WRL::ComPtr<ISequentialStream> dataStream;
// HTTP status code and reason returned by the server.
int statusCode;
std::wstring reasonPhrase;
// Whether the response has been completely received.
bool responseReceived;
};
};
};