Vanliga IHttpClientFactory
användningsproblem
I den här artikeln får du lära dig några av de vanligaste problemen som du kan stöta på när du använder IHttpClientFactory
för att skapa HttpClient
instanser.
IHttpClientFactory
är ett bekvämt sätt att konfigurera flera HttpClient
konfigurationer i DI-containern, konfigurera loggning, konfigurera återhämtningsstrategier med mera. IHttpClientFactory
kapslar också in livslängdshanteringen för och HttpMessageHandler
instanser för att förhindra problem som socketöverbelastning och förlust av HttpClient
DNS-ändringar. En översikt över hur du använder IHttpClientFactory
i .NET-programmet finns i IHttpClientFactory med .NET.
På grund av IHttpClientFactory
en komplex integrering med DI kan du stöta på problem som kan vara svåra att fånga och felsöka. Scenarierna i den här artikeln innehåller också rekommendationer som du kan använda proaktivt för att undvika potentiella problem.
HttpClient
respekterar Scoped
inte livslängden
Du kan stöta på ett problem om du behöver komma åt någon begränsad tjänst, HttpContext
till exempel , eller någon begränsad cache, inifrån HttpMessageHandler
. De data som sparas där kan antingen "försvinna" eller, tvärtom, "bevara" när de inte borde det. Detta orsakas av omfångsmatchningen för beroendeinmatning (DI) mellan programkontexten och hanterarinstansen, och det är en känd begränsning i IHttpClientFactory
.
IHttpClientFactory
skapar ett separat DI-omfång per varje HttpMessageHandler
instans. Dessa hanteringsomfång skiljer sig från programkontextomfattningar (till exempel ASP.NET omfång för inkommande kärnor eller ett användarskapat manuellt DI-omfång), så de delar inte begränsade tjänstinstanser.
Som ett resultat av den här begränsningen:
- Alla data som cachelagras "externt" i en begränsad tjänst kommer inte att vara tillgängliga i
HttpMessageHandler
. - Alla data som cachelagras "internt" inom
HttpMessageHandler
eller dess begränsade beroenden kan observeras från flera program-DI-omfång (till exempel från olika inkommande begäranden) eftersom de kan dela samma hanterare.
Överväg följande rekommendationer för att lindra den här kända begränsningen:
❌ Cachelagrar INTE någon omfångsrelaterad information (till exempel data från HttpContext
) i HttpMessageHandler
instanser eller dess beroenden för att undvika att känslig information läcker ut.
❌ Använd INTE cookies, eftersom CookieContainer
kommer att delas tillsammans med hanteraren.
✔️ ÖVERVÄG att inte lagra informationen eller bara skicka den i instansen HttpRequestMessage
.
Om du vill skicka godtycklig information tillsammans med HttpRequestMessage
kan du använda HttpRequestMessage.Options egenskapen .
✔️ ÖVERVÄG att kapsla in all omfångsrelaterad logik (till exempel autentisering) i en separat DelegatingHandler
logik som inte har skapats av IHttpClientFactory
, och använd den för att omsluta den IHttpClientFactory
-skapade hanteraren.
Om du bara vill skapa en HttpMessageHandler
utan HttpClient
anropar du IHttpMessageHandlerFactory.CreateHandler en registrerad namngiven klient. I så fall måste du skapa en HttpClient
instans själv med hjälp av den kombinerade hanteraren. Du hittar ett fullständigt körbart exempel för den här lösningen på GitHub.
Mer information finns i avsnittet Omfång för meddelandehanterare i IHttpClientFactory i IHttpClientFactory
riktlinjerna.
HttpClient
respekterar inte DNS-ändringar
Även om IHttpClientFactory
används är det fortfarande möjligt att stöta på det inaktuella DNS-problemet. Detta kan vanligtvis inträffa om en HttpClient
instans registreras i en Singleton
tjänst, eller i allmänhet lagras någonstans under en tidsperiod som är längre än den angivna HandlerLifetime
. HttpClient
hämtas också om respektive typad klient registreras av en singleton.
❌ Cachelagrar HttpClient
INTE instanser som skapats av IHttpClientFactory
under längre tidsperioder.
❌ Mata INTE in inskrivna klientinstanser i Singleton
tjänster.
✔️ ÖVERVÄG att begära en klient från IHttpClientFactory
i tid eller varje gång du behöver en. Fabriksskapade klienter är säkra att ta bort.
HttpClient
instanser som skapats av IHttpClientFactory
är avsedda att vara kortvariga.
Återvinning och återskapande
HttpMessageHandler
av "s när deras livslängd upphör att gälla är viktigt förIHttpClientFactory
att säkerställa att hanterare reagerar på DNS-ändringar.HttpClient
är knuten till en specifik hanterarinstans när den skapas, så nyaHttpClient
instanser bör begäras i tid för att säkerställa att klienten hämtar den uppdaterade hanteraren.Om du bortser från sådana instanser som
HttpClient
skapats av fabriken leder det inte till socketöverbelastning eftersom dess bortskaffande inte utlöser bortskaffandet avHttpMessageHandler
.IHttpClientFactory
spårar och tar bort resurser som används för att skapaHttpClient
instanser, särskiltHttpMessageHandler
instanserna, så snart deras livslängd går ut och de inteHttpClient
längre används.
Inskrivna klienter är också avsedda att vara kortvariga, eftersom en HttpClient
instans matas in i konstruktorn, så att den delar den typerade klientlivslängden.
Mer information finns i avsnittet om livslängdshantering HttpClient
och Undvik inskrivna klienter i singleton-tjänster i IHttpClientFactory
riktlinjerna.
HttpClient
använder för många sockets
Även om IHttpClientFactory
används är det fortfarande möjligt att stöta på socketöverbelastningsproblemet med ett specifikt användningsscenario. Som standard HttpClient
begränsar inte antalet samtidiga begäranden. Om ett stort antal HTTP/1.1-begäranden startas samtidigt utlöser var och en av dem ett nytt HTTP-anslutningsförsök eftersom det inte finns någon kostnadsfri anslutning i poolen och ingen gräns har angetts.
❌ Starta INTE ett stort antal HTTP/1.1-begäranden samtidigt utan att ange gränserna.
✔️ ÖVERVÄG att ange HttpClientHandler.MaxConnectionsPerServer (eller SocketsHttpHandler.MaxConnectionsPerServer, om du använder den som primär hanterare) till ett rimligt värde. Observera att dessa gränser endast gäller för den specifika hanterarinstansen.
✔️ ÖVERVÄG att använda HTTP/2, vilket tillåter multiplexering av begäranden via en enda TCP-anslutning.
Den inskrivna klienten har fel HttpClient
inmatad
Det kan finnas olika situationer där det är möjligt att få ett oväntat HttpClient
inmatat i en typad klient. För det mesta är rotorsaken i en felaktig konfiguration, eftersom varje efterföljande registrering av en tjänst åsidosätter den tidigare genom DI-design.
Inskrivna klienter använder namngivna klienter "under huven": att lägga till en skriven klient registrerar implicit och länkar den till en namngiven klient. Klientnamnet, om det inte uttryckligen anges, anges till typnamnet TClient
för . Detta skulle vara den första från TClient,TImplementation
paret om AddHttpClient<TClient,TImplementation>
överlagringar används.
Därför gör registrering av en typad klient två separata saker:
- Registrerar en namngiven klient (i ett enkelt standardfall är
typeof(TClient).Name
namnet ). - Registrerar en
Transient
tjänst med hjälp av ellerTClient,TImplementation
tillhandahållsTClient
.
Följande två påståenden är tekniskt likadana:
services.AddHttpClient<ExampleClient>(c => c.BaseAddress = new Uri("http://example.com"));
// -OR-
services.AddHttpClient(nameof(ExampleClient), c => c.BaseAddress = new Uri("http://example.com")) // register named client
.AddTypedClient<ExampleClient>(); // link the named client to a typed client
I ett enkelt fall kommer det också att likna följande:
services.AddHttpClient(nameof(ExampleClient), c => c.BaseAddress = new Uri("http://example.com")); // register named client
// register plain Transient service and link it to the named client
services.AddTransient<ExampleClient>(s =>
new ExampleClient(
s.GetRequiredService<IHttpClientFactory>().CreateClient(nameof(ExampleClient))));
Tänk på följande exempel på hur länken mellan inskrivna och namngivna klienter kan brytas.
Den inskrivna klienten registreras en andra gång
❌ Registrera INTE den inskrivna klienten separat – den registreras redan automatiskt av anropet AddHttpClient<T>
.
Om en typad klient felaktigt registreras en andra gång som en oformaterad transienttjänst, skriver detta över registreringen som lagts till av HttpClientFactory
, vilket bryter länken till den namngivna klienten. Det visas som om konfigurationen HttpClient
går förlorad eftersom en okonfigurerad HttpClient
matas in i den inskrivna klienten i stället.
Det kan vara förvirrande att ett "fel" HttpClient
används i stället för att utlösa ett undantag. Detta beror på att den "standard" som inte är konfigurerad HttpClient
– klienten med Options.DefaultName namnet (string.Empty
) – är registrerad som en oformaterad transienttjänst för att aktivera det mest grundläggande HttpClientFactory
användningsscenariot. Därför matas den här "standardinställningen" HttpClient
naturligt in i respektive konstruktorparameter när länken har brutits och den inskrivna klienten bara blir en vanlig tjänst.
Olika inskrivna klienter är registrerade i ett gemensamt gränssnitt
Om två olika inskrivna klienter registreras i ett gemensamt gränssnitt återanvänder båda samma namngivna klient. Det kan verka som om den första inskrivna klienten får den andra namngivna klienten "felaktigt" inmatad.
❌ Registrera INTE flera inskrivna klienter i ett enda gränssnitt utan att uttryckligen ange namnet.
✔️ ÖVERVÄG att registrera och konfigurera en namngiven klient separat och länka den sedan till en eller flera inskrivna klienter, antingen genom att ange namnet i AddHttpClient<T>
anropet eller genom att anropa AddTypedClient
under den namngivna klientkonfigurationen .
Genom att designa, registrera och konfigurera en namngiven klient med samma namn flera gånger läggs bara konfigurationsåtgärderna till i listan över befintliga. Det här beteendet HttpClientFactory
för kanske inte är uppenbart, men det är samma metod som används av mönstret Alternativ och konfigurations-API:er som Configure.
Detta är mest användbart för avancerade hanteringskonfigurationer, till exempel att lägga till en anpassad hanterare till en namngiven klient som definierats externt eller att håna en primär hanterare för tester, men det fungerar även för HttpClient
instanskonfiguration. De tre följande exemplen resulterar till exempel i en HttpClient
konfigurerad på samma sätt (båda BaseAddress
och DefaultRequestHeaders
anges):
// one configuration callback
services.AddHttpClient("example", c =>
{
c.BaseAddress = new Uri("http://example.com");
c.DefaultRequestHeaders.UserAgent.ParseAdd("HttpClient/8.0");
});
// -OR-
// two configuration callbacks
services.AddHttpClient("example", c => c.BaseAddress = new Uri("http://example.com"))
.ConfigureHttpClient(c => c.DefaultRequestHeaders.UserAgent.ParseAdd("HttpClient/8.0"));
// -OR-
// two configuration callbacks in separate AddHttpClient calls
services.AddHttpClient("example", c => c.BaseAddress = new Uri("http://example.com"));
services.AddHttpClient("example")
.ConfigureHttpClient(c => c.DefaultRequestHeaders.UserAgent.ParseAdd("HttpClient/8.0"));
Detta gör det möjligt att länka en skriven klient till en redan definierad namngiven klient och även länka flera inskrivna klienter till en enda namngiven klient. Det är mer uppenbart när överlagringar med en name
parameter används:
services.AddHttpClient("LogClient", c => c.BaseAddress = new Uri(LogServerAddress));
services.AddHttpClient<FooLogger>("LogClient");
services.AddHttpClient<BarLogger>("LogClient");
Samma sak kan också uppnås genom att anropa AddTypedClient under den namngivna klientkonfigurationen :
services.AddHttpClient("LogClient", c => c.BaseAddress = new Uri(LogServerAddress))
.AddTypedClient<FooLogger>()
.AddTypedClient<BarLogger>();
Men om du inte vill återanvända samma namngivna klient, men ändå vill registrera klienterna i samma gränssnitt, kan du göra det genom att uttryckligen ange olika namn för dem:
services.AddHttpClient<ITypedClient, ExampleClient>(nameof(ExampleClient),
c => c.BaseAddress = new Uri("http://example.com"));
services.AddHttpClient<ITypedClient, GithubClient>(nameof(GithubClient),
c => c.BaseAddress = new Uri("https://github.com"));