Dela via


Använda IHttpClientFactory för att implementera elastiska HTTP-begäranden

Dricks

Det här innehållet är ett utdrag från eBook, .NET Microservices Architecture for Containerized .NET Applications, tillgängligt på .NET Docs eller som en kostnadsfri nedladdningsbar PDF som kan läsas offline.

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.

IHttpClientFactory är ett kontrakt implementerat av DefaultHttpClientFactory, en åsiktsfabrik, tillgänglig sedan .NET Core 2.1, för att skapa HttpClient instanser som ska användas i dina program.

Problem med den ursprungliga HttpClient-klassen som är tillgänglig i .NET

Den ursprungliga och välkända HttpClient klassen kan enkelt användas, men i vissa fall används den inte korrekt av många utvecklare.

Även om den här klassen implementerar IDisposable, är det inte bra att deklarera och instansiera den i en using -instruktion eftersom den underliggande socketen HttpClient inte släpps omedelbart när objektet tas bort, vilket kan leda till ett socketöverbelastningsproblem . Mer information om det här problemet finns i blogginlägget Du använder HttpClient fel och det destabiliserar programvaran.

HttpClient Därför är avsett att instansieras en gång och återanvändas under hela programmets livslängd. Om du instansierar en HttpClient klass för varje begäran uttöms antalet tillgängliga socketar under tunga belastningar. Det här problemet resulterar i SocketException fel. Möjliga metoder för att lösa problemet baseras på skapandet av objektet som singleton eller statiskt, enligt beskrivningen HttpClient i den här Microsoft-artikeln om HttpClient-användning. Detta kan vara en bra lösning för kortlivade konsolappar eller liknande, som körs några gånger om dagen.

Ett annat problem som utvecklare stöter på är när de använder en delad instans av HttpClient i långvariga processer. I en situation där HttpClient instansieras som en singleton eller ett statiskt objekt, kan den inte hantera DNS-ändringarna enligt beskrivningen i det här problemet med GitHub-lagringsplatsen dotnet/runtime.

Problemet är dock inte riktigt i HttpClient sig, utan med standardkonstruktorn för HttpClient, eftersom det skapar en ny konkret instans av HttpMessageHandler, som är den som har problem med sockets-överbelastning och DNS-ändringar som nämns ovan.

För att åtgärda problemen ovan och för att göra HttpClient instanser hanterbara introducerade .NET Core 2.1 två metoder, varav en är IHttpClientFactory. Det är ett gränssnitt som används för att konfigurera och skapa HttpClient instanser i en app via beroendeinmatning (DI). Det innehåller även tillägg för Polly-baserade mellanprogram för att dra nytta av delegering av hanterare i HttpClient.

Alternativet är att använda SocketsHttpHandler med konfigurerad PooledConnectionLifetime. Den här metoden tillämpas på långlivade eller static singleton-instanser HttpClient . Mer information om olika strategier finns i HttpClient-riktlinjer för .NET.

Polly är ett tillfälligt bibliotek för felhantering som hjälper utvecklare att öka återhämtning i sina program genom att använda vissa fördefinierade principer på ett flytande och trådsäkert sätt.

Fördelar med att använda IHttpClientFactory

Den aktuella implementeringen av IHttpClientFactory, som också implementerar IHttpMessageHandlerFactory, erbjuder följande fördelar:

  • Tillhandahåller en central plats för namngivning och konfigurering av logiska HttpClient objekt. Du kan till exempel konfigurera en klient (tjänstagent) som är förkonfigurerad för åtkomst till en specifik mikrotjänst.
  • Kodifiera begreppet utgående mellanprogram via att delegera hanterare i HttpClient och implementera Polly-baserade mellanprogram för att dra nytta av Pollys policyer för återhämtning.
  • HttpClient har redan konceptet att delegera hanterare som kan länkas samman för utgående HTTP-begäranden. Du kan registrera HTTP-klienter i fabriken och du kan använda en Polly-hanterare för att använda Polly-principer för återförsök, CircuitBreakers och så vidare.
  • Hantera livslängden HttpMessageHandler för för att undvika de problem/problem som kan uppstå när du hanterar HttpClient livslängden själv.

Dricks

De HttpClient instanser som matas in av DI kan tas bort på ett säkert sätt eftersom de associerade HttpMessageHandler hanteras av fabriken. Inmatade HttpClient instanser är tillfälliga ur ett DI-perspektiv, medan HttpMessageHandler instanser kan betraktas som begränsade. HttpMessageHandler instanser har sina egna DI-omfång, åtskilda från programomfattningarna (till exempel ASP.NET inkommande omfång för begäran). Mer information finns i Använda HttpClientFactory i .NET.

Kommentar

Implementeringen av IHttpClientFactory (DefaultHttpClientFactory) är nära knuten till DI-implementeringen i Microsoft.Extensions.DependencyInjection NuGet-paketet. Om du behöver använda HttpClient utan DI eller med andra DI-implementeringar kan du överväga att använda en eller singleton staticHttpClient med PooledConnectionLifetime konfiguration. Mer information finns i HttpClient-riktlinjer för .NET.

Flera sätt att använda IHttpClientFactory

Det finns flera sätt att använda IHttpClientFactory i ditt program:

  • Grundläggande användning
  • Använda namngivna klienter
  • Använda inskrivna klienter
  • Använda genererade klienter

För korthets skull visar den här vägledningen det mest strukturerade sättet att använda IHttpClientFactory, vilket är att använda typade klienter (Service Agent-mönster). Alla alternativ är dock dokumenterade och visas för närvarande i den här artikeln som täcker IHttpClientFactory användningen.

