mcpField
H
httpclient-factory

httpclient-factory

github.com/codewithmukesh開発7913.0k 回インストール12 週間前に更新
リポジトリ

httpclient-factory について

外部 HTTP リクエストを扱う .NET 開発者向けの HttpClient Factory スキルです。クライアントのライフサイクル管理、レジリエンスハンドラー、横断的関心事を扱う委譲ハンドラーを説明します。

主な機能

  1. 01ファクトリー管理の HTTP クライアント
  2. 02キー付きクライアント登録
  3. 03標準レジリエンスハンドラー
  4. 04認証と相関 ID 用の委譲ハンドラー
  5. 05モックハンドラーによるテスト

活用シーン

  1. 01リトライ付きの外部 API 呼び出し
  2. 02リクエストへの認証トークン追加
  3. 03相関 ID の伝播
  4. 04HTTP 連携のテスト

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/codewithmukesh/dotnet-claude-kit --skill httpclient-factory

インストール後、Claude Code で/httpclient-factoryを入力して読み込まれたか確認します。

詳細

HttpClient ファクトリー

基本原則

  1. リクエストごとに new HttpClient() を使わないでください — HttpClient を直接作成すると、高負荷時にソケットが枯渇し、DNS の変更も反映されません。ハンドラーの有効期間を管理するには、IHttpClientFactory を使用してください。
  2. 型指定クライアントよりキー付きクライアントを使用してください — .NET 10 では、キー付き DI(.AddAsKeyed())が推奨されるパターンです。シングルトンに保持された型指定クライアントは、ハンドラーのローテーションを気付かれないまま妨げます。
  3. 回復性は必須です — 外部への HTTP 呼び出しにはすべて、リトライ、サーキットブレーカー、タイムアウトが必要です。AddStandardResilienceHandler() を使えば、適切な既定値を 1 行で設定できます。
  4. 横断的な関心事には DelegatingHandler を使用してください — 認証トークン、相関 ID、ログ記録は、サービスメソッドに散在させず、ハンドラーパイプラインに配置します。

パターン

回復性を備えた名前付きクライアント
builder.Services.AddHttpClient("github", client =>
{
    client.BaseAddress = new Uri("https://api.github.com/");
    client.DefaultRequestHeaders.UserAgent.ParseAdd("MyApp/1.0");
    client.DefaultRequestHeaders.Accept.Add(
        new MediaTypeWithQualityHeaderValue("application/json"));
})
.AddStandardResilienceHandler();

// Usage via factory
public sealed class GitHubService(IHttpClientFactory factory)
{
    public async Task<Repo?> GetRepoAsync(string owner, string name, CancellationToken ct)
    {
        var client = factory.CreateClient("github");
        return await client.GetFromJsonAsync<Repo>($"repos/{owner}/{name}", ct);
    }
}
キー付きクライアント(.NET 10 で推奨)

名前付きクライアントの設定の柔軟性と、直接のインジェクションを組み合わせます。文字列による検索は不要です。

builder.Services.AddHttpClient("payments", client =>
{
    client.BaseAddress = new Uri("https://api.payments.example.com/");
})
.AddStandardResilienceHandler()
.AddAsKeyed();  // Register as keyed scoped service

// Inject directly — no IHttpClientFactory needed
app.MapPost("/charge", async (
    [FromKeyedServices("payments")] HttpClient httpClient,
    ChargeRequest request,
    CancellationToken ct) =>
{
    var response = await httpClient.PostAsJsonAsync("charges", request, ct);
    return response.IsSuccessStatusCode
        ? TypedResults.Ok()
        : TypedResults.Problem("Payment failed");
});

グローバルに有効化するには、builder.Services.ConfigureHttpClientDefaults(b => b.AddAsKeyed()); を使用します。

標準の回復性ハンドラー

AddStandardResilienceHandler() は 5 つの戦略を連結します。

戦略既定値
レートリミッター同時リクエスト数 1000
合計タイムアウト30 秒
リトライ3 回のリトライ、ジッターを伴う指数バックオフ
サーキットブレーカー失敗率 10% で開く
試行ごとのタイムアウト1 回の試行につき 10 秒
builder.Services.AddHttpClient("api")
    .AddStandardResilienceHandler(options =>
    {
        options.Retry.MaxRetryAttempts = 5;
        options.Retry.Delay = TimeSpan.FromSeconds(1);
        options.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(60);
        options.AttemptTimeout.Timeout = TimeSpan.FromSeconds(15);

        // Disable retries for non-idempotent methods
        options.Retry.DisableForUnsafeHttpMethods();
    });
