Veelvoorkomende IHttpClientFactory
gebruiksproblemen
In dit artikel leert u enkele van de meest voorkomende problemen die u kunt tegenkomen bij het IHttpClientFactory
maken HttpClient
van exemplaren.
IHttpClientFactory
is een handige manier om meerdere HttpClient
configuraties in te stellen in de DI-container, logboekregistratie te configureren, strategieën voor tolerantie in te stellen en meer. IHttpClientFactory
omvat ook het levensduurbeheer van HttpClient
en HttpMessageHandler
exemplaren om problemen zoals socketuitputting en verlies van DNS-wijzigingen te voorkomen. Zie IHttpClientFactory met .NET voor een overzicht van het gebruik IHttpClientFactory
in uw .NET-toepassing.
Vanwege een complexe aard van IHttpClientFactory
integratie met DI, kunt u een aantal problemen raken die mogelijk moeilijk kunnen worden onderscheppen en oplossen. De scenario's in dit artikel bevatten ook aanbevelingen die u proactief kunt toepassen om potentiële problemen te voorkomen.
HttpClient
respecteert Scoped
de levensduur niet
U kunt een probleem ondervinden als u toegang wilt krijgen tot een bereikservice, bijvoorbeeld, HttpContext
of een bepaalde cache binnen het HttpMessageHandler
bereik. De gegevens die daar zijn opgeslagen, kunnen 'verdwijnen' of, andersom, 'persistent' blijven als dat niet het geval is. Dit wordt veroorzaakt doordat het bereik Afhankelijkheidsinjectie (DI) niet overeenkomt tussen de toepassingscontext en het handler-exemplaar, en het is een bekende beperking in IHttpClientFactory
.
IHttpClientFactory
maakt een afzonderlijk DI-bereik per instantie HttpMessageHandler
. Deze handlerbereiken verschillen van toepassingscontextbereiken (bijvoorbeeld ASP.NET Binnenkomende aanvraagbereik core of een handmatig DI-bereik dat door de gebruiker is gemaakt), zodat ze geen scoped service-exemplaren delen.
Als gevolg van deze beperking:
- Gegevens die extern in de cache worden opgeslagen in een bereikservice, zijn niet beschikbaar in de
HttpMessageHandler
. - Alle gegevens die intern in de cache worden opgeslagen binnen of binnen het
HttpMessageHandler
bereik ervan , kunnen worden waargenomen vanuit meerdere DI-bereiken van toepassingen (bijvoorbeeld van verschillende binnenkomende aanvragen) omdat ze dezelfde handler kunnen delen.
Houd rekening met de volgende aanbevelingen om deze bekende beperking te verlichten:
❌ SLA GEEN bereikgerelateerde informatie (zoals gegevens uit HttpContext
) in de cache op in HttpMessageHandler
de cache van exemplaren of de bijbehorende afhankelijkheden om te voorkomen dat gevoelige informatie wordt gelekt.
❌ GEBRUIK GEEN cookies, omdat de CookieContainer
cookies samen met de handler worden gedeeld.
✔️ OVERWEEG de gegevens niet op te slaan of alleen door te geven binnen het HttpRequestMessage
exemplaar.
Als u willekeurige informatie naast de HttpRequestMessage
eigenschap wilt doorgeven, kunt u de HttpRequestMessage.Options eigenschap gebruiken.
✔️ OVERWEEG alle bereikgerelateerde logica (bijvoorbeeld verificatie) in te kapselen in een afzonderlijke DelegatingHandler
logica die niet is gemaakt door de IHttpClientFactory
, en deze te gebruiken om de IHttpClientFactory
door u gemaakte handler te verpakken.
Als u alleen een HttpMessageHandler
zonder HttpClient
wilt maken, roept u IHttpMessageHandlerFactory.CreateHandler een geregistreerde benoemde client aan. In dat geval moet u zelf een HttpClient
exemplaar maken met behulp van de gecombineerde handler. U vindt een volledig uitgevoerd voorbeeld voor deze tijdelijke oplossing op GitHub.
Zie de sectie Message Handler Scopes in IHttpClientFactory in de IHttpClientFactory
richtlijnen voor meer informatie.
HttpClient
respecteert dns-wijzigingen niet
Zelfs als IHttpClientFactory
dit wordt gebruikt, is het nog steeds mogelijk om het verouderde DNS-probleem te raken. Dit kan meestal gebeuren als een HttpClient
exemplaar wordt vastgelegd in een Singleton
service, of, in het algemeen, ergens wordt opgeslagen gedurende een periode die langer is dan de opgegeven HandlerLifetime
periode. HttpClient
wordt ook vastgelegd als de respectieve getypte client wordt vastgelegd door een singleton.
❌ DO NOT cache HttpClient
instances created by IHttpClientFactory
for verlengd perioden of time.
❌Injecteer geen getypte clientexemplaren in Singleton
services.
✔️ OVERWEEG om een client tijdig IHttpClientFactory
aan te vragen of telkens wanneer u er een nodig hebt. Factory-gemaakte clients zijn veilig te verwijderen.
HttpClient
exemplaren die door IHttpClientFactory
zijn gemaakt, zijn bedoeld om kort te leven.
Recycling en recreatie
HttpMessageHandler
van 's wanneer hun levensduur verloopt, is essentieel omIHttpClientFactory
ervoor te zorgen dat de handlers reageren op DNS-wijzigingen.HttpClient
is gekoppeld aan een specifiek handlerexemplaren bij het maken ervan, zodat nieuweHttpClient
exemplaren tijdig moeten worden aangevraagd om ervoor te zorgen dat de client de bijgewerkte handler krijgt.Het verwijderen van dergelijke
HttpClient
exemplaren die door de fabriek zijn gemaakt, zal niet leiden tot uitputting van de socket, omdat de verwijdering niet leidt tot verwijdering van deHttpMessageHandler
.IHttpClientFactory
houdt resources bij die worden gebruikt voor het makenHttpClient
van exemplaren, met name de exemplaren, zodra hunHttpMessageHandler
levensduur verloopt en er geenHttpClient
resources meer worden gebruikt.
Getypte clients zijn bedoeld om ook kortlevend te zijn, omdat een HttpClient
exemplaar wordt geïnjecteerd in de constructor, zodat deze de getypeerde levensduur van de client deelt.
Zie het HttpClient
levensduurbeheer en vermijd getypte clients in de secties singleton-services in de IHttpClientFactory
richtlijnen voor meer informatie.
HttpClient
gebruikt te veel sockets
Zelfs als IHttpClientFactory
dit wordt gebruikt, is het nog steeds mogelijk om het socketuitputtingsprobleem met een specifiek gebruiksscenario te raken. HttpClient
Standaard wordt het aantal gelijktijdige aanvragen niet beperkt. Als een groot aantal HTTP/1.1-aanvragen gelijktijdig wordt gestart, wordt er uiteindelijk een nieuwe HTTP-verbindingspoging geactiveerd omdat er geen gratis verbinding in de groep is ingesteld en er geen limiet is ingesteld.
❌ START NIET een groot aantal HTTP/1.1-aanvragen tegelijkertijd zonder de limieten op te geven.
✔️ OVERWEEG instelling HttpClientHandler.MaxConnectionsPerServer (of SocketsHttpHandler.MaxConnectionsPerServer, als u deze als primaire handler) gebruikt voor een redelijke waarde. Houd er rekening mee dat deze limieten alleen van toepassing zijn op het specifieke handler-exemplaar.
✔️ OVERWEEG OM HTTP/2 te gebruiken, waardoor multiplexing-aanvragen via één TCP-verbinding mogelijk zijn.
Getypte client heeft de verkeerde HttpClient
geïnjecteerd
Er kunnen verschillende situaties zijn waarin het mogelijk is om een onverwachte HttpClient
injectie in een getypte client te krijgen. Meestal wordt de hoofdoorzaak in een onjuiste configuratie weergegeven, zoals bij DI-ontwerp elke volgende registratie van een service de vorige overschrijft.
Getypte clients gebruiken benoemde clients 'onder de schermen': het toevoegen van een getypeerde client registreert impliciet en koppelt deze aan een benoemde client. De clientnaam, tenzij expliciet opgegeven, wordt ingesteld op de typenaam van TClient
. Dit is de eerste van het TClient,TImplementation
paar als AddHttpClient<TClient,TImplementation>
er overbelastingen worden gebruikt.
Het registreren van een getypte client doet daarom twee afzonderlijke dingen:
- Registreert een benoemde client (in een eenvoudige standaardcase, de naam is
typeof(TClient).Name
). - Registreert een
Transient
service met behulp van deTClient
ofTClient,TImplementation
opgegeven service.
De volgende twee verklaringen zijn technisch hetzelfde:
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
In een eenvoudig geval is het ook vergelijkbaar met het volgende:
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))));
Bekijk de volgende voorbeelden van hoe de koppeling tussen getypte en benoemde clients kan worden verbroken.
Getypte client wordt een tweede keer geregistreerd
❌ Registreer de getypte client niet afzonderlijk. Deze wordt al automatisch geregistreerd door de AddHttpClient<T>
aanroep.
Als een getypte client ten onrechte een tweede keer wordt geregistreerd als een tijdelijke service, overschrijft dit de registratie die is toegevoegd door de HttpClientFactory
, die de koppeling naar de benoemde client breekt. Het zal manifesteren alsof de HttpClient
configuratie verloren gaat, omdat een niet-geconfigureerde HttpClient
wordt geïnjecteerd in de getypte client .
Het kan verwarrend zijn dat er in plaats van een uitzondering een 'verkeerde' HttpClient
wordt gebruikt. Dit gebeurt omdat de 'standaard' niet-geconfigureerde HttpClient
, de client met de Options.DefaultName naam (string.Empty
) - is geregistreerd als een tijdelijke service, om het meest eenvoudige HttpClientFactory
gebruiksscenario in te schakelen. Dit is de reden waarom nadat de koppeling is verbroken en de getypte client gewoon een gewone service wordt, wordt deze 'standaard' HttpClient
natuurlijk geïnjecteerd in de respectieve constructorparameter.
Verschillende getypte clients worden geregistreerd op een gemeenschappelijke interface
Als twee verschillende typen clients zijn geregistreerd op een gemeenschappelijke interface, gebruiken ze beide dezelfde benoemde client. Dit kan lijken alsof de eerste getypte client de tweede client 'verkeerd' krijgt geïnjecteerd.
❌ REGISTREER NIET meerdere getypte clients op één interface zonder expliciet de naam op te geven.
✔️ OVERWEEG om een benoemde client afzonderlijk te registreren en te configureren en deze vervolgens te koppelen aan een of meerdere getypte clients door de naam in AddHttpClient<T>
gesprek op te geven of door aan te roepen AddTypedClient
tijdens het instellen van de benoemde client .
Door een benoemde client met dezelfde naam te registreren en te configureren, voegt u de configuratieacties gewoon toe aan de lijst met bestaande clienten. Dit gedrag is HttpClientFactory
misschien niet duidelijk, maar het is dezelfde benadering die wordt gebruikt door het patroon Opties en configuratie-API's, zoals Configure.
Dit is vooral handig voor geavanceerde handlerconfiguraties, bijvoorbeeld het toevoegen van een aangepaste handler aan een benoemde client die extern is gedefinieerd, of het mocken van een primaire handler voor tests, maar het werkt ook voor HttpClient
exemplaarconfiguratie. De drie volgende voorbeelden resulteren bijvoorbeeld in een HttpClient
geconfigureerde manier (beide BaseAddress
en DefaultRequestHeaders
zijn ingesteld):
// 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"));
Hiermee kunt u een getypte client koppelen aan een al gedefinieerde benoemde client en ook verschillende getypte clients koppelen aan één benoemde client. Het is duidelijker wanneer overbelastingen met een name
parameter worden gebruikt:
services.AddHttpClient("LogClient", c => c.BaseAddress = new Uri(LogServerAddress));
services.AddHttpClient<FooLogger>("LogClient");
services.AddHttpClient<BarLogger>("LogClient");
Hetzelfde kan ook worden bereikt door aan te roepen AddTypedClient tijdens de benoemde clientconfiguratie :
services.AddHttpClient("LogClient", c => c.BaseAddress = new Uri(LogServerAddress))
.AddTypedClient<FooLogger>()
.AddTypedClient<BarLogger>();
Als u echter niet dezelfde benoemde client opnieuw wilt gebruiken, maar u de clients toch op dezelfde interface wilt registreren, kunt u dit doen door expliciet verschillende namen voor deze clients op te geven:
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"));