LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

C#中的HTTP客户端:专家级最佳实践与性能优化指南

admin
2025年3月14日 10:50 本文热度 139

在微服务架构中,或与外部API通信时,HTTP客户端是必不可少的组件。然而,许多开发人员在实现HTTP客户端时未能充分考虑性能和可用性。

本文将介绍使用C#中的HttpClient类的最佳实践,并探讨HTTP通信的一些重要方面。

1. 不要在每次请求时创建和销毁HttpClient

初学者最常犯的错误是在每次HTTP请求时创建和销毁HttpClient实例。

public async Task<stringGetStringFromApi()
{
    using (var client = new HttpClient())
    {
        return await client.GetStringAsync("https://api.example.com/data");
    }
}

为什么这是错误的?

每次创建HttpClient的新实例时,都会分配新的socket连接。当客户端进入using语句块的末尾时,socket连接并不会立即释放,而是会进入TIME_WAIT状态,这可能会持续数十秒。

在高负载下,这会导致socket耗尽(SocketException: Address already in use),因为操作系统需要几分钟来回收套接字。

2. 不要将HttpClient保留为单例

另一个常见做法是将HttpClient对象创建为单例。

public class ApiClient
{
    private static readonly HttpClient _client = new HttpClient();

    public async Task<stringGetStringFromApi()
    {
        return await _client.GetStringAsync("https://api.example.com/data");
    }
}

为什么这不是最佳选择?

HttpClient是为长期使用而设计的,但将其作为单例使用有其问题:

  • • HttpClient不会自动响应DNS变更
  • • 在某些情况下,之前设置的默认请求头可能会带到后续请求
  • • 没有自动断路器机制
  • • 无法控制同一主机的最大连接数(可能导致拥塞)

3. 使用HttpClientFactory(.NET Core 2.1及以上)

.NET Core 2.1引入了HttpClientFactory,解决了上述问题。

// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient("github", c =>
    {
        c.BaseAddress = new Uri("https://api.github.com/");
        c.DefaultRequestHeaders.Add("Accept""application/vnd.github.v3+json");
        c.DefaultRequestHeaders.Add("User-Agent""HttpClientFactory-Sample");
    });
}

// GithubService.cs
publicclassGithubService
{
    privatereadonly IHttpClientFactory _clientFactory;

    public GithubService(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public async Task<stringGetAspNetDocsIssues()
    {
        var client = _clientFactory.CreateClient("github");
        var response = await client.GetAsync("/repos/aspnet/AspNetCore.Docs/issues");
        response.EnsureSuccessStatusCode();

        returnawait response.Content.ReadAsStringAsync();
    }
}

HttpClientFactory提供以下优势:

  • • 管理底层HttpClientMessageHandler的生命周期
  • • 支持可靠的DNS刷新
  • • 应用轮询策略防止连接拥塞(轮流使用连接,而不是一次全部使用)
  • • 内置对Polly集成的支持,便于添加断路器、超时、重试等弹性政策

4. 使用强类型客户端

可以通过使用AddHttpClient<TClient>()方法注册强类型客户端,进一步改进HttpClientFactory方法。

// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient<IGithubClient, GithubClient>(c =>
    {
        c.BaseAddress = new Uri("https://api.github.com/");
        c.DefaultRequestHeaders.Add("Accept""application/vnd.github.v3+json");
        c.DefaultRequestHeaders.Add("User-Agent""HttpClientFactory-Sample");
    });
}

// GithubClient.cs
publicinterfaceIGithubClient
{
    Task<IEnumerable<GithubIssue>> GetAspNetDocsIssues();
}

publicclassGithubClient : IGithubClient
{
    privatereadonly HttpClient _httpClient;

    public GithubClient(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    publicasync Task<IEnumerable<GithubIssue>> GetAspNetDocsIssues()
    {
        var response = await _httpClient.GetAsync("/repos/aspnet/AspNetCore.Docs/issues");
        response.EnsureSuccessStatusCode();

        var content = await response.Content.ReadAsStringAsync();
        return JsonConvert.DeserializeObject<IEnumerable<GithubIssue>>(content);
    }
}

这种方法的主要优势在于:

  • • 强类型接口
  • • 依赖注入即插即用
  • • 分离关注点

5. 设置超时

HttpClient的默认超时是100秒,这可能过长。建议设置更合理的超时值。

services.AddHttpClient("github", c =>
{
    c.BaseAddress = new Uri("https://api.github.com/");
    // 将超时设置为10秒
    c.Timeout = TimeSpan.FromSeconds(10);
});

对于HttpClient设置了Timeout,所有请求默认都会使用此值。也可以在单个请求基础上设置不同的超时。

6. 实现弹性模式

HTTP通信受多种因素影响,可能出现间歇性故障。使用弹性模式来处理这些问题:

重试策略

services.AddHttpClient("github")
    .AddTransientHttpErrorPolicy(p => 
        p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));

断路器

services.AddHttpClient("github")
    .AddTransientHttpErrorPolicy(p => 
        p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));

组合策略

services.AddHttpClient("github")
    .AddTransientHttpErrorPolicy(p => 
        p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)))
    .AddTransientHttpErrorPolicy(p => 
        p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));

这种组合应用了重试策略和断路器模式:请求可能在失败后重试3次,但如果持续失败,断路器会触发并阻止进一步请求30秒。

7. 正确处理销毁

虽然HttpClient实现了IDisposable,但使用HttpClientFactory时不需要显式销毁由工厂创建的客户端。工厂负责管理客户端生命周期。

