In HttpRequest.h, add this code:
#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
{
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)
{
if (cancellationToken != concurrency::cancellation_token::none())
{
registrationToken = cancellationToken.register_callback([this]()
{
if (request != nullptr)
{
request->Abort();
}
});
}
dataEvent = concurrency::task_completion_event<void>();
}
IFACEMETHODIMP OnRedirect(IXMLHTTPRequest2*, PCWSTR)
{
return S_OK;
}
IFACEMETHODIMP OnHeadersAvailable(IXMLHTTPRequest2*, DWORD statusCode, PCWSTR reasonPhrase)
{
HRESULT hr = S_OK;
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;
}
IFACEMETHODIMP OnDataAvailable(IXMLHTTPRequest2*, ISequentialStream* stream)
{
HRESULT hr = S_OK;
try
{
dataStream = stream;
concurrency::critical_section::scoped_lock lock(dataEventLock);
dataEvent.set();
}
catch (std::bad_alloc&)
{
hr = E_OUTOFMEMORY;
}
return hr;
}
IFACEMETHODIMP OnResponseReceived(IXMLHTTPRequest2* xhr, ISequentialStream* responseStream)
{
responseReceived = true;
return OnDataAvailable(xhr, responseStream);
}
IFACEMETHODIMP OnError(IXMLHTTPRequest2*, HRESULT hrError)
{
HRESULT hr = S_OK;
try
{
concurrency::critical_section::scoped_lock lock(dataEventLock);
dataHResult = hrError;
dataEvent.set();
}
catch (std::bad_alloc&)
{
hr = E_OUTOFMEMORY;
}
return hr;
}
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;
}
unsigned int ReadData(
_Out_writes_(outputBufferSize) byte* outputBuffer,
unsigned int outputBufferSize);
private:
~HttpRequestBuffersCallback()
{
if (cancellationToken != concurrency::cancellation_token::none())
{
cancellationToken.deregister_callback(registrationToken);
}
}
concurrency::cancellation_token cancellationToken;
concurrency::cancellation_token_registration registrationToken;
Microsoft::WRL::ComPtr<IXMLHTTPRequest2> request;
concurrency::task_completion_event<void> dataEvent;
concurrency::critical_section dataEventLock;
HRESULT dataHResult;
Microsoft::WRL::ComPtr<ISequentialStream> dataStream;
int statusCode;
std::wstring reasonPhrase;
bool responseReceived;
};
};
class HttpRequest
{
public:
HttpRequest();
int GetStatusCode() const
{
return statusCode;
}
std::wstring const& GetReasonPhrase() const
{
return reasonPhrase;
}
bool IsResponseComplete() const
{
return responseComplete;
}
concurrency::task<std::wstring> GetAsync(
Windows::Foundation::Uri^ uri,
concurrency::cancellation_token cancellationToken = concurrency::cancellation_token::none());
concurrency::task<std::wstring> PostAsync(
Windows::Foundation::Uri^ uri,
PCWSTR contentType,
IStream* postStream,
uint64 postStreamSizeToSend,
concurrency::cancellation_token cancellationToken = concurrency::cancellation_token::none());
concurrency::task<std::wstring> PostAsync(
Windows::Foundation::Uri^ uri,
const std::wstring& str,
concurrency::cancellation_token cancellationToken = concurrency::cancellation_token::none());
concurrency::task<void> SendAsync(
const std::wstring& httpMethod,
Windows::Foundation::Uri^ uri,
concurrency::cancellation_token cancellationToken = concurrency::cancellation_token::none());
concurrency::task<void> ReadAsync(
Windows::Storage::Streams::IBuffer^ readBuffer,
unsigned int offsetInBuffer,
unsigned int requestedBytesToRead);
static void CreateMemoryStream(IStream **stream);
private:
concurrency::task<std::wstring> DownloadAsync(
PCWSTR httpMethod,
PCWSTR uri,
concurrency::cancellation_token cancellationToken,
PCWSTR contentType,
IStream* postStream,
uint64 postStreamBytesToSend);
Microsoft::WRL::ComPtr<Details::HttpRequestBuffersCallback> buffersCallback;
int statusCode;
std::wstring reasonPhrase;
bool responseComplete;
};
};
In HttpRequest.cpp, add this code:
#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;
class HttpRequestStringCallback
: public RuntimeClass<RuntimeClassFlags<ClassicCom>, IXMLHTTPRequest2Callback, FtmBase>
{
public:
HttpRequestStringCallback(IXMLHTTPRequest2* httpRequest,
cancellation_token ct = concurrency::cancellation_token::none()) :
request(httpRequest), cancellationToken(ct)
{
if (cancellationToken != cancellation_token::none())
{
registrationToken = cancellationToken.register_callback([this]()
{
if (request != nullptr)
{
request->Abort();
}
});
}
}
IFACEMETHODIMP OnRedirect(IXMLHTTPRequest2*, PCWSTR)
{
return S_OK;
}
IFACEMETHODIMP OnHeadersAvailable(IXMLHTTPRequest2*, DWORD statusCode, PCWSTR reasonPhrase)
{
HRESULT hr = S_OK;
try
{
this->statusCode = statusCode;
this->reasonPhrase = reasonPhrase;
}
catch (std::bad_alloc&)
{
hr = E_OUTOFMEMORY;
}
return hr;
}
IFACEMETHODIMP OnDataAvailable(IXMLHTTPRequest2*, ISequentialStream*)
{
return S_OK;
}
IFACEMETHODIMP OnResponseReceived(IXMLHTTPRequest2*, ISequentialStream* responseStream)
{
wstring wstr;
HRESULT hr = ReadUtf8StringFromSequentialStream(responseStream, wstr);
try
{
completionEvent.set(make_tuple<HRESULT, wstring>(move(hr), move(wstr)));
}
catch (std::bad_alloc&)
{
hr = E_OUTOFMEMORY;
}
return hr;
}
HRESULT ReadUtf8StringFromSequentialStream(ISequentialStream* readStream, wstring& str)
{
HRESULT hr;
wstringstream ss;
while (true)
{
ULONG cb;
char buffer[4096];
hr = readStream->Read(buffer, sizeof(buffer) - 3, &cb);
if (FAILED(hr) || (cb == 0))
{
break;
}
if (cb == sizeof(buffer) - 3)
{
ULONG subsequentBytesRead;
unsigned int i, cl;
for (i = cb - 1; (i >= 0) && ((buffer[i] & 0xC0) == 0x80); i--);
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;
}
if (cb < i + cl)
{
hr = readStream->Read(buffer + cb, i + cl - cb, &subsequentBytesRead);
if (FAILED(hr))
{
break;
}
cb += subsequentBytesRead;
}
}
int const sizeRequired = MultiByteToWideChar(CP_UTF8, 0, buffer, cb, nullptr, 0);
if (sizeRequired == 0)
{
hr = HRESULT_FROM_WIN32(GetLastError());
break;
}
unique_ptr<char16[]> wstr(new(std::nothrow) char16[sizeRequired + 1]);
if (wstr.get() == nullptr)
{
hr = E_OUTOFMEMORY;
break;
}
MultiByteToWideChar(CP_UTF8, 0, buffer, cb, wstr.get(), sizeRequired);
wstr[sizeRequired] = L'\0';
ss << wstr.get();
}
str = SUCCEEDED(hr) ? ss.str() : wstring();
return (SUCCEEDED(hr)) ? S_OK : hr;
}
IFACEMETHODIMP OnError(IXMLHTTPRequest2*, HRESULT hrError)
{
HRESULT hr = S_OK;
try
{
completionEvent.set(make_tuple<HRESULT, wstring>(move(hrError), wstring()));
}
catch (std::bad_alloc&)
{
hr = E_OUTOFMEMORY;
}
return hr;
}
task_completion_event<tuple<HRESULT, wstring>> const& GetCompletionEvent() const
{
return completionEvent;
}
int GetStatusCode() const
{
return statusCode;
}
wstring GetReasonPhrase() const
{
return reasonPhrase;
}
private:
~HttpRequestStringCallback()
{
if (cancellationToken != cancellation_token::none())
{
cancellationToken.deregister_callback(registrationToken);
}
}
cancellation_token cancellationToken;
cancellation_token_registration registrationToken;
ComPtr<IXMLHTTPRequest2> request;
task_completion_event<tuple<HRESULT, wstring>> completionEvent;
int statusCode;
wstring reasonPhrase;
};
unsigned int Web::Details::HttpRequestBuffersCallback::ReadData(
_Out_writes_(outputBufferSize) byte* outputBuffer,
unsigned int outputBufferSize)
{
concurrency::critical_section::scoped_lock lock(dataEventLock);
ULONG bytesRead;
CheckHResult(dataStream.Get()->Read(outputBuffer, outputBufferSize, &bytesRead));
if (bytesRead < outputBufferSize)
{
dataEvent = task_completion_event<void>();
}
return bytesRead;
}
task<void> Web::Details::HttpRequestBuffersCallback::CreateDataTask()
{
concurrency::critical_section::scoped_lock lock(dataEventLock);
return create_task(dataEvent, cancellationToken);
}
HttpRequest::HttpRequest() : responseComplete(true), statusCode(200)
{
}
task<wstring> HttpRequest::DownloadAsync(PCWSTR httpMethod, PCWSTR uri, cancellation_token cancellationToken,
PCWSTR contentType, IStream* postStream, uint64 postStreamSizeToSend)
{
ComPtr<IXMLHTTPRequest2> xhr;
CheckHResult(CoCreateInstance(CLSID_XmlHttpRequest, nullptr, CLSCTX_INPROC, IID_PPV_ARGS(&xhr)));
auto stringCallback = Make<HttpRequestStringCallback>(xhr.Get(), cancellationToken);
CheckHResult(stringCallback ? S_OK : E_OUTOFMEMORY);
auto completionTask = create_task(stringCallback->GetCompletionEvent());
CheckHResult(xhr->Open(httpMethod, uri, stringCallback.Get(), nullptr, nullptr, nullptr, nullptr));
if (postStream != nullptr && contentType != nullptr)
{
CheckHResult(xhr->SetRequestHeader(L"Content-Type", contentType));
}
CheckHResult(xhr->Send(postStream, postStreamSizeToSend));
return completionTask.then([this, stringCallback](tuple<HRESULT, wstring> resultTuple)
{
CheckHResult(std::get<0>(resultTuple));
statusCode = stringCallback->GetStatusCode();
reasonPhrase = stringCallback->GetReasonPhrase();
return std::get<1>(resultTuple);
});
}
task<wstring> HttpRequest::GetAsync(Uri^ uri, cancellation_token cancellationToken)
{
return DownloadAsync(L"GET",
uri->AbsoluteUri->Data(),
cancellationToken,
nullptr,
nullptr,
0);
}
void HttpRequest::CreateMemoryStream(IStream **stream)
{
auto randomAccessStream = ref new Windows::Storage::Streams::InMemoryRandomAccessStream();
CheckHResult(CreateStreamOverRandomAccessStream(randomAccessStream, IID_PPV_ARGS(stream)));
}
task<wstring> HttpRequest::PostAsync(Uri^ uri, const wstring& body, cancellation_token cancellationToken)
{
int length = 0;
ComPtr<IStream> postStream;
CreateMemoryStream(&postStream);
if (body.length() > 0)
{
int size = WideCharToMultiByte(CP_UTF8,
0,
body.c_str(),
static_cast<int>(body.length()),
nullptr,
0,
nullptr,
nullptr);
CheckHResult((size != 0) ? S_OK : HRESULT_FROM_WIN32(GetLastError()));
std::unique_ptr<char[]> tempData(new char[size]);
length = WideCharToMultiByte(CP_UTF8,
0,
body.c_str(),
static_cast<int>(body.length()),
tempData.get(),
size,
nullptr,
nullptr);
CheckHResult((length != 0) ? S_OK : HRESULT_FROM_WIN32(GetLastError()));
CheckHResult(postStream->Write(tempData.get(), length, nullptr));
}
return DownloadAsync(L"POST",
uri->AbsoluteUri->Data(),
cancellationToken,
L"text/plain;charset=utf-8",
postStream.Get(),
length);
}
task<wstring> HttpRequest::PostAsync(Uri^ uri, PCWSTR contentType, IStream* postStream,
uint64 postStreamSizeToSend, cancellation_token cancellationToken)
{
return DownloadAsync(L"POST",
uri->AbsoluteUri->Data(),
cancellationToken,
contentType,
postStream,
postStreamSizeToSend);
}
task<void> HttpRequest::SendAsync(const wstring& httpMethod, Uri^ uri, cancellation_token cancellationToken)
{
ComPtr<IXMLHTTPRequest2> xhr;
CheckHResult(CoCreateInstance(CLSID_XmlHttpRequest, nullptr, CLSCTX_INPROC, IID_PPV_ARGS(&xhr)));
buffersCallback = Make<Web::Details::HttpRequestBuffersCallback>(xhr.Get(), cancellationToken);
CheckHResult(buffersCallback ? S_OK : E_OUTOFMEMORY);
ComPtr<IXMLHTTPRequest2Callback> xhrCallback;
CheckHResult(buffersCallback.As(&xhrCallback));
CheckHResult(xhr->Open(httpMethod.c_str(),
uri->AbsoluteUri->Data(),
xhrCallback.Get(),
nullptr,
nullptr,
nullptr,
nullptr));
responseComplete = false;
CheckHResult(xhr->Send(nullptr, 0));
return buffersCallback->CreateDataTask().then([this]()
{
CheckHResult(buffersCallback->GetError());
statusCode = buffersCallback->GetStatusCode();
reasonPhrase = buffersCallback->GetReasonPhrase();
});
}
task<void> HttpRequest::ReadAsync(Windows::Storage::Streams::IBuffer^ readBuffer, unsigned int offsetInBuffer,
unsigned int requestedBytesToRead)
{
if (offsetInBuffer + requestedBytesToRead > readBuffer->Capacity)
{
throw ref new InvalidArgumentException();
}
return buffersCallback->CreateDataTask().then([this, readBuffer, offsetInBuffer, requestedBytesToRead]()
{
CheckHResult(buffersCallback->GetError());
ComPtr<IBufferByteAccess> bufferByteAccess;
CheckHResult(reinterpret_cast<IUnknown*>(readBuffer)->QueryInterface(IID_PPV_ARGS(&bufferByteAccess)));
byte* outputBuffer;
CheckHResult(bufferByteAccess->Buffer(&outputBuffer));
readBuffer->Length = buffersCallback->ReadData(outputBuffer + offsetInBuffer, requestedBytesToRead);
if (buffersCallback->IsResponseReceived() && (readBuffer->Length < requestedBytesToRead))
{
responseComplete = true;
}
});
}