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优于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;
}
-
您的实现"A"中存在问题。从
GetWebRequestHandler()
返回的实例的生存期很短(也许只是为了示例?如果这是故意这样做的,则它否定了构造函数的第二个参数HttpClient
传递false
。false
的值告诉 HttpClient 不要释放基础HttpMessageHandler
(这有助于缩放,因为它不会关闭请求的端口)。当然,这是假设HttpMessageHandler
的生存期足够长,以便您利用不打开/关闭端口的好处(这对服务器的可伸缩性有很大影响)。 因此,我在下面对选项"D"有建议。 -
还有一个选项"D",您没有在上面列出 - 使
Httpclient
实例static
并在所有 api 调用中重复使用。从内存分配和 GC 的角度来看,以及在客户端上打开端口的效率要高得多。 您没有为HttpClient
实例(及其所有基础对象)分配内存和创建内存的开销,因此也避免了通过 GC 清理它们。
请参考我提供的关于类似问题的回答 - 在 WebAPI 客户端中每次调用创建新的 HttpClient 的开销是多少?