演练:使用任务和 XML HTTP 请求进行连接

此示例演示如何将 IXMLHTTPRequest2IXMLHTTPRequest2Callback 接口与任务结合使用,以将 HTTP GET 和 POST 请求发送至通用 Windows 平台 (UWP) 应用中的 Web 服务。 通过将 IXMLHTTPRequest2 与任务组合在一起,你可以编写通过其他任务编写的代码。 例如,可以使用下载任务作为任务链的一部分。 工作取消时,下载任务也会响应。

提示

还可以使用 C++ REST SDK 从使用 C++ 应用的 UWP 应用或从桌面 C++ 应用来执行 HTTP 请求。 有关详细信息,请参阅 C++ REST SDK(Codename“Casablanca”)

有关任务的详细信息,请参阅任务并行。 有关如何在 UWP 应用中使用任务的详细信息,请参阅 C++ 中的异步编程用 C++ 为 UWP 应用创建异步操作

本文档首先演示如何创建 HttpRequest 及其支持类。 然后,演示如何从使用 C++ 和 XAML 的 UWP 应用使用此类。

有关使用 IXMLHTTPRequest2 但不使用任务的示例,请参阅快速入门:使用 XML HTTP 请求 (IXMLHTTPRequest2) 进行连接

提示

IXMLHTTPRequest2IXMLHTTPRequest2Callback 是建议在 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 请求,并定义了 HttpRequestBuffersCallbackHttpRequestStringCallback 类来处理响应。 HttpRequestBuffersCallbackHttpRequestStringCallback 类支持 HttpRequest 类;在应用程序代码中您只能使用 HttpRequest 类。

GetAsync 类的 PostAsyncHttpRequest 方法可以使您分别启动 HTTP GET 和 POST 操作。 这些方法使用 HttpRequestStringCallback 类以便将服务器响应作为字符串读取。 SendAsyncReadAsync 方法使您能够对区块中的大型内容进行流式处理。 这些方法均返回 concurrency::task 以表示操作。 GetAsyncPostAsync 方法会产生 task<std::wstring> 值,其中 wstring 部分表示服务器的响应。 SendAsyncReadAsync 方法将产生 task<void> 值;当发送和读取操作完成后这些任务将会完成。

因为 IXMLHTTPRequest2 接口是异步操作的,本示例使用 concurrency::task_completion_event 来创建一个任务,该任务将在回调对象完成或取消下载操作后完成。 HttpRequest 类从此任务创建基于任务的延续以设置最终结果。 HttpRequest 类使用基于任务的延续来确保,即使在前面的任务产生错误或取消的情况下,延续任务也会运行。 有关基于任务的延续的详细信息,请参阅任务并行

