HttpClient

重要 API

使用 HttpClient 和其他 Windows 系統。Web.Http 命名空間 API 可使用 HTTP 2.0 與 HTTP 1.1 協定傳送與接收資訊。

Tip

針對 .NET 6 或更新版本的 WinUI 3 應用程式也可以使用System.Net.Http.HttpClient(.NET HttpClient)。 它支援 IHttpClientFactory、 消去標記以及現代非同步模式。 當你需要 WinRT 專屬功能,例如憑證提示、透過 WinRT 代理管理 Cookie,或與 Windows 網路隔離整合時,請使用此Windows.Web.Http.HttpClient方法。 對於 .NET WinUI 3 應用程式中的直接 HTTP 請求,System.Net.Http.HttpClient通常會比較簡單。

HttpClient 與 Windows.Web.Http 命名空間的概觀

Windows 裡的類別。Web.Http 命名空間及相關 Windows。Web.Http.標頭Windows。Web.Http.Filters 命名空間為 Windows 應用程式提供程式介面,這些應用程式作為 HTTP 用戶端,執行基本的 GET 請求或實作下方列出的更進階 HTTP 功能。

  • 常見動詞的方法(DELETE、GETPUTPOST)。 這些請求都是以非同步操作方式發送。

  • 支援通用的認證設定與模式。

  • 對傳輸層上的安全通訊端層(SSL)詳細資料的存取。

  • 能在進階應用程式中加入自訂篩選功能。

  • 能夠取得、設定及刪除 Cookie。

  • HTTP 請求進度資訊可透過非同步方法取得。

Windows。Web.Http.HttpRequestMessage 類別代表 Windows 發送的 HTTP 請求訊息。Web.Http.HttpClient Windows。Web.Http.HttpResponseMessage 類別代表從 HTTP 請求中接收到的 HTTP 回應訊息。 HTTP 訊息由 IETF 在 RFC 2616 中定義。

Windows。Web.Http 命名空間將 HTTP 內容作為 HTTP 實體主體及標頭(包括 Cookies)來表示。 HTTP 內容可以與 HTTP 請求或 HTTP 回應相關聯。 Windows。Web.Http 命名空間提供多種不同的類別來表示 HTTP 內容。

「透過 HTTP 發送簡單 GET 請求」部分的程式碼片段使用 HttpStringContent 類別,將 HTTP GET 請求的 HTTP 回應表示為字串。

Windows。Web.Http.Headers 命名空間支援建立 HTTP 標頭與 Cookie,這些 Cookie 會作為屬性與 HttpRequestMessageHttpResponseMessage 物件關聯。

透過 HTTP 發送一個簡單的 GET 請求

如本文先前提到的,Windows。Web.Http 命名空間允許 Windows 應用程式發送 GET 請求。 以下程式碼片段示範如何使用 http://www.contoso.com 類別將 GET 要求傳送至 ,以及使用 Windows.Web.Http.HttpResponseMessage 類別讀取 GET 要求的回應。

//Create an HTTP client object
Windows.Web.Http.HttpClient httpClient = new Windows.Web.Http.HttpClient();

//Add a user-agent header to the GET request.
var headers = httpClient.DefaultRequestHeaders;

//The safe way to add a header value is to use the TryParseAdd method and verify the return value is true,
//especially if the header value is coming from user input.
string header = "MyApp/1.0";
if (!headers.UserAgent.TryParseAdd(header))
{
    throw new Exception("Invalid header value: " + header);
}

Uri requestUri = new Uri("https://www.contoso.com");

//Send the GET request asynchronously and retrieve the response as a string.
Windows.Web.Http.HttpResponseMessage httpResponse = new Windows.Web.Http.HttpResponseMessage();
string httpResponseBody = "";

