연습: 작업 및 XML HTTP 요청을 사용하여 연결

이 예제에서는 태스크와 함께 IXMLHTTPRequest2 및 IXMLHTTPRequest2Callback 인터페이스를 사용하여 UWP(유니버설 Windows 플랫폼) 앱의 웹 서비스에 HTTP GET 및 POST 요청을 보내는 방법을 보여 줍니다. IXMLHTTPRequest2를 작업과 함께 결합하여 다른 작업을 사용하여 구성되는 코드를 작성할 수 있습니다. 예를 들어 일련의 작업 중 일부로 다운로드 작업을 사용할 수 있습니다. 다운로드 작업은 작업이 취소되는 경우에도 응답할 수 있습니다.

C++ REST SDK를 사용하여 C++ 앱 또는 데스크톱 C++ 앱을 사용하여 UWP 앱에서 HTTP 요청을 수행할 수도 있습니다. 자세한 내용은 C++ REST SDK(코드 이름 "Casablanca")를 참조하세요.

작업에 대한 자세한 내용은 작업 병렬 처리를 참조하세요. UWP 앱 에서 작업을 사용하는 방법에 대한 자세한 내용은 C++ 의 비동기 프로그래밍 및 UWP 앱용 C++에서 비동기 작업 만들기를 참조하세요.

이 문서에서는 먼저 HttpRequest 및 해당 지원 클래스를 만드는 방법을 보여 줍니다. 그런 다음 C++ 및 XAML을 사용하는 UWP 앱에서 이 클래스를 사용하는 방법을 보여줍니다.

작업을 사용 IXMLHTTPRequest2 하지만 사용하지 않는 예제는 빠른 시작: XML HTTP 요청(IXMLHTTPRequest2)을 사용하여 커넥트를 참조하세요.

IXMLHTTPRequest2 UWP IXMLHTTPRequest2Callback 앱에서 사용하는 것이 좋습니다. 데스크톱 응용 프로그램에서 사용할 수 있도록 이 예제를 조정할 수도 있습니다.

필수 조건

UWP 지원은 Visual Studio 2017 이상에서 선택 사항입니다. 설치하려면 Windows 시작 메뉴 Visual Studio 설치 관리자 열고 사용 중인 Visual Studio 버전을 선택합니다. 수정 단추를 클릭하고 UWP 개발 타일이 검사 있는지 확인합니다. 선택적 구성 요소에서 C++ UWP 도구가 검사 있는지 확인합니다. Visual Studio 2017용 v141 또는 Visual Studio 2019용 v142를 사용합니다.

HttpRequest, HttpRequestBuffersCallback 및 HttpRequestStringCallback 클래스 정의

IXMLHTTPRequest2 인터페이스를 사용하여 HTTP를 통한 웹 요청을 만드는 경우 서버 응답을 받고 다른 이벤트에 대응하는 IXMLHTTPRequest2Callback 인터페이스를 구현합니다. 이 예제에서는 웹 요청을 만들기 위해 HttpRequest 클래스를 정의하고, 응답을 처리하기 위해 HttpRequestBuffersCallbackHttpRequestStringCallback 클래스를 정의합니다. HttpRequestBuffersCallbackHttpRequestStringCallback 클래스는 HttpRequest 클래스를 지원합니다. 애플리케이션 코드에서 HttpRequest 클래스만 사용하여 작업할 수 있습니다.

GetAsync 클래스의 PostAsyncHttpRequest 메서드는 각각 HTTP GET 및 POST 작업을 시작할 수 있도록 합니다. 이러한 메서드는 HttpRequestStringCallback 클래스를 사용하여 서버 응답을 문자열로 읽습니다. SendAsyncReadAsync 메서드를 사용하면 큰 콘텐츠를 청크로 스트리밍할 수 있습니다. 이러한 메서드는 각각 동시성::task를 반환하여 작업을 나타냅니다. 및 메서드는 GetAsync 서버의 응답을 나타내는 값을 wstring 생성 task<std::wstring> 합니다.PostAsync SendAsyncReadAsync 메서드는 task<void> 값을 생성합니다. 이러한 작업은 보내기 및 읽기 작업이 끝날 때 완료됩니다.

인터페이스가 IXMLHTTPRequest2 비동기적으로 작동하기 때문에 이 예제에서는 동시성::task_completion_event 사용하여 콜백 개체가 다운로드 작업을 완료하거나 취소한 후 완료되는 작업을 만듭니다. HttpRequest 클래스는 최종 결과를 설정하기 위해 이 작업에서 작업 기반 연속 작업을 만듭니다. HttpRequest 클래스는 작업 기반 연속 작업을 사용하여 이전 작업이 오류를 생성하거나 취소되는 경우에도 연속 작업이 실행되도록 합니다. 작업 기반 연속 작업에 대한 자세한 내용은 작업 병렬 처리를 참조 하세요.

취소를 지원하기 위해 HttpRequest, HttpRequestBuffersCallbackHttpRequestStringCallback 클래스는 취소 토큰을 사용합니다. 및 클래스는 HttpRequestBuffersCallback 동시성::cancellation_token::register_callback 메서드를 사용하여 작업 완료 이벤트가 취소에 응답할 수 있도록 HttpRequestStringCallback 합니다. 이 취소 콜백은 다운로드를 중단합니다. 취소에 대한 자세한 내용은 취소를 참조하세요.

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_Click, CancelButton_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();
    }
    

    앱에 취소 지원이 필요하지 않은 경우 동시성::cancellation_token::none을 메서드에 HttpRequest::PostAsyncHttpRequest::GetAsync 전달합니다.

  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++의 비동기 프로그래밍
UWP 앱용 C++ 비동기 작업 만들기
빠른 시작: XML HTTP 요청을 사용하여 커넥트(IXMLHTTPRequest2) 작업 클래스(동시성 런타임)
task_completion_event 클래스