HttpClient クラスを使用して HTTP 要求を行う

この記事では、HttpClient クラスを使用して HTTP 要求を行い、応答を処理する方法について説明します。

重要

HTTP 要求の例はすべて、次のいずれかの URL を対象とします。

通常、HTTP エンドポイントは、JavaScript Object Notation (JSON) データを返しますが、必ずしもそうであるとは限りません。 便宜上、オプションの System.Net.Http.Json NuGet パッケージには、System.Text.Json を使用して自動シリアル化および逆シリアル化を実行するHttpClientHttpContent のための拡張メソッドがいくつか用意されています。 以降の例では、これらの拡張機能を利用できる場所に注意を促します。

ヒント

この記事のソース コードはすべて、GitHub: .NET Docs リポジトリで入手できます。

HttpClient を作成します

以下の例の多くでは、同じ HttpClient インスタンスが再利用されるため、1 回構成するだけで済みます。 HttpClient を作成するには、HttpClient クラス コンストラクターを使用します。 詳細については、「HttpClient の使用に関するガイドライン」を参照してください。

// HttpClient lifecycle management best practices:
// https://learn.microsoft.com/dotnet/fundamentals/networking/http/httpclient-guidelines#recommended-use
private static HttpClient sharedClient = new()
{
    BaseAddress = new Uri("https://jsonplaceholder.typicode.com"),
};

上記のコードでは次の操作が行われます。

  • 新しい HttpClient インスタンスを static 変数として作成します。 ガイドラインに従って、アプリケーションのライフサイクル中に HttpClient インスタンスを再利用することをお勧めします。
  • HttpClient.BaseAddress"https://jsonplaceholder.typicode.com" に設定します。

この HttpClient インスタンスでは、後続の要求を行うときにベース アドレスが使用されます。 他の構成を適用するには、以下を検討してください。

ヒント

または、ファクトリパターン アプローチを使用して、任意の数のクライアントを構成し、それらを依存関係挿入サービスとして使用できるようにする HttpClient インスタンスを作成できます。 詳細については、「.NET を使用した HTTP クライアント ファクトリ」を参照してください。

HTTP 要求を行う

HTTP 要求を行うには、次のいずれかの API を呼び出します。

HTTP メソッド API
GET HttpClient.GetAsync
GET HttpClient.GetByteArrayAsync
GET HttpClient.GetStreamAsync
GET HttpClient.GetStringAsync
POST HttpClient.PostAsync
PUT HttpClient.PutAsync
PATCH HttpClient.PatchAsync
DELETE HttpClient.DeleteAsync
USER SPECIFIED HttpClient.SendAsync

USER SPECIFIED 要求は、SendAsync メソッドでは任意の有効な HttpMethodが受け入れられることを示します。

警告

HTTP 要求を行うことは、ネットワーク I/O バウンドの作業と見なされます。 同期 HttpClient.Send メソッドはありますが、使用しない正当な理由がない限り、代わりに非同期 API を使用することをお勧めします。

HTTP コンテンツ

HttpContent 型は、HTTP エンティティ本文および対応するコンテンツ ヘッダーを表すために使用されます。 本文を必要とする HTTP メソッド (または要求メソッド) POSTPUTPATCH の場合、HttpContent クラスを使用して、要求の本文を指定します。 ほとんどの例は、JSON ペイロードを使用して StringContent サブクラスを準備する方法を示していますが、さまざまなコンテンツ (MIME) の種類用の他のサブクラスが存在します。

  • ByteArrayContent: バイト配列に基づく HTTP コンテンツを提供します。
  • FormUrlEncodedContent: MIME の種類 "application/x-www-form-urlencoded" を使用してエンコードされた名前/値タプルの HTTP コンテンツを提供します。
  • JsonContent: JSON に基づく HTTP コンテンツを提供します。
  • MultipartContent: MIME の種類 "multipart/*" の指定を使用してシリアル化される HttpContent オブジェクトのコレクションを提供します。
  • MultipartFormDataContent: MIME の種類 "multipart/form-data" を使用してエンコードされたコンテンツのコンテナーを提供します。
  • ReadOnlyMemoryContent: ReadOnlyMemory<T> に基づく HTTP コンテンツを提供します。
  • StreamContent: ストリームに基づく HTTP コンテンツを提供します。
  • StringContent: 文字列に基づく HTTP コンテンツを提供します。

