我的团队维护着一个工具,负责对1000多个不同的客户网站进行快速验证。该工具是一个Windows服务(.NET 4.5.2,C#),它从队列中读取请求,并执行健康检查'对于每个请求。它通常每分钟处理500多个请求,但可以负责更多。每个请求都需要一两秒才能执行。
请求包含Uri
和执行运行状况检查所需的凭据。健康检查是针对AUTH页面的POST
,其中包含凭据(应用程序具有自定义身份验证,它不是基于头的身份验证),然后是GET
到主页,快速验证它是我们期望的主页。然后它进入应用程序中的状态页面,并对其进行一些快速检查。 GET请求必须使用auth帖子中Set-Cookie标头中的cookie。
随着工具的扩展,我们一直遇到性能问题。它目前为每个帖子创建一个新的HttpWebRequest
对象并进入该过程。第一篇文章填充了一个共享的CookieContainer,以便我们可以进入主页,然后进入状态页面。
我想更改此服务以使用 .NET 4.5中提供的HttpClient
对象。问题在于我在网上阅读的所有地方都说你想避免快速创建和破坏{{ 1}}。您应该在应用程序的生命周期中保持一个实例处于活动状态。我遇到的问题是HttpClients
似乎对一个端点非常有效,而不是很多。
我已经研究了几个选项,并且不确定哪个选项最好:
HttpClient
,并在该请求期间使用它。这意味着它将存活几秒钟,并用于3次通话。这不容易实现,但我担心每分钟创建和销毁数百HttpClient
的开销。HttpClients
并使用客户端使用{{HttpClient
来传递BaseAddres
,确定是否可以将一个HttpRequestMessages
实例用于不同的端点1}}。 SendAsync
存储Cookie,我在HttpClient
中将UseCookies
设置为false,并尝试通过HttpClientHandler
/ HttpRequest
中的标题管理Cookie ,但看起来ResponseMessages
只是在HttpClient
设置为UseCookies
时删除Cookie,因此我无法在请求之间传递Cookie。false
个实例,并在请求进入时为每个HttpClient
提取适当的实例。我不确定此内存开销虽然。此外,每个唯一Uri
仅每5分钟验证一次,因此我不确定每5分钟使用一次Uri
是否会打开不必要数量的端口。HttpClient
。也许这种旧方法在这种情况下表现更好。如果有人遇到过类似的问题,我会对如何处理这个问题表示喜欢。
谢谢!
答案 0 :(得分:1)
为每个请求创建新的HttpClients的问题是HttpClientHandler将关闭底层的TCP / IP连接。但是,如果您将每个HttpClient用于对一个主机的3个请求,然后命中另一个主机,那么在移动到新主机时保持连接打开并不会有帮助。因此,您可能不会看到每个主机一个客户端的性能问题。 HttpClient本身是一个非常轻量级的对象。创建一个产品并不会花费太多。
但是,HttpClient只是简单地将实际工作委托给HttpClientHandler,后者使用HttpWebRequest,因此不可能比直接使用HttpWebRequest有更好的性能。
如果您正在寻找更好的性能,那么我建议用新的WinHttpHandler替换HttpClientHandler,它绕过HttpWebRequest并直接转到Win32 API进行调用。
完整的源代码可用于WinHttpHandler on GitHub,因此您可以准确了解它如何处理Cookie和凭据。
我真的很想知道你是否用WinHttpHandler获得了更好的性能。
答案 1 :(得分:0)
首先,您需要修改哪一部分以满足您的需求?
var urisToCheck = new List<Uri>(); //get these somehow
//basic auth work?
var credentials = new NetworkCredential("user", "pass");
var handler = new HttpClientHandler { Credentials = credentials };
var client = new HttpClient(handler);
Parallel.ForEach(urisToCheck,
async uri =>
{
var response = await client.GetAsync(uri.AbsoluteUri);
//check for whatever you want here
}
);
答案 2 :(得分:0)
这是我的基本API客户端,它对每个请求都使用相同的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;
}
将此对象作为单例注册到依赖项注入库。重用是安全的,因为它是无状态的。
请勿为每个请求重新创建HTTPClient 。
尽可能重用Httpclient