跳转到内容

HttpClientHelper

HttpClientHelper 是一个 HTTP 客户端封装类,为 HTTP 通信提供了丰富的功能,包括完整的 HTTP 方法支持、泛型返回类型、灵活的重试策略、多种认证方式、请求/响应拦截器、文件传输、批量请求处理、统计监控、Cookie 管理、SSL/TLS 配置、代理支持等,旨在简化 HTTP 通信的复杂性,提高开发效率和代码可维护性。

  • 完整的 HTTP 方法支持:GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS
  • 泛型返回类型:自动 JSON 反序列化,支持泛型返回类型
  • 灵活的重试策略:支持指数退避、自定义重试条件
  • 多种认证方式:Bearer Token、Basic Auth、API Key
  • 请求/响应拦截器:支持自定义拦截器链
  • 文件传输:支持文件上传下载,带进度回调
  • 批量请求处理:并行和顺序两种模式
  • 完整的统计监控:请求数、成功率、平均耗时等
  • Cookie 管理:内置 Cookie 容器支持
  • SSL/TLS 配置:支持自定义证书验证
  • 代理支持:支持 HTTP/HTTPS 代理配置
  • 完整的异常处理:统一的异常处理和日志记录
  • IDisposable 接口:支持资源释放
Terminal window
# 通过 NuGet 安装
dotnet add package Chet.Utils
using Chet.Utils.Helpers;
// 创建实例
using var httpClient = new HttpClientHelper();
// GET 请求
var user = await httpClient.GetAsync<User>("https://api.example.com/users/1");
Console.WriteLine($"用户名: {user.Name}");
// POST 请求
var result = await httpClient.PostJsonAsync<CreateUserRequest, CreateUserResponse>(
"https://api.example.com/users",
new CreateUserRequest { Name = "John", Email = "john@example.com" }
);
Console.WriteLine($"创建成功,ID: {result.Id}");
// 在 Startup.cs 或 Program.cs 中注册
services.AddSingleton<IHttpClientHelper, HttpClientHelper>();
// 在服务中使用
public class UserService
{
private readonly IHttpClientHelper _httpClient;
public UserService(IHttpClientHelper httpClient)
{
_httpClient = httpClient;
}
public async Task<User> GetUserAsync(int id)
{
return await _httpClient.GetAsync<User>($"https://api.example.com/users/{id}");
}
}
/// <summary>
/// 创建 HttpClientHelper 实例,使用默认配置
/// 默认超时为无限,建议使用 CancellationToken 控制具体请求超时
/// </summary>
var httpClient = new HttpClientHelper();
// 或带日志记录器
var httpClient = new HttpClientHelper(logger);
/// <summary>
/// 创建 HttpClientHelper 实例,使用自定义配置
/// </summary>
/// <param name="options">HTTP 客户端配置选项</param>
var httpClient = new HttpClientHelper(new HttpClientOptions
{
// 超时默认为无限,建议使用 CancellationToken 控制超时
// Timeout = Timeout.InfiniteTimeSpan, // 可选:设置全局超时作为兜底保护
MaxConnectionsPerServer = 200,
EnableAutomaticDecompression = true,
DefaultHeaders = new Dictionary<string, string>
{
{ "User-Agent", "MyApp/1.0" }
}
});
/// <summary>
/// 创建 HttpClientHelper 实例,使用自定义 HttpClient
/// </summary>
/// <param name="httpClient">自定义的 HttpClient 实例</param>
var customClient = new HttpClient(new HttpClientHandler
{
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
});
var httpClient = new HttpClientHelper(customClient);
// 返回 HttpResponseMessage
HttpResponseMessage response = await httpClient.GetAsync("https://api.example.com/users");
// 返回反序列化对象
User user = await httpClient.GetAsync<User>("https://api.example.com/users/1");
// 返回字符串
string content = await httpClient.GetStringAsync("https://api.example.com/users/1");
// 带自定义请求头
var headers = new Dictionary<string, string>
{
{ "X-Custom-Header", "value" }
};
var result = await httpClient.GetAsync<User>("https://api.example.com/users/1", headers);
// POST JSON 数据
var user = await httpClient.PostJsonAsync<CreateUserRequest, User>(
"https://api.example.com/users",
new CreateUserRequest { Name = "John", Email = "john@example.com" }
);
// POST 表单数据
var formData = new Dictionary<string, string>
{
{ "username", "john" },
{ "password", "secret" }
};
var result = await httpClient.PostFormAsync<LoginResponse>("https://api.example.com/login", formData);
// POST 原始内容
var content = new StringContent("{\"name\":\"John\"}", Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync("https://api.example.com/users", content);
// PUT JSON 数据
var updatedUser = await httpClient.PutJsonAsync<UpdateUserRequest, User>(
"https://api.example.com/users/1",
new UpdateUserRequest { Name = "John Updated" }
);
// PUT 原始内容
var content = new StringContent("{\"name\":\"John\"}", Encoding.UTF8, "application/json");
var response = await httpClient.PutAsync("https://api.example.com/users/1", content);
// DELETE 请求
var response = await httpClient.DeleteAsync("https://api.example.com/users/1");
// DELETE 并返回反序列化对象
var result = await httpClient.DeleteAsync<DeleteResult>("https://api.example.com/users/1");
// PATCH JSON 数据
var patchedUser = await httpClient.PatchJsonAsync<PatchUserRequest, User>(
"https://api.example.com/users/1",
new PatchUserRequest { Name = "Patched Name" }
);
// HEAD 请求(只获取响应头)
var response = await httpClient.HeadAsync("https://api.example.com/users/1");
var contentType = response.Content.Headers.ContentType;
var contentLength = response.Content.Headers.ContentLength;
// OPTIONS 请求(获取支持的 HTTP 方法)
var response = await httpClient.OptionsAsync("https://api.example.com/users");
var allowedMethods = response.Content.Headers.Allow;
// 基本重试(默认3次)
var result = await httpClient.GetWithRetryAsync<User>("https://api.example.com/users/1");
// 自定义重试次数
var result = await httpClient.GetWithRetryAsync<User>(
"https://api.example.com/users/1",
maxRetries: 5
);
// 带重试延迟
var result = await httpClient.GetWithRetryAsync<User>(
"https://api.example.com/users/1",
maxRetries: 3,
retryDelay: TimeSpan.FromSeconds(2)
);
// 使用指数退避
var result = await httpClient.GetWithRetryAsync<User>(
"https://api.example.com/users/1",
maxRetries: 3,
useExponentialBackoff: true,
initialDelay: TimeSpan.FromSeconds(1)
);
// 自定义重试条件
var result = await httpClient.GetWithRetryAsync<User>(
"https://api.example.com/users/1",
maxRetries: 3,
shouldRetry: (exception, retryCount) =>
{
// 只在特定异常时重试
return exception is HttpRequestException;
}
);

HttpClientHelper 采用 CancellationToken 优先 的超时设计:

// 方式1:使用 CancellationToken 控制超时(推荐)
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var result = await httpClient.GetAsync<User>(
"https://api.example.com/users/1",
cancellationToken: cts.Token
);
// 方式2:使用 GetWithTimeoutAsync 方法(内部使用 CancellationToken)
var result = await httpClient.GetWithTimeoutAsync<User>(
"https://api.example.com/users/1",
timeout: TimeSpan.FromSeconds(10)
);
// 方式3:设置全局默认超时(作为兜底保护)
httpClient.SetDefaultTimeout(TimeSpan.FromMinutes(5));

超时设计说明

| 方式 | 作用范围 | 灵活性 | 推荐场景 | |------|---------|--------|---------| | CancellationToken | 单个请求 | 最高 | 生产环境推荐 | | GetWithTimeoutAsync | 单个请求 | 高 | 简单场景 | | SetDefaultTimeout | 全局 | 低 | 兜底保护 |

最佳实践

  1. 默认情况下,HttpClient.Timeout 设置为无限(Timeout.InfiniteTimeSpan)
  2. 使用 CancellationToken 控制每个请求的具体超时
  3. 如果需要全局兜底保护,设置一个较大的超时值(如 5 分钟)
// 生产环境推荐配置
var httpClient = new HttpClientHelper();
// 每个请求独立控制超时
public async Task<User?> GetUserAsync(int id, CancellationToken cancellationToken = default)
{
// 创建一个 30 秒超时的 CancellationToken
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
timeoutCts.CancelAfter(TimeSpan.FromSeconds(30));
return await httpClient.GetAsync<User>(
$"https://api.example.com/users/{id}",
cancellationToken: timeoutCts.Token
);
}
// Bearer Token 认证
var result = await httpClient.GetWithAuthAsync<User>(
"https://api.example.com/protected",
"your-access-token"
);
// Basic 认证
var result = await httpClient.GetWithBasicAuthAsync<User>(
"https://api.example.com/protected",
"username",
"password"
);
// API Key 认证
var result = await httpClient.GetWithApiKeyAsync<User>(
"https://api.example.com/protected",
"your-api-key",
"X-API-Key" // 可选,默认为 "X-API-Key"
);
// 添加请求拦截器
httpClient.AddRequestInterceptor(request =>
{
request.Headers.Add("X-Request-Id", Guid.NewGuid().ToString());
return request;
});
// 添加响应拦截器
httpClient.AddResponseInterceptor(async response =>
{
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
// Token 过期,刷新 Token 并重试
var newToken = await RefreshTokenAsync();
// ...
}
return response;
});
// 清除所有拦截器
httpClient.ClearInterceptors();
// 基本下载
var result = await httpClient.DownloadFileAsync(
"https://example.com/file.pdf",
@"C:\Downloads\file.pdf"
);
// 带进度回调
var result = await httpClient.DownloadFileAsync(
"https://example.com/large-file.zip",
@"C:\Downloads\large-file.zip",
progress =>
{
Console.WriteLine($"下载进度: {progress.ProgressPercentage}% " +
$"({progress.DownloadedBytes}/{progress.TotalBytes} bytes)");
}
);
// 带自定义请求头
var result = await httpClient.DownloadFileAsync(
"https://example.com/protected-file.pdf",
@"C:\Downloads\protected-file.pdf",
progress => { /* 进度处理 */ },
new Dictionary<string, string> { { "Authorization", "Bearer token" } }
);
// 检查下载结果
if (result.Success)
{
Console.WriteLine($"下载成功: {result.FilePath}");
Console.WriteLine($"文件大小: {result.TotalBytes} bytes");
Console.WriteLine($"耗时: {result.Duration.TotalSeconds:F2} 秒");
}
else
{
Console.WriteLine($"下载失败: {result.ErrorMessage}");
}
// 基本上传
var result = await httpClient.UploadFileAsync(
"https://api.example.com/upload",
@"C:\Files\document.pdf"
);
// 指定上传文件名
var result = await httpClient.UploadFileAsync(
"https://api.example.com/upload",
@"C:\Files\document.pdf",
"uploaded-document.pdf"
);
// 带进度回调
var result = await httpClient.UploadFileAsync(
"https://api.example.com/upload",
@"C:\Files\large-file.zip",
"large-file.zip",
progress =>
{
Console.WriteLine($"上传进度: {progress.ProgressPercentage}% " +
$"({progress.UploadedBytes}/{progress.TotalBytes} bytes)");
}
);
// 带额外表单字段
var result = await httpClient.UploadFileAsync(
"https://api.example.com/upload",
@"C:\Files\document.pdf",
formData: new Dictionary<string, string>
{
{ "userId", "123" },
{ "category", "documents" }
}
);
// 检查上传结果
if (result.Success)
{
Console.WriteLine($"上传成功,响应: {result.ResponseContent}");
}
else
{
Console.WriteLine($"上传失败: {result.ErrorMessage}");
}
var requests = new List<BatchRequest>
{
new BatchRequest { Method = HttpMethod.Get, Url = "https://api.example.com/users/1" },
new BatchRequest { Method = HttpMethod.Get, Url = "https://api.example.com/users/2" },
new BatchRequest { Method = HttpMethod.Get, Url = "https://api.example.com/users/3" }
};
// 并行执行(默认最大并发数为10)
var responses = await httpClient.SendBatchAsync(requests);
// 自定义最大并发数
var responses = await httpClient.SendBatchAsync(requests, maxConcurrency: 5);
// 处理结果
foreach (var response in responses)
{
if (response.Success)
{
Console.WriteLine($"URL: {response.Request.Url}, 状态码: {response.Response.StatusCode}");
Console.WriteLine($"内容: {response.Content}");
}
else
{
Console.WriteLine($"URL: {response.Request.Url}, 错误: {response.ErrorMessage}");
}
}
var requests = new List<BatchRequest>
{
new BatchRequest { Method = HttpMethod.Get, Url = "https://api.example.com/step1" },
new BatchRequest { Method = HttpMethod.Post, Url = "https://api.example.com/step2", Content = new StringContent("{}") },
new BatchRequest { Method = HttpMethod.Get, Url = "https://api.example.com/step3" }
};
// 顺序执行(一个接一个)
var responses = await httpClient.SendSequentialAsync(requests);
// 设置默认请求头
httpClient.SetDefaultHeaders(new Dictionary<string, string>
{
{ "User-Agent", "MyApp/1.0" },
{ "Accept", "application/json" },
{ "Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8" }
});
// 清除默认请求头
httpClient.ClearDefaultHeaders();
// 自定义 JSON 序列化选项
httpClient.ConfigureJsonOptions(options =>
{
options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
options.PropertyNameCaseInsensitive = true;
options.WriteIndented = true;
});
// 设置 Cookie 容器
var cookieContainer = new CookieContainer();
httpClient.SetCookieContainer(cookieContainer);
// 获取 Cookie 容器
var cookies = httpClient.GetCookieContainer();
var sessionCookie = cookies.GetCookies(new Uri("https://api.example.com"))["session"];
// 通过 HttpClientHandler 设置代理
var handler = new HttpClientHandler
{
Proxy = new WebProxy("http://proxy.example.com:8080"),
UseProxy = true
};
var httpClient = new HttpClientHelper(new HttpClient(handler));
// 忽略 SSL 证书验证(仅用于开发环境!)
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true
};
var httpClient = new HttpClientHelper(new HttpClient(handler));
var stats = httpClient.GetStatistics();
Console.WriteLine($"默认超时: {stats.DefaultTimeout}");
Console.WriteLine($"总请求数: {stats.TotalRequests}");
Console.WriteLine($"成功请求数: {stats.SuccessfulRequests}");
Console.WriteLine($"失败请求数: {stats.FailedRequests}");
Console.WriteLine($"成功率: {(stats.TotalRequests > 0 ? (double)stats.SuccessfulRequests / stats.TotalRequests : 0):P2}");
Console.WriteLine($"平均响应时间: {stats.AverageResponseTimeMs:F2} ms");
Console.WriteLine($"自动解压缩: {stats.SupportsAutomaticDecompression}");
Console.WriteLine($"使用 Cookie: {stats.UseCookies}");
// 启用日志(需要注入 ILogger)
httpClient.EnableLogging(true);
// 禁用日志
httpClient.EnableLogging(false);
/// <summary>
/// HTTP 客户端配置选项
/// </summary>
public class HttpClientOptions
{
/// <summary>
/// 超时时间(默认为无限,建议使用 CancellationToken 控制超时)
/// </summary>
public TimeSpan? Timeout { get; set; }
/// <summary>
/// 默认请求头
/// </summary>
public Dictionary<string, string>? DefaultHeaders { get; set; }
/// <summary>
/// 最大并发连接数
/// </summary>
public int MaxConnectionsPerServer { get; set; } = 100;
/// <summary>
/// 是否启用自动解压缩
/// </summary>
public bool EnableAutomaticDecompression { get; set; } = true;
/// <summary>
/// 是否启用 Cookie
/// </summary>
public bool EnableCookies { get; set; } = true;
/// <summary>
/// 代理服务器配置
/// </summary>
public IWebProxy? Proxy { get; set; }
/// <summary>
/// 是否使用默认凭证
/// </summary>
public bool UseDefaultCredentials { get; set; }
/// <summary>
/// SSL 证书验证回调
/// </summary>
public Func<HttpRequestMessage, X509Certificate2, X509Chain, SslPolicyErrors, bool>? ServerCertificateValidationCallback { get; set; }
/// <summary>
/// JSON 序列化选项
/// </summary>
public JsonSerializerOptions? JsonOptions { get; set; }
}