HttpContent クラスは、HttpResponseMessage.Content プロパティでアクセス可能な HttpResponseMessage の応答本文を表すためにも使用されます。

HTTP Get

GET 要求では、本文を送信することはできません。これは、(メソッド名が示すように) リソースからデータを取得 (または入手) するために使用されます。 HttpClient と URI を指定して HTTP GET 要求を行うには、HttpClient.GetAsync メソッドを使用します。

static async Task GetAsync(HttpClient httpClient)
{
    using HttpResponseMessage response = await httpClient.GetAsync("todos/3");
    
    response.EnsureSuccessStatusCode()
        .WriteRequestToConsole();
    
    var jsonResponse = await response.Content.ReadAsStringAsync();
    Console.WriteLine($"{jsonResponse}\n");

    // Expected output:
    //   GET https://jsonplaceholder.typicode.com/todos/3 HTTP/1.1
    //   {
    //     "userId": 1,
    //     "id": 3,
    //     "title": "fugiat veniam minus",
    //     "completed": false
    //   }
}

上記のコードでは次の操作が行われます。

  • "https://jsonplaceholder.typicode.com/todos/3" に対して GET 要求を行います。
  • 応答が成功したことを確認します。
  • 要求の詳細をコンソールに書き込みます。
  • 応答の本体を文字列として読み取ります。
  • JSON の応答本文をコンソールに書き込みます。

WriteRequestToConsole は、フレームワークの一部ではないカスタム拡張メソッドですが、実装方法に興味がある場合は、次の C# コードを検討してください。

static class HttpResponseMessageExtensions
{
    internal static void WriteRequestToConsole(this HttpResponseMessage response)
    {
        if (response is null)
        {
            return;
        }

        var request = response.RequestMessage;
        Console.Write($"{request?.Method} ");
        Console.Write($"{request?.RequestUri} ");
        Console.WriteLine($"HTTP/{request?.Version}");        
    }
}

この機能は、要求の詳細を次の形式でコンソールに書き込むために使用されます。

<HTTP Request Method> <Request URI> <HTTP/Version>

たとえば、https://jsonplaceholder.typicode.com/todos/3 に対する GET 要求は次のメッセージを出力します。

GET https://jsonplaceholder.typicode.com/todos/3 HTTP/1.1

JSON からの HTTP Get

https://jsonplaceholder.typicode.com/todos エンドポイントは、"todo" オブジェクトの JSON 配列を返します。 その JSON 構造は、次のようになります。

[
  {
    "userId": 1,
    "id": 1,
    "title": "example title",
    "completed": false
  },
  {
    "userId": 1,
    "id": 2,
    "title": "another example title",
    "completed": true
  },
]

C# Todo オブジェクトは、次のように定義されます。

public record class Todo(
    int? UserId = null,
    int? Id = null,
    string? Title = null,
    bool? Completed = null);

これは、省略可能な IdTitleCompletedUserId プロパティを持つ record class 型です。 record 型の詳細については、「C# のレコード型の概要」を参照してください。 厳密に型指定された C# オブジェクトにGET 要求を自動的に逆シリアル化するには、System.Net.Http.Json NuGet パッケージの一部である GetFromJsonAsync 拡張メソッドを使用します。

