Běžné problémy s používáním IHttpClientFactory
V tomto článku se dozvíte o některých nejběžnějších problémech, na které můžete narazit při vytváření IHttpClientFactory
HttpClient
instancí.
IHttpClientFactory
je pohodlný způsob nastavení více HttpClient
konfigurací v kontejneru DI, konfigurace protokolování, nastavení strategií odolnosti a další. IHttpClientFactory
také zapouzdřuje správu HttpClient
životnosti a HttpMessageHandler
instancí, aby se zabránilo problémům, jako je vyčerpání soketů a ztráta změn DNS. Přehled IHttpClientFactory
použití v aplikaci .NET najdete v tématu IHttpClientFactory s .NET.
Vzhledem ke složité povaze IHttpClientFactory
integrace s DI můžete narazit na některé problémy, které by mohly být obtížné zachytit a řešit potíže. Scénáře uvedené v tomto článku obsahují také doporučení, která můžete aktivně použít, abyste se vyhnuli potenciálním problémům.
HttpClient
nerespektuje Scoped
životnost
Můžete narazit na problém, pokud potřebujete získat přístup k jakékoli vymezené službě, HttpContext
například , nebo některé vymezené mezipaměti v rámci .HttpMessageHandler
Data uložená tam můžou buď "zmizet", nebo naopak , "zachovat", když by neměla. Příčinou je neshoda rozsahu injektáže závislostí (DI) mezi kontextem aplikace a instancí obslužné rutiny a je to známé omezení v IHttpClientFactory
.
IHttpClientFactory
vytvoří pro každou HttpMessageHandler
instanci samostatný obor DI. Tyto obory obslužné rutiny se liší od oborů kontextu aplikace (například ASP.NET rozsah příchozích požadavků Core nebo uživatelem vytvořený ruční obor DI), takže nebudou sdílet instance služby s vymezeným oborem.
V důsledku tohoto omezení:
- Veškerá data uložená v mezipaměti "externě" v rámci služby s vymezeným oborem nebudou v rámci této
HttpMessageHandler
služby k dispozici . - Všechna data uložená v mezipaměti "interně" v rámci závislostí s
HttpMessageHandler
vymezeným oborem se dají pozorovat z více oborů DI aplikace (například z různých příchozích požadavků), protože můžou sdílet stejnou obslužnou rutinu.
Zvažte následující doporučení, která vám pomůžou zmírnit toto známé omezení:
❌ DO NOT neuklánějte žádné informace související s oborem (například data z HttpContext
) uvnitř HttpMessageHandler
instancí nebo jejich závislostí, aby nedošlo k úniku citlivých informací.
❌ NEPOUŽÍVEJTE soubory cookie, protože CookieContainer
bude sdíleno společně s obslužnou rutinou.
✔️ ZVAŽTE neukládat informace nebo je předat pouze v instanci HttpRequestMessage
.
K předání libovolných informací vedle objektu HttpRequestMessage
, můžete použít HttpRequestMessage.Options vlastnost.
✔️ ZVAŽTE zapouzdření veškeré logiky související s oborem (například ověřování) v samostatném DelegatingHandler
objektu, který není vytvořen objektem IHttpClientFactory
, a použijte ji k zabalení IHttpClientFactory
obslužné rutiny -created.
Chcete-li vytvořit pouze bez HttpMessageHandler
HttpClient
, zavolejte IHttpMessageHandlerFactory.CreateHandler libovolného registrovaného pojmenovaného klienta. V takovém případě budete muset vytvořit HttpClient
instanci sami pomocí kombinované obslužné rutiny. Plně spustitelný příklad tohoto alternativního řešení najdete na GitHubu.
Další informace najdete v části Obory obslužné rutiny zpráv v části IHttpClientFactory v IHttpClientFactory
pokynech.
HttpClient
nerespektuje změny DNS
I když IHttpClientFactory
se používá, je stále možné narazit na zastaralý problém DNS. K tomu obvykle může dojít v případě, že HttpClient
se instance zachytí ve službě Singleton
, nebo obecně uložená někam po dobu delší než zadaná HandlerLifetime
. HttpClient
se zachytí také v případě, že se příslušný typový klient zachytí jednímtonem.
❌ DO NOT cache HttpClient
instance vytvořené IHttpClientFactory
po delší dobu.
❌ NEVkládat do služeb typed client instances Singleton
.
✔️ ZVAŽTE včasné vyžádání klienta IHttpClientFactory
nebo pokaždé, když ho potřebujete. Klienti vytvořená továrnou jsou bezpečně uvolněni.
HttpClient
Instance vytvořené pomocí IHttpClientFactory
jsou určeny k krátkodobému použití.
Recyklace a opětovné vytvoření
HttpMessageHandler
po vypršení jejich životnosti je nezbytné,IHttpClientFactory
aby obslužné rutiny reagovaly na změny DNS.HttpClient
je svázaný s konkrétní instancí obslužné rutiny při jeho vytvoření, takže novéHttpClient
instance by měly být požadovány včas, aby se zajistilo, že klient získá aktualizovanou obslužnou rutinu.Vyřazení takových
HttpClient
instancí vytvořených továrnou nebude vést k vyčerpání soketů, protože jeho odstranění neaktivuje likvidaciHttpMessageHandler
.IHttpClientFactory
sleduje a odstraňuje prostředky používané k vytvářeníHttpClient
instancí, konkrétněHttpMessageHandler
instancí, jakmile jejich životnost vyprší a už je nepoužíváHttpClient
.
Typoví klienti mají být krátkodobé , protože HttpClient
instance je vložena do konstruktoru, takže bude sdílet životnost zadaného klienta .
Další informace naleznete v HttpClient
tématu správa životnosti a Vyhnout se typed klientům v částech singleton services v IHttpClientFactory
pokynech.
HttpClient
používá příliš mnoho soketů.
I když IHttpClientFactory
se používá, stále je možné problém s vyčerpáním soketů vyřešit konkrétním scénářem použití. Ve výchozím nastavení HttpClient
neomezuje počet souběžných požadavků. Pokud se současně spustí velký počet požadavků HTTP/1.1, každý z nich nakonec aktivuje nový pokus o připojení HTTP, protože ve fondu není žádné bezplatné připojení a není nastavené žádné omezení.
❌ NESpouštět velký počet požadavků HTTP/1.1 současně bez určení limitů.
✔️ ZVAŽTE nastavení HttpClientHandler.MaxConnectionsPerServer (nebo SocketsHttpHandler.MaxConnectionsPerServer, pokud ho použijete jako primární obslužnou rutinu) na rozumnou hodnotu. Všimněte si, že tato omezení platí pouze pro konkrétní instanci obslužné rutiny.
✔️ ZVAŽTE POUŽITÍ PROTOKOLU HTTP/2, které umožňuje multiplexování požadavků přes jedno připojení TCP.
Zadaný klient má nesprávnou HttpClient
injektáž
Mohou existovat různé situace, kdy je možné získat neočekávanou HttpClient
injektáž do zadaného klienta. Ve většině případů bude původní příčina chybná konfigurace, protože návrh DI přepíše předchozí registraci služby.
Typoví klienti používají pojmenované klienty "pod kapotou": přidání zadaného klienta implicitně zaregistruje a propojování s pojmenovaným klientem. Název klienta, pokud není explicitně zadán, bude nastaven na název TClient
typu . To je první z TClient,TImplementation
dvojice, pokud AddHttpClient<TClient,TImplementation>
se použijí přetížení.
Proto registrace typového klienta dělá dvě samostatné věci:
- Zaregistruje pojmenovaného klienta (v jednoduchém výchozím případě je
typeof(TClient).Name
název ). - Zaregistruje
Transient
službu pomocíTClient
neboTClient,TImplementation
poskytnuté služby.
Následující dva příkazy jsou technicky stejné:
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
V jednoduchém případě se bude podobat také následujícímu:
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))));
Podívejte se na následující příklady, jak může dojít k přerušení propojení mezi zadanými a pojmenovanými klienty.
Typovaný klient se zaregistruje podruhé.
❌ NEZAREGISTRUJTE zadaného klienta samostatně – už je automaticky registrováno voláním AddHttpClient<T>
.
Pokud je typový klient chybně zaregistrován jako prosté přechodné služby, přepíše se registrace přidaná HttpClientFactory
uživatelem a přeruší propojení s pojmenovaným klientem. Bude manifestovat, jako by HttpClient
došlo ke ztrátě konfigurace, protože nekonfigurovaný HttpClient
se vloží do zadaného klienta .
Může být matoucí, že místo vyvolání výjimky se použije chyba HttpClient
. K tomu dochází, protože "výchozí" nekonfigurovaný – klient s Options.DefaultName názvem (string.Empty
) – je zaregistrovaný HttpClient
jako prosté přechodné služby, aby se povolil scénář nejzásadnějšího HttpClientFactory
použití. To je důvod, proč po přerušení propojení a typový klient se stane jen běžnou službou, tato "výchozí" HttpClient
se přirozeně vloží do příslušného parametru konstruktoru.
Různí typoví klienti se registrují na společném rozhraní.
V případě, že jsou na společném rozhraní zaregistrovaní dva různí klienti typu, budou oba znovu používat stejného pojmenovaného klienta. Může to vypadat jako první zadaný klient, který získá druhé pojmenovaného klienta nesprávně vložený.
❌ NEZAREGISTRUJTE více typech klientů v jednom rozhraní bez explicitního zadání názvu.
✔️ ZVAŽTE registraci a konfiguraci pojmenovaného klienta samostatně a pak ho propojíte s jedním nebo více zadanými klienty, a to buď zadáním názvu během AddHttpClient<T>
volání, nebo voláním AddTypedClient
během instalace pojmenovaného klienta .
Při návrhu se registrace a konfigurace pojmenovaného klienta se stejným názvem několikrát připojí akce konfigurace k seznamu existujících klientů . Toto chování HttpClientFactory
nemusí být zřejmé, ale je to stejný přístup, který používá vzor Možnosti a rozhraní API konfigurace, jako Configureje .
To je většinou užitečné pro pokročilé konfigurace obslužné rutiny, například přidání vlastní obslužné rutiny do pojmenovaného klienta definované externě nebo napodobování primární obslužné rutiny pro testy, ale funguje také pro HttpClient
konfiguraci instance. Například tři následující příklady budou mít za následek HttpClient
konfiguraci stejným způsobem (obě BaseAddress
a DefaultRequestHeaders
jsou nastaveny):
// 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"));
To umožňuje propojit typového klienta s již definovaným pojmenovaným klientem a také propojit několik typed klientů s jedním pojmenovaným klientem. Je jasnější, když se použijí přetížení s parametrem name
:
services.AddHttpClient("LogClient", c => c.BaseAddress = new Uri(LogServerAddress));
services.AddHttpClient<FooLogger>("LogClient");
services.AddHttpClient<BarLogger>("LogClient");
Stejnou věc lze dosáhnout také voláním AddTypedClient během pojmenované konfigurace klienta :
services.AddHttpClient("LogClient", c => c.BaseAddress = new Uri(LogServerAddress))
.AddTypedClient<FooLogger>()
.AddTypedClient<BarLogger>();
Pokud ale nechcete opakovaně používat stejného pojmenovaného klienta, přesto chcete zaregistrovat klienty ve stejném rozhraní, můžete to udělat tak, že pro ně explicitně zadáte různé názvy:
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"));