Sage.Http
1.0.1.21
非DI构建时存在一些瑕疵,如果在WinForm或WPF中使用,请更新到最新版本。
See the version list below for details.
dotnet add package Sage.Http --version 1.0.1.21
NuGet\Install-Package Sage.Http -Version 1.0.1.21
<PackageReference Include="Sage.Http" Version="1.0.1.21" />
<PackageVersion Include="Sage.Http" Version="1.0.1.21" />
<PackageReference Include="Sage.Http" />
paket add Sage.Http --version 1.0.1.21
#r "nuget: Sage.Http, 1.0.1.21"
#:package Sage.Http@1.0.1.21
#addin nuget:?package=Sage.Http&version=1.0.1.21
#tool nuget:?package=Sage.Http&version=1.0.1.21
Sage.Http
一个功能强大、易于使用的 .NET HTTP 客户端库,提供流式 API、弹性处理、认证管理、进度跟踪等企业级功能。
📋 系统要求
- 支持 Windows、Linux、macOS
🚀 核心特性
🔧 核心功能
- 流式 API 设计 - 链式调用,代码更简洁
- 工厂模式管理 - 统一的 HttpClient 生命周期管理
- 依赖注入支持 - 完整的 DI 容器集成
🛡️ 弹性处理
- 智能重试机制 - 指数退避策略,可配置重试条件
- 熔断器模式 - 防止级联故障,自动恢复检测
- 超时控制 - 灵活的请求超时管理
- 详细日志记录 - 完整的请求/响应日志追踪
🔐 认证与安全
- 多种认证方式 - Bearer Token、Basic Auth、自定义认证
- 认证提供者模式 - 可扩展的认证架构
- 安全最佳实践 - 防止敏感信息泄露
📊 进度与监控
- 上传/下载进度 - 实时进度报告
- 流式数据处理 - 支持 Server-Sent Events (SSE)
- 性能监控 - 内置性能指标收集
🎯 高级功能
- JSON 序列化优化 - AOT 友好,支持源生成
- 文件操作增强 - 多文件上传,进度跟踪
- 响应缓存 - 智能缓存策略
- 请求管道定制 - 可插拔的消息处理器
📦 安装
dotnet add package Sage.Http
🏗️ 快速开始
依赖注入环境配置
using Sage.Http.Extensions;
using Microsoft.Extensions.DependencyInjection;
// 基础注册
services.AddHttpRequestManagerFactory();
// 配置 JSON 序列化选项
services.AddHttpRequestManagerFactory(JsonConfiguration.SafeApiOptions);
// 配置 JSON 序列化上下文(AOT 友好)
services.AddHttpRequestManagerFactory(ShopManagerModelsJsonSerializerContext.Default);
// 同时配置选项和上下文
services.AddHttpRequestManagerFactory(
JsonConfiguration.ChineseOptions,
ShopManagerModelsJsonSerializerContext.Default
);
非依赖注入环境配置(WinForms、WPF等)
using Sage.Http.Factory;
using Sage.Http.Extensions;
using Microsoft.Extensions.Logging;
using Sage.Http.Options;
// 方式一:使用静态工厂方法创建实例
var factory = HttpRequestManagerFactory.Create();
// 使用日志工厂(可选)
var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
var factoryWithLogger = HttpRequestManagerFactory.Create(loggerFactory);
// 方式二:直接实例化工厂(等同于静态方法)
var factory2 = new HttpRequestManagerFactory(new DefaultHttpClientFactory());
var factoryWithLogger2 = new HttpRequestManagerFactory(new DefaultHttpClientFactory(), loggerFactory);
// 配置并构建 HttpRequestManager
var httpRequestManager = factory.ConfigureHttpRequestManager("api")
.WithBaseAddress("https://api.example.com")
.WithDefaultTimeout(30) // 30秒超时
.WithDefaultRetry(3, 1000) // 最多重试3次,初始延迟1秒
.WithDefaultCircuitBreaker(5, 30000) // 5次失败后熔断,30秒后尝试恢复
.WithDefaultLogging(true, false) // 启用日志,记录请求体但不记录响应体
.Build();
// 使用默认弹性策略(超时、重试、熔断)的简化配置
var simpleManager = factory.ConfigureHttpRequestManager("simple")
.WithBaseAddress("https://api.example.com")
.WithDefaultResilience() // 添加默认的弹性策略
.Build();
### 详细策略配置
除了使用默认策略外,您还可以根据需要自定义各种策略的详细配置:
```csharp
// 自定义重试策略
var retryOptions = new RetryOptions
{
MaxRetryCount = 5, // 最大重试次数
InitialDelayMilliseconds = 200, // 初始延迟(毫秒)
UseExponentialBackoff = true, // 使用指数退避算法
MaxDelayMilliseconds = 5000, // 最大延迟(毫秒)
RetryableStatusCodes = new[] { HttpStatusCode.RequestTimeout, HttpStatusCode.ServiceUnavailable }
};
// 自定义熔断策略
var circuitBreakerOptions = new CircuitBreakerOptions
{
FailureThreshold = 3, // 失败阈值
RecoveryTimeMilliseconds = 15000, // 恢复时间(毫秒)
MinimumThroughput = 10, // 最小吞吐量
FailureStatusCodes = new[] { HttpStatusCode.InternalServerError, HttpStatusCode.BadGateway }
};
// 自定义日志策略
var loggingOptions = new LoggingOptions
{
Enabled = true, // 启用日志
LogRequestHeaders = true, // 记录请求头
LogRequestBody = true, // 记录请求体
LogResponseHeaders = true, // 记录响应头
LogResponseBody = false, // 不记录响应体(可能很大)
SensitiveHeaders = new[] { "Authorization", "Cookie" } // 敏感头(将被遮蔽)
};
// 应用自定义策略
var customManager = factory.ConfigureHttpRequestManager("custom")
.WithBaseAddress("https://api.example.com")
.WithTimeout(TimeSpan.FromSeconds(45))
.WithRetry(retryOptions)
.WithCircuitBreaker(circuitBreakerOptions)
.WithLogging(loggingOptions)
.Build();
基本使用
using Sage.Http.Core;
using Sage.Http.Factory;
// 通过工厂创建管理器
var factory = serviceProvider.GetRequiredService<IHttpRequestManagerFactory>();
var manager = factory.GetHttpRequestManager("api-client");
// 简单 GET 请求
var response = await manager
.CreateRequest(HttpMethod.Get, "/users")
.SendAsync();
// JSON 反序列化
var users = await manager.GetFromJsonAsync<User[]>("/users");
📚 详细功能指南
1. 流式 API 构建请求
// 复杂请求构建
var response = await manager
.CreateRequest(HttpMethod.Post, "/api/users")
.WithHeader("X-API-Version", "2.0")
.WithBearerToken("your-jwt-token")
.WithJsonContent(new CreateUserRequest
{
Name = "John Doe",
Email = "john@example.com"
})
.WithTimeout(TimeSpan.FromSeconds(30))
.SendAsync();
// 查询参数
var users = await manager
.CreateRequest(HttpMethod.Get, "/api/users")
.WithQueryParameter("page", 1)
.WithQueryParameter("size", 20)
.WithQueryParameter("status", "active")
.SendAsync<PagedResult<User>>();
2. 认证管理
Bearer Token 认证
using Sage.Http.Authentication;
using Sage.Http.Extensions;
// 方式1:使用扩展方法
var response = await manager
.CreateRequest(HttpMethod.Get, "/protected")
.WithBearerToken("your-jwt-token")
.SendAsync();
// 方式2:使用认证提供者
var authProvider = new BearerTokenAuthProvider("your-jwt-token");
var response = await manager
.CreateRequest(HttpMethod.Get, "/protected")
.WithAuthentication(authProvider)
.SendAsync();
// 方式3:直接在 HttpClient 上设置
httpClient.AddBearerToken("your-jwt-token");
自定义认证
public class ApiKeyAuthProvider : IAuthenticationProvider
{
private readonly string _apiKey;
public ApiKeyAuthProvider(string apiKey)
{
_apiKey = apiKey;
}
public Task AuthenticateRequest(HttpRequestMessage request)
{
request.Headers.Add("X-API-Key", _apiKey);
return Task.CompletedTask;
}
}
// 使用自定义认证
var authProvider = new ApiKeyAuthProvider("your-api-key");
var response = await manager
.CreateRequest(HttpMethod.Get, "/api/data")
.WithAuthentication(authProvider)
.SendAsync();
3. JSON 操作
基础 JSON 操作
// GET 请求并反序列化
var user = await manager.GetFromJsonAsync<User>("/api/users/123");
// POST JSON 数据
var newUser = new CreateUserRequest { Name = "Jane", Email = "jane@example.com" };
var createdUser = await manager.PostAsJsonAsync<CreateUserRequest, User>(
"/api/users",
newUser
);
// PUT 更新
var updatedUser = await manager.PutAsJsonAsync<User, User>(
"/api/users/123",
user
);
// DELETE 请求
await manager.DeleteAsync("/api/users/123");
AOT 友好的 JSON 操作
// 定义 JSON 序列化上下文(AOT 支持)
[JsonSerializable(typeof(User))]
[JsonSerializable(typeof(User[]))]
[JsonSerializable(typeof(CreateUserRequest))]
public partial class ApiJsonContext : JsonSerializerContext { }
// 使用类型信息进行序列化
var users = await manager.GetFromJsonAsync("/api/users", ApiJsonContext.Default.UserArray);
var newUser = new CreateUserRequest { Name = "John" };
var response = await manager.PostAsJsonAsync(
"/api/users",
newUser,
ApiJsonContext.Default.CreateUserRequest
);
4. 文件操作
文件上传
// 单文件上传
using var fileStream = File.OpenRead("document.pdf");
var response = await manager
.CreateRequest(HttpMethod.Post, "/api/files")
.WithFileContent(fileStream, "document.pdf", "application/pdf")
.SendAsync();
// 多文件上传
var files = new[]
{
("file1", File.OpenRead("image1.jpg"), "image1.jpg", "image/jpeg"),
("file2", File.OpenRead("image2.png"), "image2.png", "image/png")
};
var response = await manager
.CreateRequest(HttpMethod.Post, "/api/files/batch")
.WithMultipartContent(content =>
{
foreach (var (name, stream, fileName, contentType) in files)
{
content.Add(new StreamContent(stream), name, fileName);
}
})
.SendAsync();
带进度的文件上传
var progress = new Progress<float>(percentage =>
{
Console.WriteLine($"上传进度: {percentage:P2}");
});
using var fileStream = File.OpenRead("large-file.zip");
var response = await manager
.CreateRequest(HttpMethod.Post, "/api/files/upload")
.WithFileContent(fileStream, "large-file.zip")
.WithUploadProgress(progress)
.SendAsync();
上传单个文件
// 从流创建文件参数
using var fileStream = File.OpenRead("file.pdf");
var fileParam = new FileParameter(fileStream, "file.pdf", "file", "application/pdf");
// 发送请求
var response = await manager.CreateRequest(HttpMethod.Post, "upload")
.WithFile(fileParam)
.SendAsync();
上传多个文件
// 创建多个文件参数,指定不同的字段名
var files = new List<FileParameter>
{
new FileParameter(File.OpenRead("file1.pdf"), "file1.pdf", "file1"),
new FileParameter(File.OpenRead("file2.jpg"), "file2.jpg", "file2", "image/jpeg")
};
// 发送请求
var response = await manager.CreateRequest(HttpMethod.Post, "upload")
.WithFiles(files)
.SendAsync();
从文件路径添加文件
// 直接从文件路径添加文件,自动检测 MIME 类型
var response = await manager.CreateRequest(HttpMethod.Post, "upload")
.AddFileFromPath("file.pdf", "file")
.SendAsync();
文件下载
使用 DownloadAsync 方法(推荐)
// 下载到文件
var progress = new Progress<float>(p => Console.WriteLine($"下载进度: {p * 100}%"));
long bytesDownloaded = await manager.DownloadAsync(
HttpMethod.Get,
"files/document.pdf",
"downloaded-document.pdf",
progress);
// 下载到流
using var stream = new MemoryStream();
long bytesDownloaded = await manager.DownloadAsync(
HttpMethod.Get,
"files/document.pdf",
stream,
progress);
使用 HttpClient 扩展方法
// 下载到文件
var progress = new Progress<float>(p => Console.WriteLine($"下载进度: {p * 100}%"));
long bytesDownloaded = await HttpClientExtensions.DownloadFileAsync(
httpClient,
"files/document.pdf",
"downloaded-document.pdf",
progress);
// 下载到流
using var stream = new MemoryStream();
long bytesDownloaded = await HttpClientExtensions.DownloadToStreamAsync(
httpClient,
"files/document.pdf",
stream,
progress);
请求进度跟踪
// 创建进度报告
var progress = new Progress<float>(p => Console.WriteLine($"上传进度: {p * 100}%"));
// 上传文件并跟踪进度
using var fileStream = File.OpenRead("large-file.zip");
var fileParam = new FileParameter(fileStream, "large-file.zip");
var response = await manager.CreateRequest(HttpMethod.Post, "upload")
.WithFile(fileParam)
.WithProgress(progress) // 设置进度报告
.SendAsync();
下载进度
// 创建进度报告
var progress = new Progress<float>(p => Console.WriteLine($"下载进度: {p * 100}%"));
// 下载文件并跟踪进度
await manager.DownloadAsync(
HttpMethod.Get,
"files/large-file.zip",
"downloaded-large-file.zip",
progress);
Server-Sent Events (SSE)
// 处理 Server-Sent Events
await foreach (var eventData in manager.GetStreamingAsync<string>(HttpMethod.Get, "events"))
{
Console.WriteLine($"收到事件: {eventData}");
}
响应扩展方法
// 获取响应的字符串内容
string content = await response.Content.ReadAsStringAsync();
// 获取响应头信息
var headers = response.Headers;
foreach (var header in headers)
{
Console.WriteLine($"{header.Key}: {string.Join(", ", header.Value)}");
}
JSON 序列化/反序列化
全局 JSON 配置
// 方式1:配置全局 JSON 序列化选项
HttpRequestManager.ConfigureJsonOptions(JsonConfiguration.SafeApiOptions);
// 方式2:配置全局 JSON 序列化上下文(AOT 推荐)
HttpRequestManager.ConfigureJsonContext(ShopManagerModelsJsonSerializerContext.Default);
HttpRequestManager.ConfigureJsonOptions(JsonConfiguration.SafeApiOptions); // 仍需配置选项以获得完整功能
// 方式3:通过依赖注入配置
services.AddHttpRequestManagerFactory(JsonConfiguration.ChineseOptions);
services.AddHttpRequestManagerFactory(ShopManagerModelsJsonSerializerContext.Default);
发送 JSON 请求
// 发送 JSON 请求(使用全局配置)
var user = new User { Name = "John", Email = "john@example.com" };
var response = await manager.CreateRequest(HttpMethod.Post, "users")
.WithJsonContent(user)
.SendAsync();
// 发送 JSON 请求(指定 JsonTypeInfo,AOT 友好)
var response = await manager.CreateRequest(HttpMethod.Post, "users")
.WithJsonContent(user, ShopManagerModelsJsonSerializerContext.Default.User)
.SendAsync();
接收 JSON 响应
// 使用全局配置
var users = await manager.GetFromJsonAsync<User[]>("/users");
// 指定 JsonTypeInfo(AOT 友好)
var users = await manager.GetFromJsonAsync("/users", ShopManagerModelsJsonSerializerContext.Default.UserArray);
错误处理
try
{
var response = await manager
.CreateRequest(HttpMethod.Get, "/api/users/999")
.SendAsync();
response.EnsureSuccessStatusCode();
var user = await response.Content.ReadFromJsonAsync<User>();
}
catch (HttpRequestException ex) when (ex.Data.Contains("StatusCode"))
{
var statusCode = (HttpStatusCode)ex.Data["StatusCode"];
switch (statusCode)
{
case HttpStatusCode.NotFound:
Console.WriteLine("用户不存在");
break;
case HttpStatusCode.Unauthorized:
Console.WriteLine("认证失败");
break;
default:
Console.WriteLine($"请求失败: {statusCode}");
break;
}
}
9. 日志记录
配置日志
// 配置熔断器选项
var circuitBreakerOptions = new CircuitBreakerOptions
{
Enabled = true,
FailureThreshold = 5, // 触发熔断的失败次数阈值
RecoveryTimeMs = 30000, // 熔断后的恢复时间(毫秒)
HalfOpenMaxRequests = 3 // 半开状态下允许的最大请求数
};
// 配置日志选项
var loggingOptions = new LoggingOptions
{
LogTiming = true,
LogRequestBody = false,
LogResponseBody = false
};
var loggingOptions = new LoggingOptions
// 添加带有熔断器的 HTTP 客户端
services.AddHttpClientWithResilience(
"resilient-client",
client => client.BaseAddress = new Uri("https://api.example.com"),
circuitBreakerOptions: circuitBreakerOptions,
loggingOptions: loggingOptions);
重试机制
重试机制可以自动重试失败的请求,使用指数退避策略避免对服务造成额外负担。
// 配置重试选项
var retryOptions = new RetryOptions
{
Enabled = true,
MaxRetryCount = 3, // 最大重试次数
InitialDelayMs = 1000, // 初始延迟(毫秒)
BackoffMultiplier = 2.0, // 退避乘数
MaxDelayMs = 30000, // 最大延迟(毫秒)
RetryStatusCodes = [408, 429, 500, 502, 503, 504] // 需要重试的状态码
};
// 配置日志选项
var loggingOptions = new LoggingOptions
{
LogTiming = true,
LogRequestBody = true, // 开启请求体记录用于调试重试
LogResponseBody = true // 开启响应体记录用于调试重试
};
// 添加带有重试机制的 HTTP 客户端
services.AddHttpClientWithResilience(
"resilient-client",
client => client.BaseAddress = new Uri("https://api.example.com"),
retryOptions: retryOptions,
loggingOptions: loggingOptions);
超时控制
超时控制可以防止请求长时间挂起,确保系统资源得到及时释放。
// 配置默认超时
var defaultTimeout = TimeSpan.FromSeconds(30);
// 配置日志选项
var loggingOptions = new LoggingOptions
{
LogTiming = true, // 超时场景下记录时间特别重要
LogRequestBody = false,
LogResponseBody = false
};
// 添加带有超时控制的 HTTP 客户端
services.AddHttpClientWithResilience(
"resilient-client",
client => client.BaseAddress = new Uri("https://api.example.com"),
defaultTimeout: defaultTimeout,
loggingOptions: loggingOptions);
// 为单个请求设置超时
var request = new HttpRequestMessage(HttpMethod.Get, "slow-endpoint");
request.Options.Set(new HttpRequestOptionsKey<TimeSpan>("RequestTimeout"), TimeSpan.FromSeconds(60));
启用 Body 日志记录
默认情况下,为了性能和安全考虑,请求和响应体的日志记录是禁用的。如果需要启用:
// 配置日志选项以启用 Body 记录
var loggingOptions = new LoggingOptions
{
LogRequestBody = true, // 记录请求体
LogResponseBody = true, // 记录响应体
MaxContentLength = 1024 // 限制记录的内容长度
};
// 使用日志配置创建 HTTP 客户端
services.AddHttpClientWithResilience("logged-client",
client => client.BaseAddress = new Uri("https://api.example.com/"),
loggingOptions: loggingOptions);
日志记录配置
默认日志处理器
AddHttpClientWithResilience 默认会将 LoggingHandler 插入管道,并使用 LoggingOptions 的默认值:
services.AddHttpClientWithResilience(
"resilient-client",
client => client.BaseAddress = new Uri("https://api.example.com"));
// 默认开启请求/响应与头部记录、耗时与请求ID;请求/响应体默认不记录
🎯 敏感头部屏蔽:SensitiveHeaders 中列出的头部信息会被自动屏蔽为 *****,保护敏感信息不被泄露。
自定义 LoggingOptions
如需自定义日志策略,可手动配置 HttpClient 管道并传入 LoggingOptions:
var loggingOptions = new LoggingOptions
{
LogRequests = true,
LogResponses = true,
LogRequestHeaders = true,
LogResponseHeaders = true,
LogRequestBody = false, // 默认不记录请求体
LogResponseBody = false, // 默认不记录响应体
MaxContentLength = 1024, // 最大内容长度
SensitiveHeaders = ["Authorization", "X-API-Key"], // 敏感头部
LogTiming = true, // 记录请求耗时
LogRequestId = true // 记录请求ID
};
services.AddHttpClientWithResilience(
"api-client",
client => client.BaseAddress = new Uri("https://api.example.com"),
loggingOptions: loggingOptions);
手动配置 LoggingHandler
如需更精细的控制,可以手动配置 LoggingHandler:
var loggingOptions = new LoggingOptions
{
LogRequestHeaders = true,
LogResponseHeaders = true,
LogRequestBody = false, // 谨慎开启,可能影响性能
LogResponseBody = false, // 谨慎开启,可能影响性能
MaxContentLength = 8192, // 记录主体内容的最大长度(字节)
SensitiveHeaders = new(StringComparer.OrdinalIgnoreCase)
{
"Authorization", "Cookie", "X-API-Key"
},
LogTiming = true,
LogRequestId = true
};
// 使用工厂获取管理器
services.AddHttpClient("my-logged-client", c => c.BaseAddress = new Uri("https://api.example.com"))
.AddHttpMessageHandler(provider =>
{
var logger = provider.GetRequiredService<ILogger<LoggingHandler>>();
return new LoggingHandler(logger, loggingOptions);
})
.AddHttpMessageHandler(_ => new TimeoutHandler(TimeSpan.FromSeconds(30)))
.AddHttpMessageHandler(p => new RetryHandler(new RetryOptions(), p.GetRequiredService<ILoggerFactory>().CreateLogger("HttpClient.my-logged-client.RetryHandler")))
.AddHttpMessageHandler(p => new CircuitBreakerHandler(new CircuitBreakerOptions(), p.GetRequiredService<ILoggerFactory>().CreateLogger("HttpClient.my-logged-client.CircuitBreakerHandler")));
从配置文件绑定
也可以从配置文件绑定:
var options = configuration.GetSection("Http:Logging").Get<LoggingOptions>() ?? new LoggingOptions();
services.AddHttpClient("my-logged-client")
.AddHttpMessageHandler(provider => new LoggingHandler(provider.GetRequiredService<ILogger<LoggingHandler>>(), options));
LoggingOptions 配置参数详解
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
LogRequests |
bool |
true |
是否记录请求信息 |
LogResponses |
bool |
true |
是否记录响应信息 |
LogRequestHeaders |
bool |
true |
是否记录请求头信息 |
LogResponseHeaders |
bool |
true |
是否记录响应头信息 |
LogRequestBody |
bool |
false |
是否记录请求体内容(谨慎开启) |
LogResponseBody |
bool |
false |
是否记录响应体内容(谨慎开启) |
MaxContentLength |
int |
4096 |
最大内容长度限制(字节),超过将被截断 |
SensitiveHeaders |
HashSet<string> |
预设敏感头部 | 敏感头部名称列表,值将被屏蔽 |
LogTiming |
bool |
true |
是否记录请求和响应的时间信息 |
LogRequestId |
bool |
true |
是否记录请求的唯一标识符 |
BinaryDetection |
BinaryContentDetectionOptions |
默认配置 | 二进制内容检测配置选项 |
BinaryContentDetectionOptions 配置参数详解
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
EnableContentAnalysis |
bool |
true |
是否启用字节级内容分析 |
ContentAnalysisSampleSize |
int |
512 |
内容分析的采样大小(字节) |
ControlCharThreshold |
double |
0.3 |
控制字符比例阈值(0.0-1.0) |
HighBitCharThreshold |
double |
0.85 |
高位字符比例阈值(0.0-1.0) |
CustomBinaryTypes |
HashSet<string> |
空集合 | 自定义二进制 MIME 类型列表 |
CustomTextTypes |
HashSet<string> |
空集合 | 自定义文本 MIME 类型列表 |
配置文件示例 (appsettings.json)
public ApiService(IHttpRequestManagerFactory factory)
}
"Http": {
"Logging": {
"LogRequests": true,
"LogResponses": true,
"LogRequestHeaders": true,
"LogResponseHeaders": true,
"LogRequestBody": false,
"LogResponseBody": false,
"MaxContentLength": 8192,
"SensitiveHeaders": [
"Authorization",
"Cookie",
"Set-Cookie",
"X-API-Key",
"X-Auth-Token",
"Bearer",
"Proxy-Authorization"
],
"LogTiming": true,
"LogRequestId": true,
"BinaryDetection": {
"EnableContentAnalysis": true,
"ContentAnalysisSampleSize": 512,
"ControlCharThreshold": 0.3,
"HighBitCharThreshold": 0.85,
"CustomBinaryTypes": [
"application/x-custom-binary",
"application/x-proprietary-format"
],
"CustomTextTypes": [
"application/x-custom-text",
"text/x-special-format"
]
}
}
}
}
使用 AddHttpClientWithResilience 配置日志
// 从配置文件读取日志选项
var loggingOptions = configuration.GetSection("Http:Logging").Get<LoggingOptions>() ?? new LoggingOptions();
// 添加带有自定义日志配置的弹性 HTTP 客户端
services.AddHttpClientWithResilience(
"api-client",
client => client.BaseAddress = new Uri("https://api.example.com"),
loggingOptions: loggingOptions);
// 配置 JSON 序列化选项
services.AddHttpClientWithResilience(
"api-client",
client => client.BaseAddress = new Uri("https://api.example.com"),
jsonOptions: JsonConfiguration.SafeApiOptions);
// 配置 JSON 序列化上下文(AOT 友好)
services.AddHttpClientWithResilience(
"api-client",
client => client.BaseAddress = new Uri("https://api.example.com"),
jsonContext: ShopManagerModelsJsonSerializerContext.Default);
// 同时配置日志、JSON 选项和上下文
services.AddHttpClientWithResilience(
"api-client",
client => client.BaseAddress = new Uri("https://api.example.com"),
loggingOptions: loggingOptions,
jsonOptions: JsonConfiguration.ChineseOptions,
jsonContext: ShopManagerModelsJsonSerializerContext.Default);
二进制内容检测配置示例
// 配置二进制内容检测选项
var loggingOptions = new LoggingOptions
{
LogRequestBody = true,
LogResponseBody = true,
BinaryDetection = new BinaryContentDetectionOptions
{
EnableContentAnalysis = true, // 启用字节级分析
ContentAnalysisSampleSize = 1024, // 分析前 1024 字节
ControlCharThreshold = 0.2, // 控制字符比例阈值 20%
HighBitCharThreshold = 0.9, // 高位字符比例阈值 90%
// 添加自定义二进制类型
CustomBinaryTypes =
{
"application/x-custom-dll",
"application/x-proprietary-exe",
"application/x-special-binary"
},
// 添加自定义文本类型
CustomTextTypes =
{
"application/x-custom-config",
"text/x-special-log"
}
}
};
// 使用配置创建 HTTP 客户端
services.AddHttpClientWithResilience(
"file-api-client",
client => client.BaseAddress = new Uri("https://fileapi.example.com"),
loggingOptions: loggingOptions);
二进制内容检测效果示例
当处理不同类型的文件时,日志输出效果如下:
// 文本文件 - 正常记录内容
// 日志输出: Response Body: {"message": "Hello World", "status": "success"}
// 图片文件 - 显示占位符
// 日志输出: Response Body: [Binary content: image/jpeg, Size: 2048 bytes]
// DLL 文件 - 显示占位符
// 日志输出: Response Body: [Binary content: application/octet-stream, Size: 4096 bytes]
// 未知二进制文件 - 通过字节分析识别
// 日志输出: Response Body: [Binary content: application/octet-stream, Size: 1024 bytes]
// PDF 文件 - 显示占位符
// 日志输出: Response Body: [Binary content: application/pdf, Size: 8192 bytes]
高级配置:针对不同场景的优化
// 开发环境 - 详细日志,包含更多二进制检测
var developmentLogging = new LoggingOptions
{
LogRequestBody = true,
LogResponseBody = true,
MaxContentLength = 8192,
BinaryDetection = new BinaryContentDetectionOptions
{
EnableContentAnalysis = true,
ContentAnalysisSampleSize = 1024, // 更大的采样大小
ControlCharThreshold = 0.1, // 更敏感的检测
HighBitCharThreshold = 0.8
}
};
// 生产环境 - 性能优化,基础二进制检测
var productionLogging = new LoggingOptions
{
LogRequestBody = false,
LogResponseBody = false,
LogTiming = true,
BinaryDetection = new BinaryContentDetectionOptions
{
EnableContentAnalysis = false, // 禁用字节分析以提升性能
ContentAnalysisSampleSize = 256, // 更小的采样大小
ControlCharThreshold = 0.3,
HighBitCharThreshold = 0.85
}
};
// 文件上传/下载场景 - 专门优化
var fileTransferLogging = new LoggingOptions
{
LogRequestBody = false, // 不记录大文件内容
LogResponseBody = false,
LogTiming = true, // 记录传输时间
MaxContentLength = 512, // 限制内容长度
BinaryDetection = new BinaryContentDetectionOptions
{
EnableContentAnalysis = true,
ContentAnalysisSampleSize = 512,
CustomBinaryTypes =
{
"application/x-msdownload", // .exe 文件
"application/x-msdos-program", // .com 文件
"application/x-dosexec", // DOS 可执行文件
"application/vnd.microsoft.portable-executable" // PE 文件
}
}
};
通过工厂获取并使用带日志的管理器:
var factory = provider.GetRequiredService<IHttpRequestManagerFactory>();
var manager = factory.GetHttpRequestManager("my-logged-client");
var text = await manager.GetStringAsync(HttpMethod.Get, "users/1");
注意:开启
LogRequestBody/LogResponseBody会读取并缓冲内容,可能影响流式处理和性能;建议配合MaxContentLength与敏感头屏蔽一起使用。将LoggingHandler放在管道前部以记录完整的往返信息。
流式处理
处理大型 JSON 数据集
// 使用 GetStreamingAsync 处理大型 JSON 数据集
await foreach (var item in manager.GetStreamingAsync<DataItem>(HttpMethod.Get, "data/stream"))
{
// 处理每个数据项,无需等待整个响应加载到内存中
await ProcessItemAsync(item);
}
使用自定义反序列化器
// 创建自定义反序列化器
var deserializer = (string json) =>
{
try
{
return JsonSerializer.Deserialize<DataItem>(json);
}
catch (JsonException ex)
{
Console.WriteLine($"反序列化错误: {ex.Message}");
return null;
}
};
// 使用自定义反序列化器处理流式数据
await foreach (var item in manager.GetStreamingAsync(HttpMethod.Get, "data/stream", deserializer))
{
if (item != null)
{
await ProcessItemAsync(item);
}
}
API 参考
HttpRequestManager 核心方法
CreateRequest(HttpMethod, string)- 创建 HTTP 请求构建器CreateRequestWithOptions(HttpMethod, string, RequestOptions)- 使用选项创建请求构建器CreateJsonRequest(HttpMethod, string, RequestOptions)- 创建 JSON 请求构建器(用于 JSON 请求体)GetAsync(string)- 发送 GET 请求PostAsync(string, HttpContent)- 发送 POST 请求PutAsync(string, HttpContent)- 发送 PUT 请求DeleteAsync(string)- 发送 DELETE 请求GetStringAsync(string)- 发送 GET 请求并返回字符串GetStreamAsync(string)- 发送 GET 请求并返回流GetStreamingAsync<T>(HttpMethod, string)- 发送请求并返回流式数据DownloadAsync(HttpMethod, string, Stream)- 下载文件到指定流
📋 配置示例
完整配置文件
{
"HttpClients": {
"Shop": {
"BaseUrl": "https://api.shop.com",
"TimeoutSeconds": 30,
"Retry": {
"Enabled": true,
"MaxRetries": 3,
"InitialDelayMs": 500,
"BackoffMultiplier": 1.5,
"MaxDelayMs": 5000,
"RetryStatusCodes": [ 408, 429, 500, 502, 503, 504 ]
},
"CircuitBreaker": {
"Enabled": false
},
"Logging": {
"LogRequestBody": false,
"LogResponseBody": false,
"LogTiming": true,
"MaxContentLength": 4096
}
}
}
}
#### 配置绑定
```csharp
public class HttpClientSettings
{
public string BaseUrl { get; set; }
public int TimeoutSeconds { get; set; }
public RetryOptions Retry { get; set; }
public CircuitBreakerOptions CircuitBreaker { get; set; }
public LoggingOptions Logging { get; set; } // 新增日志配置
}
// 注册配置
services.Configure<HttpClientSettings>(
"Shop", configuration.GetSection("HttpClients:Shop"));
services.Configure<HttpClientSettings>(
"ApiClient", configuration.GetSection("HttpClients:ApiClient"));
// 使用配置创建客户端
services.AddHttpClientWithResilience("Shop", (serviceProvider, client) =>
{
var settings = serviceProvider.GetRequiredService<IOptionsSnapshot<HttpClientSettings>>()
.Get("Shop");
client.BaseAddress = new Uri(settings.BaseUrl);
client.Timeout = TimeSpan.FromSeconds(settings.TimeoutSeconds);
},
retryOptions: configuration.GetSection("HttpClients:Shop:Retry").Get<RetryOptions>(),
circuitBreakerOptions: configuration.GetSection("HttpClients:Shop:CircuitBreaker").Get<CircuitBreakerOptions>(),
loggingOptions: configuration.GetSection("HttpClients:Shop:Logging").Get<LoggingOptions>(),
jsonOptions: JsonConfiguration.SafeApiOptions,
jsonContext: ShopManagerModelsJsonSerializerContext.Default);
🧪 测试支持
单元测试
[Test]
public async Task GetUser_ReturnsUser_WhenUserExists()
{
// Arrange
var mockFactory = new Mock<IHttpRequestManagerFactory>();
var mockManager = new Mock<HttpRequestManager>();
mockFactory.Setup(f => f.GetHttpRequestManager("test-client"))
.Returns(mockManager.Object);
mockManager.Setup(m => m.GetFromJsonAsync<User>("/users/1"))
.ReturnsAsync(new User { Id = 1, Name = "Test User" });
var service = new UserService(mockFactory.Object);
// Act
var user = await service.GetUserAsync(1);
// Assert
Assert.That(user.Name, Is.EqualTo("Test User"));
}
集成测试
public class ApiIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
private readonly IHttpRequestManagerFactory _httpFactory;
public ApiIntegrationTests(WebApplicationFactory<Program> factory)
{
_factory = factory;
_httpFactory = _factory.Services.GetRequiredService<IHttpRequestManagerFactory>();
}
[Fact]
public async Task GetUsers_ReturnsUserList()
{
var manager = _httpFactory.GetHttpRequestManager("test-client");
var users = await manager.GetFromJsonAsync<User[]>("/api/users");
Assert.NotEmpty(users);
}
}
📊 性能指标
性能监控
Sage.Http 提供了内置的性能监控功能,可以帮助您跟踪和优化 HTTP 请求的性能。
// 启用性能监控
var loggingOptions = new LoggingOptions
{
LogTiming = true, // 记录请求耗时
LogRequestId = true // 记录请求ID用于追踪
};
services.AddHttpClientWithResilience("performance-client",
client => client.BaseAddress = new Uri("https://api.example.com"),
loggingOptions: loggingOptions);
🧪 测试支持
单元测试
[Test]
public async Task GetUser_ReturnsUser_WhenUserExists()
{
// Arrange
var mockFactory = new Mock<IHttpRequestManagerFactory>();
var mockManager = new Mock<HttpRequestManager>();
mockFactory.Setup(f => f.GetHttpRequestManager("test-client"))
.Returns(mockManager.Object);
mockManager.Setup(m => m.GetFromJsonAsync<User>("/users/1"))
.ReturnsAsync(new User { Id = 1, Name = "Test User" });
var service = new UserService(mockFactory.Object);
// Act
var user = await service.GetUserAsync(1);
// Assert
Assert.That(user.Name, Is.EqualTo("Test User"));
}
集成测试
public class ApiIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
private readonly IHttpRequestManagerFactory _httpFactory;
public ApiIntegrationTests(WebApplicationFactory<Program> factory)
{
_factory = factory;
_httpFactory = _factory.Services.GetRequiredService<IHttpRequestManagerFactory>();
}
[Fact]
public async Task GetUsers_ReturnsUserList()
{
var manager = _httpFactory.GetHttpRequestManager("test-client");
var users = await manager.GetFromJsonAsync<User[]>("/api/users");
Assert.NotEmpty(users);
}
}
📊 性能指标
- 内存使用: 相比原生 HttpClient 减少 ~15% 内存分配
- 响应时间: 平均响应时间提升 ~8%(得益于连接池优化)
- 错误恢复: 熔断器模式可将故障恢复时间减少 ~60%
- 开发效率: 流式 API 减少 ~40% 样板代码
🔄 迁移指南
从 HttpClient 迁移
// 原来的代码
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var json = JsonSerializer.Serialize(data);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync("/api/users", content);
// 迁移后的代码
var response = await manager
.CreateRequest(HttpMethod.Post, "/api/users")
.WithBearerToken(token)
.WithJsonContent(data)
.SendAsync();
🤝 贡献指南
我们欢迎社区贡献!请遵循以下步骤:
- Fork 项目
- 创建功能分支 (
git checkout -b feature/AmazingFeature) - 提交更改 (
git commit -m 'Add some AmazingFeature') - 推送到分支 (
git push origin feature/AmazingFeature) - 开启 Pull Request
开发环境设置
git clone https://gitcode.com/liupenglai/Sage.git
cd Sage/Sage.Http
dotnet restore
dotnet build
dotnet test
📄 许可证
本项目采用 Apache 2.0 许可证 - 查看 LICENSE 文件了解详情。
🆘 支持
- 📖 文档
- 🐛 问题报告
- 💬 讨论区
- 📧 邮件支持
- 📦 NuGet 包:全部的Sage.NET
Sage.Http - 让 HTTP 请求变得简单而强大 🚀
| 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 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.Http (>= 9.0.10)
-
net9.0
- Microsoft.Extensions.Http (>= 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 |
修复一个非DI构建器的操作