static async Task GetFromJsonAsync(HttpClient httpClient)
{
    var todos = await httpClient.GetFromJsonAsync<List<Todo>>(
        "todos?userId=1&completed=false");

    Console.WriteLine("GET https://jsonplaceholder.typicode.com/todos?userId=1&completed=false HTTP/1.1");
    todos?.ForEach(Console.WriteLine);
    Console.WriteLine();

    // Expected output:
    //   GET https://jsonplaceholder.typicode.com/todos?userId=1&completed=false HTTP/1.1
    //   Todo { UserId = 1, Id = 1, Title = delectus aut autem, Completed = False }
    //   Todo { UserId = 1, Id = 2, Title = quis ut nam facilis et officia qui, Completed = False }
    //   Todo { UserId = 1, Id = 3, Title = fugiat veniam minus, Completed = False }
    //   Todo { UserId = 1, Id = 5, Title = laboriosam mollitia et enim quasi adipisci quia provident illum, Completed = False }
    //   Todo { UserId = 1, Id = 6, Title = qui ullam ratione quibusdam voluptatem quia omnis, Completed = False }
    //   Todo { UserId = 1, Id = 7, Title = illo expedita consequatur quia in, Completed = False }
    //   Todo { UserId = 1, Id = 9, Title = molestiae perspiciatis ipsa, Completed = False }
    //   Todo { UserId = 1, Id = 13, Title = et doloremque nulla, Completed = False }
    //   Todo { UserId = 1, Id = 18, Title = dolorum est consequatur ea mollitia in culpa, Completed = False }
}

上のコードでは以下の操作が行われます。

  • GET 要求は、"https://jsonplaceholder.typicode.com/todos?userId=1&completed=false" に対して行われます。
    • クエリ文字列は、要求のフィルター条件を表します。
  • 成功すると、応答は、List<Todo> に自動的に逆シリアル化されます。
  • 要求の詳細は、各 Todo オブジェクトと共にコンソールに書き込まれます。

HTTP Post

POST 要求は、処理のためにデータをサーバーに送信します。 要求の Content-Type ヘッダーは、本文で送信されている MIME の種類を示します。 HttpClientUri を指定して HTTP POST 要求を行うには、HttpClient.PostAsync メソッドを使用します。

static async Task PostAsync(HttpClient httpClient)
{
    using StringContent jsonContent = new(
        JsonSerializer.Serialize(new
        {
            userId = 77,
            id = 1,
            title = "write code sample",
            completed = false
        }),
        Encoding.UTF8,
        "application/json");

    using HttpResponseMessage response = await httpClient.PostAsync(
        "todos",
        jsonContent);

    response.EnsureSuccessStatusCode()
        .WriteRequestToConsole();
    
    var jsonResponse = await response.Content.ReadAsStringAsync();
    Console.WriteLine($"{jsonResponse}\n");

    // Expected output:
    //   POST https://jsonplaceholder.typicode.com/todos HTTP/1.1
    //   {
    //     "userId": 77,
    //     "id": 201,
    //     "title": "write code sample",
    //     "completed": false
    //   }
}

上記のコードでは次の操作が行われます。

  • 要求の JSON 本文 (MIME の種類 "application/json") を使用して StringContent インスタンスを準備します。
  • "https://jsonplaceholder.typicode.com/todos" に対して POST 要求を行います。
  • 応答が成功したことを確認し、要求の詳細をコンソールに書き込みます。
  • 応答本文を文字列としてコンソールに書き込みます。

JSON としての HTTP Post

POST 要求引数を自動的にシリアル化し、厳密に型指定された C# オブジェクトに応答を逆シリアル化するには、System.Net.Http.Json NuGet パッケージの一部である PostAsJsonAsync 拡張メソッドを使用します。

static async Task PostAsJsonAsync(HttpClient httpClient)
{
    using HttpResponseMessage response = await httpClient.PostAsJsonAsync(
        "todos", 
        new Todo(UserId: 9, Id: 99, Title: "Show extensions", Completed: false));

    response.EnsureSuccessStatusCode()
        .WriteRequestToConsole();

    var todo = await response.Content.ReadFromJsonAsync<Todo>();
    Console.WriteLine($"{todo}\n");

    // Expected output:
    //   POST https://jsonplaceholder.typicode.com/todos HTTP/1.1
    //   Todo { UserId = 9, Id = 201, Title = Show extensions, Completed = False }
}

上記のコードでは次の操作が行われます。

  • Todo インスタンスを JSON としてシリアル化し、"https://jsonplaceholder.typicode.com/todos" に対して POST 要求を行います。
  • 応答が成功したことを確認し、要求の詳細をコンソールに書き込みます。
  • 応答本文を Todo インスタンスに逆シリアル化し、Todo をコンソールに書き込みます。

HTTP Put