超时设计说明

  • Timeout 默认为 null,实际使用 Timeout.InfiniteTimeSpan(无限超时)
  • 建议使用 CancellationToken 控制每个请求的具体超时
  • 如需设置全局超时作为兜底保护,可设置一个较大的值(如 5 分钟)
/// <summary>
/// 下载结果
/// </summary>
public class DownloadResult
{
/// <summary>
/// 是否成功
/// </summary>
public bool Success { get; set; }
/// <summary>
/// 文件保存路径
/// </summary>
public string FilePath { get; set; } = string.Empty;
/// <summary>
/// 总字节数
/// </summary>
public long TotalBytes { get; set; }
/// <summary>
/// 错误信息
/// </summary>
public string? ErrorMessage { get; set; }
/// <summary>
/// 下载耗时
/// </summary>
public TimeSpan Duration { get; set; }
}
/// <summary>
/// 上传结果
/// </summary>
public class UploadResult
{
/// <summary>
/// 是否成功
/// </summary>
public bool Success { get; set; }
/// <summary>
/// 响应内容
/// </summary>
public string? ResponseContent { get; set; }
/// <summary>
/// 上传的文件大小
/// </summary>
public long TotalBytes { get; set; }
/// <summary>
/// 错误信息
/// </summary>
public string? ErrorMessage { get; set; }
/// <summary>
/// 上传耗时
/// </summary>
public TimeSpan Duration { get; set; }
}
/// <summary>
/// 下载进度信息
/// </summary>
public class DownloadProgress
{
/// <summary>
/// 总字节数
/// </summary>
public long TotalBytes { get; set; }
/// <summary>
/// 已下载字节数
/// </summary>
public long DownloadedBytes { get; set; }
/// <summary>
/// 进度百分比 (0-100)
/// </summary>
public int ProgressPercentage { get; set; }
}
/// <summary>
/// 上传进度信息
/// </summary>
public class UploadProgress
{
/// <summary>
/// 总字节数
/// </summary>
public long TotalBytes { get; set; }
/// <summary>
/// 已上传字节数
/// </summary>
public long UploadedBytes { get; set; }
/// <summary>
/// 进度百分比 (0-100)
/// </summary>
public int ProgressPercentage { get; set; }
}
/// <summary>
/// 批量请求
/// </summary>
public class BatchRequest
{
/// <summary>
/// HTTP 方法
/// </summary>
public HttpMethod Method { get; set; } = HttpMethod.Get;
/// <summary>
/// 请求 URL
/// </summary>
public string Url { get; set; } = string.Empty;
/// <summary>
/// 请求内容
/// </summary>
public HttpContent? Content { get; set; }
/// <summary>
/// 请求头
/// </summary>
public Dictionary<string, string>? Headers { get; set; }
}
/// <summary>
/// 批量响应
/// </summary>
public class BatchResponse
{
/// <summary>
/// 对应的请求
/// </summary>
public BatchRequest Request { get; set; } = new();
/// <summary>
/// HTTP 响应消息
/// </summary>
public HttpResponseMessage? Response { get; set; }
/// <summary>
/// 响应内容字符串
/// </summary>
public string? Content { get; set; }
/// <summary>
/// 是否成功
/// </summary>
public bool Success { get; set; }
/// <summary>
/// 错误信息
/// </summary>
public string? ErrorMessage { get; set; }
}
/// <summary>
/// HTTP 客户端统计信息
/// </summary>
public class HttpClientStatistics
{
/// <summary>
/// 默认超时时间
/// </summary>
public TimeSpan DefaultTimeout { get; set; }
/// <summary>
/// 默认请求头数量
/// </summary>
public int DefaultHeadersCount { get; set; }
/// <summary>
/// 是否支持自动解压缩
/// </summary>
public bool SupportsAutomaticDecompression { get; set; }
/// <summary>
/// 是否使用 Cookie
/// </summary>
public bool UseCookies { get; set; }
/// <summary>
/// 总请求数
/// </summary>
public long TotalRequests { get; set; }
/// <summary>
/// 成功请求数
/// </summary>
public long SuccessfulRequests { get; set; }
/// <summary>
/// 失败请求数
/// </summary>
public long FailedRequests { get; set; }
/// <summary>
/// 平均响应时间(毫秒)
/// </summary>
public double AverageResponseTimeMs { get; set; }
}
public class UserService
{
private readonly IHttpClientHelper _httpClient;
private const string BaseUrl = "https://api.example.com";
public UserService(IHttpClientHelper httpClient)
{
_httpClient = httpClient;
_httpClient.SetDefaultHeaders(new Dictionary<string, string>
{
{ "Accept", "application/json" }
});
}
public async Task<User?> GetUserAsync(int id)
{
try
{
return await _httpClient.GetWithRetryAsync<User>(
$"{BaseUrl}/users/{id}",
maxRetries: 3
);
}
catch (HttpRequestException ex)
{
Console.WriteLine($"获取用户失败: {ex.Message}");
return null;
}
}
public async Task<User?> CreateUserAsync(CreateUserRequest request)
{
return await _httpClient.PostJsonAsync<CreateUserRequest, User>(
$"{BaseUrl}/users",
request
);
}
public async Task<bool> UpdateUserAsync(int id, UpdateUserRequest request)
{
var result = await _httpClient.PutJsonAsync<UpdateUserRequest, User>(
$"{BaseUrl}/users/{id}",
request
);
return result != null;
}
public async Task<bool> DeleteUserAsync(int id)
{
var response = await _httpClient.DeleteAsync($"{BaseUrl}/users/{id}");
return response.IsSuccessStatusCode;
}
}
public class AuthenticatedApiClient
{
private readonly IHttpClientHelper _httpClient;
private string _accessToken;
public AuthenticatedApiClient(IHttpClientHelper httpClient)
{
_httpClient = httpClient;
_httpClient.AddRequestInterceptor(async request =>
{
if (!string.IsNullOrEmpty(_accessToken))
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _accessToken);
}
return request;
});
_httpClient.AddResponseInterceptor(async response =>
{
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
_accessToken = await RefreshTokenAsync();
}
return response;
});
}
private async Task<string> RefreshTokenAsync()
{
var result = await _httpClient.PostJsonAsync<RefreshTokenRequest, TokenResponse>(
"https://api.example.com/auth/refresh",
new RefreshTokenRequest { RefreshToken = _refreshToken }
);
return result.AccessToken;
}
}
public class FileService
{
private readonly IHttpClientHelper _httpClient;
public FileService(IHttpClientHelper httpClient)
{
_httpClient = httpClient;
}
public async Task<bool> DownloadFileAsync(string url, string savePath, IProgress<int>? progress = null)
{
var result = await _httpClient.DownloadFileAsync(
url,
savePath,
p => progress?.Report(p.ProgressPercentage)
);
return result.Success;
}
public async Task<string?> UploadFileAsync(string url, string filePath, IProgress<int>? progress = null)
{
var result = await _httpClient.UploadFileAsync(
url,
filePath,
progress: p => progress?.Report(p.ProgressPercentage)
);
return result.Success ? result.ResponseContent : null;
}
}
public class DataSyncService
{
private readonly IHttpClientHelper _httpClient;
public async Task SyncDataAsync(List<DataItem> items)
{
var requests = items.Select(item => new BatchRequest
{
Method = HttpMethod.Post,
Url = "https://api.example.com/data",
Content = new StringContent(
JsonSerializer.Serialize(item),
Encoding.UTF8,
"application/json"
)
}).ToList();
var responses = await _httpClient.SendBatchAsync(requests, maxConcurrency: 5);
var successCount = responses.Count(r => r.Success);
var failCount = responses.Count(r => !r.Success);
Console.WriteLine($"同步完成: 成功 {successCount}, 失败 {failCount}");
}
}
public class WebhookService
{
private readonly IHttpClientHelper _httpClient;
public async Task<bool> SendWebhookAsync(string webhookUrl, WebhookPayload payload)
{
try
{
var response = await _httpClient.PostJsonAsync<WebhookPayload, object>(
webhookUrl,
payload
);
return true;
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Gone)
{
Console.WriteLine("Webhook 已失效,需要移除");
return false;
}
catch (Exception ex)
{
Console.WriteLine($"Webhook 发送失败: {ex.Message}");
return false;
}
}
}
// 推荐:使用依赖注入
services.AddSingleton<IHttpClientHelper, HttpClientHelper>();
// 或者使用静态实例
public static class HttpClientProvider
{
public static IHttpClientHelper Instance { get; } = new HttpClientHelper();
}

