HttpClient与HttpWebRequest相比,可提供更好的性能和安全性以及更少的连接

时间:2015-01-06 07:07:37

标签: c# asp.net-web-api httpwebrequest httpclient

我做了一些简单的比较测试,我发现的一些信息是

单个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")

我很感激任何帮助,让我了解应采取哪种方法,以实现最大性能,最小化连接并确保安全性不受影响。

3 个答案:

答案 0 :(得分:21)

如果你使用异步中的任何一个,它应该对性能观点有利,因为它不会阻塞等待响应的资源,你将获得良好的吞吐量。

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

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

要记住的另一件事是你不应该在'using'块中使用HttpClient来允许一次又一次地为其他web请求重用相同的资源。

有关详细信息,请参阅以下主题

Do HttpClient and HttpClientHandler have to be disposed?

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

  1. 您的实施'A'存在问题。从GetWebRequestHandler()返回的实例的生命周期是短暂的(可能只是为了示例?)。如果这是故意的,它会否定false传递给HttpClient构造函数的第二个参数。 false的值告诉HttpClient不要处置基础HttpMessageHandler(这有助于扩展,因为它不会关闭请求的端口)。当然,这是假设HttpMessageHandler的生命周期足够长,您可以利用不打开/关闭端口的好处(这对服务器的可扩展性有很大影响)。因此,我在下面提出了选项'D'的建议。

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

  3. 请参阅我提供的有关类似问题的答案 - What is the overhead of creating a new HttpClient per call in a WebAPI client?