Sage.Http 2.0.0.16

dotnet add package Sage.Http --version 2.0.0.16
                    
NuGet\Install-Package Sage.Http -Version 2.0.0.16
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Sage.Http" Version="2.0.0.16" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Sage.Http" Version="2.0.0.16" />
                    
Directory.Packages.props
<PackageReference Include="Sage.Http" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Sage.Http --version 2.0.0.16
                    
#r "nuget: Sage.Http, 2.0.0.16"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Sage.Http@2.0.0.16
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Sage.Http&version=2.0.0.16
                    
Install as a Cake Addin
#tool nuget:?package=Sage.Http&version=2.0.0.16
                    
Install as a Cake Tool

Sage.Http

NuGet Version NuGet Downloads License

Sage.Http 是一个面向 .NET 的 HttpClient 管理与链式请求库。它保留 IHttpClientFactory 的使用习惯,同时提供统一配置、链式请求、重试、熔断、时间窗口限流、超时、事件订阅、消息处理器、拦截器和 Standalone 模式,适合 ASP.NET Core、Worker Service、WinForms、WPF、控制台程序和 Native AOT 项目。

为什么使用

  • 少写样板代码:统一注册命名客户端,业务代码直接 CreateRequest(...).GetStringAsync()
  • 保留官方行为:CreateClient(name)IHttpClientFactory.CreateClient(name) 一样,每次返回新的 HttpClient 实例;未注册名称会得到默认客户端。
  • AOT 友好:JSON 主路径支持 JsonTypeInfo<T> 源生成;不适合 AOT 的便捷 API 已标注 RequiresUnreferencedCode / RequiresDynamicCode
  • 弹性策略清晰:重试、熔断、超时、固定窗口/滑动窗口限流可以按客户端配置。
  • 可观测:可订阅 RetryEventCircuitBreakerEventRateLimiterEvent,也可用拦截器或 Handler 接入日志、鉴权、TraceId。
  • 支持非 DI 程序:WinForms/WPF/控制台可用 StandaloneHttpClientManager

安装

dotnet add package Sage.Http

常用命名空间:

using Sage.Http.Configuration;
using Sage.Http.Core;
using Sage.Http.Events;
using Sage.Http.Extensions;
using Sage.Http.Handlers;

30 秒上手

ASP.NET Core / Worker Service

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpClientManager();

builder.Services.AddHttpClient("github", options =>
{
    options.BaseAddress = new Uri("https://api.github.com");
    options.Timeout = TimeSpan.FromSeconds(30);
    options.AddDefaultHeader("User-Agent", "SageHttpDemo/1.0");

    options.ConfigureRetry(r =>
    {
        r.MaxRetryAttempts = 3;
        r.BaseDelay = TimeSpan.FromSeconds(1);
        r.RetryOnStatusCodes = [408, 429, 500, 502, 503, 504];
    });
});

var app = builder.Build();

app.MapGet("/repos", async (IHttpClientManager http) =>
{
    var json = await http
        .CreateRequest("github", "/repos/dotnet/runtime")
        .GetStringAsync();

    return Results.Text(json, "application/json");
});

app.Run();

注册规则:

  • DI 模式下,请在 IServiceCollection 构建阶段使用 services.AddHttpClient("name", options => ...) 注册可复用客户端。
  • IHttpClientManager.RegisterClient(...) 不支持运行期动态注册。
  • 低频临时请求可使用 CreateClient(name, configure)CreateRequest(name, uri, configure),临时配置不会写入 DI 注册表。

WinForms / WPF / 控制台

using var manager = StandaloneHttpClientManager.Create(config =>
{
    config.RegisterClient("api", options =>
    {
        options.BaseAddress = new Uri("https://jsonplaceholder.typicode.com");
        options.Timeout = TimeSpan.FromSeconds(30);
        options.ConfigureRetry(r =>
        {
            r.MaxRetryAttempts = 3;
            r.BaseDelay = TimeSpan.FromSeconds(1);
        });
    });
});