認証トークン注入用 DelegatingHandler
public sealed class AuthenticationHandler(ITokenService tokenService)
    : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var token = await tokenService.GetAccessTokenAsync(cancellationToken);
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
        return await base.SendAsync(request, cancellationToken);
    }
}

// Registration
builder.Services.AddTransient<AuthenticationHandler>();
builder.Services.AddHttpClient("api")
    .AddHttpMessageHandler<AuthenticationHandler>()
    .AddStandardResilienceHandler();
相関 ID 伝播用 DelegatingHandler
public sealed class CorrelationIdHandler(IHttpContextAccessor httpContextAccessor)
    : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (httpContextAccessor.HttpContext?.Request.Headers
                .TryGetValue("X-Correlation-Id", out var correlationId) is true)
        {
            request.Headers.Add("X-Correlation-Id", correlationId.ToString());
        }
        return base.SendAsync(request, cancellationToken);
    }
}
SocketsHttpHandler の設定
builder.Services.AddHttpClient("advanced")
    .UseSocketsHttpHandler((handler, _) =>
    {
        handler.PooledConnectionLifetime = TimeSpan.FromMinutes(2);
        handler.PooledConnectionIdleTimeout = TimeSpan.FromMinutes(1);
        handler.MaxConnectionsPerServer = 100;
        handler.AutomaticDecompression =
            DecompressionMethods.GZip | DecompressionMethods.Brotli;
    });
モックハンドラーを使用したテスト
public sealed class MockHttpHandler(
    HttpStatusCode statusCode,
    string content) : HttpMessageHandler
{
    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return Task.FromResult(new HttpResponseMessage(statusCode)
        {
            Content = new StringContent(content, Encoding.UTF8, "application/json")
        });
    }
}

// In test
var handler = new MockHttpHandler(HttpStatusCode.OK, """{"id":1}""");
var client = new HttpClient(handler) { BaseAddress = new Uri("https://api.test/") };
var service = new MyService(client);

アンチパターン

リクエストごとに HttpClient を作成しない
// BAD — socket exhaustion under load, ignores DNS changes
public async Task<string> GetDataAsync()
{
    using var client = new HttpClient();
    return await client.GetStringAsync("https://api.example.com/data");
}

// GOOD — factory-managed
public async Task<string> GetDataAsync(CancellationToken ct)
{
    var client = factory.CreateClient("api");
    return await client.GetStringAsync("https://api.example.com/data", ct);
}
シングルトンに型指定クライアントを保持しない
// BAD — transient HttpClient captured by singleton defeats handler rotation
services.AddSingleton<MySingletonService>();
services.AddHttpClient<MySingletonService>();

// GOOD — use keyed client or IHttpClientFactory in singletons
services.AddSingleton<MySingletonService>();
services.AddHttpClient("myservice").AddAsKeyed(ServiceLifetime.Singleton);
共有クライアントの DefaultRequestHeaders を変更しない
// BAD — not thread-safe
httpClient.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", token);

// GOOD — use DelegatingHandler or per-request HttpRequestMessage
using var request = new HttpRequestMessage(HttpMethod.Get, "/api/data");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
await httpClient.SendAsync(request, ct);
CancellationToken を忘れない
// BAD — no cancellation support
var result = await httpClient.GetFromJsonAsync<Order>("/orders/1");

// GOOD — always pass CancellationToken
var result = await httpClient.GetFromJsonAsync<Order>("/orders/1", cancellationToken);
複数の回復性ハンドラーを重ねて設定しない
// BAD — conflicting resilience strategies
builder.AddStandardResilienceHandler();
builder.AddStandardHedgingHandler();

// GOOD — one standard handler, or a custom pipeline
builder.AddStandardResilienceHandler();

選択ガイド

シナリオ推奨事項
新しい .NET 10 プロジェクトAddAsKeyed() を使ったキー付きクライアント
シングルトンサービスで HttpClient が必要IHttpClientFactory を介した名前付きクライアント、またはキー付きシングルトン
外部 API の呼び出しすべてのクライアントに AddStandardResilienceHandler() を設定
認証トークンの注入AddHttpMessageHandler で登録した DelegatingHandler
ヘッジング(並列リクエスト)レイテンシーが重要な呼び出しには AddStandardHedgingHandler()
冪等でないメソッドリトライオプションで DisableForUnsafeHttpMethods() を設定
カスタムリトライロジックAddResilienceHandler("name", builder => ...)
コネクションプールの制御PooledConnectionLifetime を指定した UseSocketsHttpHandler
API クライアントの生成AddRefitClient<T>() を使った Refit
統合テストカスタムの HttpMessageHandler または MockHttpMessageHandler

コメント