Delen via


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, HttpContextof een bepaalde cache binnen het HttpMessageHandlerbereik. 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 HttpRequestMessageeigenschap 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 IHttpClientFactorydoor u gemaakte handler te verpakken.

Als u alleen een HttpMessageHandler zonder HttpClientwilt 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 HandlerLifetimeperiode. 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 HttpMessageHandlervan 's wanneer hun levensduur verloopt, is essentieel om IHttpClientFactory ervoor te zorgen dat de handlers reageren op DNS-wijzigingen. HttpClient is gekoppeld aan een specifiek handlerexemplaren bij het maken ervan, zodat nieuwe HttpClient 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 de HttpMessageHandler. IHttpClientFactory houdt resources bij die worden gebruikt voor het maken HttpClient van exemplaren, met name de exemplaren, zodra hun HttpMessageHandler levensduur verloopt en er geen HttpClient 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:

  1. Registreert een benoemde client (in een eenvoudige standaardcase, de naam is typeof(TClient).Name).
  2. Registreert een Transient service met behulp van de TClient of TClient,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 HttpClientconfiguratie 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"));

Zie ook