var text = await manager
    .CreateRequest("api", "/posts/1")
    .GetStringAsync();

Standalone 模式也要求在 StandaloneHttpClientManager.Create(config => ...) 回调里完成注册;管理器创建后不再支持动态注册。

核心用法

创建客户端

// 严格获取已注册客户端;名称不存在会抛异常,适合业务代码尽早发现配置问题。
using var registered = manager.GetClient("api");

// 与 IHttpClientFactory 一致;名称不存在时返回默认配置客户端。
using var client = manager.CreateClient("api");

// 一次性临时客户端;不会影响后续同名客户端。
using var temp = manager.CreateClient("temp-api", options =>
{
    options.BaseAddress = new Uri("https://api.example.com");
    options.Timeout = TimeSpan.FromSeconds(10);
});

链式请求

var text = await manager
    .CreateRequest("api", "/orders")
    .WithQuery("page", 1)
    .WithQuery("size", 20)
    .WithHeader("X-Trace-Id", Guid.NewGuid().ToString("N"))
    .GetStringAsync();

常用方法:

  • WithQuery / WithQueries:查询参数。
  • WithHeader:普通请求头。
  • WithContentHeader:内容头,例如 Content-TypeContent-Language
  • WithJsonBody / PostAsJsonAsync:JSON 请求体。
  • WithFormBody / PostAsFormAsync:表单请求体。
  • WithFile / WithMultipart:文件上传和 Multipart 请求。
  • GetStringAsync / GetByteArrayAsync / GetStreamAsync / GetFromJsonAsync:读取响应内容。

内容头说明:Content-Type 等内容头最终必须写入 HttpContent.Headers。推荐直接使用 WithContentHeader;如果使用 WithHeader("Content-Type", "..."),Sage.Http 会在请求包含 body 时自动转写,否则会抛出 InvalidOperationException,避免静默丢失。

JSON 与 Native AOT

Native AOT 项目推荐使用 JsonSerializerContext 源生成。

using System.Text.Json.Serialization;

public sealed class OrderRequest
{
    public long ProductId { get; set; }
    public int Quantity { get; set; }
}

public sealed class OrderResponse
{
    public string? OrderNo { get; set; }
}

[JsonSerializable(typeof(OrderRequest))]
[JsonSerializable(typeof(OrderResponse))]
internal partial class AppJsonContext : JsonSerializerContext
{
}

发送和读取:

var request = new OrderRequest
{
    ProductId = 1001,
    Quantity = 2
};

var order = await manager
    .CreateRequest("api", "/orders")
    .PostAsJsonAsync(request, AppJsonContext.Default.OrderRequest)
    .ReadFromJsonAsync(AppJsonContext.Default.OrderResponse);

非 AOT 项目也可以使用 JsonSerializerOptions 便捷重载,例如 PostAsJsonAsync(value)GetFromJsonAsync<T>()。这些 API 更方便,但不建议作为 Native AOT 主路径。

表单与文件上传

var token = await manager
    .CreateRequest("api", "/login")
    .PostAsFormAsync(new Dictionary<string, string>
    {
        ["username"] = "admin",
        ["password"] = "123456"
    })
    .ReadAsStringAsync();
await using var file = File.OpenRead("c:/tmp/demo.png");

await manager
    .CreateRequest("api", "/upload")
    .WithFormBody(new Dictionary<string, string>
    {
        ["remark"] = "demo"
    })
    .WithFile("file", file, "demo.png", "image/png")
    .PostAsync();

弹性策略

