Ana içeriğe geç

Bölüm 14:HTTP/2'den HTTP/3'e

8 dakikalık okumaGüncelleme:

Ağlar'a “Sen”i Katmak yazısının parçası: adres çubuğuna yazdığın tek bir isteğin peşinden bakırla fiberin fiziğine kadar inen uzun bir teknik yolculuk.

Tüm bölümler
  1. Giriş
  2. Adres Çubuğundan İsteğe
  3. Paket Anahtarlamalı Yol
  4. İsimden Adrese
  5. Önbellek, Tazelik ve Güven
  6. Port Numarası ve Soket
  7. Bağlantısız Taşıma: UDP
  8. Üç Adımlı El Sıkışma
  9. Simetrik Şifreleme
  10. Açık Anahtarlı Kriptografi
  11. TLS El Sıkışması
  12. Sertifika Zinciri
  13. HTTP Mesajının Anatomisi
  14. Durum, Çerez ve Oturum
  15. HTTP/2'den HTTP/3'e
  16. İçerik Dağıtım Ağları
  17. Diğer Uygulama Protokolleri
  18. Katmanlı Mimari
  19. Kapsülleme ve Başlıklar
  20. Paket Yakalama ve Çözümleme
  21. Ağ Arayüz Kartı
  22. Fiziksel Adresleme
  23. Hata Sezme ve CRC
  24. Adres Çözümleme: ARP
  25. Anahtarlama ve Ortam Erişimi
  26. Ağ Topolojileri
  27. Sanal Yerel Ağlar
  28. Kablosuz Erişim: 802.11
  29. Radyo Kanalı ve Kapasite
  30. Kablosuz Ağ Güvenliği
  31. Hücresel Ağlar
  32. Spektrum ve Kuşaklar
  33. Hareketlilik ve Aktarma
  34. Adres Kiralama: DHCP
  35. Adresin Anatomisi
  36. Adres Tükenmesi ve IPv6
  37. Yönlendirici Mimarisi
  38. Kuyruk Yönetimi
  39. Cihaz Yapılandırma
  40. Yönlendirme Protokolleri
  41. Kontrol Düzlemi ve SDN
  42. Ağ İzleme ve Yönetimi
  43. Alanlar Arası Yönlendirme
  44. Adres Çevirisi
  45. Ara Kutular
  46. Tünelleme ve IPsec
  47. Güvenlik Duvarları
  48. Operatör ve Metro Ağları
  49. Bitten Sinyale
  50. Çoklama Teknikleri
  51. İletim Ortamları
  52. Kablo Sonlandırma
  53. Fiber Optik İletim
  54. Bakır Erişim Ağı: DSL
  55. Pasif Optik Ağlar
  56. Kablo Erişim Ağları
  57. Denizaltı Kabloları
  58. Veri Merkezi Ağları
  59. Kapsülleme Çözme
  60. Güvenilir Aktarım
  61. Tıkanıklık Denetimi
  62. Trafik Çözümleme
  63. Gecikme Bütçesi
  64. Son Söz

Bir sayfa yüzlerce parçadan oluşuyor ve her parça için yolu baştan yürümek eski çözümdü. İstek satırını ve durum kodlarını açarken, tek bağlantıya kaç isteğin sığdığını tek cümleyle bırakmıştım.

Borcu burada ödüyorum. Aynı yolun üstünde kaç araba var, ve arkadaki neden bekliyor?

Bağlantıyı açık bırakmak neyi kurtarıyor?

Kalıcı olmayan kipte bir taşıma bağlantısı en çok tek bir nesne taşıyordu. Klasik anlatım dört adımdı: bağlan, ne istediğini söyle, cevabı al, bağlantıyı kes. Dördü de her nesnede baştan tekrarlanıyordu.

Bedeli gidiş-dönüş süresiyle ölçebilirsin. Kalıcı olmayan kipte her nesne iki tur yiyor: biri bağlantı kurulumu, biri istek ve yanıtın ilk baytları. Üç el sıkışmanın RTT bedelini sayarken bu turun ilkini adım adım görmüştük.

