Dela via


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. IHttpClientFactorykapslar 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, HttpContexttill 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 HttpRequestMessagekan 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 HttpClientanropar 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 HttpMessageHandlerav "s när deras livslängd upphör att gälla är viktigt för IHttpClientFactory att säkerställa att hanterare reagerar på DNS-ändringar. HttpClient är knuten till en specifik hanterarinstans när den skapas, så nya HttpClient 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 av HttpMessageHandler. IHttpClientFactory spårar och tar bort resurser som används för att skapa HttpClient instanser, särskilt HttpMessageHandler instanserna, så snart deras livslängd går ut och de inte HttpClient 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 TClientfö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:

  1. Registrerar en namngiven klient (i ett enkelt standardfall är typeof(TClient).Namenamnet ).
  2. Registrerar en Transient tjänst med hjälp av eller TClient,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 HttpClientgå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"));

Se även