Software · Jan 11, 2024 · 11 dk okuma
HTTP'yi Gerçekten Anlamak: Modern Web Uygulamalarının Temel Taşı
HTTP, request ve response alışverişinden ibaret değildir. Method, status code, header, cookie, session, cache ve connection davranışlarını anlamak, web uygulamalarında doğru mimari kararlar vermenin temelidir.

HTTP'yi anlamak, bir browser'ın server'a request gönderdiğini ve server'ın response döndürdüğünü bilmekten daha fazlasıdır. HTTP; bir işlemin ne anlama geldiğini, hangi koşullarda tekrar edilebileceğini, response'un kim tarafından saklanabileceğini ve farklı katmanların aynı mesajı nasıl yorumlayacağını belirleyen bir application-layer protocol'dür. Modern web uygulamalarında doğru çalışan bir API ile üretim ortamında güvenilir davranan bir API arasındaki fark, çoğu zaman bu ayrıntılarda ortaya çıkar.
Bir uygulama geliştirirken HTTP kararları doğrudan kullanıcı deneyimini, güvenliği ve altyapı maliyetini etkiler. Yanlış cache ayarı eski veriyi gösterebilir, hatalı cookie yapılandırması session güvenliğini bozabilir, gereksiz connection açılması latency'yi artırabilir. HTTP başlangıç seviyesinde öğretilir; fakat gerçek ustalık, protocol'ün farklı bileşenler arasında oluşturduğu sözleşmeyi anlayıp doğru kullanmaktır.
HTTP bir mesaj formatından fazlasıdır
HTTP'nin temelinde request ve response modeli bulunur. Client bir request oluşturur; server bu request'i değerlendirir ve bir response üretir. Browser, mobil uygulama veya başka bir backend service client olabilir. Server ise doğrudan application server olmak zorunda değildir; arada CDN, reverse proxy, load balancer veya API gateway bulunabilir.
Bir HTTP request, method, target, headers ve gerektiğinde body içerir. Örneğin GET /products/42 HTTP/1.1 satırı, client'ın /products/42 kaynağının temsilini istediğini belirtir. Host: shop.example.com header'ı hedef host'u tanımlar. HTTP/1.1'de bu alanlar metinsel biçimde taşınırken HTTP/2 ve HTTP/3 aynı semantiği farklı framing mekanizmalarıyla iletir.
Response tarafında status code, headers ve isteğe bağlı body bulunur. 200 OK, request'in başarıyla işlendiğini belirtir; ancak response body'nin mutlaka JSON olacağını söylemez. İçeriğin türünü Content-Type header'ı açıklar. application/json, text/html veya image/webp gibi değerler client'ın gelen veriyi nasıl yorumlayacağını belirler.
Burada kritik ayrım HTTP'nin resource ile representation kavramları arasındadır. /products/42 bir resource'u tanımlayabilir; server aynı resource için JSON, HTML veya başka bir representation döndürebilir. Content negotiation sırasında Accept gibi header'lar bu seçimi etkileyebilir. Dolayısıyla URL'yi bir dosya yolu, response body'yi de resource'un kendisi olarak düşünmek her zaman doğru değildir.
HTTP method'ları da yalnızca CRUD işlemlerine karşılık gelen isimler değildir. GET representation okumak için kullanılır ve safe method olarak tanımlanır: client, server üzerinde istenen bir state değişikliği beklemez. HEAD, GET ile ilişkili metadata'yı response body olmadan alır. POST, gönderilen representation'ın hedef resource tarafından işlenmesini ister; yeni kayıt oluşturmak bunun olası sonuçlarından biridir.
PUT, hedef resource'un state'inin gönderilen representation ile oluşturulmasını veya değiştirilmesini ifade eder. PATCH, kısmi değişiklik uygular. DELETE, hedef resource ile mevcut functionality arasındaki ilişkiyi kaldırmayı amaçlar. Bu method'ların semantiği API tasarımında önemlidir çünkü client, proxy ve retry mekanizmaları işlemin tekrarlanabilirliğine ilişkin beklentilerini buradan çıkarır.
Özellikle idempotency kavramı belirleyicidir. Aynı request'in birden fazla uygulanması, server'ın amaçlanan state'i açısından tek uygulamayla aynı etkiye sahipse işlem idempotent kabul edilir. PUT ve DELETE bu semantiğe sahiptir; POST ise genel olarak sahip değildir. DELETE request'inin ikinci kez 404 döndürmesi idempotency'yi bozmaz: response değişebilir, ancak amaçlanan state zaten gerçekleşmiştir.
Status code ve header davranışı belirler
HTTP status code'ları debugging sırasında görülen sayılardan ibaret değildir. Client'ın request sonucunu nasıl yorumlayacağını belirleyen protocol sinyalleridir. İlk rakam genel sınıfı gösterir: 1xx bilgilendirme, 2xx başarı, 3xx yönlendirme, 4xx client kaynaklı problem ve 5xx server kaynaklı problem anlamına gelir.
Bu sınıflandırma tek başına yeterli değildir. 200 OK başarılı bir response'u ifade ederken 201 Created yeni bir resource oluşturulduğunu bildirir. 204 No Content, başarılı işlemin ardından response content gönderilmeyeceğini belirtir. 202 Accepted ise işlemin kabul edildiğini, fakat henüz tamamlanmamış olabileceğini söyler. Asenkron bir job başlatıldığında 202 kullanmak, işi gerçekten bitmeden 200 ile tamamlanmış göstermeye göre daha doğru bir sözleşme oluşturabilir.
Hata durumlarında da ayrım önemlidir. 400 Bad Request, request'in geçersiz olduğunu gösterebilir. 401 Unauthorized, geçerli authentication credentials bulunmadığını veya sağlanmadığını belirtir; isimlendirmesine rağmen authentication ile ilgilidir. 403 Forbidden, server'ın request'i anladığı fakat yerine getirmeyi reddettiği durumdur. 404 Not Found, resource bulunamadığında kullanılır; bazı sistemler yasaklı bir resource'un varlığını gizlemek için de 404 döndürebilir.
409 Conflict, mevcut resource state'iyle çatışan işlemler için uygundur. 412 Precondition Failed, conditional request içinde belirtilen koşul sağlanmadığında kullanılır. 429 Too Many Requests rate limiting için anlamlıdır. 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable ve 504 Gateway Timeout ise farklı hata noktalarını ifade eder. Özellikle gateway üzerinden çalışan sistemlerde bu ayrımlar sorunun application server'da mı yoksa upstream iletişiminde mi oluştuğunu anlamaya yardım eder.
Headers, HTTP mesajına eklenen süs bilgiler değildir. Request'in ve response'un nasıl işleneceğini değiştiren metadata taşırlar. Content-Type body'nin media type'ını belirtirken Accept client'ın hangi representation türlerini kabul ettiğini ifade eder. Authorization credentials taşır. Location, redirect veya yeni oluşturulan resource'un adresini bildirebilir.
ETag, belirli bir representation sürümünü tanımlayan validator'dır. Client daha önce aldığı ETag değerini If-None-Match ile gönderdiğinde server representation değişmemişse 304 Not Modified döndürebilir. Böylece aynı body tekrar taşınmaz. Ancak 304, normal bir 200 response'unun küçük versiyonu değildir; cache'te bulunan representation ile birlikte yorumlanması gereken bir validation sonucudur.
Vary header'ı da caching açısından belirleyicidir. Örneğin response, Accept-Language değerine göre değişiyorsa Vary: Accept-Language uygun cache key ayrımının yapılmasını sağlar. Bunu belirtmemek, bir dil için oluşturulan representation'ın başka bir client'a sunulmasına neden olabilir.
Stateless olmak kullanıcı state'inin olmadığı anlamına gelmez
HTTP için sık kullanılan açıklama, protocol'ün stateless olduğudur. Bu doğrudur fakat eksiktir. Stateless olmak, her request'in anlamlandırılması için protocol'ün önceki request'lerden zorunlu bir application session state'i tutmaması demektir. Bir connection'ın açık kalması veya server'ın session verisi saklaması bu özelliği ortadan kaldırmaz.
Eksik kalan mekanizma, birbirinden bağımsız HTTP request'lerinin application düzeyinde nasıl ilişkilendirildiğidir. Cookie ve session tam burada devreye girer. Server, response içinde Set-Cookie göndererek browser'dan belirli bir değeri saklamasını isteyebilir. Browser sonraki uygun request'lerde bu değeri Cookie header'ı içinde geri gönderir.
Örneğin Set-Cookie: session_id=abc123; HttpOnly; Secure; SameSite=Lax; Path=/ bir session identifier taşıyabilir. Buradaki değer yalnızca örnektir; gerçek sistemlerde tahmin edilmesi mümkün olmayan, güvenli biçimde üretilmiş token'lar kullanılmalıdır. HttpOnly, JavaScript'in cookie'ye erişimini sınırlar. Secure, cookie'nin HTTPS üzerinden gönderilmesini gerektirir. SameSite=Lax, bazı cross-site request senaryolarında cookie gönderimini sınırlandırarak CSRF riskini azaltmaya yardımcı olur.
Cookie ile session aynı şey değildir. Cookie browser'da tutulan ve belirli request'lere eklenen veridir. Session ise application'ın bir client ile ilişkilendirdiği state'tir. Server-side session modelinde cookie çoğunlukla yalnızca session identifier içerir; asıl session state server tarafındaki bir store'da tutulur.
Alternatif olarak application, imzalı token kullanarak bazı bilgileri client'a taşıyabilir. JWT bunun bilinen örneklerinden biridir. Fakat JWT kullanmak sistemi otomatik olarak daha güvenli veya ölçeklenebilir yapmaz. Token iptali, süre dolumu, yetki değişiklikleri ve token'ın nerede saklandığı ayrı tasarım kararlarıdır.
Authentication ile authorization arasındaki ayrım da burada netleşir. Authentication, request'i yapan kimliğin doğrulanmasıdır. Authorization, doğrulanan kimliğin belirli bir işlemi gerçekleştirme iznine sahip olup olmadığının değerlendirilmesidir. Geçerli session'a sahip olmak, her resource'a erişim hakkı vermez.
Browser tabanlı application'larda cookie ile taşınan authentication bilgileri otomatik olarak request'e eklenebildiği için CSRF ayrıca düşünülmelidir. SameSite yardımcıdır ancak tüm senaryolar için tek başına yeterli savunma kabul edilmemelidir. State değiştiren işlemlerde uygun CSRF token doğrulaması veya güvenilir Origin kontrolleri kullanılabilir. XSS ise farklı bir tehdittir; HttpOnly, script'in cookie değerini doğrudan okumasını engellese de ele geçirilmiş sayfa bağlamında yetkisiz request gönderilmesini tek başına önlemez.
Cache ve connection aynı performans problemini çözmez
HTTP performansı anlatılırken en yaygın hata, bütün gecikmeyi connection maliyetine indirgemektir. Oysa bir request'in toplam süresi DNS resolution, connection kurulumu, TLS handshake, network RTT, server processing ve body transfer gibi farklı bileşenlerden etkilenir. Cache, connection reuse ve multiplexing bu bileşenlerin farklı yerlerine müdahale eder.
Cache-Control, response'un nasıl saklanabileceğini ve yeniden kullanılabileceğini belirleyen temel header'dır. Cache-Control: public, max-age=3600, uygun cache'lerin response'u 3600 saniye boyunca fresh kabul edebilmesini sağlar. private, response'un shared cache tarafından saklanmasını engeller ancak private cache kullanımına izin verebilir. no-store, cache'in response'u saklamamasını ister.
no-cache ismi yanıltıcıdır. Response'un hiçbir zaman saklanamayacağı anlamına gelmez; saklanmış response'un yeniden kullanılmadan önce validation gerektirdiğini belirtir. max-age=0 ise response'u hemen stale duruma getirir. Bu seçeneklerin davranışları birbirine benzeyebilir fakat semantik olarak aynı değildir.
Authenticated response'larda cache politikası özellikle dikkat gerektirir. Kullanıcıya özel içerik için private kullanılabilir. Hassas verilerin cache'te tutulmaması gerekiyorsa no-store daha uygun olabilir. Öte yandan her authenticated endpoint'e refleks olarak no-store eklemek, güvenli biçimde cache'lenebilecek verilerin gereksiz yere tekrar transfer edilmesine yol açabilir.
Connection tarafında tarihsel değişim farklıdır. HTTP/1.0 döneminde her request için yeni TCP connection açılması yaygın bir modeldi. HTTP/1.1 persistent connection kullanımını varsayılan hale getirdi. Böylece aynı TCP connection üzerinden birden fazla request/response alışverişi yapılabilir.
Connection: keep-alive ifadesi sık görülür fakat HTTP/1.1'de persistence zaten varsayılan davranıştır. Connection: close connection'ın kapatılacağını belirtmek için kullanılır. Keep-Alive header'ı bazı HTTP/1.x bağlantılarında timeout gibi parametreler taşıyabilir; bunların desteklenmesi ve uygulanması endpoint'e bağlıdır.
Buradaki kritik ayrım persistent connection ile HTTP/2 multiplexing arasındadır. HTTP/1.1 aynı connection'ı tekrar kullanabilir fakat response sıralaması ve framing nedeniyle eşzamanlı request işleme konusunda sınırlamaları vardır. HTTP/2, mesajları binary frame'lere ayırır ve aynı connection üzerinde birden fazla stream'i multiplex eder. Böylece birden fazla request'in frame'leri birbirine karışmadan aynı connection üzerinden ilerleyebilir.
HTTP/2'nin header compression mekanizması HPACK'tir. Tekrarlanan header alanlarının taşınma maliyetini azaltır. Ancak HTTP/2 TCP üzerinde çalıştığı için packet loss durumunda TCP seviyesindeki head-of-line blocking etkisi devam eder. Farklı HTTP/2 stream'leri application düzeyinde bağımsız olsa bile kaybolan TCP verisi sonraki byte'ların teslimini bekletebilir.
HTTP/3 bu noktada mimari bir değişiklik getirir. TCP yerine UDP üzerinde çalışan QUIC transport'unu kullanır. QUIC, TLS 1.3 tabanlı güvenliği protocol'e entegre eder ve bağımsız stream'ler sunar. Bir stream'e ait kayıp veri, aynı QUIC connection üzerindeki diğer stream'lerin teslimini TCP'deki gibi zorunlu olarak bloke etmez.
Bu, HTTP/3'ün her koşulda HTTP/2'den hızlı olduğu anlamına gelmez. Network kalitesi, server desteği, congestion control, connection reuse ve application davranışı sonucu değiştirir. HTTP/3 ayrıca QUIC stream'leri üzerinde QPACK header compression kullanır. Performans kararını yalnızca protocol sürümüne bakarak vermek yerine gerçek network koşullarında ölçmek gerekir.
Bir checkout request'i bütün katmanları görünür kılar
Bir e-ticaret application'ında checkout endpoint'ini düşünelim. Browser, alışveriş sepetindeki ürünleri satın almak için POST /api/orders request'i gönderiyor. Request body JSON formatında ve Content-Type: application/json header'ı bulunuyor. Browser ayrıca authentication için session cookie gönderiyor.
Application server önce session identifier'ı doğrular. Ardından authorization kontrolü yapar, sepetin mevcut durumunu okur, stok ve fiyat koşullarını değerlendirir. Sipariş oluşturma işlemi database transaction'ı içinde gerçekleştirilebilir. Ancak ödeme sağlayıcısı gibi dış sistemler devreye girdiğinde tek bir database transaction'ı bütün işlemi atomik hale getiremez.
En önemli problem request'in sonucunun client tarafından bilinmediği durumdur. Server siparişi oluşturmuş olabilir, fakat response network kesintisi nedeniyle browser'a ulaşmamış olabilir. Browser request'i yeniden gönderirse ikinci bir sipariş oluşma riski vardır. Bu nedenle POST request'lerini koşulsuz retry etmek tehlikelidir.
Bu endpoint için idempotency key kullanmak savunulabilir bir çözümdür. Client, tek bir mantıksal satın alma girişimi için benzersiz bir değer üretir ve bunu örneğin Idempotency-Key header'ı ile gönderir. Bu header yaygın bir API tasarım pratiğidir; uygulanma ayrıntıları server'ın sözleşmesine bağlıdır. Server aynı key ile gelen request'leri tanır ve işlemin ikinci kez gerçekleştirilmesini önler.
Yalnızca key'i database'e yazmak yeterli değildir. Eşzamanlı iki request aynı key ile geldiğinde ikisinin de yeni işlem başlatmasını engelleyen atomic bir kontrol gerekir. Key'in hangi kullanıcıya ve hangi request payload'ına ait olduğu da doğrulanmalıdır. Aksi halde aynı key'in farklı işlemler için yanlışlıkla kullanılması beklenmedik sonuçlar doğurabilir.
Başarılı sipariş oluşturma response'u 201 Created olabilir ve Location: /api/orders/7842 içerebilir. Sipariş arka planda işleniyorsa 202 Accepted ve job durumunu sorgulamaya yarayan bir resource daha uygun olabilir. Response body'de sipariş identifier'ı, durum ve client'ın sonraki adımı için gereken bilgiler bulunabilir.
Bu response'un cache davranışı ayrıca belirlenmelidir. Sipariş ve ödeme bilgileri hassas olduğundan Cache-Control: no-store güçlü bir varsayılan tercihtir. Aynı application'ın herkese açık ürün kataloğu ise CDN üzerinde cache'lenebilir. İki endpoint'e aynı cache politikasını uygulamak performans veya güvenlik açısından gereksiz bir ödün yaratır.
Checkout sırasında bağlantının kesilmesi, HTTP status code'unun application hatası anlamına geldiği durumdan farklıdır. Client hiç response alamamışsa işlemin gerçekleşmediğini varsayamaz. Bu ayrım retry politikalarının merkezindedir. Timeout, her zaman server'ın işlem yapmadığı anlamına gelmez.
Operasyon tarafında request'in geçtiği katmanlar da incelenmelidir. Browser DevTools içindeki Network paneli request headers, response headers, timing ve protocol bilgisini gösterir. curl -v header alışverişini incelemek için kullanılabilir. Proxy ve application log'ları ise request'in hangi noktada geciktiğini anlamayı sağlar.
Örneğin browser'da görülen 504 Gateway Timeout, application'ın hiç çalışmadığını kanıtlamaz. Gateway upstream response'u zamanında alamamıştır; application işlemi tamamlamış fakat response gecikmiş olabilir. Böyle bir durumda timeout değerini artırmak tek başına çözüm değildir. İşlemin tekrar uygulanması halinde ne olacağı da tasarlanmalıdır.
Doğru HTTP semantiğini önce tasarla
Benim önerim, HTTP optimizasyonuna protocol sürümünü değiştirmekle değil, endpoint sözleşmesini doğru kurmakla başlamaktır. Her endpoint için method'un anlamı, başarılı ve başarısız sonuçların status code'ları, authentication gereksinimi, cache politikası ve retry davranışı açık olmalıdır. Bu kararlar net değilse HTTP/3'e geçmek uygulamanın en tehlikeli hatalarını çözmez.
İlk kontrol noktası state değiştiren işlemlerdir. Safe olmayan request'ler için authentication ve authorization kontrol edilmeli, gerektiğinde CSRF savunması kurulmalıdır. Tekrar gönderildiğinde maddi veya mantıksal zarar oluşturabilecek işlemlerde idempotency mekanizması tasarlanmalıdır. Conditional updates için ETag ve If-Match kullanımı, kayıp update problemlerini önleyebilir; eşleşmeyen validator durumunda 412 Precondition Failed anlamlı bir sonuçtur.
İkinci kontrol noktası representation ve caching davranışıdır. Public ve user-specific response'lar ayrılmalı, Cache-Control bilinçli kullanılmalı, gerekiyorsa Vary ve validators eklenmelidir. Sonrasında gerçek trafik üzerinde latency ölçülmeli; DNS, TLS, server processing, transfer ve cache hit davranışlarının hangisinin maliyet oluşturduğu belirlenmelidir.
HTTP/2 modern web uygulamaları için güçlü bir varsayılan tercihtir. HTTP/3 ise özellikle uygun network koşullarında ve altyapı desteği bulunduğunda değerlendirilebilir. Fakat protocol seçimi, application'ın doğruluk modelinin yerine geçmez. Server'ın aynı ödeme request'ini iki kez işleyip işlemeyeceği, stream multiplexing'den daha temel bir mimari sorudur.
Bu yaklaşımın sınırı, HTTP'nin doğrudan kontrolünüzde olmadığı sistemlerdir. Yönetilen platformlar, eski proxy'ler veya üçüncü taraf API'ler bazı header'ları değiştirebilir, protocol desteğini kısıtlayabilir ya da kendi retry davranışlarını uygulayabilir. Böyle durumlarda ideal semantiği varsaymak yerine gerçek wire behavior test edilmeli ve entegrasyon sözleşmesi belgelenmelidir.
HTTP'yi iyi bilen geliştirici, yalnızca doğru response döndüren endpoint yazmaz. Response kaybolduğunda, cache yanlış representation tuttuğunda, proxy bağlantıyı kapattığında veya client aynı işlemi tekrar gönderdiğinde sistemin nasıl davranacağını da tasarlar. Senior seviyesindeki fark, protocol terimlerini ezberlemekten çok bu sınır durumlarını önceden modelleyebilmektir.
yorumlar
0Henüz yorum yok. İlk notu sen bırak.
Yorum yazmak için giriş yap.
Gmail ile saniyeler içinde, ya da e-posta ile kaydol.