builder.Services.AddHttpClient("order-api", options =>
{
    options.BaseAddress = new Uri("https://api.example.com");

    options.ConfigureRetry(r =>
    {
        r.MaxRetryAttempts = 3;
        r.BaseDelay = TimeSpan.FromSeconds(1);
        r.BackoffType = BackoffType.Exponential;
        r.UseJitter = true;
        r.RetryOnStatusCodes = [408, 429, 500, 502, 503, 504];
    });

    options.ConfigureCircuitBreaker(cb =>
    {
        cb.FailureRatio = 0.5;
        cb.SamplingDuration = TimeSpan.FromSeconds(30);
        cb.MinimumThroughput = 10;
        cb.BreakDuration = TimeSpan.FromSeconds(30);
    });

    options.ConfigureRateLimiter(rl =>
    {
        rl.PermitLimit = 60;
        rl.Window = TimeSpan.FromMinutes(1);
        rl.QueueLimit = 0;
        rl.Algorithm = RateLimiterAlgorithm.FixedWindow;
    });

    options.Resilience ??= new ResilienceOptions();
    options.Resilience.EnableTimeout = true;
    options.Resilience.Timeout = TimeSpan.FromSeconds(20);
});

限流语义:ConfigureRateLimiter 是时间窗口限流,不是最大并发数限制。

  • FixedWindow:固定窗口,适合“每分钟最多 N 次”。
  • SlidingWindow:滑动窗口,配合 SegmentsPerWindow 分段,窗口边界更平滑。
  • QueueLimit = 0:超过限制后直接拒绝。

Handler 与拦截器

Handler 适合通用能力,例如日志、鉴权、TraceId、User-Agent:

builder.Services.AddTransient<CorrelationIdHandler>();

builder.Services.AddHttpClient("api", options =>
{
    options.BaseAddress = new Uri("https://api.example.com");
    options.AddHandler<CorrelationIdHandler>();
});

需要运行时参数时,用工厂方式:

builder.Services.AddHttpClient("api", options =>
{
    options.AddHandler(sp => new LoggingHandler(
        sp.GetRequiredService<ILogger<LoggingHandler>>(),
        requestLogLevel: LogLevel.Information,
        responseLogLevel: LogLevel.Information,
        logRequestBody: false,
        logResponseBody: false));
});

拦截器适合轻量修改请求或观察响应:

builder.Services.AddHttpClient("api", options =>
{
    options.AddRequestInterceptor(request =>
    {
        request.Headers.TryAddWithoutValidation("X-Trace-Id", Guid.NewGuid().ToString("N"));
    });
});

建议避免同时用拦截器和 LoggingHandler 记录同类日志,否则可能重复输出。

事件订阅

var retrySubscription = manager.Subscribe<RetryEvent>("api", e =>
{
    Console.WriteLine($"Retry {e.AttemptNumber}: {e.RequestUri}");
});

var allSubscription = manager.Subscribe<HttpClientEvent>(e =>
{
    Console.WriteLine($"{e.GetType().Name}: {e.ClientName}");
});

说明:

  • Subscribe<RetryEvent>("api", ...) 只接收指定客户端的重试事件。
  • Subscribe<HttpClientEvent>(...) 可接收 RetryEventCircuitBreakerEventRateLimiterEvent 等派生事件。
  • 返回的 IEventSubscription 应在不需要时 Dispose()
  • useWeakReference: true 会弱引用处理器目标对象,目标被 GC 回收后订阅自动失效;长期订阅建议保存返回对象。

响应验证与错误处理

try
{
    var response = await manager
        .CreateRequest("api", "/orders/1001")
        .GetAsync()
        .ValidateResponseAsync();

    var json = await response.Content.ReadAsStringAsync();
}
catch (HttpResponseException ex)
{
    Console.WriteLine($"StatusCode: {(int)ex.StatusCode}");
    Console.WriteLine(ex.ErrorContent);
}

读取方法的生命周期规则:

  • GetStringAsyncGetByteArrayAsyncReadAsStringAsyncReadAsByteArrayAsyncReadFromJsonAsync 会在读取完成后释放响应。
  • GetStreamAsyncTask<HttpResponseMessage>.ReadAsStreamAsync() 返回的流持有响应生命周期,调用方必须释放该流。
await using var stream = await manager
    .CreateRequest("api", "/files/report.zip")
    .GetStreamAsync();

连接与性能

高并发或长期运行服务建议使用 SocketsHttpHandler

