HttpClient против HttpWebRequest для повышения производительности, безопасности и уменьшения количества соединений

Я обнаружил, что один 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")

Я был бы признателен за любую помощь, чтобы дать мне понять, какой подход я должен использовать для достижения максимальной производительности, минимизации подключений и обеспечения безопасности.

Ответ 1

Если вы используете любой из них с async, он должен быть хорош для точки зрения производительности, поскольку он не будет блокировать ресурсы, ожидающие ответа, и вы получите хорошую пропускную способность.

HttpClient предпочтительнее, чем HttpWebRequest из-за методов async, доступных из коробки, и вам не придется беспокоиться о написании методов begin/end.

В основном, когда вы используете асинхронный вызов (используя любой из классов), он не будет блокировать ресурсы, ожидающие ответа, и любой другой запрос будет использовать ресурсы для дальнейших вызовов.

Другое дело иметь в виду, что вы не должны использовать HttpClient в блоке "using", чтобы повторно использовать одни и те же ресурсы снова и снова для других веб-запросов.

Для получения дополнительной информации см. следующий поток

Должны ли быть удалены HttpClient и HttpClientHandler?

Ответ 2

Это мой 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;
}

Ответ 3

  • В вашей реализации "A" есть проблема. Срок службы экземпляра, возвращаемого из GetWebRequestHandler(), недолговечен (возможно, только для примера?). Если это было сделано специально, это отрицает передачу false для второго параметра конструктора HttpClient. Значение false указывает HttpClient не удалять базовый HttpMessageHandler (который помогает при масштабировании, так как он не закрывает порт для запроса). Это, конечно, предполагает, что срок службы HttpMessageHandler достаточно длинный, чтобы вы могли воспользоваться преимуществами не открытия/закрытия портов (что сильно влияет на масштабируемость вашего сервера). Таким образом, у меня есть рекомендация ниже опции "D".

  • Существует также опция "D", которую вы не перечисляете выше, - сделать экземпляр HttpClient static и повторно использовать все вызовы api. Это намного эффективнее от распределения памяти и перспективы GC, а также открытия портов на клиенте. У вас нет накладных расходов на выделение памяти и создание экземпляров HttpClient (и всех его базовых объектов), и, таким образом, для них также можно очистить через GC.

Пожалуйста, обратитесь к моему ответу, предоставленному по аналогичному вопросу - Каковы накладные расходы на создание нового HttpClient на звонок в клиенте WebAPI?