try
{
    //Send the GET request
    httpResponse = await httpClient.GetAsync(requestUri);
    httpResponse.EnsureSuccessStatusCode();
    httpResponseBody = await httpResponse.Content.ReadAsStringAsync();
}
catch (Exception ex)
{
    httpResponseBody = "Error: " + ex.HResult.ToString("X") + " Message: " + ex.Message;
}
// pch.h
#pragma once
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Web.Http.Headers.h>

// main.cpp : Defines the entry point for the console application.
#include "pch.h"
#include <iostream>
using namespace winrt;
using namespace Windows::Foundation;

int main()
{
    init_apartment();

    // Create an HttpClient object.
    Windows::Web::Http::HttpClient httpClient;

    // Add a user-agent header to the GET request.
    auto headers{ httpClient.DefaultRequestHeaders() };

    // The safe way to add a header value is to use the TryParseAdd method, and verify the return value is true.
    // This is especially important if the header value is coming from user input.
    std::wstring header{ L"MyApp/1.0" };
    if (!headers.UserAgent().TryParseAdd(header))
    {
        throw L"Invalid header value: " + header;
    }

    Uri requestUri{ L"https://www.contoso.com" };

    // Send the GET request asynchronously, and retrieve the response as a string.
    Windows::Web::Http::HttpResponseMessage httpResponseMessage;
    std::wstring httpResponseBody;

    try
    {
        // Send the GET request.
        httpResponseMessage = httpClient.GetAsync(requestUri).get();
        httpResponseMessage.EnsureSuccessStatusCode();
        httpResponseBody = httpResponseMessage.Content().ReadAsStringAsync().get();
    }
    catch (winrt::hresult_error const& ex)
    {
        httpResponseBody = ex.message();
    }
    std::wcout << httpResponseBody;
}

透過 HTTP POST 傳送二進位資料

以下 C++/WinRT 程式碼範例說明利用表單資料與 POST 請求,將少量二進位資料以檔案上傳方式上傳至網頁伺服器。 程式碼使用 HttpBufferContent 類別來表示二進位資料,並使用 HttpMultipartFormDataContent 類別來表示多部分表單資料。

Note

呼叫 get (如下方程式碼範例所示)並不適合用於 UI 執行緒。 關於此種情況下正確的技術,請參見 C++/WinRT 的並行與非同步操作

// pch.h
#pragma once
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Security.Cryptography.h>
#include <winrt/Windows.Storage.Streams.h>
#include <winrt/Windows.Web.Http.Headers.h>

// main.cpp : Defines the entry point for the console application.
#include "pch.h"
#include <iostream>
#include <sstream>
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Storage::Streams;

int main()
{
    init_apartment();

    auto buffer{
        Windows::Security::Cryptography::CryptographicBuffer::ConvertStringToBinary(
            L"A sentence of text to encode into binary to serve as sample data.",
            Windows::Security::Cryptography::BinaryStringEncoding::Utf8
        )
    };
    Windows::Web::Http::HttpBufferContent binaryContent{ buffer };
    // You can use the 'image/jpeg' content type to represent any binary data;
    // it's not necessarily an image file.
    binaryContent.Headers().Append(L"Content-Type", L"image/jpeg");

    Windows::Web::Http::Headers::HttpContentDispositionHeaderValue disposition{ L"form-data" };
    binaryContent.Headers().ContentDisposition(disposition);
    // The 'name' directive contains the name of the form field representing the data.
    disposition.Name(L"fileForUpload");
    // Here, the 'filename' directive is used to indicate to the server a file name
    // to use to save the uploaded data.
    disposition.FileName(L"file.dat");

    Windows::Web::Http::HttpMultipartFormDataContent postContent;
    postContent.Add(binaryContent); // Add the binary data content as a part of the form data content.

    // Send the POST request asynchronously, and retrieve the response as a string.
    Windows::Web::Http::HttpResponseMessage httpResponseMessage;
    std::wstring httpResponseBody;

    try
    {
        // Send the POST request.
        Uri requestUri{ L"https://www.contoso.com/post" };
        Windows::Web::Http::HttpClient httpClient;
        httpResponseMessage = httpClient.PostAsync(requestUri, postContent).get();
        httpResponseMessage.EnsureSuccessStatusCode();
        httpResponseBody = httpResponseMessage.Content().ReadAsStringAsync().get();
    }
    catch (winrt::hresult_error const& ex)
    {
        httpResponseBody = ex.message();
    }
    std::wcout << httpResponseBody;
}

