HttpClient vs HttpWebRequest 以获得更好的性能、安全性和更少的连接

Posted

技术标签:

【中文标题】HttpClient vs HttpWebRequest 以获得更好的性能、安全性和更少的连接【英文标题】:HttpClient vs HttpWebRequest for better performance, security and less connections 【发布时间】:2015-01-06 07:07:37 【问题描述】:

我发现一个 HttpClient 可以被多个请求共享。如果共享,并且请求到达相同的目的地,则多个请求可以重用连接。 WebRequest 需要为每个请求重新创建连接。

我还在示例中查找了有关使用 HttpClient 的其他方法的一些文档。

以下文章总结了高速NTLM认证的连接共享:HttpWebRequest.UnsafeAuthenticatedConnectionSharing

我尝试过的可能实现如下所示

一)

private WebRequestHandler GetWebRequestHandler()

    CredentialCache credentialCache = new CredentialCache();
    credentialCache.Add(ResourceUriCanBeAnyUri, "NTLM", CredentialCache.DefaultNetworkCredentials);
    WebRequestHandler handler = new WebRequestHandler
    
        UnsafeAuthenticatedConnectionSharing = true,
        Credentials = credentialCache
    ;

    return handler;


using (HttpClient client = new HttpClient(GetWebRequestHandler(), false))


B)

using (HttpClient client = new HttpClient)


C)

HttpWebRequest req = (HttpWebRequest)WebRequest.Create("some uri string")

如果能帮助我了解我应该采用哪种方法以实现最大性能、最小化连接并确保不影响安全性,我将不胜感激。

【问题讨论】:

HttpClient 是镇上新来的酷孩子,据说它是最好的,支持异步/任务,并且比其他的更便携(还有 WebClient)。但是它需要 .NET 4.5+。话虽如此,我认为如果使用得当,它们之间的原始性能不会有太大差异。 我你用HttpClient看看这个帖子YOU'RE USING HTTPCLIENT WRONG AND IT IS DESTABILIZING YOUR SOFTWARE 肯定会使用 HttpClient,除了它处理连接池、开箱即用的 async/await 之外,它还通过它们提供了更大的灵活性 Handlers 并且也更容易使用 HttpClient 编写单元测试。 HttpClient 旨在被实例化一次并在应用程序的整个生命周期中重复使用。特别是在服务器应用程序中,为每个请求创建一个新的 HttpClient 实例将耗尽重负载下可用的套接字数量。这将导致 SocketException 错误。示例:asp.net/web-api/overview/advanced/… 先看看这个:docs.microsoft.com/en-us/archive/blogs/timomta/… DefaultConnectionLimit 参数可能会导致客户端瓶颈 【参考方案1】:

如果您将它们中的任何一个与异步一起使用,从性能角度来看应该是好的,因为它不会阻塞等待响应的资源,并且您将获得良好的吞吐量。

HttpClient 优于 HttpWebRequest,因为异步方法可用开箱即用,您不必担心编写开始/结束方法。

基本上,当您使用异步调用(使用任一类)时,它不会阻塞等待响应的资源,任何其他请求都会利用这些资源进行进一步的调用。

要记住的另一件事是,您不应该在“使用”块中使用 HttpClient,以允许将相同的资源一次又一次地重用于其他 Web 请求。

更多信息请看下面的帖子

Do HttpClient and HttpClientHandler have to be disposed?

【讨论】:

是的,刚刚学会了自己处理 HttpClient。似乎是现在的大讨论。长话短说,不要丢弃它! HttpWeRequest 也支持异步,所以这个答案已经过时了【参考方案2】:

这是我的 ApiClient,它只创建一次 HttpClient。将此对象注册为单例到您的依赖注入库。重用是安全的,因为它是无状态的。不要为每个请求重新创建 HTTPClient。尽可能复用Httpclient

using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
//You need to install package Newtonsoft.Json > https://www.nuget.org/packages/Newtonsoft.Json/
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;


