Delen via


IHttpClientFactory gebruiken om tolerante HTTP-aanvragen te implementeren

Aanbeveling

Deze inhoud is een fragment uit het eBook, .NET Microservices Architecture for Containerized .NET Applications, beschikbaar op .NET Docs of als een gratis downloadbare PDF die offline kan worden gelezen.

PDF- downloaden

.NET Microservices Architectuur voor Gecontaineriseerde .NET Applicaties eBook omslagminiatuur.

IHttpClientFactory is een contract dat wordt geïmplementeerd door DefaultHttpClientFactory, een gespecificeerde fabriek, beschikbaar sinds .NET Core 2.1, voor het maken van HttpClient-instanties voor gebruik in uw toepassingen.

Problemen met de oorspronkelijke HttpClient-klasse die beschikbaar is in .NET

De oorspronkelijke en bekende HttpClient klasse kan eenvoudig worden gebruikt, maar in sommige gevallen wordt deze niet goed gebruikt door veel ontwikkelaars.

Hoewel deze klasse IDisposableimplementeert, is het declareren en instantiëren binnen een using instructie niet aan te raden, omdat wanneer het HttpClient object wordt verwijderd, de onderliggende socket niet direct wordt vrijgegeven, wat kan leiden tot een probleem van socketuitputting. Raadpleeg het blogbericht "Je gebruikt HttpClient verkeerd en dat destabiliseert je software"voor meer informatie over dit probleem.

Daarom is HttpClient bedoeld om één keer te worden geïnstantieerd en hergebruikt gedurende de levensduur van een toepassing. Het instantiëren van een HttpClient-klasse voor elke aanvraag zal het aantal sockets dat beschikbaar is onder zware belastingen uitputten. Dit probleem leidt tot SocketException fouten. Mogelijke benaderingen om dit probleem op te lossen zijn gebaseerd op het maken van het HttpClient-object als singleton of statisch, zoals wordt uitgelegd in dit Microsoft-artikel over het gebruik van HttpClient. Dit kan een goede oplossing zijn voor console-apps met korte levensduur of vergelijkbare, die een paar keer per dag worden uitgevoerd.

Een ander probleem dat ontwikkelaars tegenkomen, is bij het gebruik van een gedeeld exemplaar van HttpClient in langlopende processen. In een situatie waarin de HttpClient wordt geïnstantieerd als een singleton of een statisch object, kan de DNS-wijzigingen niet worden verwerkt, zoals beschreven in dit probleem van de GitHub-opslagplaats dotnet/runtime.

Het probleem is echter niet echt met HttpClient per se, maar met de standaardconstructor voor HttpClient, omdat er een nieuw concreet exemplaar van HttpMessageHandlerwordt gemaakt, wat het geval is met sockets uitputting en DNS-wijzigingen die hierboven worden genoemd.

Om de hierboven genoemde problemen op te lossen en om HttpClient exemplaren beheerbaar te maken, heeft .NET Core 2.1 twee benaderingen geïntroduceerd, een van deze methoden is IHttpClientFactory. Het is een interface die wordt gebruikt voor het configureren en creëren van HttpClient instanties in een app via Dependency Injection (DI). Het biedt ook extensies voor op Polly gebaseerde middleware om te profiteren van het delegeren van handlers in HttpClient.

Het alternatief is om SocketsHttpHandler te gebruiken met geconfigureerde PooledConnectionLifetime. Deze benadering wordt toegepast op exemplaren met een lange levensduur, static of singleton HttpClient. Zie HttpClient-richtlijnen voor .NET-voor meer informatie over verschillende strategieën.

Polly- is een tijdelijke bibliotheek voor foutafhandeling waarmee ontwikkelaars tolerantie aan hun toepassingen kunnen toevoegen door een aantal vooraf gedefinieerde beleidsregels op een vloeiende en threadveilige manier te gebruiken.