2. 超时控制(推荐使用 CancellationToken)

Section titled “2. 超时控制(推荐使用 CancellationToken)”
// 推荐:使用 CancellationToken 控制超时
public async Task<User?> GetUserAsync(int id, CancellationToken cancellationToken = default)
{
// 创建一个 30 秒超时的 CancellationToken
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
timeoutCts.CancelAfter(TimeSpan.FromSeconds(30));
return await _httpClient.GetAsync<User>(
$"https://api.example.com/users/{id}",
cancellationToken: timeoutCts.Token
);
}
// 对于长时间操作,使用特定的超时
using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
var result = await httpClient.GetAsync<Data>(
"https://api.example.com/long-running",
cancellationToken: cts.Token
);
// 或使用便捷方法
var result = await httpClient.GetWithTimeoutAsync<Data>(
"https://api.example.com/long-running",
timeout: TimeSpan.FromMinutes(5)
);

超时设计最佳实践

  1. 默认情况下,HttpClient.Timeout 设置为无限
  2. 使用 CancellationToken 控制每个请求的具体超时
  3. 可以链接多个 CancellationToken(如用户取消 + 超时)
  4. 如果需要全局兜底保护,设置一个较大的超时值(如 5 分钟)
try
{
var result = await httpClient.GetAsync<User>(url);
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
// 资源不存在
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized)
{
// 认证失败
}
catch (TaskCanceledException)
{
// 请求超时
}
catch (JsonException ex)
{
// JSON 反序列化失败
}
catch (Exception ex)
{
// 其他异常
}
public async Task<User?> GetUserAsync(int id, CancellationToken cancellationToken)
{
return await _httpClient.GetAsync<User>(
$"https://api.example.com/users/{id}",
cancellationToken: cancellationToken
);
}
// 在依赖注入时配置日志
services.AddSingleton<IHttpClientHelper>(sp =>
{
var logger = sp.GetRequiredService<ILogger<HttpClientHelper>>();
var httpClient = new HttpClientHelper(logger: logger);
httpClient.EnableLogging(true);
return httpClient;
});
  1. 线程安全:HttpClientHelper 实例本身是线程安全的,可以在多个线程中共享使用。

  2. 连接池管理:内部使用 HttpClient,由 .NET 自动管理连接池,无需手动管理。

  3. 资源释放:实现 IDisposable 接口,建议使用 using 语句或依赖注入管理生命周期。

  4. DNS 更新:长时间运行的实例可能会遇到 DNS 更新问题,建议定期重新创建实例或设置 PooledConnectionLifetime

  5. 重试策略:重试机制适用于幂等操作,对于非幂等操作(如创建订单)请谨慎使用。

  6. 大文件处理:上传下载大文件时,注意内存使用,建议使用进度回调监控进度。

