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
<PackageReference Include="Sage.Http" Version="2.0.0.16" />
<PackageVersion Include="Sage.Http" Version="2.0.0.16" />
<PackageReference Include="Sage.Http" />
paket add Sage.Http --version 2.0.0.16
#r "nuget: Sage.Http, 2.0.0.16"
#:package Sage.Http@2.0.0.16
#addin nuget:?package=Sage.Http&version=2.0.0.16
#tool nuget:?package=Sage.Http&version=2.0.0.16
Sage.Http
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。 - 弹性策略清晰:重试、熔断、超时、固定窗口/滑动窗口限流可以按客户端配置。
- 可观测:可订阅
RetryEvent、CircuitBreakerEvent、RateLimiterEvent,也可用拦截器或 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-Type、Content-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>(...)可接收RetryEvent、CircuitBreakerEvent、RateLimiterEvent等派生事件。- 返回的
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);
}
读取方法的生命周期规则:
GetStringAsync、GetByteArrayAsync、ReadAsStringAsync、ReadAsByteArrayAsync、ReadFromJsonAsync会在读取完成后释放响应。GetStreamAsync和Task<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"));
配置文件可以覆盖基础属性、默认请求头、重试、熔断、超时、固定窗口/滑动窗口限流。只配置 Retry、CircuitBreaker 或 RateLimiter 子节点时会自动启用对应策略;只配置 EnableRetry 等开关而省略子节点时会使用该策略默认值。PrimaryHandlerFactory、HandlerFactories、拦截器、证书回调、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.0或net10.0。 - 客户端注册代码,尤其是
BaseAddress、Timeout、重试、熔断、限流和 Handler 配置。 - 完整异常类型、异常消息、HTTP 状态码和关键日志。
- 是否运行在 Native AOT、WinForms、WPF、Worker Service 或 ASP.NET Core 环境。
许可证
Apache-2.0
本项目采用 Apache License 2.0 开源协议。详情见包内 LICENSE.txt 或 Apache 官方协议说明。
| Product | Versions 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. |
-
net10.0
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.6)
- Microsoft.Extensions.DependencyInjection (>= 10.0.6)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.6)
- Microsoft.Extensions.Http (>= 10.0.6)
- Microsoft.Extensions.Http.Resilience (>= 10.5.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.6)
- Microsoft.Extensions.Options (>= 10.0.6)
-
net8.0
- Microsoft.Bcl.Cryptography (>= 10.0.6)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.6)
- Microsoft.Extensions.DependencyInjection (>= 10.0.6)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.6)
- Microsoft.Extensions.Http (>= 10.0.6)
- Microsoft.Extensions.Http.Resilience (>= 10.5.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.6)
- Microsoft.Extensions.Options (>= 10.0.6)
-
net9.0
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.6)
- Microsoft.Extensions.DependencyInjection (>= 10.0.6)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.6)
- Microsoft.Extensions.Http (>= 10.0.6)
- Microsoft.Extensions.Http.Resilience (>= 10.5.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.6)
- Microsoft.Extensions.Options (>= 10.0.6)
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 |
新增支持AOT的配置解析逻辑