Voordelen van het gebruik van IHttpClientFactory

De huidige implementatie van IHttpClientFactory, die ook IHttpMessageHandlerFactoryimplementeert, biedt de volgende voordelen:

  • Biedt een centrale locatie voor het benoemen en configureren van logische HttpClient objecten. U kunt bijvoorbeeld een client (serviceagent) configureren die vooraf is geconfigureerd voor toegang tot een specifieke microservice.
  • Codificeer het concept van uitgaande middleware door het delegeren van handlers in HttpClient en implementeer op Polly gebaseerde middleware om te profiteren van de veerkrachtmechanismen van Polly.
  • HttpClient beschikt al over het concept van het delegeren van handlers die kunnen worden gekoppeld voor uitgaande HTTP-aanvragen. U kunt HTTP-clients registreren bij de factory en u kunt een Polly-handler gebruiken om Polly-beleid te gebruiken voor opnieuw proberen, CircuitBreakers, enzovoort.
  • Beheer de levensduur van HttpMessageHandler om de genoemde problemen te voorkomen die kunnen optreden bij het zelf beheren van de levensduur van HttpClient.

Aanbeveling

De HttpClient instanties die door DI worden geïnjecteerd, kunnen veilig worden verwijderd, omdat de bijbehorende HttpMessageHandler wordt beheerd door de fabriek. Opgenomen HttpClient exemplaren zijn tijdelijk vanuit het perspectief van DI, terwijl HttpMessageHandler exemplaren kunnen worden beschouwd als gescopeerd. HttpMessageHandler exemplaren hebben hun eigen DI-bereiken, afzonderlijke van de toepassingsbereiken (bijvoorbeeld ASP.NET binnenkomende aanvraagbereiken). Zie HttpClientFactory gebruiken in .NETvoor meer informatie.

Notitie

De implementatie van IHttpClientFactory (DefaultHttpClientFactory) is nauw gekoppeld aan de DI-implementatie in het Microsoft.Extensions.DependencyInjection NuGet-pakket. Als u HttpClient zonder DI of met andere DI-implementaties wilt gebruiken, kunt u overwegen om een static of singleton-HttpClient te gebruiken met PooledConnectionLifetime ingesteld. Zie HttpClient-richtlijnen voor .NET-voor meer informatie.

Meerdere manieren om IHttpClientFactory te gebruiken

Er zijn verschillende manieren waarop u IHttpClientFactory in uw toepassing kunt gebruiken:

  • Basisgebruik
  • Benoemde clients gebruiken
  • Getypte clients gebruiken
  • Gegenereerde clients gebruiken

Uit oogpunt van beknoptheid toont deze richtlijn de meest gestructureerde manier om IHttpClientFactoryte gebruiken, namelijk het gebruik van getypte clients (Service Agent-patroon). Alle opties zijn echter gedocumenteerd en staan momenteel vermeld in dit artikel over het IHttpClientFactory gebruik.

Notitie

Als uw app cookies vereist, is het misschien beter om het gebruik van IHttpClientFactory in uw app te voorkomen. Zie Richtlijnen voor het gebruik van HTTP-clientsvoor alternatieve manieren om clients te beheren.

Getypte clients gebruiken met IHttpClientFactory

Wat is een 'Typed Client'? Het is slechts een HttpClient die vooraf is geconfigureerd voor een bepaald gebruik. Deze configuratie kan specifieke waarden bevatten, zoals de basisserver, HTTP-headers en time-outs.

In het volgende diagram ziet u hoe Typed Clients worden gebruikt met IHttpClientFactory.

Diagram dat laat zien hoe gespecificeerde clients worden gebruikt met IHttpClientFactory.

Afbeelding 8-4. Het gebruik van IHttpClientFactory met getypeerde clientklassen.