PUT 要求メソッドは、既存のリソースを置き換えるか、要求本文ペイロードを使用して新しいリソースを作成します。 HttpClient と URI を指定して HTTP PUT 要求を行うには、HttpClient.PutAsync メソッドを使用します。

static async Task PutAsync(HttpClient httpClient)
{
    using StringContent jsonContent = new(
        JsonSerializer.Serialize(new 
        {
            userId = 1,
            id = 1,
            title = "foo bar",
            completed = false
        }),
        Encoding.UTF8,
        "application/json");

    using HttpResponseMessage response = await httpClient.PutAsync(
        "todos/1",
        jsonContent);

    response.EnsureSuccessStatusCode()
        .WriteRequestToConsole();
    
    var jsonResponse = await response.Content.ReadAsStringAsync();
    Console.WriteLine($"{jsonResponse}\n");

    // Expected output:
    //   PUT https://jsonplaceholder.typicode.com/todos/1 HTTP/1.1
    //   {
    //     "userId": 1,
    //     "id": 1,
    //     "title": "foo bar",
    //     "completed": false
    //   }
}

上記のコードでは次の操作が行われます。

  • 要求の JSON 本文 (MIME の種類 "application/json") を使用して StringContent インスタンスを準備します。
  • "https://jsonplaceholder.typicode.com/todos/1" に対して PUT 要求を行います。
  • 応答が成功したことを確認し、要求の詳細と JSON の応答本文をコンソールに書き込みます。

JSON としての HTTP Put

PUT 要求引数を自動的にシリアル化し、厳密に型指定された C# オブジェクトに応答を逆シリアル化するには、System.Net.Http.Json NuGet パッケージの一部である PutAsJsonAsync 拡張メソッドを使用します。

static async Task PutAsJsonAsync(HttpClient httpClient)
{
    using HttpResponseMessage response = await httpClient.PutAsJsonAsync(
        "todos/5",
        new Todo(Title: "partially update todo", Completed: true));

    response.EnsureSuccessStatusCode()
        .WriteRequestToConsole();

    var todo = await response.Content.ReadFromJsonAsync<Todo>();
    Console.WriteLine($"{todo}\n");

    // Expected output:
    //   PUT https://jsonplaceholder.typicode.com/todos/5 HTTP/1.1
    //   Todo { UserId = , Id = 5, Title = partially update todo, Completed = True }
}

上記のコードでは次の操作が行われます。

  • Todo インスタンスを JSON としてシリアル化し、"https://jsonplaceholder.typicode.com/todos/5" に対して PUT 要求を行います。
  • 応答が成功したことを確認し、要求の詳細をコンソールに書き込みます。
  • 応答本文を Todo インスタンスに逆シリアル化し、Todo をコンソールに書き込みます。

HTTP Patch

PATCH 要求は、既存のリソースに対する部分的な更新です。 これによって新しいリソースは作成されません。また、既存のリソースを置き換えることを目的としたものでもなく、 リソースの一部のみを更新します。 HttpClient と URI を指定して HTTP PATCH 要求を行うには、HttpClient.PatchAsync メソッドを使用します。

static async Task PatchAsync(HttpClient httpClient)
{
    using StringContent jsonContent = new(
        JsonSerializer.Serialize(new
        {
            completed = true
        }),
        Encoding.UTF8,
        "application/json");

    using HttpResponseMessage response = await httpClient.PatchAsync(
        "todos/1",
        jsonContent);

    response.EnsureSuccessStatusCode()
        .WriteRequestToConsole();

    var jsonResponse = await response.Content.ReadAsStringAsync();
    Console.WriteLine($"{jsonResponse}\n");

    // Expected output
    //   PATCH https://jsonplaceholder.typicode.com/todos/1 HTTP/1.1
    //   {
    //     "userId": 1,
    //     "id": 1,
    //     "title": "delectus aut autem",
    //     "completed": true
    //   }
}

上記のコードでは次の操作が行われます。

  • 要求の JSON 本文 (MIME の種類 "application/json") を使用して StringContent インスタンスを準備します。
  • "https://jsonplaceholder.typicode.com/todos/1" に対して PATCH 要求を行います。
  • 応答が成功したことを確認し、要求の詳細と JSON の応答本文をコンソールに書き込みます。