public class MyApiClient : IDisposable

    private readonly TimeSpan _timeout;
    private HttpClient _httpClient;
    private HttpClientHandler _httpClientHandler;
    private readonly string _baseUrl;
    private const string ClientUserAgent = "my-api-client-v1";
    private const string MediaTypeJson = "application/json";

    public MyApiClient(string baseUrl, TimeSpan? timeout = null)
    
        _baseUrl = NormalizeBaseUrl(baseUrl);
        _timeout = timeout ?? TimeSpan.FromSeconds(90);   
    

    public async Task<string> PostAsync(string url, object input)
    
        EnsureHttpClientCreated();

        using (var requestContent = new StringContent(ConvertToJsonString(input), Encoding.UTF8, MediaTypeJson))
        
            using (var response = await _httpClient.PostAsync(url, requestContent))
            
                response.EnsureSuccessStatusCode();
                return await response.Content.ReadAsStringAsync();
            
        
    

    public async Task<TResult> PostAsync<TResult>(string url, object input) where TResult : class, new()
    
        var strResponse = await PostAsync(url, input);

        return JsonConvert.DeserializeObject<TResult>(strResponse, new JsonSerializerSettings
        
            ContractResolver = new CamelCasePropertyNamesContractResolver()
        );
    

    public async Task<TResult> GetAsync<TResult>(string url) where TResult : class, new()
    
        var strResponse = await GetAsync(url);

        return JsonConvert.DeserializeObject<TResult>(strResponse, new JsonSerializerSettings
        
            ContractResolver = new CamelCasePropertyNamesContractResolver()
        );
    

    public async Task<string> GetAsync(string url)
    
        EnsureHttpClientCreated();

        using (var response = await _httpClient.GetAsync(url))
        
            response.EnsureSuccessStatusCode();
            return await response.Content.ReadAsStringAsync();
        
    

    public async Task<string> PutAsync(string url, object input)
    
        return await PutAsync(url, new StringContent(JsonConvert.SerializeObject(input), Encoding.UTF8, MediaTypeJson));
    

    public async Task<string> PutAsync(string url, HttpContent content)
    
        EnsureHttpClientCreated();

        using (var response = await _httpClient.PutAsync(url, content))
        
            response.EnsureSuccessStatusCode();
            return await response.Content.ReadAsStringAsync();
        
    

    public async Task<string> DeleteAsync(string url)
    
        EnsureHttpClientCreated();

        using (var response = await _httpClient.DeleteAsync(url))
        
            response.EnsureSuccessStatusCode();
            return await response.Content.ReadAsStringAsync();
        
    

    public void Dispose()
    
        _httpClientHandler?.Dispose();
        _httpClient?.Dispose();
    

    private void CreateHttpClient()
    
        _httpClientHandler = new HttpClientHandler
        
            AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip
        ;

        _httpClient = new HttpClient(_httpClientHandler, false)
        
            Timeout = _timeout
        ;

        _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(ClientUserAgent);

        if (!string.IsNullOrWhiteSpace(_baseUrl))
        
            _httpClient.BaseAddress = new Uri(_baseUrl);
        

        _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeJson));
    

    private void EnsureHttpClientCreated()
    
        if (_httpClient == null)
        
            CreateHttpClient();
        
    

    private static string ConvertToJsonString(object obj)
    
        if (obj == null)
        
            return string.Empty;
        

        return JsonConvert.SerializeObject(obj, new JsonSerializerSettings
        
            ContractResolver = new CamelCasePropertyNamesContractResolver()
        );
    

    private static string NormalizeBaseUrl(string url)
    
        return url.EndsWith("/") ? url : url + "/";
    

用法;

using ( var client = new MyApiClient("http://localhost:8080"))

    var response = client.GetAsync("api/users/findByUsername?username=alper").Result;
    var userResponse = client.GetAsync<MyUser>("api/users/findByUsername?username=alper").Result;

【讨论】:

应该等待而不是 .Result 那些方法。【参考方案3】:

    您的实现“A”中存在问题。从GetWebRequestHandler() 返回的实例的生命周期是短暂的(也许只是为了示例?)。如果这是故意这样做的,它将否定 falseHttpClient 构造函数的第二个参数传递。 false 的值告诉 HttpClient 不要处理底层的HttpMessageHandler(这有助于扩展,因为它不会关闭请求的端口)。当然,这是假设HttpMessageHandler 的生命周期足够长,您可以利用不打开/关闭端口的好处(这对您的服务器的可伸缩性有很大影响)。因此,我有以下选项“D”的建议。

    还有一个您没有在上面列出的选项“D” - 使 Httpclient 实例 static 并在所有 api 调用中重复使用。从内存分配和 GC 的角度来看,这更有效 - 以及在客户端上打开端口。您无需为HttpClient 实例(及其所有底层对象)分配内存和创建开销,因此也无需通过 GC 对它们进行清理。

请参考我提供的关于类似问题的回答 - What is the overhead of creating a new HttpClient per call in a WebAPI client?

【讨论】:

以上是关于HttpClient vs HttpWebRequest 以获得更好的性能、安全性和更少的连接的主要内容,如果未能解决你的问题,请参考以下文章

HttpClient vs HttpWebRequest 以获得更好的性能、安全性和更少的连接

HttpClient不适用于使用Xamarin在VS中构建的IO应用程序

.NET Core 2.2 HttpClient/WebClient vs Curl - .NET 库对于某些服务器来说非常慢

如果你想在Java代码中写一个Http客户端,你会选择哪一种方式?Okhttp vs Apache vs Jdk

如何更改 HttpClient 响应的编码

C# 9+ 中的 HttpClient 空警告