builder.Services.AddHttpClient("api", options =>
{
    options.BaseAddress = new Uri("https://api.example.com");

    options.UseHighPerformanceHandler(
        maxConnectionsPerServer: 100,
        connectionLifetime: TimeSpan.FromMinutes(10),
        connectionIdleTimeout: TimeSpan.FromMinutes(2),
        enableHttp2: true);
});

建议:

  • 高频业务请求优先注册命名客户端,再使用 CreateClient(name)GetClient(name)CreateRequest(name, uri)
  • CreateClient(name, configure) / CreateRequest(name, uri, configure) 适合低频临时请求。
  • 不要把 typed client 或 HttpClient 长期错误地缓存到单例里,除非你清楚连接池和 DNS 刷新的影响。
  • 请求/响应 body 日志只建议调试时开启,并注意敏感信息脱敏。

JSON 配置

消费端可以直接把 HttpClients 配置节交给 Sage.Http 注册。内置配置读取不会使用 ConfigurationBinder.Bind,因此不会扫描 HttpClientOptions.JsonSerializerOptions,适合 Native AOT 项目。缺失的配置节点会保留选项默认值,格式错误或范围不合法的值会被忽略。

{
  "HttpClients": {
    "api": {
      "BaseAddress": "https://api.example.com",
      "Timeout": "00:00:30",
      "HttpVersion": "2.0",
      "SslProtocols": "Tls12,Tls13",
      "DefaultHeaders": {
        "Accept": "application/json",
        "User-Agent": "Sage.Http.Sample/1.0"
      },
      "MaxResponseContentBufferSize": 1048576,
      "AllowAutoRedirect": true,
      "MaxAutomaticRedirections": 5,
      "UseDefaultCredentials": false,
      "UseCookies": true,
      "Resilience": {
        "EnableRetry": true,
        "Retry": {
          "MaxRetryAttempts": 3,
          "BaseDelay": "00:00:01",
          "MaxDelay": "00:00:30",
          "BackoffType": "Exponential",
          "UseJitter": true,
          "RetryOnStatusCodes": [ 408, 429, 500, 502, 503, 504 ],
          "RetryOnStatusCodeRanges": [
            {
              "Start": 520,
              "End": 529
            }
          ],
          "ExcludeStatusCodes": [ 400, 401, 403, 404 ],
          "RetryOnExceptionTypes": [
            "System.Net.Http.HttpRequestException",
            "System.Threading.Tasks.TaskCanceledException"
          ],
          "ExcludeExceptionTypes": [
            "System.OperationCanceledException"
          ]
        },
        "EnableCircuitBreaker": true,
        "CircuitBreaker": {
          "FailureRatio": 0.5,
          "SamplingDuration": "00:00:30",
          "MinimumThroughput": 10,
          "BreakDuration": "00:00:30",
          "HandleStatusCodes": [ 409, 423 ],
          "HandleStatusCodeRanges": [
            {
              "Start": 500,
              "End": 599
            }
          ],
          "ExcludeStatusCodes": [ 400, 401, 403, 404 ],
          "HandleTransientHttpStatusCodes": true,
          "HandleAllNonSuccessStatusCodes": false
        },
        "EnableRateLimiter": true,
        "RateLimiter": {
          "PermitLimit": 100,
          "QueueLimit": 10,
          "Window": "00:01:00",
          "AutoReplenishment": true,
          "Algorithm": "FixedWindow",
          "SegmentsPerWindow": 4
        },
        "EnableTimeout": true,
        "Timeout": "00:00:10",
        "EnableEvents": true
      }
    }
  }
}
builder.Services.AddHttpClientManager(builder.Configuration);

// 等价写法:
// builder.Services.AddHttpClientManager();
// builder.Services.AddHttpClientsFromConfiguration(builder.Configuration.GetSection("HttpClients"));

配置文件可以覆盖基础属性、默认请求头、重试、熔断、超时、固定窗口/滑动窗口限流。只配置 RetryCircuitBreakerRateLimiter 子节点时会自动启用对应策略;只配置 EnableRetry 等开关而省略子节点时会使用该策略默认值。PrimaryHandlerFactoryHandlerFactories、拦截器、证书回调、RetryOptions.ShouldRetry 这类委托或运行时代码不能从 JSON 配置表达,需要继续使用代码配置。