// 不需要using语句
public async Task<stringGetDataFromApi()
{
    var client = _clientFactory.CreateClient("named-client");
    return await client.GetStringAsync("/api/data");
}

8. 处理取消请求

使用CancellationToken来允许取消长时间运行的请求:

public async Task<stringGetLongRunningDataAsync(CancellationToken cancellationToken = default)
{
    var client = _clientFactory.CreateClient("named-client");
    var response = await client.GetAsync("/api/longrunning", cancellationToken);
    response.EnsureSuccessStatusCode();
    
    return await response.Content.ReadAsStringAsync();
}

允许从控制器取消请求:

[HttpGet]
public async Task<IActionResult> Get(CancellationToken cancellationToken)
{
    try
    {
        var data = await _apiClient.GetLongRunningDataAsync(cancellationToken);
        return Ok(data);
    }
    catch (OperationCanceledException)
    {
        // 请求已取消,无需进一步处理
        return StatusCode(499); // 客户端关闭请求
    }
}

9. 添加请求和响应记录

为HTTP调用添加日志记录,以帮助调试和监控:

services.AddHttpClient("github")
    .AddHttpMessageHandler(() => new LoggingHandler(_loggerFactory));

publicclassLoggingHandler : DelegatingHandler
{
    privatereadonly ILogger _logger;

    public LoggingHandler(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<LoggingHandler>();
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        _logger.LogInformation("Making request to {Url}", request.RequestUri);
        
        try
        {
            // 测量请求时间
            var stopwatch = Stopwatch.StartNew();
            var response = awaitbase.SendAsync(request, cancellationToken);
            stopwatch.Stop();
            
            _logger.LogInformation("Received response from {Url} with status code {StatusCode} in {ElapsedMilliseconds}ms",
                request.RequestUri, response.StatusCode, stopwatch.ElapsedMilliseconds);
                
            return response;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error making HTTP request to {Url}", request.RequestUri);
            throw;
        }
    }
}

10. 压缩

为了提高性能,尤其是在处理大型响应时,启用HTTP压缩:

services.AddHttpClient("github")
    .ConfigureHttpClient(client =>
    {
        client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));
        client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("deflate"));
    });

要处理压缩的响应:

public async Task<stringGetCompressedDataAsync()
{
    var client = _clientFactory.CreateClient("github");
    var response = await client.GetAsync("/api/largedata");
    response.EnsureSuccessStatusCode();
    
    // HttpClient自动处理解压缩
    return await response.Content.ReadAsStringAsync();
}

11. 处理认证

常见的认证方法包括:

基本认证

services.AddHttpClient("authenticated-client")
    .ConfigureHttpClient(client =>
    {
        var credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes("username:password"));
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials);
    });

Bearer令牌

services.AddHttpClient("authenticated-client")
    .ConfigureHttpClient(client =>
    {
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer""your-access-token");
    });

动态认证处理

使用HttpClientFactoryAddHttpMessageHandler方法来添加认证处理:

services.AddTransient<AuthenticationHandler>();
services.AddHttpClient("authenticated-client")
    .AddHttpMessageHandler<AuthenticationHandler>();

publicclassAuthenticationHandler : DelegatingHandler
{
    privatereadonly ITokenService _tokenService;

    public AuthenticationHandler(ITokenService tokenService)
    {
        _tokenService = tokenService;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // 动态获取令牌
        var token = await _tokenService.GetTokenAsync();
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
        
        returnawaitbase.SendAsync(request, cancellationToken);
    }
}

12. 处理并发请求

要并行发送多个请求:

public async Task<IEnumerable<Product>> GetProductsAsync(IEnumerable<int> productIds)
{
    var client = _clientFactory.CreateClient("product-api");
    
    var tasks = productIds.Select(id => 
        client.GetFromJsonAsync<Product>($"/api/products/{id}"));
    
    return await Task.WhenAll(tasks);
}

然而,要小心避免启动太多并行请求。考虑批处理或使用信号量来限制并发请求数:

public async Task<IEnumerable<Product>> GetProductsWithSemaphoreAsync(IEnumerable<int> productIds)
{
    var client = _clientFactory.CreateClient("product-api");
    var results = new List<Product>();
    
    // 限制最多5个并发请求
    usingvar semaphore = new SemaphoreSlim(5);
    var tasks = productIds.Select(async id => 
    {
        await semaphore.WaitAsync();
        try
        {
            returnawait client.GetFromJsonAsync<Product>($"/api/products/{id}");
        }
        finally
        {
            semaphore.Release();
        }
    });
    
    returnawait Task.WhenAll(tasks);
}

正确使用HttpClient对于创建高性能、可靠和可维护的应用程序至关重要。通过采用HttpClientFactory和遵循本文中的最佳实践,您可以避免常见的陷阱并构建能够有效处理HTTP通信的强大应用程序。

记住这些关键点:

  • • 使用HttpClientFactory而不是直接实例化HttpClient
  • • 使用强类型客户端
  • • 设置合理的超时
  • • 实现弹性模式
  • • 正确处理认证和并发
  • • 考虑性能优化如压缩

通过这些实践,您的应用程序将更好地处理网络通信的挑战,并为用户提供更好的体验。

阅读原文:https://mp.weixin.qq.com/s/YOTpp0llYylmAeMGZHcDsQ


该文章在 2025/3/14 10:50:18 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2025 ClickSun All Rights Reserved