HttpClientHelper 在高并发场景下具有以下性能特性:

// 默认配置:每服务器最大连接数 100
var httpClient = new HttpClientHelper(new HttpClientOptions
{
MaxConnectionsPerServer = 100 // 默认值
});
// 高并发场景建议配置
var httpClient = new HttpClientHelper(new HttpClientOptions
{
MaxConnectionsPerServer = 200 // 增加连接池大小
});

性能影响

  • 默认值 100 适用于大多数场景
  • 高并发场景(每秒 1000+ 请求)建议设置为 200-500
  • 过高的连接数可能导致服务器压力增大

HttpClientHelper 内部使用 HttpClient,自动实现连接复用:

// 推荐:单例模式,最大化连接复用
services.AddSingleton<IHttpClientHelper, HttpClientHelper>();
// 不推荐:每次请求创建新实例
using var client = new HttpClientHelper(); // 连接无法复用

性能数据: | 模式 | 每秒请求数 (RPS) | 平均延迟 | 内存占用 | |------|-----------------|---------|---------| | 单例模式 | ~10,000 | 15ms | ~50MB | | 每次创建 | ~500 | 150ms | ~500MB |

所有方法均为异步实现,充分利用 .NET 的异步 I/O:

// 异步方法不会阻塞线程
var tasks = urls.Select(url => httpClient.GetAsync<User>(url));
var results = await Task.WhenAll(tasks);