要發佈實際二進位檔案的內容(而非上述明確的二進位資料),你會發現使用 HttpStreamContent 物件會比較簡單。 建構一個物件,並將呼叫 StorageFile.OpenReadAsync 所傳回的值作為引數傳遞給其建構函式。 這個方法會回傳你二進位檔案中的資料串流。

另外,如果你上傳的是大型檔案(大於約 10MB),我們建議你使用 Windows 執行階段 背景傳輸 API。

透過 HTTP 發佈 JSON 資料

以下範例會將一些 JSON 上傳到端點,然後寫出回應主體。

using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Windows.Storage.Streams;
using Windows.Web.Http;

private async Task TryPostJsonAsync()
{
    try
    {
        // Construct the HttpClient and Uri. This endpoint is for test purposes only.
        HttpClient httpClient = new HttpClient();
        Uri uri = new Uri("https://www.contoso.com/post");

        // Construct the JSON to post.
        HttpStringContent content = new HttpStringContent(
            "{ \"firstName\": \"Eliot\" }",
            UnicodeEncoding.Utf8,
            "application/json");

        // Post the JSON and wait for a response.
        HttpResponseMessage httpResponseMessage = await httpClient.PostAsync(
            uri,
            content);

        // Make sure the post succeeded, and write out the response.
        httpResponseMessage.EnsureSuccessStatusCode();
        var httpResponseBody = await httpResponseMessage.Content.ReadAsStringAsync();
        Debug.WriteLine(httpResponseBody);
    }
    catch (Exception ex)
    {
        // Write out any exceptions.
        Debug.WriteLine(ex);
    }
}
// pch.h
#pragma once
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Security.Cryptography.h>
#include <winrt/Windows.Storage.Streams.h>
#include <winrt/Windows.Web.Http.Headers.h>

// main.cpp : Defines the entry point for the console application.
#include "pch.h"
#include <iostream>
#include <sstream>
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Storage::Streams;

int main()
{
    init_apartment();

    Windows::Web::Http::HttpResponseMessage httpResponseMessage;
    std::wstring httpResponseBody;

    try
    {
        // Construct the HttpClient and Uri. This endpoint is for test purposes only.
        Windows::Web::Http::HttpClient httpClient;
        Uri requestUri{ L"https://www.contoso.com/post" };

        // Construct the JSON to post.
        Windows::Web::Http::HttpStringContent jsonContent(
            L"{ \"firstName\": \"Eliot\" }",
            UnicodeEncoding::Utf8,
            L"application/json");

        // Post the JSON, and wait for a response.
        httpResponseMessage = httpClient.PostAsync(
            requestUri,
            jsonContent).get();

        // Make sure the post succeeded, and write out the response.
        httpResponseMessage.EnsureSuccessStatusCode();
        httpResponseBody = httpResponseMessage.Content().ReadAsStringAsync().get();
        std::wcout << httpResponseBody.c_str();
    }
    catch (winrt::hresult_error const& ex)
    {
        std::wcout << ex.message().c_str();
    }
}

處理錯誤

HttpClient 打的呼叫可能會有兩種不同的失敗方式,而你處理每種方式都不一樣。

  • 伺服器會回應錯誤狀態碼。 請求完成後,伺服器回傳 4xx 或 5xx 狀態碼(例如 404 未找到或 503 服務不可用)。 這不會引發例外。 呼叫會正常傳回一個 HttpResponseMessage,其 PostAsync 屬性為 GetAsync
  • 請求在收到回應前失敗。 客戶端完全無法完成交換(例如,沒有網路連線、主機名稱無法解析、連線逾時,或 TLS 協商失敗)。 這會拋出例外狀況。

