使用HttpClient C#

时间:2016-05-13 15:50:41

标签: c# http httpclient

我的团队维护着一个工具,负责对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似乎对一个端点非常有效,而不是很多。

我已经研究了几个选项,并且不确定哪个选项最好:

  1. 为每个请求创建一个新的HttpClient,并在该请求期间使用它。这意味着它将存活几秒钟,并用于3次通话。这不容易实现,但我担心每分钟创建和销毁数百HttpClient的开销。
  2. 通过避免使用HttpClients并使用客户端使用{{HttpClient来传递BaseAddres,确定是否可以将一个HttpRequestMessages实例用于不同的端点1}}。 我还没能用这种方法找出饼干。为避免SendAsync存储Cookie,我在HttpClient中将UseCookies设置为false,并尝试通过HttpClientHandler / HttpRequest中的标题管理Cookie ,但看起来ResponseMessages只是在HttpClient设置为UseCookies时删除Cookie,因此我无法在请求之间传递Cookie。编辑:Cookie工作正常,因为它们是按域存储。
  3. 在某种字典中存储数百个不同的false个实例,并在请求进入时为每个HttpClient提取适当的实例。我不确定此内存开销虽然。此外,每个唯一Uri仅每5分钟验证一次,因此我不确定每5分钟使用一次Uri是否会打开不必要数量的端口。
  4. 继续使用HttpClient。也许这种旧方法在这种情况下表现更好。
  5. 如果有人遇到过类似的问题,我会对如何处理这个问题表示喜欢。

    谢谢!

3 个答案:

答案 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