Problemas comuns IHttpClientFactory
de uso
Neste artigo, você aprenderá alguns dos problemas mais comuns que você pode encontrar ao usar IHttpClientFactory
para criar HttpClient
instâncias.
IHttpClientFactory
é uma maneira conveniente de configurar várias HttpClient
configurações no contêiner DI, configurar registros, configurar estratégias de resiliência e muito mais. IHttpClientFactory
também incorpora o gerenciamento de tempo de vida de instâncias e HttpMessageHandler
para evitar problemas como esgotamento do soquete e perda de HttpClient
alterações de DNS. Para obter uma visão geral sobre como usar IHttpClientFactory
em seu aplicativo .NET, consulte IHttpClientFactory com .NET.
Devido a uma natureza complexa da integração com DI, você pode resolver alguns problemas que podem ser difíceis de IHttpClientFactory
detetar e solucionar problemas. Os cenários listados neste artigo também contêm recomendações, que você pode aplicar proativamente para evitar possíveis problemas.
HttpClient
não respeita Scoped
a vida
Você pode ter um problema se precisar acessar qualquer serviço com escopo, por exemplo, HttpContext
ou algum cache com escopo, de dentro do HttpMessageHandler
. Os dados salvos lá podem "desaparecer", ou, ao contrário, "persistir" quando não deveriam. Isso é causado pela incompatibilidade de escopo de DI (Injeção de Dependência) entre o contexto do aplicativo e a instância do manipulador, e é uma limitação conhecida no IHttpClientFactory
.
IHttpClientFactory
cria um escopo de DI separado para cada HttpMessageHandler
instância. Esses escopos do manipulador são distintos dos escopos de contexto do aplicativo (por exemplo, ASP.NET escopo de solicitação de entrada principal ou um escopo DI manual criado pelo usuário), portanto, eles não compartilharão instâncias de serviço com escopo.
Como resultado desta limitação:
- Quaisquer dados armazenados em cache "externamente" em um serviço com escopo não estarão disponíveis no
HttpMessageHandler
. - Todos os dados armazenados em cache "internamente" dentro das dependências de escopo podem ser observados a
HttpMessageHandler
partir de vários escopos DI de aplicativos (por exemplo, de diferentes solicitações de entrada), pois eles podem compartilhar o mesmo manipulador.
Considere as seguintes recomendações para ajudar a aliviar essa limitação conhecida:
❌NÃO armazene em cache nenhuma informação relacionada ao escopo (como dados de ) dentro HttpMessageHandler
de instâncias ou suas dependências para evitar o vazamento de HttpContext
informações confidenciais.
❌ NÃO utilize cookies, pois os CookieContainer
serão partilhados juntamente com o manipulador.
✔️ CONSIDERE não armazenar as informações, ou apenas passá-las dentro da HttpRequestMessage
instância.
Para passar informações arbitrárias ao lado do HttpRequestMessage
, você pode usar a HttpRequestMessage.Options propriedade.
✔️ CONSIDERE encapsular toda a lógica relacionada ao escopo (por exemplo, autenticação) em um separado DelegatingHandler
que não seja criado pelo , e use-a IHttpClientFactory
para encapsular o IHttpClientFactory
manipulador -created.
Para criar apenas um HttpMessageHandler
sem HttpClient
, chame IHttpMessageHandlerFactory.CreateHandler qualquer cliente nomeado registrado. Nesse caso, você mesmo precisará criar uma HttpClient
instância usando o manipulador combinado. Você pode encontrar um exemplo totalmente executável para essa solução alternativa no GitHub.
Para obter mais informações, consulte a seção Escopos do manipulador de mensagens em IHttpClientFactory nas IHttpClientFactory
diretrizes.
HttpClient
não respeita as alterações de DNS
Mesmo se IHttpClientFactory
for usado, ainda é possível resolver o problema de DNS obsoleto. Isso geralmente pode acontecer se uma HttpClient
instância for capturada em um Singleton
serviço ou, em geral, armazenada em algum lugar por um período de tempo maior do que o especificado HandlerLifetime
. HttpClient
também será capturado se o respetivo cliente digitado for capturado por um singleton.
❌ NÃO armazene em cache HttpClient
instâncias criadas por IHttpClientFactory
períodos prolongados de tempo.
❌ NÃO injete instâncias de cliente digitadas em Singleton
serviços.
✔️ CONSIDERE solicitar um cliente em tempo hábil ou sempre que precisar de IHttpClientFactory
um. Os clientes criados na fábrica são seguros para descartar.
HttpClient
As instâncias criadas por IHttpClientFactory
destinam-se a ser de curta duração.
Reciclar e recriar
HttpMessageHandler
's quando sua vida útil expira é essencial paraIHttpClientFactory
garantir que os manipuladores reajam às alterações de DNS.HttpClient
está vinculado a uma instância de manipulador específica após sua criação, portanto, novasHttpClient
instâncias devem ser solicitadas em tempo hábil para garantir que o cliente obtenha o manipulador atualizado.O descarte de tais
HttpClient
instâncias criadas pela fábrica não levará ao esgotamento da tomada, pois seu descarte não desencadeia oHttpMessageHandler
descarte do .IHttpClientFactory
rastreia e elimina os recursos usados para criarHttpClient
instâncias, especificamente asHttpMessageHandler
instâncias, assim que sua vida útil expira e nãoHttpClient
há mais uso.
Os clientes tipados também devem ser de curta duração , pois uma HttpClient
instância é injetada no construtor, portanto, ela compartilhará o tempo de vida do cliente digitado.
Para obter mais informações, consulte as HttpClient
IHttpClientFactory
seções Gerenciamento de tempo de vida e Evitar clientes digitados em serviços singleton nas diretrizes.
HttpClient
usa muitos soquetes
Mesmo se IHttpClientFactory
for usado, ainda é possível resolver o problema de exaustão do soquete com um cenário de uso específico. Por padrão, HttpClient
não limita o número de solicitações simultâneas. Se um grande número de solicitações HTTP/1.1 forem iniciadas simultaneamente ao mesmo tempo, cada uma delas acabará acionando uma nova tentativa de conexão HTTP, porque não há conexão livre no pool e nenhum limite é definido.
❌ NÃO inicie um grande número de solicitações HTTP/1.1 simultaneamente ao mesmo tempo sem especificar os limites.
✔️ CONSIDERE a configuração HttpClientHandler.MaxConnectionsPerServer (ou SocketsHttpHandler.MaxConnectionsPerServer, se você usá-lo como um manipulador primário) para um valor razoável. Observe que esses limites só se aplicam à instância específica do manipulador.
✔️ CONSIDERE o uso de HTTP/2, que permite solicitações de multiplexação em uma única conexão TCP.
Cliente digitado tem a injeção errada HttpClient
Pode haver várias situações em que é possível obter uma injeção inesperada HttpClient
em um cliente digitado. Na maioria das vezes, a causa raiz estará em uma configuração errada, pois, pelo design DI, qualquer registro subsequente de um serviço substitui o anterior.
Clientes tipados usam clientes nomeados "sob o capô": adicionar um cliente digitado implicitamente registra e o vincula a um cliente nomeado. O nome do cliente, a menos que explicitamente fornecido, será definido como o nome do tipo de TClient
. Este seria o primeiro do TClient,TImplementation
par se AddHttpClient<TClient,TImplementation>
sobrecargas são usadas.
Portanto, registrar um cliente digitado faz duas coisas separadas:
- Registra um cliente nomeado (em um caso padrão simples, o nome é
typeof(TClient).Name
). - Registra um
Transient
serviço usando oTClient
ouTClient,TImplementation
fornecido.
As duas afirmações seguintes são tecnicamente as mesmas:
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
Em um caso simples, também será semelhante ao seguinte:
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))));
Considere os exemplos a seguir de como o vínculo entre clientes digitados e nomeados pode ser quebrado.
Cliente digitado é registrado uma segunda vez
❌NÃO registre o cliente digitado separadamente — ele já está registrado automaticamente pela AddHttpClient<T>
chamada.
Se um cliente digitado HttpClientFactory
for erroneamente registrado uma segunda vez como um serviço transitório simples, isso substituirá o registro adicionado pelo , quebrando o link para o cliente nomeado. Ele se manifestará como se a HttpClient
configuração do 's fosse perdida, pois um não configurado HttpClient
será injetado no cliente digitado.
Pode ser confuso que, em vez de lançar uma exceção, um "errado" HttpClient
seja usado. Isso acontece porque o "padrão" não configurado HttpClient
— o cliente com o Options.DefaultName nome (string.Empty
) — é registrado como um serviço transitório simples, para habilitar o cenário de uso mais básico HttpClientFactory
. É por isso que depois que o link é quebrado e o cliente digitado se torna apenas um serviço comum, esse "padrão" HttpClient
será naturalmente injetado no respetivo parâmetro do construtor.
Diferentes clientes tipados são registrados em uma interface comum
No caso de dois clientes tipados diferentes serem registrados em uma interface comum, ambos reutilizariam o mesmo cliente nomeado. Isso pode parecer que o primeiro cliente digitado recebendo o segundo cliente nomeado "erroneamente" injetado.
❌ NÃO registre vários clientes digitados em uma única interface sem especificar explicitamente o nome.
✔️ CONSIDERE registrar e configurar um cliente nomeado separadamente e, em seguida, vinculá-lo a um ou vários clientes digitados, especificando o nome na AddHttpClient<T>
chamada ou chamando AddTypedClient
durante a configuração do cliente nomeado.
Ao projetar, registrar e configurar um cliente nomeado com o mesmo nome várias vezes apenas acrescenta as ações de configuração à lista de ações existentes. Esse comportamento de pode não ser óbvio, mas é a mesma abordagem usada pelo padrão Opções e APIs de HttpClientFactory
configuração como Configure.
Isso é útil principalmente para configurações avançadas de manipulador, por exemplo, adicionando um manipulador personalizado a um cliente nomeado definido externamente ou simulando um manipulador primário para testes, mas também funciona para HttpClient
configuração de instância. Por exemplo, os três exemplos a seguir resultarão em um HttpClient
configurado da mesma maneira (ambos BaseAddress
e DefaultRequestHeaders
são definidos):
// 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"));
Isso permite vincular um cliente digitado a um cliente nomeado já definido e também vincular vários clientes tipados a um único cliente nomeado. É mais óbvio quando sobrecargas com um name
parâmetro são usadas:
services.AddHttpClient("LogClient", c => c.BaseAddress = new Uri(LogServerAddress));
services.AddHttpClient<FooLogger>("LogClient");
services.AddHttpClient<BarLogger>("LogClient");
A mesma coisa também pode ser alcançada chamando AddTypedClient durante a configuração do cliente nomeado:
services.AddHttpClient("LogClient", c => c.BaseAddress = new Uri(LogServerAddress))
.AddTypedClient<FooLogger>()
.AddTypedClient<BarLogger>();
No entanto, se você não quiser reutilizar o mesmo cliente nomeado, mas ainda deseja registrar os clientes na mesma interface, você pode fazê-lo especificando explicitamente nomes diferentes para eles:
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"));