若要支持取消,HttpRequestHttpRequestBuffersCallbackHttpRequestStringCallback 类将使用取消标记。 HttpRequestBuffersCallbackHttpRequestStringCallback 类使用 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
    {
    
    // Implementation of IXMLHTTPRequest2Callback used when partial buffers are needed from the response.
    // When only the complete response is needed, use HttpRequestStringCallback instead.
    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;
    };
    
    };
    
    // Utility class for performing asynchronous HTTP requests.
    // This class only supports one outstanding request at a time.
    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;
    };
    
    };
    
  6. 在 HttpRequest.cpp 中,添加此代码:

    #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;
    
    // Implementation of IXMLHTTPRequest2Callback used when only the complete response is needed.
    // When processing chunks of response data as they are received, use HttpRequestBuffersCallback instead.
    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;
    };
    
    // Copy bytes from the sequential stream into the buffer provided until
    // we reach the end of one or the other.
    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;
    }
    
    // Create a task that completes when data is available, in an exception-safe way.
    task<void> Web::Details::HttpRequestBuffersCallback::CreateDataTask()
    {
        concurrency::critical_section::scoped_lock lock(dataEventLock);
        return create_task(dataEvent, cancellationToken);
    }
    
    HttpRequest::HttpRequest() : responseComplete(true), statusCode(200)
    {
    }
    
    // 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().
    task<wstring> HttpRequest::DownloadAsync(PCWSTR httpMethod, PCWSTR uri, cancellation_token cancellationToken,
        PCWSTR contentType, IStream* postStream, uint64 postStreamSizeToSend)
    {
        // Create an IXMLHTTPRequest2 object.
        ComPtr<IXMLHTTPRequest2> xhr;
        CheckHResult(CoCreateInstance(CLSID_XmlHttpRequest, nullptr, CLSCTX_INPROC, IID_PPV_ARGS(&xhr)));
    
        // Create callback.
        auto stringCallback = Make<HttpRequestStringCallback>(xhr.Get(), cancellationToken);
        CheckHResult(stringCallback ? S_OK : E_OUTOFMEMORY);
    
        auto completionTask = create_task(stringCallback->GetCompletionEvent());
    
        // Create a request.
        CheckHResult(xhr->Open(httpMethod, uri, stringCallback.Get(), nullptr, nullptr, nullptr, nullptr));
    
        if (postStream != nullptr && contentType != nullptr)
        {
            CheckHResult(xhr->SetRequestHeader(L"Content-Type", contentType));
        }
    
        // Send the request.
        CheckHResult(xhr->Send(postStream, postStreamSizeToSend));
    
        // Return a task that completes when the HTTP operation completes. 
        // We pass the callback to the continuation because the lifetime of the 
        // callback must exceed the operation to ensure that cancellation 
        // works correctly.
        return completionTask.then([this, stringCallback](tuple<HRESULT, wstring> resultTuple)
        {
            // If the GET operation failed, throw an Exception.
            CheckHResult(std::get<0>(resultTuple));
    
            statusCode = stringCallback->GetStatusCode();
            reasonPhrase = stringCallback->GetReasonPhrase();
    
            return std::get<1>(resultTuple);
        });
    }
    
    // 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().
    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)));
    }
    
    // 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().
    task<wstring> HttpRequest::PostAsync(Uri^ uri, const wstring& body, cancellation_token cancellationToken)
    {
        int length = 0;
        ComPtr<IStream> postStream;
        CreateMemoryStream(&postStream);
    
        if (body.length() > 0)
        {
            // Get the required buffer size.
            int size = WideCharToMultiByte(CP_UTF8,                         // UTF-8
                                           0,                               // Conversion type
                                           body.c_str(),                    // Unicode string to convert
                                           static_cast<int>(body.length()), // Size
                                           nullptr,                         // Output buffer
                                           0,                               // Output buffer size
                                           nullptr,
                                           nullptr);
            CheckHResult((size != 0) ? S_OK : HRESULT_FROM_WIN32(GetLastError()));
    
            std::unique_ptr<char[]> tempData(new char[size]);
            length = WideCharToMultiByte(CP_UTF8,                         // UTF-8
                                         0,                               // Conversion type
                                         body.c_str(),                    // Unicode string to convert
                                         static_cast<int>(body.length()), // Size
                                         tempData.get(),                  // Output buffer
                                         size,                            // Output buffer 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);
    }
    
    // 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().
    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);
    }
    
    // Send a request but don't return the response.  Instead, let the caller read it with ReadAsync().
    task<void> HttpRequest::SendAsync(const wstring& httpMethod, Uri^ uri, cancellation_token cancellationToken)
    {
        // Create an IXMLHTTPRequest2 object.
        ComPtr<IXMLHTTPRequest2> xhr;
        CheckHResult(CoCreateInstance(CLSID_XmlHttpRequest, nullptr, CLSCTX_INPROC, IID_PPV_ARGS(&xhr)));
    
        // Create callback.
        buffersCallback = Make<Web::Details::HttpRequestBuffersCallback>(xhr.Get(), cancellationToken);
        CheckHResult(buffersCallback ? S_OK : E_OUTOFMEMORY);
    
        ComPtr<IXMLHTTPRequest2Callback> xhrCallback;
        CheckHResult(buffersCallback.As(&xhrCallback));
    
        // Open and send the request.
        CheckHResult(xhr->Open(httpMethod.c_str(),
                               uri->AbsoluteUri->Data(),
                               xhrCallback.Get(),
                               nullptr,
                               nullptr,
                               nullptr,
                               nullptr));
    
        responseComplete = false;
    
        CheckHResult(xhr->Send(nullptr, 0));
    
        // Return a task that completes when the HTTP operation completes.
        // Since buffersCallback holds a reference on the callback, the lifetime of the callback will exceed
        // the operation and ensure that cancellation works correctly.
        return buffersCallback->CreateDataTask().then([this]()
        {
            CheckHResult(buffersCallback->GetError());
    
            statusCode = buffersCallback->GetStatusCode();
            reasonPhrase = buffersCallback->GetReasonPhrase();
        });
    }
    
    // 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.
    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 a task that completes when a read completes. 
        // We pass the callback to the continuation because the lifetime of the 
        // callback must exceed the operation to ensure that cancellation 
        // works correctly.
        return buffersCallback->CreateDataTask().then([this, readBuffer, offsetInBuffer, requestedBytesToRead]()
        {
            CheckHResult(buffersCallback->GetError());
    
            // Get a pointer to the location to copy data into.
            ComPtr<IBufferByteAccess> bufferByteAccess;
            CheckHResult(reinterpret_cast<IUnknown*>(readBuffer)->QueryInterface(IID_PPV_ARGS(&bufferByteAccess)));
            byte* outputBuffer; // Returned internal pointer, do not free this value.
            CheckHResult(bufferByteAccess->Buffer(&outputBuffer));
    
            // Copy bytes from the sequential stream into the buffer provided until
            // we reach the end of one or the other.
            readBuffer->Length = buffersCallback->ReadData(outputBuffer + offsetInBuffer, requestedBytesToRead);
            if (buffersCallback->IsResponseReceived() && (readBuffer->Length < requestedBytesToRead))
            {
                responseComplete = true;
            }
        });
    }
    