In de bovenstaande afbeelding gebruikt een ClientService (gebruikt door een controller of clientcode) een HttpClient gemaakt door de geregistreerde IHttpClientFactory. Deze fabriek wijst een HttpMessageHandler van een pool toe aan de HttpClient. De HttpClient kan worden geconfigureerd met het beleid van Polly bij het registreren van de IHttpClientFactory in de DI-container met de extensiemethode AddHttpClient.

Als u de bovenstaande structuur wilt configureren, voegt u IHttpClientFactory toe aan uw toepassing door het Microsoft.Extensions.Http NuGet-pakket te installeren dat de AddHttpClient extensiemethode voor IServiceCollectionbevat. Met deze extensiemethode wordt de interne DefaultHttpClientFactory-klasse geregistreerd die moet worden gebruikt als een singleton voor de interface IHttpClientFactory. Het definieert een tijdelijke configuratie voor de HttpMessageHandlerBuilder. Deze berichthandler (HttpMessageHandler object), afkomstig uit een pool, wordt gebruikt door de HttpClient die is teruggekregen van de fabriek.

In het volgende fragment ziet u hoe AddHttpClient() kan worden gebruikt om getypte clients (serviceagents) te registreren die HttpClientmoeten gebruiken.

// Program.cs
//Add http client services at ConfigureServices(IServiceCollection services)
builder.Services.AddHttpClient<ICatalogService, CatalogService>();
builder.Services.AddHttpClient<IBasketService, BasketService>();
builder.Services.AddHttpClient<IOrderingService, OrderingService>();

Als de clientservices worden geregistreerd zoals getoond in het vorige codefragment, maakt de DefaultClientFactory een standaard HttpClient voor elke service. De getypeerde client wordt als tijdelijk geregistreerd bij de DI-container. In de voorgaande code registreert AddHttpClient()CatalogService, BasketService, OrderingService als tijdelijke services, zodat ze rechtstreeks kunnen worden geïnjecteerd en gebruikt zonder dat er extra registraties nodig zijn.

U kunt ook exemplaarspecifieke configuratie in de registratie toevoegen om bijvoorbeeld het basisadres te configureren en enkele beleidsregels voor tolerantie toe te voegen, zoals wordt weergegeven in het volgende:

builder.Services.AddHttpClient<ICatalogService, CatalogService>(client =>
{
    client.BaseAddress = new Uri(builder.Configuration["BaseUrl"]);
})
    .AddPolicyHandler(GetRetryPolicy())
    .AddPolicyHandler(GetCircuitBreakerPolicy());

In dit volgende voorbeeld ziet u de configuratie van een van de bovenstaande beleidsregels:

static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
        .WaitAndRetryAsync(6, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
}

Meer informatie over het gebruik van Polly vindt u in het Volgende artikel.

Levensduur van HttpClient

Telkens wanneer u een HttpClient-object uit de IHttpClientFactorykrijgt, wordt er een nieuw exemplaar geretourneerd. Elke HttpClient gebruikt echter een HttpMessageHandler die is gegroepeerd en opnieuw wordt gebruikt door de IHttpClientFactory om het resourceverbruik te verminderen, zolang de levensduur van de HttpMessageHandlerniet is verlopen.

Pooling van handlers is wenselijk omdat elke handler doorgaans zijn eigen onderliggende HTTP-verbindingen beheert; het maken van meer handlers dan nodig is, kan leiden tot verbindingsvertragingen. Sommige handlers houden verbindingen ook voor onbepaalde tijd open, waardoor de handler niet kan reageren op DNS-wijzigingen.

De HttpMessageHandler objecten in de pool hebben een levensduur die de tijdsduur is waarop een HttpMessageHandler exemplaar in de pool opnieuw kan worden gebruikt. De standaardwaarde is twee minuten, maar kan worden overschreven per getypte client. Als u deze wilt overschrijven, roept u SetHandlerLifetime() aan op de IHttpClientBuilder die wordt geretourneerd bij het maken van de client, zoals wordt weergegeven in de volgende code:

