HttpClient vs HttpWebRequest 具有更好的性能、安全性和更少的连接

本文关键字:安全性 连接 性能 HttpWebRequest 更好 HttpClient vs | 更新日期: 2023-09-27 18:36:27

我发现单个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))
{
}

二)

using (HttpClient client = new HttpClient)
{
}

(三))

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

我将不胜感激任何帮助,让我了解我应该采取哪种方法,以实现最大性能,最大限度地减少连接并确保安全性不受影响。

HttpClient vs HttpWebRequest 具有更好的性能、安全性和更少的连接

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

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

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

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

有关详细信息,请参阅以下线程

HttpClient 和 HttpClientHandler 必须被释放吗?

这是我

的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;
}
  1. 您的实现"A"中存在问题。从 GetWebRequestHandler() 返回的实例的生存期很短(也许只是为了示例?如果这是故意这样做的,则它否定了构造函数的第二个参数HttpClient传递falsefalse 的值告诉 HttpClient 不要释放基础HttpMessageHandler(这有助于缩放,因为它不会关闭请求的端口)。当然,这是假设HttpMessageHandler的生存期足够长,以便您利用不打开/关闭端口的好处(这对服务器的可伸缩性有很大影响)。 因此,我在下面对选项"D"有建议。

  2. 还有一个选项"D",您没有在上面列出 - 使Httpclient实例static并在所有 api 调用中重复使用。从内存分配和 GC 的角度来看,以及在客户端上打开端口的效率要高得多。 您没有为HttpClient实例(及其所有基础对象)分配内存和创建内存的开销,因此也避免了通过 GC 清理它们。

请参考我提供的关于类似问题的回答 - 在 WebAPI 客户端中每次调用创建新的 HttpClient 的开销是多少?