Photo by Amy Lister on Unsplash

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 portsayı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 Behaviorproblemlerine 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 ve 408 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…

--

--

Furkan Güngör
Furkan Güngör

Written by Furkan Güngör

Solution Developer — I want to change the world, give me the source code.

No responses yet

Write a response