诊断输出

库内部保留了诊断输出开关,默认不会写入 Console。本地排查内部管道行为时可临时开启:

SageHttpDiagnostics.EnableConsoleOutput = true;

业务日志推荐使用 LoggingHandler 或自定义 Handler 接入 ILogger

常见选择

场景 推荐方式
ASP.NET Core / Worker Service AddHttpClientManager + services.AddHttpClient("name", options => ...)
WinForms / WPF / 控制台 StandaloneHttpClientManager.Create(config => ...)
Native AOT JSON JsonSerializerContext + JsonTypeInfo<T> 重载
临时一次性请求 CreateRequest(name, uri, configure)
严格要求客户端已注册 GetClient(name)
与官方 IHttpClientFactory 行为一致 CreateClient(name)
每分钟 N 次限制 ConfigureRateLimiter
最大并发控制 自定义 Handler 或按业务自行实现

发布包内文档说明

NuGet 包页面只展示当前 README。为了避免私有仓库地址或相对文档路径在 NuGet 页面上不可访问,常用安装、注册、请求、弹性策略、事件、AOT、诊断和性能建议已经整理在本文档中。

如果需要排查问题,请优先提供以下信息:

  • 使用的 Sage.Http 版本和目标框架,例如 net9.0net10.0
  • 客户端注册代码,尤其是 BaseAddressTimeout、重试、熔断、限流和 Handler 配置。
  • 完整异常类型、异常消息、HTTP 状态码和关键日志。
  • 是否运行在 Native AOT、WinForms、WPF、Worker Service 或 ASP.NET Core 环境。

许可证

Apache-2.0

本项目采用 Apache License 2.0 开源协议。详情见包内 LICENSE.txt 或 Apache 官方协议说明。

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Sage.Http:

Package Downloads
Sage.CloudStorage.Qiniu

Sage.CloudStorage.Qiniu 是一个基于 .NET 平台的现代化七牛云存储 SDK,采用完全异步设计,提供了对七牛云对象存储、CDN 等服务的简单易用的 API 封装。该库基于 Sage.Http 构建,具有高性能、可扩展的七牛云服务访问能力,特别适合企业级应用和大文件处理场景。 ## 核心优势 - **现代化API设计**:完全异步,符合.NET最佳实践 - **模块化架构**:各组件职责明确,易于扩展和维护 - **丰富的事件机制**:提供上传进度通知和完成事件 - **智能上传策略**:自动选择最佳上传方式和分片大小 - **完善的错误处理**:提供详细的错误信息和恢复机制 ## 功能特性 - **完整的对象存储支持**:上传、下载、管理、删除等操作 - **高级上传功能**: - 智能分片上传(自动优化分片大小) - 断点续传支持 - 并发控制 - 实时进度监控 - **CDN管理**:刷新、预取、带宽查询、日志下载 - **数据处理**:图片处理、音视频转码等 - **批量操作**:批量上传、删除等

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.0.16 95 5/11/2026
2.0.0.15 116 4/29/2026
2.0.0.14 97 4/29/2026
2.0.0.13 105 4/28/2026
2.0.0.12 114 4/16/2026
2.0.0.11 130 1/14/2026
2.0.0.10 178 12/12/2025
2.0.0.9 459 11/18/2025
2.0.0.8 243 10/27/2025
2.0.0.7 160 10/25/2025
2.0.0.6 168 10/25/2025
2.0.0.5 164 10/25/2025
2.0.0.4 158 10/25/2025
2.0.0.3 174 10/25/2025
2.0.0.2 200 10/24/2025
2.0.0.1 228 10/23/2025
2.0.0 237 10/23/2025
1.0.1.25 228 10/19/2025
1.0.1.24 231 10/19/2025
1.0.1.23 318 10/19/2025 1.0.1.23 is deprecated because it has critical bugs.
Loading failed

新增支持AOT的配置解析逻辑