Sage.Http
2.0.0
See the version list below for details.
dotnet add package Sage.Http --version 2.0.0
NuGet\Install-Package Sage.Http -Version 2.0.0
<PackageReference Include="Sage.Http" Version="2.0.0" />
<PackageVersion Include="Sage.Http" Version="2.0.0" />
<PackageReference Include="Sage.Http" />
paket add Sage.Http --version 2.0.0
#r "nuget: Sage.Http, 2.0.0"
#:package Sage.Http@2.0.0
#addin nuget:?package=Sage.Http&version=2.0.0
#tool nuget:?package=Sage.Http&version=2.0.0
Sage.Http
一个基于 .NET 9 和 C# 13 的现代化 HTTP 客户端封装库,提供简化的配置、弹性策略和事件监控。
✨ 特性
- ✅ 简化配置 - 统一的配置模型,支持 TLS、HTTP 版本、超时等
- ✅ 弹性策略 - 内置重试、熔断、限流支持(基于 Polly)
- ✅ 事件监控 - 完整的事件系统,支持按客户端订阅
- ✅ DI 友好 - 完美集成 ASP.NET Core 依赖注入
- ✅ Standalone 模式 - 支持 WinForms/WPF/Debug 应用
- ✅ 线程安全 - 并发场景下的安全使用
- ✅ 内存安全 - 弱引用支持,避免内存泄漏
- ✅ AOT 友好 - 支持 Native AOT 编译
📦 安装
# 通过 NuGet 安装
dotnet add package Sage.Http
# 或通过 Package Manager
Install-Package Sage.Http
依赖项:
- .NET 9.0+
- Microsoft.Extensions.Http.Resilience 9.0.0+
🚀 快速开始
ASP.NET Core(DI 模式)
// Program.cs
using Sage.Http.Extensions;
using Sage.Http.Configuration;
var builder = WebApplication.CreateBuilder(args);
// 1. 注册 HTTP 客户端管理器
builder.Services.AddHttpClientManager();
// 2. 配置命名客户端
builder.Services.AddHttpClient("github", options =>
{
options.BaseAddress = new Uri("https://api.github.com");
options.Timeout = TimeSpan.FromSeconds(30);
options.AddDefaultHeader("User-Agent", "MyApp/1.0");
options.UseRetryPolicy(RetryPolicy.Default);
});
var app = builder.Build();
app.Run();
在服务中使用:
using Sage.Http.Core;
using Sage.Http.Events;
public class GitHubService
{
private readonly IHttpClientManager _manager;
private readonly IEventSubscription _retrySubscription;
private readonly ILogger<GitHubService> _logger;
public GitHubService(IHttpClientManager manager, ILogger<GitHubService> logger)
{
_manager = manager;
_logger = logger;
// 订阅重试事件
_retrySubscription = _manager.Subscribe<RetryEvent>("github", OnRetry);
}
private void OnRetry(RetryEvent e)
{
_logger.LogWarning("正在重试请求 {Uri},第 {Attempt} 次,延迟 {Delay}ms",
e.RequestUri, e.AttemptNumber, e.Delay.TotalMilliseconds);
}
public async Task<User> GetUserAsync(string username)
{
var client = _manager.GetClient("github");
return await client.GetFromJsonAsync<User>($"/users/{username}");
}
}
WinForms(Standalone 模式)
using Sage.Http.Core;
using Sage.Http.Configuration;
using Sage.Http.Events;
public partial class MainForm : Form
{
private StandaloneHttpClientManager? _manager;
private IEventSubscription? _retrySubscription;
private IEventSubscription? _circuitSubscription;
public MainForm()
{
InitializeComponent();
InitializeHttpClient();
}
private void InitializeHttpClient()
{
_manager = StandaloneHttpClientManager.Create();
// 配置 API 客户端
_manager.RegisterClient("api", options =>
{
options.BaseAddress = new Uri("https://api.example.com");
options.Timeout = TimeSpan.FromSeconds(30);
// 配置重试
options.ConfigureRetry(retry =>
{
retry.MaxRetryAttempts = 3;
retry.BaseDelay = TimeSpan.FromSeconds(2);
});
// 配置熔断器
options.ConfigureCircuitBreaker(cb =>
{
cb.FailureRatio = 0.5;
cb.BreakDuration = TimeSpan.FromMinutes(1);
});
});
// 订阅事件
_retrySubscription = _manager.Subscribe<RetryEvent>("api", e =>
{
Invoke(() =>
{
statusLabel.Text = $"重试中... ({e.AttemptNumber}/3)";
statusLabel.ForeColor = Color.Orange;
});
});
_circuitSubscription = _manager.Subscribe<CircuitBreakerEvent>("api", e =>
{
Invoke(() =>
{
if (e.NewState == CircuitState.Open)
{
statusLabel.Text = "服务暂时不可用";
statusLabel.ForeColor = Color.Red;
}
else if (e.NewState == CircuitState.Closed)
{
statusLabel.Text = "服务已恢复";
statusLabel.ForeColor = Color.Green;
}
});
});
}
private async void btnLoad_Click(object sender, EventArgs e)
{
try
{
var client = _manager!.GetClient("api");
var data = await client.GetFromJsonAsync<List<Item>>("/items");
dataGrid.DataSource = data;
statusLabel.Text = "加载成功";
statusLabel.ForeColor = Color.Green;
}
catch (Exception ex)
{
MessageBox.Show($"加载失败: {ex.Message}", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
// 释放资源
_retrySubscription?.Dispose();
_circuitSubscription?.Dispose();
_manager?.Dispose();
base.OnFormClosing(e);
}
}
📖 详细文档
配置选项
基础配置
builder.Services.AddHttpClient("myapi", options =>
{
// 基础地址
options.BaseAddress = new Uri("https://api.example.com");
// 超时设置
options.Timeout = TimeSpan.FromSeconds(30);
// HTTP 版本
options.HttpVersion = HttpVersion.Version20; // HTTP/2.0
// TLS 版本
options.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13;
// 自动重定向
options.AllowAutoRedirect = true;
options.MaxAutomaticRedirections = 50;
// 默认请求头
options.AddDefaultHeader("User-Agent", "MyApp/1.0");
options.AddDefaultHeader("Accept", "application/json");
});
预设重试策略
// 方式1:使用预设策略
options.UseRetryPolicy(RetryPolicy.Default);
// - Default: 重试 408, 429, 500, 502, 503, 504
// - Aggressive: 重试所有 5xx 错误和 408, 429
// - Conservative: 仅重试 503, 504
// - None: 不重试
// 方式2:自定义配置
options.ConfigureRetry(retry =>
{
retry.MaxRetryAttempts = 3;
retry.BaseDelay = TimeSpan.FromSeconds(2);
retry.BackoffType = BackoffType.Exponential;
retry.UseJitter = true;
// 指定重试的状态码
retry.RetryOnStatusCodes = new HashSet<int> { 408, 429, 500, 502, 503, 504 };
// 或指定状态码范围
retry.RetryOnStatusCodeRanges = new List<StatusCodeRange>
{
new(500, 599) // 所有 5xx 错误
};
// 排除某些状态码
retry.ExcludeStatusCodes = new HashSet<int> { 501 };
// 指定重试的异常类型
retry.RetryOnExceptionTypes = new HashSet<string>
{
"System.Net.Http.HttpRequestException",
"System.Net.Sockets.SocketException"
};
});
熔断器配置
options.ConfigureCircuitBreaker(cb =>
{
cb.FailureRatio = 0.5; // 失败率阈值 50%
cb.SamplingDuration = TimeSpan.FromSeconds(30); // 采样时间窗口
cb.MinimumThroughput = 10; // 最小请求数
cb.BreakDuration = TimeSpan.FromMinutes(1); // 熔断持续时间
});
限流器配置
options.ConfigureRateLimiter(limiter =>
{
limiter.PermitLimit = 100; // 每个时间窗口最多 100 个请求
limiter.Window = TimeSpan.FromMinutes(1); // 时间窗口
limiter.QueueLimit = 10; // 等待队列长度
});
事件系统
订阅事件
// 订阅特定客户端的重试事件
var subscription = _manager.Subscribe<RetryEvent>("myapi", e =>
{
Debug.WriteLine($"重试 {e.AttemptNumber}: {e.RequestUri}");
});
// 订阅所有客户端的熔断事件
var allSubscription = _manager.Subscribe<CircuitBreakerEvent>(e =>
{
Debug.WriteLine($"[{e.ClientName}] 熔断器状态: {e.OldState} → {e.NewState}");
});
// 使用 using 自动取消订阅
using var tempSubscription = _manager.Subscribe<RetryEvent>("myapi", HandleRetry);
// 手动取消订阅
subscription.Dispose();
弱引用订阅(避免内存泄漏)
// 使用弱引用,当订阅者被 GC 时自动失效
_manager.Subscribe<RetryEvent>("myapi", OnRetry, useWeakReference: true);
// 即使忘记调用 Dispose(),也不会造成内存泄漏
事件类型
// 重试事件
public class RetryEvent : HttpClientEvent
{
public int AttemptNumber { get; init; } // 重试次数
public Exception? Exception { get; init; } // 异常信息
public TimeSpan Delay { get; init; } // 延迟时间
public Uri? RequestUri { get; init; } // 请求 URI
public int? StatusCode { get; init; } // 状态码
}
// 熔断器事件
public class CircuitBreakerEvent : HttpClientEvent
{
public CircuitState OldState { get; init; } // 旧状态
public CircuitState NewState { get; init; } // 新状态
public Exception? Exception { get; init; } // 异常信息
public string? Reason { get; init; } // 原因
}
// 限流事件
public class RateLimiterEvent : HttpClientEvent
{
public bool IsRejected { get; init; } // 请求是否被拒绝
public Uri? RequestUri { get; init; } // 请求 URI(如果可用)
}
消息处理器
内置处理器
using Sage.Http.Handlers;
// 日志处理器(可通过 DI 构造)
options.AddHandler<LoggingHandler>();
// 关联 ID(用于分布式追踪)— 建议使用工厂重载
options.AddHandler(_ => new CorrelationIdHandler());
// Bearer Token 认证 — 使用异步 Token 提供器
options.AddHandler(_ => new BearerTokenHandler(GetTokenAsync));
// 自定义请求头 — 使用工厂提供字典
options.AddHandler(_ => new CustomHeaderHandler(new Dictionary<string, string>
{
["X-API-Key"] = "your-api-key",
["X-Custom-Header"] = "custom-value"
}));
// User-Agent — 支持字符串或产品名+版本
options.AddHandler(_ => new UserAgentHandler("MyApp", "1.0.0"));
#### 为何推荐工厂重载
- 防止 Standalone 模式下处理器实例被多个客户端复用导致并发问题。
- 便于按客户端/环境注入不同参数(如 Header、策略、密钥)。
- 与 DI 生命周期对齐:每次添加时可获得最新的依赖实例。
- 支持需要运行时参数的处理器(例如从配置或服务获取值)。
- 与过时的实例重载区分,避免后续升级引入行为差异。
#### 何时使用泛型 vs 工厂
- 使用 `AddHandler<THandler>()`:处理器构造函数完全由 DI 解析,且无自定义运行时参数。
- 使用 `AddHandler(factory)`:需要传入运行时参数或显式控制实例创建,或在 Standalone 模式中无 DI 时。
示例:在 Standalone 模式使用日志处理器
```csharp
using Microsoft.Extensions.Logging;
var loggerFactory = LoggerFactory.Create(b =>
{
b.AddConsole();
b.SetMinimumLevel(LogLevel.Information);
});
var logger = loggerFactory.CreateLogger<LoggingHandler>();
options.AddHandler(_ => new LoggingHandler(logger));
示例:在 DI 模式使用日志处理器(推荐)
// 由 DI 注入 ILogger<LoggingHandler>
options.AddHandler<LoggingHandler>();
#### 自定义处理器
```csharp
public class ApiKeyHandler : DelegatingHandler
{
private readonly string _apiKey;
public ApiKeyHandler(string apiKey)
{
_apiKey = apiKey;
}
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
request.Headers.Add("X-API-Key", _apiKey);
return base.SendAsync(request, cancellationToken);
}
}
// 使用(建议使用工厂重载)
options.AddHandler(_ => new ApiKeyHandler("your-api-key"));
拦截器
// 仅拦截请求(同步)
options.AddRequestInterceptor(req =>
{
req.Headers.Add("X-Trace-Id", Activity.Current?.Id ?? Guid.NewGuid().ToString());
});
// 异步拦截响应并记录耗时
options.AddResponseInterceptorAsync(async (res, ct) =>
{
var elapsed = res.Headers.TryGetValues("X-Elapsed", out var values)
? values.FirstOrDefault()
: null;
Debug.WriteLine($"Response {res.StatusCode}, Elapsed={elapsed}");
});
// 统一处理异常
options.AddErrorInterceptor(ex =>
{
Debug.WriteLine($"HTTP 错误: {ex.Message}");
});
选择处理器还是拦截器
- 处理器适合严格控制管道顺序、与重试/熔断策略协同。
- 拦截器更轻量,便于观测与简单变更,不依赖 DI。
- 二者可并存,但应避免重复记录日志;尽量选择一种方案。
- 无 DI 的 Standalone 模式建议用拦截器进行日志;DI 模式优先
LoggingHandler。 - 如需统一在一个位置处理请求、响应、异常,拦截器更直观。
示例:使用拦截器实现日志(替代 LoggingHandler)
options.AddInterceptor(
onRequest: req => Debug.WriteLine($"-> {req.Method} {req.RequestUri}"),
onResponse: res => Debug.WriteLine($"<- {(int)res.StatusCode} {res.ReasonPhrase}"),
onError: ex => Debug.WriteLine($"!! {ex.Message}")
);
提示:若同时启用 LoggingHandler 与上述拦截器日志,可能出现重复日志,请根据需要选择其一。
JSON 配置
appsettings.json
{
"HttpClients": {
"github": {
"BaseAddress": "https://api.github.com",
"Timeout": "00:00:30",
"HttpVersion": "2.0",
"DefaultHeaders": {
"User-Agent": "MyApp/1.0",
"Accept": "application/vnd.github.v3+json"
},
"Resilience": {
"EnableRetry": true,
"Retry": {
"MaxRetryAttempts": 3,
"BaseDelay": "00:00:02",
"BackoffType": "Exponential",
"UseJitter": true,
"RetryOnStatusCodes": [408, 429, 500, 502, 503, 504]
},
"EnableCircuitBreaker": true,
"CircuitBreaker": {
"FailureRatio": 0.5,
"SamplingDuration": "00:00:30",
"MinimumThroughput": 10,
"BreakDuration": "00:01:00"
},
"EnableEvents": true
}
}
}
}
加载配置
// 从配置文件加载
var config = builder.Configuration
.GetSection("HttpClients:github")
.Get<HttpClientOptions>();
builder.Services.AddHttpClient("github", config);
🎯 使用场景
场景1:调用第三方 API
builder.Services.AddHttpClient("weather", options =>
{
options.BaseAddress = new Uri("https://api.weatherapi.com");
options.AddDefaultHeader("X-API-Key", configuration["WeatherApiKey"]);
options.UseRetryPolicy(RetryPolicy.Default);
options.AddHandler<LoggingHandler>();
});
public class WeatherService
{
private readonly IHttpClientManager _manager;
public async Task<Weather> GetWeatherAsync(string city)
{
var client = _manager.GetClient("weather");
return await client.GetFromJsonAsync<Weather>($"/current.json?q={city}");
}
}
场景2:微服务间调用
builder.Services.AddHttpClient("order-service", options =>
{
options.BaseAddress = new Uri("http://order-service:8080");
// 配置重试(考虑网络波动)
options.ConfigureRetry(retry =>
{
retry.MaxRetryAttempts = 5;
retry.RetryOnStatusCodes = new HashSet<int> { 408, 503, 504 };
});
// 配置熔断(防止雪崩)
options.ConfigureCircuitBreaker(cb =>
{
cb.FailureRatio = 0.3;
cb.BreakDuration = TimeSpan.FromSeconds(30);
});
// 添加服务间认证(工厂重载)
options.AddHandler(_ => new BearerTokenHandler(() => GetServiceTokenAsync()));
// 添加追踪 ID(工厂重载)
options.AddHandler(_ => new CorrelationIdHandler());
});
场景3:批量下载(限流)
builder.Services.AddHttpClient("downloader", options =>
{
options.Timeout = TimeSpan.FromMinutes(5);
// 限制并发下载数
options.ConfigureRateLimiter(limiter =>
{
limiter.PermitLimit = 5; // 同时最多 5 个请求
limiter.QueueLimit = 20; // 队列最多 20 个
});
});
public class DownloadService
{
public async Task DownloadFilesAsync(List<string> urls)
{
var client = _manager.GetClient("downloader");
var tasks = urls.Select(url => DownloadFileAsync(client, url));
await Task.WhenAll(tasks);
}
private async Task DownloadFileAsync(HttpClient client, string url)
{
var response = await client.GetAsync(url);
var content = await response.Content.ReadAsByteArrayAsync();
// 保存文件...
}
}
🔍 高级用法
动态创建客户端
// 运行时动态创建客户端(不需要预注册)
var client = _manager.CreateClient("temp", options =>
{
options.BaseAddress = new Uri("https://dynamic-api.com");
options.Timeout = TimeSpan.FromSeconds(10);
});
var result = await client.GetStringAsync("/data");
多环境配置
// appsettings.Development.json
{
"HttpClients": {
"api": {
"BaseAddress": "https://dev-api.example.com",
"Resilience": {
"EnableRetry": false // 开发环境禁用重试,便于调试
}
}
}
}
// appsettings.Production.json
{
"HttpClients": {
"api": {
"BaseAddress": "https://api.example.com",
"Resilience": {
"EnableRetry": true,
"EnableCircuitBreaker": true
}
}
}
}
条件订阅
// 只在开发环境订阅事件
if (builder.Environment.IsDevelopment())
{
_manager.Subscribe<RetryEvent>(e =>
{
Debug.WriteLine($"[DEV] Retry: {e.ClientName} - {e.RequestUri}");
});
}
📊 性能优化建议
- 使用
GetClient而非CreateClient(如果客户端配置固定) - 启用 HTTP/2(默认已启用)
- 合理配置超时和重试次数
- 使用弱引用订阅(长期运行的应用)
- 避免在循环中创建临时客户端
🐛 故障排查
常见问题
Q: 订阅的事件没有触发?
// 确保在配置中启用了事件
options.Resilience = new ResilienceOptions
{
EnableEvents = true, // 必须设置为 true
EnableRetry = true
};
Q: 重试没有生效?
// 检查状态码是否在重试列表中
options.ConfigureRetry(retry =>
{
retry.RetryOnStatusCodes = new HashSet<int> { 500, 502, 503 };
// 确保包含你遇到的状态码
});
Q: 内存泄漏?
// 确保在窗体关闭时释放资源
protected override void OnFormClosing(FormClosingEventArgs e)
{
_subscription?.Dispose();
_manager?.Dispose();
base.OnFormClosing(e);
}
// 或使用弱引用订阅
_manager.Subscribe<RetryEvent>("api", OnRetry, useWeakReference: true);
📝 完整示例项目
参考 /Examples 目录下的完整示例:
- AspNetCoreExample - ASP.NET Core Web API 示例
- WinFormsExample - WinForms 桌面应用示例
- DebugExample - 控制台应用示例
🔗 相关链接
许可证
MIT License - 详见 LICENSE 文件。
贡献
欢迎提交 Issue 和 Pull Request!
更新日志
详见 CHANGELOG.md。
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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 was computed. 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. |
-
net9.0
- Microsoft.Extensions.DependencyInjection (>= 9.0.10)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.10)
- Microsoft.Extensions.Http (>= 9.0.10)
- Microsoft.Extensions.Http.Resilience (>= 9.10.0)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.10)
- Microsoft.Extensions.Options (>= 9.0.10)
- System.Text.Json (>= 9.0.10)
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.11 | 104 | 1/14/2026 | |
| 2.0.0.10 | 150 | 12/12/2025 | |
| 2.0.0.9 | 440 | 11/18/2025 | |
| 2.0.0.8 | 225 | 10/27/2025 | |
| 2.0.0.7 | 142 | 10/25/2025 | |
| 2.0.0.6 | 149 | 10/25/2025 | |
| 2.0.0.5 | 145 | 10/25/2025 | |
| 2.0.0.4 | 138 | 10/25/2025 | |
| 2.0.0.3 | 150 | 10/25/2025 | |
| 2.0.0.2 | 178 | 10/24/2025 | |
| 2.0.0.1 | 210 | 10/23/2025 | |
| 2.0.0 | 211 | 10/23/2025 | |
| 1.0.1.25 | 209 | 10/19/2025 | |
| 1.0.1.24 | 211 | 10/19/2025 | |
| 1.0.1.23 | 292 | 10/19/2025 | |
| 1.0.1.22 | 294 | 10/19/2025 | |
| 1.0.1.21 | 298 | 10/19/2025 | |
| 1.0.1.20 | 300 | 10/19/2025 | |
| 1.0.1.19 | 209 | 10/15/2025 | |
| 1.0.1.18 | 208 | 10/13/2025 |
重构版本,回归原生HttpClient用法,简化配置增强功能。