Kommentar

Om din app kräver cookies kan det vara bättre att undvika att använda IHttpClientFactory i din app. Alternativa sätt att hantera klienter finns i Riktlinjer för att använda HTTP-klienter

Använda inskrivna klienter med IHttpClientFactory

Vad är då en "typad klient"? Det är bara en HttpClient som är förkonfigurerad för viss användning. Den här konfigurationen kan innehålla specifika värden som basservern, HTTP-huvuden eller tidsgränser.

Följande diagram visar hur typerade klienter används med IHttpClientFactory:

Diagram showing how typed clients are used with IHttpClientFactory.

Bild 8-4. Använda IHttpClientFactory med typerade klientklasser.

I bilden ovan använder en ClientService (som används av en styrenhet eller klientkod) en HttpClient som skapats av den registrerade IHttpClientFactory. Den här fabriken tilldelar en HttpMessageHandler från en pool till HttpClient. HttpClient Kan konfigureras med Pollys principer när du IHttpClientFactory registrerar i DI-containern med tilläggsmetoden AddHttpClient.

Om du vill konfigurera strukturen ovan lägger du till IHttpClientFactory i ditt program genom att installera NuGet-paketet Microsoft.Extensions.Http som innehåller AddHttpClient tilläggsmetoden för IServiceCollection. Den här tilläggsmetoden registrerar den interna DefaultHttpClientFactory klassen som ska användas som en singleton för gränssnittet IHttpClientFactory. Den definierar en tillfällig konfiguration för HttpMessageHandlerBuilder. Den här meddelandehanteraren (HttpMessageHandler -objektet), som hämtas från en pool, används av den HttpClient som returneras från fabriken.

I nästa kodfragment kan du se hur AddHttpClient() du kan använda för att registrera typade klienter (tjänstagenter) som behöver använda HttpClient.

// 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>();

Genom att registrera klienttjänsterna enligt föregående kodfragment blir DefaultClientFactory det en standard HttpClient för varje tjänst. Den inskrivna klienten registreras som tillfällig med DI-container. I föregående kod AddHttpClient() registrerar du CatalogService, BasketService, OrderingService som tillfälliga tjänster så att de kan matas in och användas direkt utan ytterligare registreringar.

Du kan också lägga till instansspecifik konfiguration i registreringen för att till exempel konfigurera basadressen och lägga till några återhämtningsprinciper, som du ser i följande:

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

I nästa exempel kan du se konfigurationen av någon av ovanstående principer:

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

Mer information om hur du använder Polly finns i artikeln Nästa.

HttpClient-livslängd

Varje gång du hämtar ett HttpClient objekt från IHttpClientFactoryreturneras en ny instans. Men var HttpClient och en använder en HttpMessageHandler som är poolad och återanvänds av IHttpClientFactory för att minska resursförbrukningen, så länge livslängden HttpMessageHandlerinte har upphört att gälla.

Poolning av hanterare är önskvärt eftersom varje hanterare vanligtvis hanterar sina egna underliggande HTTP-anslutningar. om du skapar fler hanterare än nödvändigt kan det leda till anslutningsfördröjningar. Vissa hanterare håller även anslutningarna öppna på obestämd tid, vilket kan hindra hanteraren från att reagera på DNS-ändringar.

Objekten HttpMessageHandler i poolen har en livslängd som är den tid som en HttpMessageHandler instans i poolen kan återanvändas. Standardvärdet är två minuter, men det kan åsidosättas per typerad klient. Om du vill åsidosätta den anropar SetHandlerLifetime() du den IHttpClientBuilder som returneras när klienten skapas, enligt följande kod:

//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));

Varje typad klient kan ha ett eget konfigurerat hanterares livslängdsvärde. Ange livslängden till InfiniteTimeSpan för att inaktivera hanterarens förfallodatum.

Implementera dina inskrivna klientklasser som använder den inmatade och konfigurerade HttpClient

Som ett föregående steg måste du ha definierat dina typade klientklasser, till exempel klasserna i exempelkoden, till exempel "BasketService", "CatalogService", "OrderingService" osv. – En typad klient är en klass som accepterar ett HttpClient objekt (matas in via konstruktorn) och använder det för att anropa någon fjärr-HTTP-tjänst. Till exempel:

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;
    }
}

Den inskrivna klienten (CatalogService i exemplet) aktiveras av DI (beroendeinmatning), vilket innebär att den kan acceptera alla registrerade tjänster i konstruktorn, förutom HttpClient.

En typad klient är i själva verket ett tillfälligt objekt, vilket innebär att en ny instans skapas varje gång en behövs. Den tar emot en ny HttpClient instans varje gång den skapas. Objekten HttpMessageHandler i poolen är dock de objekt som återanvänds av flera HttpClient instanser.

Använda dina typerade klientklasser

När du har implementerat dina inskrivna klasser kan du få dem registrerade och konfigurerade med AddHttpClient(). Därefter kan du använda dem där tjänster matas in av DI, till exempel i Razor-sidkod eller en MVC-webbappkontrollant, som visas i koden nedan från 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
        }

        }
}

Fram tills nu visar kodfragmentet ovan endast exemplet på att utföra vanliga HTTP-begäranden. Men "magin" finns i följande avsnitt där den visar hur alla HTTP-begäranden som görs av HttpClient kan ha motståndskraftiga principer som återförsök med exponentiell backoff, kretsbrytare, säkerhetsfunktioner med autentiseringstoken eller till och med andra anpassade funktioner. Och alla dessa kan göras bara genom att lägga till principer och delegera hanterare till dina registrerade typade klienter.

Ytterligare resurser