Yaygın IHttpClientFactory kullanım sorunları

Bu makalede, IHttpClientFactory kullanarak HttpClient örnekleri oluştururken karşılaşabileceğiniz en yaygın sorunlardan bazılarını öğreneceksiniz.

IHttpClientFactory, DI kapsayıcısında birden çok HttpClient yapılandırma ayarlamak, loglamayı yapılandırmak, dayanıklılık stratejilerini belirlemek ve daha fazlası için kullanışlı bir yoldur. IHttpClientFactory, yuva tükenmesi ve DNS değişikliklerini kaybetme gibi sorunları önlemek için, HttpClient ve HttpMessageHandler örneklerinin yaşam süresi yönetimini de kapsüller. .NET uygulamanızda IHttpClientFactory nasıl kullanacağınız hakkında genel bir bakış için .NET ile IHttpClientFactory belgesine bakın.

DI ile tümleştirmenin IHttpClientFactory karmaşık yapısı nedeniyle, yakalaması ve gidermesi zor olabilecek bazı sorunlarla karşılaşabilirsiniz. Bu makalede listelenen senaryolar, olası sorunları önlemek için proaktif olarak uygulayabileceğiniz öneriler de içerir.

HttpClient Scoped yaşam süresine saygı göstermez

kapsamındaki herhangi bir hizmete(örneğin, HttpContext) veya bazı kapsamlı önbelleklere içinden HttpMessageHandlererişmeniz gerekiyorsa bir sorunla karşılaşırsınız. Oraya kaydedilen veriler "kaybolabilir" veya tersine, olmaması gerektiğinde "kalıcı olabilir". Uygulama bağlamı ile işleyici örneği arasındaki Bağımlılık Ekleme (DI) kapsamı uyuşmazlığı, IHttpClientFactoryiçinde bilinen bir sınırlamadır.

IHttpClientFactory her HttpMessageHandler örnek için ayrı bir DI kapsamı oluşturur. Bu işleyici kapsamları uygulama bağlamı kapsamlarından (örneğin, ASP.NET Çekirdek gelen istek kapsamı veya kullanıcı tarafından oluşturulan bir el ile DI kapsamı) farklıdır, bu nedenle kapsamlı hizmet örneklerini paylaşmaz.

Bu sınırlamanın bir sonucu olarak:

  • Kapsamı belirlenmiş bir hizmette "harici olarak" önbelleğe alınmış hiçbir veri HttpMessageHandler içinde mevcut olmayacaktır.
  • HttpMessageHandler "dahili olarak" veya kapsamındaki bağımlılıkları içinde önbelleğe alınan tüm veriler, aynı işleyiciyi paylaşabilecekleri için birden çok uygulama DI kapsamından (örneğin, farklı gelen isteklerden) gözlemlenebilir.

Bu bilinen sınırlamayı hafifletmeye yardımcı olmak için aşağıdaki önerileri göz önünde bulundurun:

❌ Hassas bilgilerin sızmasını önlemek için kapsamla ilgili bilgileri (örneğin, HttpContext'den gelen veriler gibi) HttpMessageHandler örneklerinin veya bağımlılıklarının içinde önbelleğe ALMAYIN.

❌ Tanımlama bilgilerini KULLANMAYIN, çünkü CookieContainer işleyici ile birlikte paylaşılacaktır.

✔️ Bilgileri depolamamayı veya yalnızca HttpRequestMessage örnek içinde aktarmayı GÖZ ÖNÜNDE BULUNDURUN.

HttpRequestMessage ile birlikte rastgele bilgiler geçirmek için HttpRequestMessage.Options özelliğini kullanabilirsiniz.

✔️ Tüm kapsamla ilgili (örneğin, kimlik doğrulama) mantığı, DelegatingHandler tarafından oluşturulmamış ayrı bir içinde kapsüllemeyi düşünün ve bunu IHttpClientFactory tarafından oluşturulan işleyiciyi sarmalamak için kullanın.

Yalnızca bir HttpMessageHandler oluşturmak için, HttpClient olmadan, herhangi bir kayıtlı adlandırılmış istemci için IHttpMessageHandlerFactory.CreateHandler çağırın. Bu durumda, birleştirilmiş işleyiciyi kullanarak kendiniz bir HttpClient örnek oluşturmanız gerekir. GitHub'da bu geçici çözüm için tam olarak çalıştırılabilir bir örnek bulabilirsiniz.

Daha fazla bilgi için yönergelerdeki IHttpClientFactory'deIHttpClientFactory İleti İşleyici Kapsamları bölümüne bakın.

HttpClient DNS değişikliklerine saygı göstermez

IHttpClientFactory kullanılsa bile, bayat DNS sorununu yaşamak mümkündür. Bu durum genellikle bir HttpClient örneğin bir Singleton hizmette yakalanması veya genel olarak belirtilenden HandlerLifetimedaha uzun bir süre boyunca bir yerde depolanması durumunda gerçekleşebilir. HttpClient ayrıca, ilgili türü belirtilen istemci bir tekil tarafından yakalanırsa yakalanır.

❌tarafından HttpClient oluşturulan örnekleri uzun süreler boyunca önbelleğe IHttpClientFactory ALMAYIN.

❌ Tür değiştirilmiş istemci örneklerini Singleton hizmetlerine ENJEKTE ETMEYİN.

✔️ IHttpClientFactory'den bir istemciyi zamanında veya her ihtiyaç duyduğunuzda talep etmeyi DİKKATE ALIN. Fabrikada oluşturulan istemcilerin imha edilmesi güvenlidir.

HttpClienttarafından IHttpClientFactory oluşturulan örneklerin kısa ömürlü olması amaçlanmıştır.

  • İşleyicilerin DNS değişikliklerine tepki vermelerini sağlamak için HttpMessageHandler yaşam süreleri dolduğunda bunları geri dönüştürmek ve yeniden oluşturmak IHttpClientFactoryçok önemlidir. HttpClient oluşturulurken belirli bir işleyici örneğine bağlıdır, bu nedenle istemcinin güncelleştirilmiş işleyiciyi edineceğinden emin olmak için yeni HttpClient örneklerin zamanında istenmesi gerekir.

  • "Fabrika tarafından oluşturulan bu tür HttpClient örneklerin atılması, yuvanın tükenmesine yol açmaz, çünkü bu eylem HttpMessageHandler öğesinin atılmasını tetiklemez." IHttpClientFactory, HttpClient örneklerini oluşturmak için kullanılan kaynakları izler ve bunları, özellikle HttpMessageHandler örneklerinin kullanım ömrü sona erdiğinde ve artık onları kullanan HttpClient kalmadığında, elden çıkarır.

Yazılan istemcilerin de kısa ömürlü olması amaçlanır ve oluşturucuya bir HttpClient örnek eklendiğinden, yazılan istemci ömrünü paylaşır.

Daha fazla bilgi için yönergelerdeki HttpClient yaşam süresi yönetimi ve tekil hizmetlerde yazılı istemcilerden kaçınma bölümlerine IHttpClientFactory bakın.

HttpClient çok fazla yuva kullanıyor

IHttpClientFactory kullanılsa bile, belirli bir kullanım senaryosunda yuva tükenme sorunuyla karşılaşmak mümkündür. Varsayılan olarak, HttpClient eşzamanlı istek sayısını sınırlamaz. Aynı anda çok sayıda HTTP/1.1 isteği başlatılırsa, havuzda boş bağlantı olmadığından ve sınır ayarlanmadığından her biri yeni bir HTTP bağlantı girişimi tetikler.

❌ Sınırları belirtmeden aynı anda çok sayıda HTTP/1.1 isteği başlatmaYIN.

✔️ Bunu birincil işleyici olarak kullanıyorsanız, makul bir değere ayarlamayı HttpClientHandler.MaxConnectionsPerServer (veya SocketsHttpHandler.MaxConnectionsPerServer) GÖZ ÖNÜNDE BULUNDURUN. Bu sınırların yalnızca belirli işleyici örneği için geçerli olduğunu unutmayın.

✔️ Tek bir TCP bağlantısı üzerinden isteklerin birden çok katına alınmasına izin veren HTTP/2 kullanmayı göz önünde bulundurun.

Yazılan istemci yanlış HttpClient eklenmiş

Türlenmiş bir istemciye beklenmeyen HttpClient bir eklemenin mümkün olduğu çeşitli durumlar olabilir. Çoğu zaman, DI tasarımına göre bir hizmetin sonraki kayıtları öncekini geçersiz kılacağından, kök neden genellikle hatalı bir yapılandırmada bulunur.

Yazılan istemciler adlandırılmış istemcileri "arka planda" kullanır: yazılan bir istemciyi örtük olarak kaydeder ve adlandırılmış bir istemciye bağlar. İstemci adı, açıkça belirtilmediği sürece TClient tür adına ayarlanır. Bu, aşırı yüklemeler kullanılıyorsa çiftten TClient,TImplementation ilki olur.

Bu nedenle, türü belirlenmiş bir istemciyi kaydetmek iki ayrı işlem yapar:

  1. Adlandırılmış bir istemciyi kaydeder (basit bir varsayılan örnekte, adı olur typeof(TClient).Name).
  2. TClient veya TClient,TImplementation öğesini kullanarak bir Transient hizmeti kaydeder.

Aşağıdaki iki deyim teknik olarak aynıdır:

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

Basit bir durumda, aşağıdakine benzer olacaktır:

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))));