System.Net.Http.Json NuGet パッケージには、PATCH 要求用の拡張メソッドはありません。

HTTP Delete

DELETE 要求は、既存のリソースを削除します。 DELETE 要求は "べき等" ですが、"安全" ではありません。つまり、同じリソースに対する複数の DELETE 要求によって同じ結果が生成されますが、要求はリソースの状態に影響しません。 HttpClient と URI を指定して HTTP DELETE 要求を行うには、HttpClient.DeleteAsync メソッドを使用します。

static async Task DeleteAsync(HttpClient httpClient)
{
    using HttpResponseMessage response = await httpClient.DeleteAsync("todos/1");
    
    response.EnsureSuccessStatusCode()
        .WriteRequestToConsole();

    var jsonResponse = await response.Content.ReadAsStringAsync();
    Console.WriteLine($"{jsonResponse}\n");

    // Expected output
    //   DELETE https://jsonplaceholder.typicode.com/todos/1 HTTP/1.1
    //   {}
}

上記のコードでは次の操作が行われます。

  • "https://jsonplaceholder.typicode.com/todos/1" に対して DELETE 要求を行います。
  • 応答が成功したことを確認し、要求の詳細をコンソールに書き込みます。

ヒント

(PUT 要求と同様に) DELETE 要求に対する応答には、本文が含まれる場合と含まれない場合があります。

HTTP Head

HEAD 要求は、GET 要求と似ています。 これは、リソースを返す代わりに、リソースに関連付けられているヘッダーを返します。 HEAD 要求に対する応答は本文を返しません。 HttpClient と URI を指定して HTTP HEAD 要求を行うには、HttpMethodHttpMethod.Head に設定して HttpClient.SendAsync メソッドを使用します。

static async Task HeadAsync(HttpClient httpClient)
{
    using HttpRequestMessage request = new(
        HttpMethod.Head, 
        "https://www.example.com");

    using HttpResponseMessage response = await httpClient.SendAsync(request);

    response.EnsureSuccessStatusCode()
        .WriteRequestToConsole();

    foreach (var header in response.Headers)
    {
        Console.WriteLine($"{header.Key}: {string.Join(", ", header.Value)}");
    }
    Console.WriteLine();

    // Expected output:
    //   HEAD https://www.example.com/ HTTP/1.1
    //   Accept-Ranges: bytes
    //   Age: 550374
    //   Cache-Control: max-age=604800
    //   Date: Wed, 10 Aug 2022 17:24:55 GMT
    //   ETag: "3147526947"
    //   Server: ECS, (cha / 80E2)
    //   X-Cache: HIT
}

上記のコードでは次の操作が行われます。

  • "https://www.example.com/" に対して HEAD 要求を行います。
  • 応答が成功したことを確認し、要求の詳細をコンソールに書き込みます。
  • すべての応答ヘッダーについて反復し、それぞれをコンソールに書き込みます。

HTTP Options

OPTIONS 要求は、サーバーまたはエンドポイントでサポートされる HTTP メソッドを識別するために使用されます。 HttpClient と URI を指定して HTTP OPTIONS 要求を行うには、HttpMethodHttpMethod.Options に設定して HttpClient.SendAsync メソッドを使用します。

static async Task OptionsAsync(HttpClient httpClient)
{
    using HttpRequestMessage request = new(
        HttpMethod.Options, 
        "https://www.example.com");

    using HttpResponseMessage response = await httpClient.SendAsync(request);

    response.EnsureSuccessStatusCode()
        .WriteRequestToConsole();

    foreach (var header in response.Content.Headers)
    {
        Console.WriteLine($"{header.Key}: {string.Join(", ", header.Value)}");
    }
    Console.WriteLine();

    // Expected output
    //   OPTIONS https://www.example.com/ HTTP/1.1
    //   Allow: OPTIONS, GET, HEAD, POST
    //   Content-Type: text/html; charset=utf-8
    //   Expires: Wed, 17 Aug 2022 17:28:42 GMT
    //   Content-Length: 0
}