Sayıya dökelim. 60 bağlı nesne taşıyan bir sayfa düşün ve gidiş-dönüş süresi 40 ms olsun. Yalnızca kurulum ve istek turları 4.800 ms tutuyor, yani 4,8 saniye. Dosya aktarım süreleri bu hesaba daha hiç girmedi; sayfa açılmadan önce sırf yolu kurmak için beş saniyeye yakın bekliyordun.

Ödediğin şey yalnızca gecikme de değil. Her yeni bağlantı işletim sistemi tarafında tampon, durum kaydı ve kontrol bloğu istiyor; bunların hepsi altmış kez birden kuruluyor.

HTTP/1.1 varsayılanı tersine çevirdi. RFC 9112’de kalıcı bağlantı varsayılandır: sunucu yanıtı gönderdikten sonra bağlantıyı açık bırakıyor ve aynı bağlantıdan birden çok istek ile yanıt geçiyor. Varsayılandan çıkmak için Connection başlığına close yazman gerekiyor. Kapatmak artık istisna.

Kazanç burada görünüyor. Kalıcı bağlantı, sayfadaki bütün bağlı nesnelerin toplam bedelini tek gidiş-dönüşe kadar indirebiliyor. Aynı sayfa iki gidiş-dönüşe, yani 80 ms’ye iniyor: biri kurulum, öbürü bütün nesneler için. Aradaki fark 60 kat.

Sıra zorunluluğu kimi bekletiyor?

HTTP/1.1 bir de boru hattını getirdi. İstemci yanıtı beklemeden istekleri arka arkaya gönderebiliyor. Sunucu güvenli metotları paralel işleyebilir, ama yanıtları isteklerin geldiği sırayla göndermek zorunda.

Zorunluluğun sebebi sade. HTTP/1.1’de istekleri yanıtlarla eşleştiren bir kimlik alanı yok; tek dayanak varış sırası. Protokolün tek varsayımı da güvenilir ve sırayla teslim eden bir taşıma. HTTP/1.0’da durum daha dardı: bir bağlantıda aynı anda yalnızca tek bir istek açık kalabiliyordu.

Sonuç şu: küçük bir nesne, önündeki büyük nesnenin arkasında beklemek zorunda kalıyor. Buna baş engellemesi (head-of-line blocking) deniyor. Burada gördüğün, uygulama katmanındaki baş engellemesi.

tek bağlantı, yanıtlar isteklerin geliş sırasında
  istekler :  A ---> B ---> C
  yanıtlar :  [============ A ============][ B ][ C ]
                                            ^ B hazırdı, A bitene kadar bekledi

2. bölümde paket anahtarlamayı ve kuyruk gecikmesini otoyol şeritleriyle anlatmıştım; tek şeritte önündeki aracı geçemezsin. Tarayıcılar bu şeritten kaçmak için aynı sunucuya paralel bağlantılar açıyor. RFC 9112 kaçışın sebebini açıkça yazıyor. Standardın eski sürümleri paralel bağlantı sayısına sayısal bir tavan koyuyordu. O tavan kaldırıldı, çünkü pek çok uygulama için pratik değildi. Yerine ölçülü olma öğüdü kaldı.

Ölçülü olmayan taraf kazanıyor. 9 bağlantının paylaştığı bir hatta tek bağlantı isteyen onda bir pay alıyor, 11 bağlantı açan ise hattın yarısını alıyor. Tıkanıklık denetimini polisleyen kimse yok. Dahası, tıkanık olmayan bir ağda bile eşzamanlı başlayan gönderimler, daha az bağlantıyla hiç oluşmayacak bir tıkanıklık yaratabiliyor.

Çerçeveye bölmek yetiyor mu?

HTTP/2 ilk olarak 2015’te RFC 7540 ile yayımlandı; yürürlükteki belgesi RFC 9113 oldu. Metotlar, durum kodları ve başlık alanlarının çoğu HTTP/1.1’den olduğu gibi kaldı. Değişen anlam değil, taşıma biçimi.

