6. 在 HttpRequest.h 中,添加此代码:
namespace Web
{
namespace Details
{
// 用于执行异步HTTP请求的实用程序类。
// 此类一次只支持一个未完成的请求。
class HttpRequest
{
public:
HttpRequest();
int GetStatusCode() const
{
return statusCode;
}
std::wstring const& GetReasonPhrase() const
{
return reasonPhrase;
}
// Whether the response has been completely received, if using ReadAsync().
bool IsResponseComplete() const
{
return responseComplete;
}
// Start an HTTP GET on the specified URI. The returned task completes once the entire response
// has been received, and the task produces the HTTP response text. The status code and reason
// can be read with GetStatusCode() and GetReasonPhrase().
concurrency::task<std::wstring> GetAsync(
Windows::Foundation::Uri^ uri,
concurrency::cancellation_token cancellationToken = concurrency::cancellation_token::none());
// Start an HTTP POST on the specified URI, using a string body. The returned task produces the
// HTTP response text. The status code and reason can be read with GetStatusCode() and GetReasonPhrase().
concurrency::task<std::wstring> PostAsync(
Windows::Foundation::Uri^ uri,
PCWSTR contentType,
IStream* postStream,
uint64 postStreamSizeToSend,
concurrency::cancellation_token cancellationToken = concurrency::cancellation_token::none());
// Start an HTTP POST on the specified URI, using a stream body. The returned task produces the
// HTTP response text. The status code and reason can be read with GetStatusCode() and GetReasonPhrase().
concurrency::task<std::wstring> PostAsync(
Windows::Foundation::Uri^ uri,
const std::wstring& str,
concurrency::cancellation_token cancellationToken = concurrency::cancellation_token::none());
// Send a request but don't return the response. Instead, let the caller read it with ReadAsync().
concurrency::task<void> SendAsync(
const std::wstring& httpMethod,
Windows::Foundation::Uri^ uri,
concurrency::cancellation_token cancellationToken = concurrency::cancellation_token::none());
// Read a chunk of data from the HTTP response, up to a specified length or until we reach the end
// of the response, and store the value in the provided buffer. This is useful for large content,
// enabling the streaming of the result.
concurrency::task<void> ReadAsync(
Windows::Storage::Streams::IBuffer^ readBuffer,
unsigned int offsetInBuffer,
unsigned int requestedBytesToRead);
static void CreateMemoryStream(IStream **stream);
private:
// Start a download of the specified URI using the specified method. The returned task produces the
// HTTP response text. The status code and reason can be read with GetStatusCode() and GetReasonPhrase().
concurrency::task<std::wstring> DownloadAsync(
PCWSTR httpMethod,
PCWSTR uri,
concurrency::cancellation_token cancellationToken,
PCWSTR contentType,
IStream* postStream,
uint64 postStreamBytesToSend);
// Referenced pointer to the callback, if using SendAsync/ReadAsync.
Microsoft::WRL::ComPtr<Details::HttpRequestBuffersCallback> buffersCallback;
int statusCode;
std::wstring reasonPhrase;
// Whether the response has been completely received, if using ReadAsync().
bool responseComplete;
};
}
7. 在 HttpRequest.cpp 中,先定义一个类HttpRequestStringCallback :
#include "pch.h"
#include "HttpRequest.h"
#include <robuffer.h>
#include <shcore.h>
using namespace concurrency;
using namespace Microsoft::WRL;
using namespace Platform;
using namespace std;
using namespace Web;
using namespace Windows::Foundation;
using namespace Windows::Storage::Streams;
//当只需要完整的响应时,使用IXMLHTTPRequest2Callback的实现。
//在处理收到的响应数据块时,请改用HttpRequestBuffersCallback。
class HttpRequestStringCallback
: public RuntimeClass<RuntimeClassFlags<ClassicCom>, IXMLHTTPRequest2Callback, FtmBase>
{
public:
HttpRequestStringCallback(IXMLHTTPRequest2* httpRequest,
cancellation_token ct = concurrency::cancellation_token::none()) :
request(httpRequest), cancellationToken(ct)
{
// Register a callback function that aborts the HTTP operation when
// the cancellation token is canceled.
if (cancellationToken != cancellation_token::none())
{
registrationToken = cancellationToken.register_callback([this]()
{
if (request != nullptr)
{
request->Abort();
}
});
}
}
// 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;
}
catch (std::bad_alloc&)
{
hr = E_OUTOFMEMORY;
}
return hr;
}
// Called when a portion of the entity body has been received.
IFACEMETHODIMP OnDataAvailable(IXMLHTTPRequest2*, ISequentialStream*)
{
return S_OK;
}
// Called when the entire entity response has been received.
IFACEMETHODIMP OnResponseReceived(IXMLHTTPRequest2*, ISequentialStream* responseStream)
{
wstring wstr;
HRESULT hr = ReadUtf8StringFromSequentialStream(responseStream, wstr);
// We must not propagate exceptions back to IXHR2.
try
{
completionEvent.set(make_tuple<HRESULT, wstring>(move(hr), move(wstr)));
}
catch (std::bad_alloc&)
{
hr = E_OUTOFMEMORY;
}
return hr;
}
// Simulate the functionality of DataReader.ReadString().
// This is needed because DataReader requires IRandomAccessStream and this
// code has an ISequentialStream that does not have a conversion to IRandomAccessStream like IStream does.
HRESULT ReadUtf8StringFromSequentialStream(ISequentialStream* readStream, wstring& str)
{
// Convert the response to Unicode wstring.
HRESULT hr;
// Holds the response as a Unicode string.
wstringstream ss;
while (true)
{
ULONG cb;
char buffer[4096];
// Read the response as a UTF-8 string. Since UTF-8 characters are 1-4 bytes long,
// we need to make sure we only read an integral number of characters. So we'll
// start with 4093 bytes.
hr = readStream->Read(buffer, sizeof(buffer) - 3, &cb);
if (FAILED(hr) || (cb == 0))
{
break; // Error or no more data to process, exit loop.
}
if (cb == sizeof(buffer) - 3)
{
ULONG subsequentBytesRead;
unsigned int i, cl;
// Find the first byte of the last UTF-8 character in the buffer.
for (i = cb - 1; (i >= 0) && ((buffer[i] & 0xC0) == 0x80); i--);
// Calculate the number of subsequent bytes in the UTF-8 character.
if (((unsigned char)buffer[i]) < 0x80)
{
cl = 1;
}
else if (((unsigned char)buffer[i]) < 0xE0)
{
cl = 2;
}
else if (((unsigned char)buffer[i]) < 0xF0)
{
cl = 3;
}
else
{
cl = 4;
}
// Read any remaining bytes.
if (cb < i + cl)
{
hr = readStream->Read(buffer + cb, i + cl - cb, &subsequentBytesRead);
if (FAILED(hr))
{
break; // Error, exit loop.
}
cb += subsequentBytesRead;
}
}
// First determine the size required to store the Unicode string.
int const sizeRequired = MultiByteToWideChar(CP_UTF8, 0, buffer, cb, nullptr, 0);
if (sizeRequired == 0)
{
// Invalid UTF-8.
hr = HRESULT_FROM_WIN32(GetLastError());
break;
}
unique_ptr<char16[]> wstr(new(std::nothrow) char16[sizeRequired + 1]);
if (wstr.get() == nullptr)
{
hr = E_OUTOFMEMORY;
break;
}
// Convert the string from UTF-8 to UTF-16LE. This can never fail, since
// the previous call above succeeded.
MultiByteToWideChar(CP_UTF8, 0, buffer, cb, wstr.get(), sizeRequired);
wstr[sizeRequired] = L'\0'; // Terminate the string.
ss << wstr.get(); // Write the string to the stream.
}
str = SUCCEEDED(hr) ? ss.str() : wstring();
return (SUCCEEDED(hr)) ? S_OK : hr; // Don't return S_FALSE.
}
// 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
{
completionEvent.set(make_tuple<HRESULT, wstring>(move(hrError), wstring()));
}
catch (std::bad_alloc&)
{
hr = E_OUTOFMEMORY;
}
return hr;
}
// Retrieves the completion event for the HTTP operation.
task_completion_event<tuple<HRESULT, wstring>> const& GetCompletionEvent() const
{
return completionEvent;
}
int GetStatusCode() const
{
return statusCode;
}
wstring GetReasonPhrase() const
{
return reasonPhrase;
}
private:
~HttpRequestStringCallback()
{
// Unregister the callback.
if (cancellationToken != cancellation_token::none())
{
cancellationToken.deregister_callback(registrationToken);
}
}
// Signals that the download operation was canceled.
cancellation_token cancellationToken;
// Used to unregister the cancellation token callback.
cancellation_token_registration registrationToken;
// The IXMLHTTPRequest2 that processes the HTTP request.
ComPtr<IXMLHTTPRequest2> request;
// Task completion event that is set when the
// download operation completes.
task_completion_event<tuple<HRESULT, wstring>> completionEvent;
int statusCode;
wstring reasonPhrase;
};
8. 接下来继续完成这个类的成员函数代码的添加:
//将字节从顺序流复制到提供的缓冲区中,直到
//我们到达了一个或另一个的终点。
unsigned int Web::Details::HttpRequestBuffersCallback::ReadData(
_Out_writes_(outputBufferSize) byte* outputBuffer,
unsigned int outputBufferSize)
{
// Lock the data event while doing the read, to ensure that any bytes we don't read will
// result in the correct event getting triggered.
concurrency::critical_section::scoped_lock lock(dataEventLock);
ULONG bytesRead;
CheckHResult(dataStream.Get()->Read(outputBuffer, outputBufferSize, &bytesRead));
if (bytesRead < outputBufferSize)
{
// We need to reset the data event, which we can only do by creating a new one.
dataEvent = task_completion_event<void>();
}
return bytesRead;
}
9. 在代码中加入对完成任务的处理函数:
//以异常安全的方式创建一个在数据可用时完成的任务。
task<void> Web::Details::HttpRequestBuffersCallback::CreateDataTask()
{
concurrency::critical_section::scoped_lock lock(dataEventLock);
return create_task(dataEvent, cancellationToken);
}
HttpRequest::HttpRequest() : responseComplete(true), statusCode(200)
{
}