Använda IHttpClientFactory för att implementera elastiska HTTP-begäranden
Tips
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.
IHttpClientFactory är ett kontrakt som implementerats 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 inte släpps omedelbart när det HttpClient
objektet tas bort, vilket kan leda till ett socketöverbelastning problem. Mer information om det här problemet finns i blogginlägget Du använder HttpClient fel och det destabiliserar din programvara.
Därför är HttpClient
avsedd 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 socketar som är tillgängliga under tunga belastningar. Det här problemet kommer att resultera i fel SocketException
. Möjliga metoder för att lösa problemet baseras på skapandet av HttpClient
-objektet som singleton eller statiskt, enligt beskrivningen 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 det inte hantera ändringar i DNS enligt beskrivningen i den här problemrapporten för dotnet/runtime-förvaret på GitHub.
Men problemet är inte riktigt med HttpClient
i sig, men med standardkonstruktor för HttpClient, eftersom det skapar en ny konkret instans av HttpMessageHandler, som är den som har 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 var IHttpClientFactory. Det är ett gränssnitt som används för att konfigurera och skapa HttpClient
-instans i en app via Dependency Injection (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, static
- eller singleton-HttpClient
-instanser. 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 motståndskraften 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 även 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
redan har 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 för HttpMessageHandler för att undvika de problem som kan uppstå när du själv hanterar
HttpClient
:s livslängd.
Tips
De HttpClient
instanser som matas in av DI kan tas bort på ett säkert sätt eftersom den 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 separata från programomfattningarna (till exempel ASP.NET inkommande omfång för begäran). Mer information finns i Using HttpClientFactory in .NET.
Not
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 tillsammans med andra DI-implementeringar bör du överväga att använda en static
eller en ensamstående HttpClient
med PooledConnectionLifetime
konfigurerad. 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änd typspecifika klienter
- Använda genererade klienter
För korthets skull visar den här vägledningen det mest strukturerade sättet att använda IHttpClientFactory
, som är att använda typade klienter (serviceagentmönster). Alla alternativ är dock dokumenterade och visas för närvarande i den här artikeln om IHttpClientFactory
användning.
Obs
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 användning av HTTP-klienter.
Hur du använder typspecifierade klienter med IHttpClientFactory
Vad är egentligen en "typad klient"? Det är bara en HttpClient
som är förkonfigurerad för specifik 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
:
Bild 8-4. Använda IHttpClientFactory
med typerade klientklasser.
I bilden ovan använder en ClientService
(som används av en kontrollant eller klientkod) en HttpClient
som skapats av den registrerade IHttpClientFactory
. Fabriken tilldelar en HttpMessageHandler
från poolen till HttpClient
.
HttpClient
kan konfigureras med Pollys principer när du registrerar IHttpClientFactory
i DI-containern med tilläggsmetoden AddHttpClient.
Om du vill konfigurera strukturen ovan lägger du till IHttpClientFactory i ditt program genom att installera Microsoft.Extensions.Http
NuGet-paketet 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 tas från en pool, används av HttpClient
som returneras från fabriken.
I nästa kodfragment kan du se hur AddHttpClient()
kan användas för att registrera inskrivna 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 skapar DefaultClientFactory
en standard HttpClient
för varje tjänst. Den typade klienten registreras som övergående hos DI-container. I föregående kod registrerar AddHttpClient()
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 nästa artikel .
HttpClient-livslängd
Varje gång du får ett HttpClient
objekt från IHttpClientFactory
returneras en ny instans. Men varje HttpClient
använder en HttpMessageHandler
som poolas och återanvänds av IHttpClientFactory
för att minska resursförbrukningen, så länge som HttpMessageHandler
:s livslängd inte 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.
De HttpMessageHandler
objekten 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 du SetHandlerLifetime()
på 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 sin egen konfigurerade livslängd för hanterare. Ange livslängden till InfiniteTimeSpan
för att inaktivera hanterarens förfallodatum.
Implementera dina typsatta klientklasser som använder den konfigurerade och injicerade HttpClient
Som ett föregående steg måste du ha definierat dina Typed Client-klasser, 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ärransluten 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;
}
}
Typed-klienten (CatalogService
i exemplet) aktiveras av DI (Dependency Injection), vilket innebär att den kan acceptera alla registrerade tjänster i konstruktorn, utöver 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. De HttpMessageHandler
objekten 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 allt detta kan göras endast genom att lägga till policys och delegera hanterare till dina registrerade typade klienter.
Ytterligare resurser
HttpClient-riktlinjer för .NEThttps://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelines
Använda HttpClientFactory i .NEThttps://learn.microsoft.com/en-us/dotnet/core/extensions/httpclient-factory
Använda HttpClientFactory i ASP.NET Corehttps://learn.microsoft.com/aspnet/core/fundamentals/http-requests
HttpClientFactory-källkod på
dotnet/runtime
GitHub-lagringsplatsenhttps://github.com/dotnet/runtime/tree/release/7.0/src/libraries/Microsoft.Extensions.Http/Polly (.NET-biblioteket för motståndskraft och övergående felhantering)https://thepollyproject.azurewebsites.net/