
Deep Dive to HttpClient
Web tabanlı uygulamalar geliştirirken, API’lere ve uzak sunuculara HTTP istekleri göndermek için kullanılan en yaygın araçlardan biri HttpClient
sınıfıdır. Ancak, HttpClient
nesnesini yanlış kullanmak ciddi performans sorunlarına ve kaynak tüketiminde beklenmedik problemlere yol açabilir. 🧐
.NET
platformunda HttpClient
, HTTP protokolü üzerinden istek yapabilen, yanıt alabilen ve bunları yönetebilen bir sınıftır. Genellikle RESTful API'lere istek yapmak, JSON verileri almak veya sunucularla haberleşmek için kullanılır. HttpClient
, asenkron yapıda çalışır ve Task-based Asynchronous Pattern (TAP)
ile entegrasyonu sayesinde yüksek performanslı HTTP istekleri yapmaya olanak tanır.
Temel Kullanım
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
using HttpClient client = new HttpClient();
HttpResponseMessage response = await client.GetAsync("https://api.example.com/data");
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
}
Bu temel kullanım, küçük ve tek seferlik işlemler için uygun olabilir. Ancak, HttpClient
'in yaşam döngüsünü doğru yönetmezsek Port Exhaustion
ve DNS Behavior
gibi sorunlarla karşılaşabiliriz.
HttpClient
instance’leri uzun ömürlü olmalıdır. Application Lifetime
boyunca yeniden kullanılmalıdır. Her instance kendi connection pool
(bağlantı havuzu) kullanır. Uygulama yüksek yük altında çalışıyorsa bu yöntem sürekli olarak yeni bağlantılar oluşturacaktır.
Port Exhaustion Problemi
HttpClient
nesnesini yanlış yönetmenin en yaygın sonuçlarından biri Port Exhaustion
yani port tükenmesi problemidir. HttpClient
'i her istekte yeni bir nesne olarak oluşturursanız, sistemdeki kullanılabilir ephemeral port
sayısını hızla tüketebilirsiniz. Aşağıdaki gibi bir kullanım buna neden olabilir:
public async Task<string> GetDataAsync()
{
using var client = new HttpClient();
return await client.GetStringAsync("https://api.example.com/data");
}
Bu kod her çağrıldığında yeni bir HttpClient
örneği oluşturduğu için kısa sürede ephemeral port
sayısı tükenebilir ve sistem yeni bağlantı oluşturamaz. Bu yüzden HttpClient
nesnesini tekrar kullanılabilir hale getirmek gereklidir.
Çözüm olarak HttpClient
nesnesini statik olarak tanımlayabilirsiniz:
private static readonly HttpClient client = new HttpClient();
Bu yaklaşım, HttpClient
nesnesinin yeniden kullanılmasını sağlar ve port tükenmesi riskini azaltır. Ancak, bu yöntem de bazı durumlarda DNS Behavior
problemlerine yol açabilir.
DNS Behavior Problemi
.NET
'te HttpClient
'in varsayılan davranışlarından biri, DNS (Domain Name System) çözümlemelerini yalnızca nesne oluşturulurken yapmasıdır. Eğer HttpClient
uzun süre kullanılacaksa ve arka plandaki sunucu IP adresini değiştirirse, istemci yanlış bir adrese istek yapmaya devam edebilir. Bu durum, özellikle bulut tabanlı hizmetlerde veya yük dengeleme (load balancing) senaryolarında sorunlara yol açabilir.
Http Client Factory
HttpClientFactory
, HttpClient
nesnelerinin daha yönetilebilir şekilde oluşturulmasını sağlar. .NET Core 2.1
ile tanıtılan bu yapı, özellikle port tükenmesi ve DNS çözümleme problemlerini önlemek için geliştirilmiştir. Kullanımı aşağıdaki gibidir:
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var services = new ServiceCollection();
services.AddHttpClient();
var provider = services.BuildServiceProvider();
var clientFactory = provider.GetRequiredService<IHttpClientFactory>();
var client = clientFactory.CreateClient();
HttpResponseMessage response = await client.GetAsync("https://api.example.com/data");
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
}
HttpClientFactory
, HttpClient
nesnelerinin oluşturulması ve yönetilmesi sürecini basitleştirir. Ayrıca, HttpMessageHandler
örneklerini yeniden kullanarak kaynak tüketimini optimize eder ve DNS çözümleme problemlerini minimize eder.
HttpClientFactory
ile belirli konfigürasyonlara sahip Named Clients
oluşturabilirsiniz. Bu, farklı API'ler veya hizmetler için özelleştirilmiş HttpClient
örnekleri oluşturmanıza olanak tanır.
services.AddHttpClient("myApi", client =>
{
client.BaseAddress = new Uri("https://api.example.com/");
client.DefaultRequestHeaders.Add("Accept", "application/json");
});
var client = clientFactory.CreateClient("myApi");
IHttpClientFactory
kullanmak, manuel olarak oluşturmanın çoğu sorununu çözer. Ancak CreateClient
yönteminden her yeni instance
alındığında default
parametreleri yapıladırmanız gerekmektedir.
AddHttpClient
metodu ile Named Client
oluşturmak burada oluşacak kod tekrarını önleyecektir.
Named Client
kullanmanın dezavantajı her seferinde vermiş olduğunuz ismi kullanmak olacaktır. Aynı davranışı elde etmenin daha iyi bir yolu olduğunu düşünüyorum. AddClient<TClient>
yöntemini kullanarak daha güçlü tip güvenliği sağlayacaktır.
services.AddHttpClient<MyApiClient>(client =>
{
client.BaseAddress = new Uri("https://api.example.com/");
});
Typed Client
Transient
olarak kaydedilir. Kullandığınız client
objeleri eğer Singleton
bir servis içindeyse servisin ömrü boyunca client
objesinin önbelleğe alınmasına neden olur.
Bu senaryoda typed client
DNS değişikliklerine tepkisiz kalır. Eğer böyle bir senaryonuz varsa SocketHttpHandler
ve PooledConnectionLifeTime
ayarlarını kullanabilirsiniz.
services.AddHttpClient<MyApiClient>()
.ConfigurePrimaryHttpMessageHandler(() =>
new SocketsHttpHandler()
{
PooledConnectionLifetime = TimeSpan.FromMinutes(5)
});
Resilience with Polly
Polly
, yeniden deneme (retry), zaman aşımı (timeout), devre kesici (circuit breaker) gibi dayanıklılık politikaları sağlar.
using Polly;
using Polly.Extensions.Http;
var retryPolicy = HttpPolicyExtensions
.HandleTransientHttpError()
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
services.AddHttpClient("resilientClient")
.AddPolicyHandler(retryPolicy);
- Geçici HTTP hatalarını (
5xx
ve408
kodları gibi) yakalar. - İlk hatadanın ardından
2^n
saniye bekleyerek toplamda üç kez yeniden dener. AddHttpClient
extension metodları ile kolayca entegre edilir.
Tüm bu yapılandırmalar, yüksek trafikli ve büyük ölçekli uygulamalarda başarılı bir ağ yönetiminin temelini atmaktadır. Her bağlantı, belirli bir süre boyunca doğru şekilde yönetilerek, hem performansı hem de güvenliği artırır. Bu yapılandırmanın gücü, her bir HTTP isteğinin yalnızca gerçekten gerekli olduğunda yeni bir bağlantı oluşturması, diğer zamanlarda ise mevcut bağlantıların yeniden kullanılmasını sağlamasıdır. Tıpkı bir mühendisin bir fabrikanın üretim hattındaki her aşamayı düzenlemesi gibi, SocketsHttpHandler ve PooledConnectionLifetime ile her ağ bağlantısı bir işçi gibi çalışır; verimli, düzenli ve güçlü bir şekilde…