windows C++-windows C++-使用任务和 XML HTTP 请求进行连接(二)

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)
{
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值