因為回傳的錯誤狀態碼不會拋出,僅檢查例外是不夠的。 檢查回應 捕捉例外。

請檢查回應狀態碼

請閱讀 HttpResponseMessage.IsSuccessStatusCode 來測試伺服器是否回傳成功(2xx)碼。 若要根據特定狀態碼進行分支處理,請讀取 HttpResponseMessage.StatusCodeHttpStatusCode 值)和 ReasonPhrase

呼叫 EnsureSuccessStatusCode,如前述範例,是一個捷徑,當狀態碼不是成功碼時會拋出例外,讓你能在同一區塊中處理兩種失敗模式 catch 。 只有當你希望非成功代碼被視為錯誤時才會呼叫它。 如果你想看 4xx 或 5xx 回覆的正文,請直接查看 IsSuccessStatusCode

Windows.Web.Http.HttpClient httpClient = new Windows.Web.Http.HttpClient();
Uri requestUri = new Uri("https://www.contoso.com");

Windows.Web.Http.HttpResponseMessage response = await httpClient.GetAsync(requestUri);

if (response.IsSuccessStatusCode)
{
    string body = await response.Content.ReadAsStringAsync();
    // Process the successful response.
}
else
{
    // The server responded, but with an error status code.
    System.Diagnostics.Debug.WriteLine($"Request failed: {(int)response.StatusCode} {response.StatusCode} ({response.ReasonPhrase})");
}

分類網路例外

當請求在收到回應前擲回例外時(例如無法解析名稱,或連線失敗或逾時),例外的 HResult 會指出底層的網路錯誤。 傳給 Windows。Web.WebError.GetStatus 以取得描述原因的 WebErrorStatus 值(例如 HostNameNotResolvedCannotConnectTimeoutConnectionReset)。 用它來決定是通知使用者、退回或重試。

WebError.GetStatus 不適用於 HTTP 錯誤回應(4xx 或 5xx 狀態碼),因為伺服器有回應。 請檢查 HttpResponseMessage.IsSuccessStatusCodeHttpResponseMessage.StatusCode 來處理這些,而不是呼叫 EnsureSuccessStatusCode

Windows.Web.Http.HttpClient httpClient = new Windows.Web.Http.HttpClient();
Uri requestUri = new Uri("https://www.contoso.com");

try
{
    Windows.Web.Http.HttpResponseMessage response = await httpClient.GetAsync(requestUri);
    if (!response.IsSuccessStatusCode)
    {
        // Handle the non-success HTTP status code (for example, 404 Not Found or 503 Service Unavailable).
        System.Diagnostics.Debug.WriteLine($"HTTP error: {(int)response.StatusCode} {response.StatusCode}");
        return;
    }
    string body = await response.Content.ReadAsStringAsync();
    // Process the successful response.
}
catch (Exception ex)
{
    Windows.Web.WebErrorStatus status = Windows.Web.WebError.GetStatus(ex.HResult);

    switch (status)
    {
        case Windows.Web.WebErrorStatus.HostNameNotResolved:
        case Windows.Web.WebErrorStatus.CannotConnect:
        case Windows.Web.WebErrorStatus.Timeout:
        case Windows.Web.WebErrorStatus.ConnectionReset:
            // A transient connectivity problem. Retrying with backoff may succeed.
            System.Diagnostics.Debug.WriteLine($"Network error: {status}.");
            break;
        case Windows.Web.WebErrorStatus.Unknown:
            // GetStatus couldn't map the HRESULT to a WebErrorStatus value.
            System.Diagnostics.Debug.WriteLine($"Unmapped error. HRESULT: 0x{ex.HResult:X8} {ex.Message}");
            break;
        default:
            System.Diagnostics.Debug.WriteLine($"Web error: {status}");
            break;
    }
}