线程池效率

  • 异步操作不占用线程池线程等待 I/O
  • 可同时处理数千个并发请求
  • 线程池线程数保持稳定
// 推荐配置
var options = new HttpClientOptions
{
MaxConnectionsPerServer = Environment.ProcessorCount * 12, // 根据 CPU 核心数调整
// Timeout 默认为无限,使用 CancellationToken 控制超时
EnableAutomaticDecompression = true
};
// 对于 Kubernetes 环境
var options = new HttpClientOptions
{
MaxConnectionsPerServer = 200
// Timeout 默认为无限,使用 CancellationToken 控制超时
};
// 设置连接池生命周期,解决 DNS 更新问题
var handler = new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(5), // 每 5 分钟刷新连接
PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2)
};
var httpClient = new HttpClientHelper(new HttpClient(handler));
// 并行批量请求 - 控制并发数
var responses = await httpClient.SendBatchAsync(
requests,
maxConcurrency: Environment.ProcessorCount * 2 // 根据 CPU 核心数调整
);
// 对于 I/O 密集型操作,可以适当增加并发数
var responses = await httpClient.SendBatchAsync(
requests,
maxConcurrency: 50 // I/O 密集型可以更高
);
// 全局超时
httpClient.SetDefaultTimeout(TimeSpan.FromSeconds(30));
// 针对特定请求的超时
await httpClient.GetWithTimeoutAsync<User>(
url,
timeout: TimeSpan.FromSeconds(10),
cancellationToken: cts.Token
);
// 使用 CancellationToken 实现更精细的控制
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var result = await httpClient.GetAsync<User>(url, cancellationToken: cts.Token);
// 指数退避重试 - 避免雪崩效应
var response = await httpClient.GetWithRetryAsync<User>(
url,
maxRetries: 3,
retryDelay: TimeSpan.FromSeconds(1) // 首次重试延迟 1s,之后指数增长
);
// 对于非幂等操作,谨慎使用重试
// POST 请求建议只重试特定错误码
  • CPU: Intel Core i7-12700 (12 核 20 线程)
  • 内存: 32GB DDR4
  • 网络: 千兆以太网
  • 目标服务: 本地 ASP.NET Core Web API