İkili çerçeveleme geldi. Nesneler çerçevelere bölünüyor ve çerçeveler iç içe geçirilerek gönderiliyor; küçük nesne artık büyüğün bitmesini beklemiyor. Gönderim sırası da geliş sırasına göre değil, istemcinin bildirdiği önceliğe göre kurulabiliyor. Başlık alanları ayrıca sıkıştırılarak kodlanıyor.

Paralellik sayıyla korunuyor. SETTINGS_MAX_CONCURRENT_STREAMS için önerilen alt sınır 100 eşzamanlı akış ve gerekçesi açık: paralelliği gereksiz yere kısmamak. Her akış 65.535 baytlık bir başlangıç penceresiyle açılıyor. Pencere, alıcının “şu kadar baytı onaylamadan gönderme” demesinin yoludur ve adı flow control (akış denetimi); hızlı bir göndereni yavaş bir alıcının tamponunu taşırmaktan alıkoyar.

İş burada bitmiyor. HTTP/2 uygulama katmanındaki baş engellemesini çözüyor, taşıma katmanındakini çözmüyor. RFC 9113 bunu kendi giriş bölümünde açıkça yazıyor. Sebebi tek cümle: tek taşıma bağlantısı verinin sırayla teslim edilmesini garanti ediyor. Kaybolan ya da sırası bozulan tek bir paket, o paketle hiç ilgisi olmayan bütün işlemleri de durduruyor.

Çoklamayı uygulama katmanına koydular, ama alttaki taşımanın kayıp kurtarma mekanizması onu göremiyor. Tarayıcıların hâlâ paralel bağlantı açma teşviki buradan geliyor.

Taşımanın kendisi neden değişti?

Çıplak bir taşıma bağlantısı kendi başına hiçbir güvenlik getirmiyor. Şifreleme ayrı bir katmandan geliyor ve ayrı bir el sıkışma istiyor. Eski kurulumda iki el sıkışma arka arkaya çalışıyordu: önce taşımanın güvenilirlik ve tıkanıklık durumu, sonra güvenlik katmanının kimlik ve anahtar durumu. Biri bitmeden öbürü başlamıyordu.

HTTP/3 anlamları hiç değiştirmiyor. RFC 9114 aynı HTTP anlamlarını yeni bir taşıma protokolünün üstüne taşıyor: QUIC. Yığın şöyle diziliyor: HTTP/3, altında QUIC, onun altında UDP, onun altında IP.

QUIC yeni bir fikir de icat etmiyor. Bağlantı kurulumu, hata denetimi ve tıkanıklık denetimi için bilinen taşıma çözümlerini uygulama tarafına taşıyor. Spesifikasyon algoritmaların koşut olduğunu kendisi söylüyor.

Asıl fark akışlarda. QUIC’te akış çoklaması ve akış başına flow control taşıma protokolünün kendi işi. Güvenilir aktarım akış başına ayrı tutuluyor, tıkanıklık denetimi ise ortak: bütün akışlar tek bir bütçeyi paylaşıyor. Paket kaybında yalnızca o pakette verisi bulunan akışlar yeniden gönderimi bekliyor. Ötekiler yoluna devam ediyor.

Üç kuşağı aynı kayıp üstünde karşılaştırmak istersen:

Aynı kayıp, üç kuşakta üç sonuç
HTTP/1.1HTTP/2HTTP/3ABCAABBCCAABBCCB ve C, A bitene kadar bekliyor — engelleme uygulamadakayıpkayıp segment üçünü birden durduruyor — engelleme taşımadakayıpyalnızca C bekliyor; A ve B akmaya devam ediyor
HTTP/1.1: sıra zorunluluğu. Tek bağlantıda yanıtlar isteklerin geldiği sırayla dönmek zorunda, çünkü hangi yanıtın hangi isteğe ait olduğunu söyleyen bir kimlik alanı yok. B hazır olsa bile A bitene kadar bekliyor.
  1. HTTP/1.1: sıra zorunluluğu. Tek bağlantıda yanıtlar isteklerin geldiği sırayla dönmek zorunda, çünkü hangi yanıtın hangi isteğe ait olduğunu söyleyen bir kimlik alanı yok. B hazır olsa bile A bitene kadar bekliyor.
  2. HTTP/2: aynı bağlantıda üç akış birden. Her nesne kendi akış kimliğini taşıyan çerçevelere bölünüyor ve çerçeveler karışık akabiliyor. Uygulama katmanındaki engelleme kalkıyor — B artık Ayı beklemiyor.
  3. Ama engelleme yok olmadı, bir kat aşağı indi. Üç akış tek bir TCP bağlantısında. Bir segment kaybolduğunda TCP sırayı korumak için **bütün** akışı bekletiyor; kaybolan çerçeve Cye ait olsa bile A ve B de duruyor. Taşıma katmanı akışların varlığından habersiz.
  4. HTTP/3: sıra her akışta ayrı tutuluyor. QUIC akış kavramını taşıma katmanına indiriyor ve her akış kendi sıra numarasını taşıyor. Kaybolan paket yalnızca kendi akışını bekletiyor; A ile B akmayı sürdürüyor.

Ayrım paket sınırında duruyor. Çoklama, farklı akışların STREAM çerçevelerini aynı QUIC paketlerine iç içe koyuyor. Tek pakete birden çok akışın verisi konabiliyor. O paketin kaybı hepsini birden durduruyor. Gerçekleştirimlere pakete gereğinden fazla akış koymamaları öğütleniyor.

DerinleşmeQUIC neden çekirdeğe değil, UDP üstüne kuruldu?

Cevap teknik olmaktan çok dağıtımla ilgili.

Klasik bir taşıma protokolü çekirdeğin içinde yaşar. Yeni bir taşıma yazmak, dünyadaki bütün işletim sistemlerinin onu göndermesini beklemek demek. İki klasik taşıma protokolü 40 yıl boyunca alanın tamamını tuttu; sebebi rakipsiz olmaları değil, rakibin oraya girememesiydi.

QUIC kilidi kırmak için taşımayı çekirdeğin dışına çıkardı. Altta kalan tek şey UDP, ve UDP her yerde zaten var: yeni bir çekirdek sürümü de yeni bir soket ailesi de gerekmiyor.

Bunun pratik karşılığı yayılma hızı. QUIC önce Google sunucularında, Chrome’da ve mobil YouTube uygulamasında çalıştı; standartlaşması sonradan geldi. Bir tarayıcı güncellemesi, bir sunucu güncellemesi, ve yeni taşıma sahada. Aynı işi çekirdek üstünden yapmak yıllar alırdı.

Bedeli de var. Uygulama tarafında çalışan bir taşıma, çekirdeğin yıllarca ayarlanmış kodundan ve donanım hızlandırmalarından mahrum kalıyor. Şifreleme her pakette çalışıyor ve bu işlemci zamanı kullanıcının makinesinden çıkıyor.

El sıkışma da tek tura indi. QUIC, TLS 1.3’ü taşıma katmanının içine aldı; güvenilirlik, tıkanıklık denetimi, kimlik doğrulama ve şifreleme durumu tek el sıkışmada birlikte kuruluyor. İstemci daha önce sakladığı oturum biletini kullanırsa el sıkışma gecikmesi sıfıra iniyor ve uygulama verisi ilk pakette gidiyor. O biletin bedelini biliyorsun: tekrar oynatma saldırısına karşı koruma yok. RFC 9114 bu yüzden karşı önlemleri zorunlu tutuyor.

Bir kazanç daha var. QUIC bağlantısı adresle değil, bağlantı kimliğiyle tanınıyor ve bu kimlik QUIC sürüm 1’de en çok 20 bayt olabiliyor. Uç noktanın adresi ve portu değişse bile bağlantı yaşamaya devam ediyor; buna bağlantı göçü deniyor. Değişim her zaman kasıtlı da değil; aradaki NAT kutusu yeni bir giden port atadığında da oluyor. Her iki durumda da yeni yolun doğrulanması gerekiyor, ve doğrulama başarısızsa yalnızca o yol kullanılmıyor. Göçü yalnızca istemci başlatabiliyor, üstelik el sıkışma doğrulanmadan başlatamıyor.