上記のコードでは次の操作が行われます。

  • HTTP 要求 OPTIONS"https://www.example.com/" に送信します。
  • 応答が成功したことを確認し、要求の詳細をコンソールに書き込みます。
  • すべての応答コンテンツ ヘッダーについて反復し、それぞれをコンソールに書き込みます。

HTTP Trace

TRACE 要求は、要求メッセージのアプリケーションレベルのループバックを提供するため、デバッグに役立ちます。 HTTP TRACE 要求を行うには、HttpMethod.Trace を使用して HttpRequestMessage を作成します。

using HttpRequestMessage request = new(
    HttpMethod.Trace, 
    "{ValidRequestUri}");

注意事項

HTTP メソッド TRACE は、すべての HTTP サーバーによってサポートされているわけではありません。 これは、不正に使用すると、セキュリティ上の脆弱性を暴露する可能性があります。 詳細については、「Open Web Application Security Project (OWASP): クロス サイト トレーシング」を参照してください。

HTTP 応答を処理する

HTTP 応答を処理するときは常に、HttpResponseMessage 型を操作します。 応答の有効性を評価する場合、いくつかのメンバーが使用されます。 HTTP 状態コードは、HttpResponseMessage.StatusCode プロパティを介して使用できます。 クライアント インスタンスを指定して要求を送信したとしましょう。

using HttpResponseMessage response = await httpClient.SendAsync(request);

responseOK (HTTP 状態コード 200) であることを確認するには、次の例に示すように評価することができます。

if (response is { StatusCode: HttpStatusCode.OK })
{
    // Omitted for brevity...
}

成功した応答を表す他の HTTP 状態コードがあります。たとえば、CREATED (HTTP 状態コード 201), ACCEPTED (HTTP 状態コード 202), NO CONTENT (HTTP 状態コード 204)、RESET CONTENT (HTTP 状態コード 205) などです。 HttpResponseMessage.IsSuccessStatusCode プロパティを使用して、これらのコードを評価することもできます。これにより、応答状態コードは、200 から 299 の範囲内であることが保証されます。

if (response.IsSuccessStatusCode)
{
    // Omitted for brevity...
}

フレームワークで HttpRequestException をスローする必要がある場合、HttpResponseMessage.EnsureSuccessStatusCode() メソッドを呼び出すことができます。

response.EnsureSuccessStatusCode();

このコードは、応答状態コードが 200 から 299 の範囲内にない場合、HttpRequestException をスローします。

有効なコンテンツの HTTP 応答

有効な応答では、Content プロパティを使用して応答本文にアクセスできます。 本文は HttpContent インスタンスとして使用でき、ストリーム、バイト配列、または文字列として本文にアクセスするために使用できます。

await using Stream responseStream =
    await response.Content.ReadAsStreamAsync();

前のコードでは、responseStream を使用して、応答本文を読み取ることができます。

byte[] responseByteArray = await response.Content.ReadAsByteArrayAsync();

前のコードでは、responseByteArray を使用して、応答本文を読み取ることができます。

string responseString = await response.Content.ReadAsStringAsync();

前のコードでは、responseString を使用して、応答本文を読み取ることができます。

最後に、HTTP エンドポイントによって JSON が返されることがわかっている場合は、System.Net.Http.Json NuGet パッケージを使用して応答本文を任意の有効な C# オブジェクトに逆シリアル化できます。

T? result = await response.Content.ReadFromJsonAsync<T>();

前のコードで、result は、型 T として逆シリアル化された応答本文です。

HTTP エラー処理

HTTP 要求が失敗すると、HttpRequestException がスローされます。 その例外をキャッチするだけでは十分でない場合があります。処理を検討する必要がある可能性のある例外が他にスローされる可能性があるためです。 たとえば、呼び出し元のコードでは、要求が完了する前に取り消されたキャンセル トークンを使用している可能性があります。 このシナリオでは、TaskCanceledException をキャッチします。