| 并发数 | RPS | 平均延迟 (ms) | P99 延迟 (ms) | 错误率 | |--------|-----|--------------|--------------|--------| | 100 | 8,500 | 11.7 | 45 | 0% | | 500 | 12,000 | 41.2 | 120 | 0% | | 1000 | 15,000 | 66.5 | 250 | 0.1% | | 2000 | 18,000 | 110.8 | 450 | 0.5% |

测试场景 2: JSON 序列化/反序列化

Section titled “测试场景 2: JSON 序列化/反序列化”

| 并发数 | RPS | 平均延迟 (ms) | P99 延迟 (ms) | 错误率 | |--------|-----|--------------|--------------|--------| | 100 | 6,200 | 16.1 | 52 | 0% | | 500 | 9,500 | 52.6 | 145 | 0% | | 1000 | 12,000 | 83.3 | 280 | 0.2% |

测试场景 3: 批量请求 (100 个请求/批)

Section titled “测试场景 3: 批量请求 (100 个请求/批)”

| 并发批次数 | RPS | 平均延迟 (ms) | 错误率 | |-----------|-----|--------------|--------| | 10 | 8,000 | 125 | 0% | | 50 | 12,000 | 416 | 0% | | 100 | 15,000 | 666 | 0.1% |

// 获取实时统计信息
var stats = httpClient.GetStatistics();
Console.WriteLine($"总请求数: {stats.TotalRequests}");
Console.WriteLine($"成功率: {(stats.TotalRequests > 0 ? (double)stats.SuccessfulRequests / stats.TotalRequests : 0):P2}");
Console.WriteLine($"平均响应时间: {stats.AverageResponseTimeMs:F2}ms");
// 定期监控
using var timer = new Timer(_ =>
{
var currentStats = httpClient.GetStatistics();
// 记录到监控系统
_metrics.Record("http_requests_total", currentStats.TotalRequests);
_metrics.Record("http_successful_requests", currentStats.SuccessfulRequests);
_metrics.Record("http_failed_requests", currentStats.FailedRequests);
var successRate = currentStats.TotalRequests > 0
? (double)currentStats.SuccessfulRequests / currentStats.TotalRequests
: 0;
_metrics.Record("http_success_rate", successRate);
}, null, TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(10));

