我做了一些简单的比较测试,我发现的一些信息是
单个HttpClient可以由多个请求共享,如果共享并且请求到达同一目的地,多个请求可以重用连接 WebRequest需要为每个请求重新创建连接。
我还查阅了一些关于使用HttpClient示例的其他方法的文档
以下文章总结了高速NTLM身份验证的连接共享
HttpWebRequest.UnsafeAuthenticatedConnectionSharing
我尝试的可能实现如下所示
A)
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")
我很感激任何帮助,让我了解应采取哪种方法,以实现最大性能,最小化连接并确保安全性不受影响。
答案 0 :(得分:21)
如果你使用异步中的任何一个,它应该对性能观点有利,因为它不会阻塞等待响应的资源,你将获得良好的吞吐量。
由于开箱即用的异步方法,HttpClient优于HttpWebRequest,您不必担心编写开始/结束方法。
基本上当你使用异步调用(使用任何一个类)时,它不会阻塞等待响应的资源,任何其他请求都会利用这些资源进行进一步的调用。
要记住的另一件事是你不应该在'using'块中使用HttpClient来允许一次又一次地为其他web请求重用相同的资源。
有关详细信息,请参阅以下主题
答案 1 :(得分:1)
这是我的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;
}
答案 2 :(得分:0)
您的实施'A'存在问题。从GetWebRequestHandler()
返回的实例的生命周期是短暂的(可能只是为了示例?)。如果这是故意的,它会否定false
传递给HttpClient
构造函数的第二个参数。 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?