在 UWP 应用中使用 HttpRequest 类

本节演示在 UWP 应用中如何使用 HttpRequest 类。 应用程序会提供一个输入框,该输入框定义了一个 URL 资源、用于执行 GET 和 POST 操作的按钮命令和用于取消当前操作的按钮命令。

使用 HttpRequest 类

  1. 在 MainPage.xaml 中,如下所示定义 StackPanel 元素。

    <StackPanel HorizontalAlignment="Left" Width="440"
                Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <TextBox x:Name="InputTextBox" TextWrapping="Wrap" 
                 Text="http://www.fourthcoffee.com/"/>
        <StackPanel Orientation="Horizontal">
            <Button x:Name="GetButton" Content="Get" Background="Green" 
                Click="GetButton_Click"/>
            <Button x:Name="PostButton" Content="Post" Background="Blue" 
                Click="PostButton_Click"/>
            <Button x:Name="CancelButton" Content="Cancel" Background="Red"
                IsEnabled="False" Click="CancelButton_Click"/>
            <ProgressRing x:Name="ResponseProgressRing" />
        </StackPanel>
        <TextBlock x:Name="ResponseTextBlock" TextWrapping="Wrap"/>
    </StackPanel>
    
  2. 在 MainPage.xaml.h 中,添加此 #include 指令:

    #include "HttpRequest.h"
    
  3. 在 MainPage.xaml.h 中,将这些 private 成员变量添加到 MainPage 类中:

    // Produces HTTP requets.
    Web::HttpRequest m_httpRequest;
    // Enables us to cancel the active HTTP request.
    concurrency::cancellation_token_source m_cancelHttpRequestSource;
    
  4. 在 MainPage.xaml.h 中,声明 private 方法 ProcessHttpRequest

    // Displays the result of the provided HTTP request on the UI.
    void ProcessHttpRequest(concurrency::task<std::wstring> httpRequest);
    
  5. 在 MainPage.xaml.cpp 中,添加这些 using 语句:

    using namespace concurrency;
    using namespace std;
    using namespace Web;
    
  6. 在 MainPage.xaml.cpp 中,实现 GetButton_Click 类的 PostButton_ClickCancelButton_ClickMainPage 方法。

    void MainPage::GetButton_Click(Object^ sender, RoutedEventArgs^ e)
    {
        // Create a new cancellation token source for the web request.
        m_cancelHttpRequestSource = cancellation_token_source();
    
        // Set up the GET request parameters.
        auto uri = ref new Uri(InputTextBox->Text);
        auto token = m_cancelHttpRequestSource.get_token();
    
        // Send the request and then update the UI.
        ProcessHttpRequest(m_httpRequest.GetAsync(uri, token));
    }
    
    void MainPage::PostButton_Click(Object^ sender, RoutedEventArgs^ e)
    {
        // Create a new cancellation token source for the web request.
        m_cancelHttpRequestSource = cancellation_token_source();
    
        // Set up the POST request parameters.
        auto uri = ref new Uri(InputTextBox->Text);
        wstring postData(L"This is sample POST data.");
        auto token = m_cancelHttpRequestSource.get_token();
    
        // Send the request and then update the UI.
        ProcessHttpRequest(m_httpRequest.PostAsync(uri, postData, token));
    }
    
    void MainPage::CancelButton_Click(Object^ sender, RoutedEventArgs^ e)
    {
        // Disable the Cancel button.
        // It will be re-enabled during the next web request.
        CancelButton->IsEnabled = false;
    
        // Initiate cancellation.
        m_cancelHttpRequestSource.cancel();
    }
    

    提示

    如果你的应用程序不需要取消支持,请将 concurrency::cancellation_token::none 传递给 HttpRequest::GetAsyncHttpRequest::PostAsync 方法。

  7. 在 MainPage.xaml.cpp 中,实现 MainPage::ProcessHttpRequest 方法。

    // Displays the result of the provided HTTP request on the UI.
    void MainPage::ProcessHttpRequest(task<wstring> httpRequest)
    {
        // Enable only the Cancel button.
        GetButton->IsEnabled = false;
        PostButton->IsEnabled = false;
        CancelButton->IsEnabled = true;
    
        // Clear the previous response and start the progress ring.
        ResponseTextBlock->Text = "";
        ResponseProgressRing->IsActive = true;
    
        // Create a continuation that shows the results on the UI.
        // The UI must be updated on the ASTA thread. 
        // Therefore, schedule the continuation to run on the current context.
        httpRequest.then([this](task<wstring> previousTask)
        {
            try
            {
                //
                // Show the result on the UI.
    
                wstring response = previousTask.get();
                if (m_httpRequest.GetStatusCode() == 200)
                {
                    // The request succeeded. Show the response.
                    ResponseTextBlock->Text = ref new String(response.c_str());
                }
                else
                {
                    // The request failed. Show the status code and reason.
                    wstringstream ss;
                    ss << L"The server returned "
                       << m_httpRequest.GetStatusCode()
                       << L" ("
                       << m_httpRequest.GetReasonPhrase()
                       << L')';
                    ResponseTextBlock->Text = ref new String(ss.str().c_str());
                }
            }
            catch (const task_canceled&)
            {
                // Indicate that the operation was canceled.
                ResponseTextBlock->Text = "The operation was canceled";
            }
            catch (Exception^ e)
            {
                // Indicate that the operation failed.
                ResponseTextBlock->Text = "The operation failed";
    
                // TODO: Handle the error further.
                (void)e;
            }
    
            // Enable the Get and Post buttons.
            GetButton->IsEnabled = true;
            PostButton->IsEnabled = true;
            CancelButton->IsEnabled = false;
    
            // Stop the progress ring.
            ResponseProgressRing->IsActive = false;
    
        }, task_continuation_context::use_current());
    }
    
  8. 在项目属性中,在“链接器”的“输入”下,指定 shcore.libmsxml6.lib

这是正在运行的应用程序:

The running Windows Runtime app.

后续步骤

并发运行时演练

另请参阅

任务并行
PPL 中的取消操作
使用 C++ 进行异步编程
用 C++ 为 UWP 应用创建异步操作
快速入门:使用 XML HTTP 请求 (IXMLHTTPRequest2) 进行连接task 类(并发运行时)
task_completion_event 类