//Set 5 min as the lifetime for the HttpMessageHandler objects in the pool used for the Catalog Typed Client
builder.Services.AddHttpClient<ICatalogService, CatalogService>()
    .SetHandlerLifetime(TimeSpan.FromMinutes(5));

Elke getypeerde client kan een eigen geconfigureerde handler-levensduurwaarde hebben. Stel de levensduur in op InfiniteTimeSpan om het verloop van de handler uit te schakelen.

Implementeer uw getypte clientklassen die gebruikmaken van de geïnjecteerde en geconfigureerde HttpClient

Als vorige stap moet u de getypte clientklassen definiëren, zoals de klassen in de voorbeeldcode, zoals 'BasketService', 'CatalogService', 'OrderingService', enzovoort: een getypte client is een klasse die een HttpClient-object accepteert (geïnjecteerd via de constructor) en deze gebruikt om een externe HTTP-service aan te roepen. Bijvoorbeeld:

public class CatalogService : ICatalogService
{
    private readonly HttpClient _httpClient;
    private readonly string _remoteServiceBaseUrl;

    public CatalogService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<Catalog> GetCatalogItems(int page, int take,
                                               int? brand, int? type)
    {
        var uri = API.Catalog.GetAllCatalogItems(_remoteServiceBaseUrl,
                                                 page, take, brand, type);

        var responseString = await _httpClient.GetStringAsync(uri);

        var catalog = JsonConvert.DeserializeObject<Catalog>(responseString);
        return catalog;
    }
}

De Typed Client (CatalogService in het voorbeeld) wordt geactiveerd door DI (Afhankelijkheidsinjectie), wat betekent dat deze elke geregistreerde service in de constructor kan gebruiken, naast HttpClient.

Een getypte client is in feite een tijdelijk object, wat betekent dat er telkens een nieuw exemplaar wordt gemaakt wanneer er een nodig is. Het ontvangt een nieuwe HttpClient instantie telkens wanneer deze wordt gemaakt. De objecten in de HttpMessageHandler-pool zijn echter de objecten die opnieuw worden gebruikt door meerdere HttpClient-instanties.

Uw getypte clientklassen gebruiken

Als u uw getypte klassen hebt geïmplementeerd, kunt u ze laten registreren en configureren met AddHttpClient(). Daarna kunt u ze gebruiken waar services worden geïnjecteerd door DI, zoals in Razor-paginacode of een MVC-web-appcontroller, weergegeven in de onderstaande code van eShopOnContainers:

namespace Microsoft.eShopOnContainers.WebMVC.Controllers
{
    public class CatalogController : Controller
    {
        private ICatalogService _catalogSvc;

        public CatalogController(ICatalogService catalogSvc) =>
                                                           _catalogSvc = catalogSvc;

        public async Task<IActionResult> Index(int? BrandFilterApplied,
                                               int? TypesFilterApplied,
                                               int? page,
                                               [FromQuery]string errorMsg)
        {
            var itemsPage = 10;
            var catalog = await _catalogSvc.GetCatalogItems(page ?? 0,
                                                            itemsPage,
                                                            BrandFilterApplied,
                                                            TypesFilterApplied);
            //… Additional code
        }

        }
}

Tot nu toe toont het bovenstaande codefragment alleen het voorbeeld van het uitvoeren van reguliere HTTP-aanvragen. Maar de 'magie' komt in de volgende secties waar het laat zien hoe alle HTTP-aanvragen die door HttpClient worden gedaan, resiliente beleidsmaatregelen kunnen hebben, zoals herhaalde pogingen met exponentieel uitstel, circuitonderbrekers, beveiligingsfuncties met behulp van verificatietokens of zelfs andere aangepaste functies. Al deze acties kunnen worden uitgevoerd door beleid toe te voegen en handlers te delegeren aan uw geregistreerde Typo Clients.

Aanvullende informatiebronnen