Yazılan ve adlandırılmış istemciler arasındaki bağlantının nasıl bozulabileceğine ilişkin aşağıdaki örnekleri göz önünde bulundurun.

Yazılan istemci ikinci kez kaydedilir

❌Yazılı istemciyi ayrı kaydetmeyin; AddHttpClient<T> çağrısı ile zaten otomatik olarak kaydedilir.

Tipini girilen bir istemci yanlışlıkla Basit Geçici hizmet olarak ikinci kez kaydedilirse, IHttpClientFactory tarafından eklenen kaydın üzerine yazılır ve adlı istemcinin bağlantısını bozar. Konfigürasyonu kaybolmuş gibi görünecek şekilde, yapılandırılmamış bir HttpClient'nin yerine, tür belirtilmiş istemci içine enjekte edilir.

Özel durum oluşturmak yerine "yanlış" HttpClient kullanılması kafa karıştırıcı olabilir. Bunun nedeni, "varsayılan" olarak yapılandırılmamış olan HttpClient (Options.DefaultName adıyla geçen string.Empty istemcisi), en temel IHttpClientFactory kullanım senaryosunu etkinleştirmek amacıyla düz bir geçici hizmet olarak kaydedilmiş olmasıdır. Bu nedenle, bağlantı koptuğunda ve yazılan istemci sıradan bir hizmet haline geldikten sonra, bu "varsayılan" HttpClient doğal bir şekilde ilgili oluşturucu parametresine eklenir.

Farklı türdeki istemciler ortak bir arabirime kaydedilir

İki farklı türe sahip istemcinin ortak bir arabirimde kayıtlı olması durumunda ikisi de aynı adlandırılmış istemciyi yeniden kullanabilir. Bu, ilk yazılan istemcinin yanlışlıkla enjekte edilen ikinci adlandırılmış istemci gibi görünebilir.

❌ Adı açıkça belirtmeden tek bir arabirimde birden çok türe bağlı istemci kaydetmeYİN.

Adlandırılmış bir istemciyi kaydedip yapılandırmayı ayrı ayrı düşünün ve ardından bu istemciyi bir veya birden çok türü istemciye , çağrıda adı belirterek veya adlandırılmış istemci kurulumu sırasında arayarak bağlamayı göz önünde bulundurun.

Tasarım gereği, aynı ada sahip adlandırılmış bir istemciyi birkaç kez kaydedip yapılandırmak, yapılandırma eylemlerini var olan istemciler listesine ekler. Bu davranış IHttpClientFactory için belirgin olmayabilir, ancak Seçenekler deseni ve tarafından kullanılan yaklaşımla aynıdır ve Configure gibi yapılandırma API'leri de kullanır.

Bu çoğunlukla gelişmiş işleyici yapılandırmaları için yararlıdır; örneğin, harici olarak tanımlanan adlandırılmış bir istemciye özel işleyici ekleme veya testler için birincil işleyiciyi simüle etme, ancak örnek yapılandırması için HttpClient de çalışır. Örneğin, aşağıdaki üç örnek, aynı şekilde yapılandırılmış bir HttpClient ile sonuçlanır (hem BaseAddress hem de DefaultRequestHeaders ayarlanır).

// 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"));

Bu, türü belirlenmiş bir istemciyi zaten tanımlanmış adlandırılmış bir istemciye bağlamayı ve ayrıca birkaç türü belirlenmiş istemciyi tek bir adlandırılmış istemciye bağlamayı sağlar. name parametresi kullanıldığında aşırı yüklemeler daha belirgin hale gelir.

services.AddHttpClient("LogClient", c => c.BaseAddress = new Uri(LogServerAddress));

services.AddHttpClient<FooLogger>("LogClient");
services.AddHttpClient<BarLogger>("LogClient");

Aynı şey, adlandırılmış istemci yapılandırması sırasında AddTypedClient çağrılarak da elde edilebilir.

services.AddHttpClient("LogClient", c => c.BaseAddress = new Uri(LogServerAddress))
    .AddTypedClient<FooLogger>()
    .AddTypedClient<BarLogger>();

Ancak, aynı adlandırılmış istemciyi yeniden kullanmak istemiyorsanız ancak yine de istemcileri aynı arabirime kaydetmek istiyorsanız, bunlar için açıkça farklı adlar belirterek bunu yapabilirsiniz:

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"));

Ayrıca bkz.