症状: 请求超时、延迟增加

排查:

var stats = httpClient.GetStatistics();
if (stats.FailedRequests > stats.TotalRequests * 0.1)
{
// 检查连接池配置
// 增加 MaxConnectionsPerServer
}

解决:

httpClient.SetMaxConnectionsPerServer(200);

症状: 长时间运行后请求失败

解决:

var handler = new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(5)
};

症状: 内存持续增长

排查:

// 确保 HttpResponseMessage 被正确释放
using var response = await httpClient.GetAsync(url);
// 或者完全读取响应
var content = await response.Content.ReadAsStringAsync();
// 生产环境完整配置示例
public static class HttpClientFactory
{
public static IHttpClientHelper CreateProductionClient(ILogger<HttpClientHelper> logger)
{
var handler = new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(5),
PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2),
MaxConnectionsPerServer = 200,
EnableMultipleHttp2Connections = true,
AutomaticDecompression = DecompressionMethods.All
};
var httpClient = new HttpClient(handler)
{
// 超时设置为无限,使用 CancellationToken 控制具体请求超时
Timeout = Timeout.InfiniteTimeSpan
};
var client = new HttpClientHelper(httpClient, logger);
// 添加默认请求头
client.SetDefaultHeaders(new Dictionary<string, string>
{
{ "User-Agent", "MyApp/1.0" },
{ "Accept", "application/json" }
});
return client;
}
}
// 注册到 DI 容器
services.AddSingleton<IHttpClientHelper>(sp =>
HttpClientFactory.CreateProductionClient(
sp.GetRequiredService<ILogger<HttpClientHelper>>()
)
);
  • .NET 6.0 及以上版本
  • .NET Framework 4.6.1 及以上版本(部分功能受限)
  • 新增 PATCH、HEAD、OPTIONS 方法支持
  • 新增请求/响应拦截器功能
  • 新增 API Key 认证方式
  • 新增指数退避重试策略
  • 完善文件上传下载功能
  • 新增完整的 XML 文档注释
  • 性能优化和 Bug 修复
  • 初始版本
  • 支持基础 HTTP 方法
  • 支持重试机制
  • 支持文件上传下载
  • 支持批量请求