C#的HttpClient类如何发送HTTP请求?

来源:这里教程网 时间:2026-02-21 17:25:26 作者:

使用httpclient时需复用实例或使用httpclientfactory管理生命周期。1.避免为每个请求创建新httpclient实例,以防止端口耗尽和dns解析浪费;2.推荐将httpclient声明为静态或使用httpclientfactory进行依赖注入,以实现连接复用并解决dns缓存问题;3.httpclientfactory通过管理httpmessagehandler的生命周期,既提升性能又确保dns更新及时生效。

C#的HttpClient类如何发送HTTP请求?

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策略,使得重试逻辑与业务代码分离,更加清晰和可维护。这显著提升了客户端的健壮性和对外部服务波动的容忍度。

相关推荐