使用httpclient时需复用实例或使用httpclientfactory管理生命周期。1.避免为每个请求创建新httpclient实例,以防止端口耗尽和dns解析浪费;2.推荐将httpclient声明为静态或使用httpclientfactory进行依赖注入,以实现连接复用并解决dns缓存问题;3.httpclientfactory通过管理httpmessagehandler的生命周期,既提升性能又确保dns更新及时生效。
HttpClient
在C#中是发送HTTP请求的核心工具,它提供了一套简洁而强大的API来处理各种Web交互,无论是GET、POST还是其他请求,都能轻松应对。
解决方案
使用C#的
HttpClient
发送HTTP请求,最基本的步骤通常涉及创建
HttpClient
实例,构造
HttpRequestMessage
(或直接使用其便捷方法),然后发送请求并处理响应。一个常见的误区是为每个请求都创建一个新的
HttpClient
实例,这其实是效率低下的做法,甚至可能导致端口耗尽。更推荐的做法是复用同一个
HttpClient
实例,或者使用
HttpClientFactory
来管理它们的生命周期。
我们来看一个发送GET请求的例子。假设你需要从某个API获取数据:
using System; using System.Net.Http; using System.Threading.Tasks; public class HttpRequestSender { // 推荐的做法:复用HttpClient实例 // 实际项目中,更推荐使用HttpClientFactory private static readonly HttpClient _httpClient = new HttpClient(); public async Task GetExampleAsync(string url) { try { // 发送GET请求并等待响应 HttpResponseMessage response = await _httpClient.GetAsync(url); // 确保请求成功(状态码2xx) response.EnsureSuccessStatusCode(); // 读取响应内容 string responseBody = await response.Content.ReadAsStringAsync(); Console.WriteLine($"GET 请求成功,响应内容:n{responseBody}"); } catch (HttpRequestException e) { Console.WriteLine($"GET 请求出错: {e.Message}"); // 这里可以根据e.StatusCode进行更细致的错误处理 } catch (TaskCanceledException e) when (e.InnerException is TimeoutException) { Console.WriteLine($"GET 请求超时: {e.Message}"); } catch (Exception e) { Console.WriteLine($"发生未知错误: {e.Message}"); } } public async Task PostExampleAsync(string url, string jsonContent) { try { // 准备POST请求的内容 StringContent content = new StringContent(jsonContent, System.Text.Encoding.UTF8, "application/json"); // 发送POST请求 HttpResponseMessage response = await _httpClient.PostAsync(url, content); response.EnsureSuccessStatusCode(); // 检查状态码 string responseBody = await response.Content.ReadAsStringAsync(); Console.WriteLine($"POST 请求成功,响应内容:n{responseBody}"); } catch (HttpRequestException e) { Console.WriteLine($"POST 请求出错: {e.Message}"); } catch (Exception e) { Console.WriteLine($"发生未知错误: {e.Message}"); } } } // 调用示例 /* public class Program { public static async Task Main(string[] args) { HttpRequestSender sender = new HttpRequestSender(); // 假设这是一个真实存在的API地址 await sender.GetExampleAsync("https://jsonplaceholder.typicode.com/todos/1"); string postData = "{"title":"foo","body":"bar","userId":1}"; await sender.PostExampleAsync("https://jsonplaceholder.typicode.com/posts", postData); } } */
这段代码展示了
HttpClient
的基本用法。你会发现,它高度依赖异步操作(
async
/
await
),这是现代C#进行I/O操作的推荐方式,因为它能有效避免阻塞线程,提升应用程序的响应能力和并发性能。
如何处理HttpClient的生命周期和连接池问题?
这是一个老生常谈但又极其关键的问题。许多开发者,包括我自己在初学时,都曾陷入为每个请求创建新
HttpClient
的陷阱。表面上看,这似乎很合理,用完即丢,避免资源泄露。但实际上,
HttpClient
内部管理着连接池,每次新建实例,都会创建一个新的底层TCP连接,这不仅开销大,还可能导致端口耗尽(
SocketException: Only one usage of each socket address (protocol/network address/port) is normally permitted
)。更糟糕的是,新的连接意味着每次请求都需要重新进行DNS解析,这在服务地址不变的情况下完全是浪费。
正确的姿势是复用
HttpClient
实例。一个
HttpClient
实例可以安全地被多个线程并发使用。它被设计成一个长期存活的对象。在桌面应用或控制台应用中,你完全可以将其声明为静态成员,或者作为单例注入。
然而,长期复用一个
HttpClient
也并非没有缺点,最典型的就是DNS缓存问题。
HttpClient
实例一旦创建,它内部的DNS解析结果就会被缓存。如果你的服务部署在负载均衡器后面,IP地址可能会动态变化,或者在服务迁移后IP地址更新,但
HttpClient
可能仍然尝试连接旧的IP地址,导致请求失败。
为了解决这个DNS缓存和生命周期管理的平衡问题,.NET Core 2.1及更高版本引入了
IHttpClientFactory
。这是处理
HttpClient
生命周期的最佳实践。
IHttpClientFactory
不是直接返回
HttpClient
实例,而是返回一个“逻辑”
HttpClient
实例,它背后会从池中借用或创建
HttpMessageHandler
,这个
HttpMessageHandler
才是实际管理连接和DNS缓存的部分。
IHttpClientFactory
会定期回收旧的
HttpMessageHandler
,从而解决了DNS缓存过时的问题,同时又保持了连接复用带来的性能优势。
在ASP.NET Core应用中,你通常会在
Startup.cs
的
ConfigureServices
方法中注册它:
// 在Startup.cs的ConfigureServices方法中 services.AddHttpClient(); // 注册默认的HttpClient // 或者注册一个具名客户端 services.AddHttpClient("myApi", client => { client.BaseAddress = new Uri("https://api.example.com/"); client.DefaultRequestHeaders.Add("Accept", "application/json"); }); // 还可以注册一个类型化客户端 services.AddHttpClient<MyApiService>(); // MyApiService会通过构造函数注入HttpClient
然后,在你的服务类中通过构造函数注入
HttpClient
或
IHttpClientFactory
。这样,框架会为你处理
HttpClient
的创建、复用和销毁,让你能够专注于业务逻辑。
发送不同类型的HTTP请求(如PUT、DELETE)和携带请求头、认证信息有哪些技巧?
HttpClient
不仅支持GET和POST,对于PUT、DELETE等HTTP动词也提供了类似的便捷方法,或者你可以通过
HttpRequestMessage
来构建更复杂的请求。
发送PUT和DELETE请求:
using System.Net.Http; using System.Text; using System.Threading.Tasks; public class AdvancedHttpRequestSender { private static readonly HttpClient _httpClient = new HttpClient(); public async Task PutExampleAsync(string url, string jsonContent) { StringContent content = new StringContent(jsonContent, Encoding.UTF8, "application/json"); HttpResponseMessage response = await _httpClient.PutAsync(url, content); response.EnsureSuccessStatusCode(); string responseBody = await response.Content.ReadAsStringAsync(); Console.WriteLine($"PUT 请求成功,响应内容:n{responseBody}"); } public async Task DeleteExampleAsync(string url) { HttpResponseMessage response = await _httpClient.DeleteAsync(url); response.EnsureSuccessStatusCode(); Console.WriteLine($"DELETE 请求成功,状态码: {response.StatusCode}"); } }
携带请求头:
有几种方式可以添加请求头:
-
全局请求头 (
DefaultRequestHeaders
): 如果你的所有请求都需要相同的头,比如
User-Agent
或
Accept
,可以设置在
HttpClient
实例的
DefaultRequestHeaders
上。
_httpClient.DefaultRequestHeaders.Add("User-Agent", "MyC#App/1.0"); _httpClient.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
-
针对单个请求的请求头 (
HttpRequestMessage
): 对于特定请求才需要的头,或者需要覆盖全局设置的头,可以使用
HttpRequestMessage
。
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "https://api.example.com/data"); request.Headers.Add("X-Custom-Header", "MyValue"); HttpResponseMessage response = await _httpClient.SendAsync(request);
认证信息:
认证通常通过请求头来传递,最常见的是Bearer Token(OAuth 2.0)和Basic Authentication。
-
Bearer Token: 这是现代API中最常见的认证方式。
_httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "your_access_token_here"); // 或者针对单个请求 HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "https://api.example.com/secure_data"); request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "your_access_token_here"); HttpResponseMessage response = await _httpClient.SendAsync(request);
-
Basic Authentication: 虽然不如Bearer Token安全,但在某些场景下仍在使用。它需要将用户名和密码用冒号连接后进行Base64编码。
string authString = Convert.ToBase64String(Encoding.ASCII.GetBytes("username:password")); _httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", authString);
处理HttpClient请求中的异常、超时和重试机制?
网络请求总是充满不确定性,异常处理、超时设置和重试机制是构建健壮客户端的关键。
异常处理:
HttpClient
的请求可能会抛出几种类型的异常:
-
HttpRequestException
: 这是最常见的,当HTTP请求本身失败时(例如,DNS解析失败、连接中断、服务器返回非2xx状态码时调用
EnsureSuccessStatusCode()
),就会抛出此异常。
HttpRequestException
有一个
StatusCode
属性,可以让你检查具体的HTTP状态码,从而进行更细致的错误处理(例如,401未授权、404未找到、500服务器内部错误等)。
try { HttpResponseMessage response = await _httpClient.GetAsync(url); response.EnsureSuccessStatusCode(); // 如果状态码不是2xx,这里会抛出HttpRequestException // ... } catch (HttpRequestException ex) { Console.WriteLine($"请求失败: {ex.Message}"); if (ex.StatusCode.HasValue) { Console.WriteLine($"HTTP状态码: {ex.StatusCode.Value}"); } }
-
TaskCanceledException
: 当请求被取消或超时时,会抛出此异常。特别是当请求超时时,它的
InnerException
通常是
TimeoutException
。
try { // ... } catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException) { Console.WriteLine($"请求超时: {ex.Message}"); } catch (TaskCanceledException ex) { Console.WriteLine($"请求被取消: {ex.Message}"); }
超时设置:
HttpClient
有一个
Timeout
属性,用于设置请求的超时时间。如果在这个时间内没有收到响应,请求就会被取消并抛出
TaskCanceledException
(内部带有
TimeoutException
)。
_httpClient.Timeout = TimeSpan.FromSeconds(10); // 设置10秒超时
这个超时是针对整个请求过程的,包括连接、发送请求和接收响应。对于某些需要长时间处理的请求,你可能需要适当延长这个时间。
重试机制:
对于瞬时性错误(例如网络抖动、服务器短暂过载、429 Too Many Requests),简单的重试通常能解决问题。手动实现重试逻辑会比较繁琐,因为它需要处理延迟、指数退避(每次重试间隔时间逐渐增加)以及最大重试次数等。
在.NET生态系统中,Polly是一个非常流行的弹性策略库,它提供了优雅的方式来实现重试、断路器、超时等多种弹性策略。使用Polly,你可以像这样定义一个重试策略:
// 这是一个概念性的示例,Polly的实际用法会更详细 // using Polly; // using Polly.Extensions.Http; // 定义一个重试策略:重试3次,每次重试间隔时间递增 // var retryPolicy = HttpPolicyExtensions // .HandleTransientHttpError() // 处理瞬时HTTP错误(5xx, 408, DNS等) // .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound) // 也可以处理特定状态码 // .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); // 然后在发送请求时应用这个策略 // HttpResponseMessage response = await retryPolicy.ExecuteAsync(() => _httpClient.GetAsync(url));
将Polly与
IHttpClientFactory
结合使用是最佳实践,
IHttpClientFactory
允许你在注册
HttpClient
时直接添加Polly策略,使得重试逻辑与业务代码分离,更加清晰和可维护。这显著提升了客户端的健壮性和对外部服务波动的容忍度。
word js json 编码 app access 端口 工具 ai dns c# dns解析失败 asic 构造函数 Token 线程 delete 并发 对象 异步 http 负载均衡