using var cts = new CancellationTokenSource();
try
{
    // Assuming:
    //   httpClient.Timeout = TimeSpan.FromSeconds(10)

    using var response = await httpClient.GetAsync(
        "http://localhost:5001/sleepFor?seconds=100", cts.Token);
}
catch (OperationCanceledException ex) when (cts.IsCancellationRequested)
{
    // When the token has been canceled, it is not a timeout.
    Console.WriteLine($"Canceled: {ex.Message}");
}

同様に、HTTP 要求を行うときに、HttpClient.Timeout を超える前にサーバーが応答しない場合は、同じ例外がスローされます。 ただし、このシナリオでは、TaskCanceledException をキャッチしたときに Exception.InnerException を評価することで、タイムアウトが発生したか区別できます。

try
{
    // Assuming:
    //   httpClient.Timeout = TimeSpan.FromSeconds(10)

    using var response = await httpClient.GetAsync(
        "http://localhost:5001/sleepFor?seconds=100");
}
catch (OperationCanceledException ex) when (ex.InnerException is TimeoutException tex)
{
    Console.WriteLine($"Timed out: {ex.Message}, {tex.Message}");
}

前のコードでは、内部例外が TimeoutException の場合、タイムアウトが発生し、キャンセル トークンによって要求が取り消されませんでした。

HttpRequestException をキャッチしたときに HTTP 状態コードを評価するには、HttpRequestException.StatusCode プロパティを評価します。

try
{
    // Assuming:
    //   httpClient.Timeout = TimeSpan.FromSeconds(10)

    using var response = await httpClient.GetAsync(
        "http://localhost:5001/doesNotExist");

    response.EnsureSuccessStatusCode();
}
catch (HttpRequestException ex) when (ex is { StatusCode: HttpStatusCode.NotFound })
{
    // Handle 404
    Console.WriteLine($"Not found: {ex.Message}");
}

前のコードでは、EnsureSuccessStatusCode() メソッドが呼び出され、応答が成功しなかった場合に例外がスローされます。 その後、HttpRequestException.StatusCode プロパティが評価され、応答が 404 (HTTP 状態コード 404) であったかどうかが判断されます。 ユーザーに代わって EnsureSuccessStatusCode を暗黙的に呼び出す HttpClient に対するヘルパー メソッドがいくつかあります。次の API を検討してください。

ヒント

HttpResponseMessage を返さない HTTP 要求を行うために使用されるすべての HttpClient メソッドは、ユーザーに代わって EnsureSuccessStatusCode を暗黙的に呼び出します。

これらのメソッドを呼び出すときは、HttpRequestException を処理し、HttpRequestException.StatusCode プロパティを評価して、応答の HTTP 状態コードを決定できます。

try
{
    // These extension methods will throw HttpRequestException
    // with StatusCode set when the HTTP request status code isn't 2xx:
    //
    //   GetByteArrayAsync
    //   GetStreamAsync
    //   GetStringAsync

    using var stream = await httpClient.GetStreamAsync(
        "https://localhost:5001/doesNotExists");
}
catch (HttpRequestException ex) when (ex is { StatusCode: HttpStatusCode.NotFound })
{
    // Handle 404
    Console.WriteLine($"Not found: {ex.Message}");
}

コードで HttpRequestException をスローする必要があるシナリオがある場合があります。 HttpRequestException() コンストラクターはパブリックであり、それを使用してカスタム メッセージで例外をスローできます。

try
{
    using var response = await httpClient.GetAsync(
        "https://localhost:5001/doesNotExists");

    // Throw for anything higher than 400.
    if (response is { StatusCode: >= HttpStatusCode.BadRequest })
    {
        throw new HttpRequestException(
            "Something went wrong", inner: null, response.StatusCode);
    }
}
catch (HttpRequestException ex) when (ex is { StatusCode: HttpStatusCode.NotFound })
{
    Console.WriteLine($"Not found: {ex.Message}");
}

HTTP プロキシ

HTTP プロキシは、次の 2 つの方法のいずれかで構成できます。 既定値が HttpClient.DefaultProxy プロパティで指定されます。 または、HttpClientHandler.Proxy プロパティでプロキシを指定することもできます。

グローバルな既定のプロキシ

HttpClient.DefaultProxy は、コンストラクターを介して渡された HttpClientHandler でプロキシが明示的に設定されていない場合に、すべての HttpClient インスタンスで使用される既定のプロキシを決定する静的プロパティです。

