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ě, HttpContextnapří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 HttpMessageHandlersluž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í IHttpClientFactoryobsluž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í HttpMessageHandlerpo 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 likvidaci HttpMessageHandler. 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 TClienttypu . 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:

  1. Zaregistruje pojmenovaného klienta (v jednoduchém výchozím případě je typeof(TClient).Namenázev ).
  2. Zaregistruje Transient službu pomocí TClient nebo TClient,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á HttpClientFactoryuživatelem a přeruší propojení s pojmenovaným klientem. Bude manifestovat, jako by HttpClientdoš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"));

Viz také