web-dev-qa-db-fra.com

HttpClient vs HttpWebRequest pour de meilleures performances, sécurité et moins de connexions

J'ai découvert qu'un seul HttpClient pouvait être partagé par plusieurs demandes. S'ils sont partagés et que les demandes sont vers la même destination, plusieurs demandes peuvent réutiliser les connexions. WebRequest doit recréer la connexion pour chaque demande.

J'ai également recherché de la documentation sur d'autres façons d'utiliser HttpClient dans des exemples.

L'article suivant résume le partage de connexion authentifié NTLM à grande vitesse: HttpWebRequest.UnsafeAuthenticatedConnectionSharing

Les implémentations possibles que j'ai essayées sont présentées ci-dessous

UNE)

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

J'apprécierais toute aide pour me faire comprendre quelle approche je devrais adopter afin d'atteindre des performances maximales, de minimiser les connexions et de s'assurer que la sécurité n'est pas affectée.

24

Si vous utilisez l'un d'eux avec async, cela devrait être bon pour le point de vue des performances car il ne bloquera pas les ressources en attente de la réponse et vous obtiendrez un bon débit.

HttpClient est préféré à HttpWebRequest en raison des méthodes asynchrones disponibles et vous n'avez pas à vous soucier de l'écriture des méthodes de début/fin.

Fondamentalement, lorsque vous utilisez un appel asynchrone (en utilisant l'une des classes), il ne bloquera pas les ressources en attente de la réponse et toute autre demande utiliserait les ressources pour effectuer d'autres appels.

Une autre chose à garder à l'esprit que vous ne devez pas utiliser HttpClient dans le bloc "using" pour permettre la réutilisation des mêmes ressources encore et encore pour d'autres requêtes Web.

Voir le fil suivant pour plus d'informations

HttpClient et HttpClientHandler doivent-ils être supprimés?

21
Sujit Singh

C'est mon ApiClient qui crée le HttpClient pour une seule fois. Enregistrez cet objet en tant que singleton dans votre bibliothèque d'injection de dépendances. Il est sûr de le réutiliser car il est apatride. Ne recréez PAS HTTPClient pour chaque demande. Réutilisez Httpclient autant que possible

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 + "/";
    }
}

L'usage;

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;
}
1
Alper Ebicoglu
  1. Il y a un problème dans votre implémentation 'A'. La durée de vie de l'instance renvoyée par GetWebRequestHandler() est de courte durée (peut-être juste pour le plaisir de l'exemple?). Si cela a été fait exprès, cela annule le passage de false pour le 2ème paramètre du constructeur HttpClient. La valeur de false indique au HttpClient de ne pas supprimer le HttpMessageHandler sous-jacent (ce qui facilite la mise à l'échelle car il ne ferme pas le port pour la demande). Ceci, bien sûr, en supposant que la durée de vie du HttpMessageHandler est suffisamment longue pour que vous profitiez de l'avantage de ne pas ouvrir/fermer les ports (ce qui a un impact important sur l'évolutivité de votre serveur). Ainsi, j'ai une recommandation ci-dessous de l'option "D".

  2. Il y a aussi une option 'D' que vous ne listez pas ci-dessus - pour rendre l'instance Httpclientstatic et réutilisée pour tous les appels api. Ceci est beaucoup plus efficace du point de vue de l'allocation de mémoire et du GC - ainsi que de l'ouverture des ports sur le client. Vous n'avez pas la surcharge d'allouer de la mémoire et de créer des instances HttpClient (et tous ses objets sous-jacents) et évitez ainsi le nettoyage via GC pour eux aussi.

Veuillez vous référer à ma réponse fournie sur une question similaire - Quel est le surcoût de la création d'un nouveau HttpClient par appel dans un client WebAPI?

0
Dave Black