このプロパティから返される既定のインスタンスは、プラットフォームに応じて異なる規則のセットに従って初期化されます。

  • Windows の場合: 環境変数からプロキシ構成を読み取るか、それらが定義されていない場合はユーザーのプロキシ設定から読み取ります。
  • macOS の場合: 環境変数からプロキシ構成を読み取るか、それらが定義されていない場合はシステムのプロキシ設定から読み取ります。
  • Linux の場合: 環境変数からプロキシ構成を読み取ります。または、それらが定義されていない場合、このプロパティはすべてのアドレスをバイパスする未構成のインスタンスを初期化します。

Windows と Unix ベースのプラットフォームで DefaultProxy の初期化に使用される環境変数は、次のとおりです。

  • HTTP_PROXY: HTTP 要求で使用されるプロキシ サーバー。
  • HTTPS_PROXY: HTTPS 要求で使用されるプロキシ サーバー。
  • ALL_PROXY: HTTP_PROXYHTTPS_PROXY が定義されていない場合に HTTP 要求や HTTPS 要求で使用されるプロキシ サーバー。
  • NO_PROXY: プロキシから除外するホスト名のコンマ区切りの一覧。 アスタリスクはワイルドカードとして使用できません。サブドメインと一致させる場合、先頭ドットを使用します。 例: NO_PROXY=.example.com (先頭ドットあり) は www.example.com に一致しますが example.com には一致しません。 NO_PROXY=example.com (先頭ドットなし) は www.example.com に一致しません。 この動作は、今後、他のエコシステムに合わせる目的で再検討される可能性があります。

環境変数で大文字と小文字が区別されるシステムでは、変数名はすべて小文字またはすべて大文字にできます。 小文字の名前が最初にチェックされます。

プロキシ サーバーには、ホスト名または IP アドレスを指定でき、その後に必要に応じてコロンとポート番号を指定できます。または、必要に応じてプロキシ認証用のユーザー名とパスワードを含む http URL を指定できます。 URL は、https ではなく http で始まる必要があり、ホスト名、IP、またはポートの後にテキストを含めることはできません。

クライアントごとのプロキシ

HttpClientHandler.Proxy プロパティによって、インターネット リソースへの要求の処理に使う WebProxy オブジェクトが指定されます。 プロキシを使わない場合は、Proxy プロパティを GlobalProxySelection.GetEmptyWebProxy() メソッドから返されるプロキシ インスタンスに設定します。

ローカル コンピューターまたはアプリケーション構成ファイルでは、既定のプロキシを使うことを指定できます。 Proxy プロパティが指定されている場合、Proxy プロパティのプロキシ設定によってローカル コンピューターまたはアプリケーション構成ファイルがオーバーライドされ、ハンドラーでは指定されたプロキシ設定が使用されます。 構成ファイルでプロキシが指定されておらず、Proxy プロパティが指定されていない場合、ハンドラーではローカル コンピューターから継承されたプロキシ設定が使用されます。 プロキシ設定が存在しない場合、要求はサーバーに直接送信されます。

HttpClientHandler クラスは、ローカル コンピューターの設定から継承されたワイルドカード文字を使用してプロキシ バイパス リストを解析します。 たとえば、HttpClientHandler クラスは、ブラウザーからの "nt*" というバイパス リストを、"nt.*" という正規表現として解析します。 そのため、URL http://nt.com は、HttpClientHandler クラスを使ってプロキシをバイパスします。

HttpClientHandler クラスでは、ローカル プロキシのバイパスがサポートされています。 次のいずれかの条件が満たされている場合、クラスは宛先をローカルと見なします。

  1. 宛先にフラット名が含まれている (URL にドットがない)。
  2. 宛先にループバック アドレス (Loopback または IPv6Loopback) が含まれている、または宛先にローカル コンピューターに割り当てられた IPAddress が含まれている。
  3. 宛先のドメイン サフィックスが、ローカル コンピューターのドメイン サフィックス (DomainName) と一致する。

プロキシの構成について詳しくは、次を参照してください。

関連項目