同樣的模式也適用於 C++/WinRT,將 winrt::hresult_error::code 作為 GetStatus 的輸入。

// #include <winrt/Windows.Web.h>
try
{
    auto response{ httpClient.GetAsync(requestUri).get() };
    if (!response.IsSuccessStatusCode())
    {
        // Handle the non-success HTTP status code (for example, 404 Not Found or 503 Service Unavailable).
    }
    else
    {
        auto body{ response.Content().ReadAsStringAsync().get() };
        // Process the successful response.
    }
}
catch (winrt::hresult_error const& ex)
{
    Windows::Web::WebErrorStatus status{ Windows::Web::WebError::GetStatus(ex.code()) };

    if (status == Windows::Web::WebErrorStatus::HostNameNotResolved ||
        status == Windows::Web::WebErrorStatus::CannotConnect ||
        status == Windows::Web::WebErrorStatus::Timeout ||
        status == Windows::Web::WebErrorStatus::ConnectionReset)
    {
        // A transient connectivity problem. Retrying with backoff may succeed.
    }
    else
    {
        // Inspect status, or fall back to ex.code() and ex.message().
    }
}

重試暫時性故障

HttpClient 不會幫你重試失敗的請求。 對於暫時性失敗(如上述連線錯誤,或伺服器代碼如 429 Too Many Requests、503 Service Unavailable、504 閘道逾時),請自行使用指數退讓(Exponential backoff)重試請求,並在伺服器發送回應標頭時尊重 Retry-After 該回應標頭。 限制嘗試次數,且不要重試非暫時性錯誤,例如 400 錯誤請求或 404 未找到。

Windows.Web.Http 中的例外

當無效的統一資源識別項 (URI) 字串傳遞至 Windows.Foundation.Uri 物件的建構函式時,會擲回例外狀況。

.NET:Windows。Foundation.Uri 型態在 C# 和 VB 中以 System.Uri 形式出現。

在 C# 和 Visual Basic 中,可以透過使用 .NET 4.5 中的 System.Uri 類別以及 System.Uri.TryCreate 方法來測試使用者在建構 URI 前收到的字串來避免此錯誤。

在 C++ 中,沒有方法可以嘗試解析字串成 URI。 如果應用程式從使用者取得 Windows.Foundation.Uri 的輸入,則建構函式應置於 try/catch 區塊中。 若拋出例外,應用程式可通知使用者並請求更換主機名稱。

Windows。Web.Http 缺乏便利功能。 因此,使用 HttpClient 及其他類別的應用程式必須使用 HRESULT 值。

在使用 C++/WinRT 的應用程式中, winrt::hresult_error 結構代表應用程式執行時提出的例外。 winrt:hresult_error:code 函式會回傳指派給特定例外的 HRESULTwinrt:hresult_error:message 函式會回傳與 HRESULT 值相關聯的系統提供的字串。 更多資訊請參閱 C++/WinRT 的錯誤處理

可能的 HRESULT 值列於 Winerror.h 標頭檔案中。 你的應用程式可以根據異常原因,針對特定的 HRESULT 值進行篩選,修改應用程式行為。

在使用 C#、VB.NET .NET Framework 4.5 的應用程式中,System.Exception 代表應用程式執行中發生異常時的錯誤。 System.Exception.HResult 屬性會回傳指派給該特定例外的 HRESULTSystem.Exception.Message 屬性會回傳描述該例外的訊息。

C++/CX 已被 C++/WinRT 取代。 但在使用 C++/CX 的應用程式中, Platform::Exception 代表應用程式執行時發生異常時的錯誤。 Platform::Exception::HResult 屬性會傳回指派給該特定例外的 HRESULTPlatform::Exception::Message 屬性會回傳系統提供的字串,該字串與 HRESULT 值相關聯。

對於大多數參數驗證錯誤,回傳的 HRESULTE_INVALIDARG。 對於某些非法方法呼叫,回傳的 HRESULTE_ILLEGAL_METHOD_CALL