HTTP/3 tarafında eşleme sade. Her istek, istemcinin açtığı çift yönlü bir QUIC akışında gidiyor ve bir akışta yalnızca tek bir istek gönderilebiliyor. İkinci istek yeni akış demek. Akış kimliği 62 bitlik bir tam sayı; en düşük bit akışı kimin açtığını, ikinci bit tek yönlü mü çift yönlü mü olduğunu söylüyor. İki bit dört akış türü üretiyor.

HTTP/3 mesajının parçaları        çerçeve      tür kodu
  başlık bölümü                   HEADERS      0x01
  gövde                           DATA         0x00
  fragman başlıkları              HEADERS      0x01
  bağlantı ayarları               SETTINGS     0x04
  kapanış duyurusu                GOAWAY       0x07

Gövdesi olmayan bir yanıtta DATA çerçevesi hiç gönderilmiyor; gövde 0 bayt ise ortada çerçeve de yok. Her uç ayrıca en az 3 tek yönlü akışa izin vermek zorunda: bir HTTP denetim akışı ve başlıkları sıkıştıran QPACK’in iki akışı. Bu akışlara en az 1.024 baytlık flow control kredisi verilmesi öğütleniyor.

Bunu kendi makinende görebilirsin. Sürüm seçimi TLS el sıkışmasında ALPN uzantısındaki h3 belirteciyle bildiriliyor, sunucu da desteğini Alt-Svc yanıt başlığıyla duyuruyor. HTTP/3 için zorunlu tek bir UDP portu yok; duyuruda port bu yüzden açıkça yazılıyor.

curl -sI https://cloudflare-quic.com/ | grep -i '^alt-svc'
# alt-svc: h3=":443"; ma=86400

Sonra trafiği dinle. İlk QUIC datagramı en az 1.200 bayt olmak zorunda ve gerekirse PADDING çerçeveleriyle şişiriliyor; sebebi yükseltme saldırısını sınırlamak. Yol o boyu taşıyamıyorsa QUIC hiç kullanılmıyor. QUIC en küçük IP paketini 1.280 bayt varsayıyor. IPv6’da bundan 40 baytlık IP başlığı ile 8 baytlık UDP başlığı düşülüyor ve datagram tavanı 1.232 bayta iniyor. IPv4 başlığı daha kısa olduğu için orada tavan 1.252 bayt oluyor.

sudo tcpdump -n -c 5 'udp port 443'

Şifreli HTTP’nin klasik varsayılan port numarası 443, şifresizinki 80. UDP engellenmiş bir ağda hiçbir şey göremezsin, çünkü orada QUIC bağlantısı hiç kurulamıyor. Standart bu durumda istemcinin eski sürümlere düşmesini bekliyor.

Özet

Peki, ne öğrendik?

  • Kalıcı bağlantı varsayılan oldu: sunucu yanıttan sonra kapatmıyor, kapatmak için Connection başlığına close yazmak gerekiyor.
  • Baş engellemesi sıra zorunluluğundan doğuyor: istekleri yanıtlarla eşleştiren bir kimlik alanı olmayınca tek dayanak varış sırası kalıyor.
  • HTTP/2 çerçeveledi ama taşımayı değiştirmedi: iç içe geçen çerçeveler uygulama katmanını kurtarıyor, kaybolan tek paket yine hepsini durduruyor.
  • QUIC güvenilirliği akış başına ayırdı: kayıp yalnızca o pakette verisi olan akışları bekletiyor, el sıkışma ise tek tura iniyor.

Aynı yolun üstünde artık birden çok araba var ve hiçbiri ötekini beklemiyor. İsteğin yola çıktı; peki gerçekten adres çubuğuna yazdığın makineye mi gidiyor?

15. bölüme devam et: İçerik Dağıtım Ağları