HttpClientHelper
HttpClientHelper 帮助类
Section titled “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 接口:支持资源释放
# 通过 NuGet 安装dotnet add package Chet.Utilsusing 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}"); }}无参构造函数
Section titled “无参构造函数”/// <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" } }});带自定义 HttpClient
Section titled “带自定义 HttpClient”/// <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);基础 HTTP 请求
Section titled “基础 HTTP 请求”GET 请求
Section titled “GET 请求”// 返回 HttpResponseMessageHttpResponseMessage 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 请求
Section titled “POST 请求”// 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 请求
Section titled “PUT 请求”// 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 请求
Section titled “DELETE 请求”// 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 请求
Section titled “PATCH 请求”// PATCH JSON 数据var patchedUser = await httpClient.PatchJsonAsync<PatchUserRequest, User>( "https://api.example.com/users/1", new PatchUserRequest { Name = "Patched Name" });HEAD 请求
Section titled “HEAD 请求”// 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 请求
Section titled “OPTIONS 请求”// 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 | 全局 | 低 | 兜底保护 |
最佳实践:
- 默认情况下,HttpClient.Timeout 设置为无限(Timeout.InfiniteTimeSpan)
- 使用 CancellationToken 控制每个请求的具体超时
- 如果需要全局兜底保护,设置一个较大的超时值(如 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}");}并行批量请求
Section titled “并行批量请求”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}"); }}顺序批量请求
Section titled “顺序批量请求”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);设置默认请求头
Section titled “设置默认请求头”// 设置默认请求头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 序列化
Section titled “配置 JSON 序列化”// 自定义 JSON 序列化选项httpClient.ConfigureJsonOptions(options =>{ options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; options.PropertyNameCaseInsensitive = true; options.WriteIndented = true;});Cookie 管理
Section titled “Cookie 管理”// 设置 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/TLS 配置
Section titled “SSL/TLS 配置”// 忽略 SSL 证书验证(仅用于开发环境!)var handler = new HttpClientHandler{ ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true};var httpClient = new HttpClientHelper(new HttpClient(handler));获取统计信息
Section titled “获取统计信息”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}");启用/禁用日志
Section titled “启用/禁用日志”// 启用日志(需要注入 ILogger)httpClient.EnableLogging(true);
// 禁用日志httpClient.EnableLogging(false);HttpClientOptions
Section titled “HttpClientOptions”/// <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 分钟)
DownloadResult
Section titled “DownloadResult”/// <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; }}UploadResult
Section titled “UploadResult”/// <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; }}DownloadProgress
Section titled “DownloadProgress”/// <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; }}UploadProgress
Section titled “UploadProgress”/// <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; }}BatchRequest
Section titled “BatchRequest”/// <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; }}BatchResponse
Section titled “BatchResponse”/// <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; }}HttpClientStatistics
Section titled “HttpClientStatistics”/// <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; }}完整使用示例
Section titled “完整使用示例”示例1:调用 REST API
Section titled “示例1:调用 REST API”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; }}示例2:带认证的 API 调用
Section titled “示例2:带认证的 API 调用”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; }}示例3:文件上传下载服务
Section titled “示例3:文件上传下载服务”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; }}示例4:批量数据同步
Section titled “示例4:批量数据同步”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}"); }}示例5:Webhook 调用
Section titled “示例5:Webhook 调用”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; } }}1. 使用单例模式
Section titled “1. 使用单例模式”// 推荐:使用依赖注入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));超时设计最佳实践:
- 默认情况下,HttpClient.Timeout 设置为无限
- 使用 CancellationToken 控制每个请求的具体超时
- 可以链接多个 CancellationToken(如用户取消 + 超时)
- 如果需要全局兜底保护,设置一个较大的超时值(如 5 分钟)
3. 异常处理
Section titled “3. 异常处理”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){ // 其他异常}4. 使用取消令牌
Section titled “4. 使用取消令牌”public async Task<User?> GetUserAsync(int id, CancellationToken cancellationToken){ return await _httpClient.GetAsync<User>( $"https://api.example.com/users/{id}", cancellationToken: cancellationToken );}5. 日志记录
Section titled “5. 日志记录”// 在依赖注入时配置日志services.AddSingleton<IHttpClientHelper>(sp =>{ var logger = sp.GetRequiredService<ILogger<HttpClientHelper>>(); var httpClient = new HttpClientHelper(logger: logger); httpClient.EnableLogging(true); return httpClient;});-
线程安全:HttpClientHelper 实例本身是线程安全的,可以在多个线程中共享使用。
-
连接池管理:内部使用 HttpClient,由 .NET 自动管理连接池,无需手动管理。
-
资源释放:实现 IDisposable 接口,建议使用 using 语句或依赖注入管理生命周期。
-
DNS 更新:长时间运行的实例可能会遇到 DNS 更新问题,建议定期重新创建实例或设置
PooledConnectionLifetime。 -
重试策略:重试机制适用于幂等操作,对于非幂等操作(如创建订单)请谨慎使用。
-
大文件处理:上传下载大文件时,注意内存使用,建议使用进度回调监控进度。
高并发性能分析与优化
Section titled “高并发性能分析与优化”HttpClientHelper 在高并发场景下具有以下性能特性:
1. 连接池管理
Section titled “1. 连接池管理”// 默认配置:每服务器最大连接数 100var httpClient = new HttpClientHelper(new HttpClientOptions{ MaxConnectionsPerServer = 100 // 默认值});
// 高并发场景建议配置var httpClient = new HttpClientHelper(new HttpClientOptions{ MaxConnectionsPerServer = 200 // 增加连接池大小});性能影响:
- 默认值 100 适用于大多数场景
- 高并发场景(每秒 1000+ 请求)建议设置为 200-500
- 过高的连接数可能导致服务器压力增大
2. 连接复用
Section titled “2. 连接复用”HttpClientHelper 内部使用 HttpClient,自动实现连接复用:
// 推荐:单例模式,最大化连接复用services.AddSingleton<IHttpClientHelper, HttpClientHelper>();
// 不推荐:每次请求创建新实例using var client = new HttpClientHelper(); // 连接无法复用性能数据: | 模式 | 每秒请求数 (RPS) | 平均延迟 | 内存占用 | |------|-----------------|---------|---------| | 单例模式 | ~10,000 | 15ms | ~50MB | | 每次创建 | ~500 | 150ms | ~500MB |
3. 异步 I/O
Section titled “3. 异步 I/O”所有方法均为异步实现,充分利用 .NET 的异步 I/O:
// 异步方法不会阻塞线程var tasks = urls.Select(url => httpClient.GetAsync<User>(url));var results = await Task.WhenAll(tasks);线程池效率:
- 异步操作不占用线程池线程等待 I/O
- 可同时处理数千个并发请求
- 线程池线程数保持稳定
高并发优化建议
Section titled “高并发优化建议”1. 连接池配置
Section titled “1. 连接池配置”// 推荐配置var options = new HttpClientOptions{ MaxConnectionsPerServer = Environment.ProcessorCount * 12, // 根据 CPU 核心数调整 // Timeout 默认为无限,使用 CancellationToken 控制超时 EnableAutomaticDecompression = true};
// 对于 Kubernetes 环境var options = new HttpClientOptions{ MaxConnectionsPerServer = 200 // Timeout 默认为无限,使用 CancellationToken 控制超时};2. DNS 刷新策略
Section titled “2. DNS 刷新策略”// 设置连接池生命周期,解决 DNS 更新问题var handler = new SocketsHttpHandler{ PooledConnectionLifetime = TimeSpan.FromMinutes(5), // 每 5 分钟刷新连接 PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2)};var httpClient = new HttpClientHelper(new HttpClient(handler));3. 批量请求优化
Section titled “3. 批量请求优化”// 并行批量请求 - 控制并发数var responses = await httpClient.SendBatchAsync( requests, maxConcurrency: Environment.ProcessorCount * 2 // 根据 CPU 核心数调整);
// 对于 I/O 密集型操作,可以适当增加并发数var responses = await httpClient.SendBatchAsync( requests, maxConcurrency: 50 // I/O 密集型可以更高);4. 超时策略
Section titled “4. 超时策略”// 全局超时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);5. 重试策略优化
Section titled “5. 重试策略优化”// 指数退避重试 - 避免雪崩效应var response = await httpClient.GetWithRetryAsync<User>( url, maxRetries: 3, retryDelay: TimeSpan.FromSeconds(1) // 首次重试延迟 1s,之后指数增长);
// 对于非幂等操作,谨慎使用重试// POST 请求建议只重试特定错误码性能测试结果
Section titled “性能测试结果”- CPU: Intel Core i7-12700 (12 核 20 线程)
- 内存: 32GB DDR4
- 网络: 千兆以太网
- 目标服务: 本地 ASP.NET Core Web API
测试场景 1: 简单 GET 请求
Section titled “测试场景 1: 简单 GET 请求”| 并发数 | 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));常见性能问题排查
Section titled “常见性能问题排查”1. 连接池耗尽
Section titled “1. 连接池耗尽”症状: 请求超时、延迟增加
排查:
var stats = httpClient.GetStatistics();if (stats.FailedRequests > stats.TotalRequests * 0.1){ // 检查连接池配置 // 增加 MaxConnectionsPerServer}解决:
httpClient.SetMaxConnectionsPerServer(200);2. DNS 缓存问题
Section titled “2. DNS 缓存问题”症状: 长时间运行后请求失败
解决:
var handler = new SocketsHttpHandler{ PooledConnectionLifetime = TimeSpan.FromMinutes(5)};3. 内存泄漏
Section titled “3. 内存泄漏”症状: 内存持续增长
排查:
// 确保 HttpResponseMessage 被正确释放using var response = await httpClient.GetAsync(url);// 或者完全读取响应var content = await response.Content.ReadAsStringAsync();生产环境推荐配置
Section titled “生产环境推荐配置”// 生产环境完整配置示例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 及以上版本(部分功能受限)
v2.0.0
Section titled “v2.0.0”- 新增 PATCH、HEAD、OPTIONS 方法支持
- 新增请求/响应拦截器功能
- 新增 API Key 认证方式
- 新增指数退避重试策略
- 完善文件上传下载功能
- 新增完整的 XML 文档注释
- 性能优化和 Bug 修复
v1.0.0
Section titled “v1.0.0”- 初始版本
- 支持基础 HTTP 方法
- 支持重试机制
- 支持文件上传下载
- 支持批量请求