Bölüm 14:HTTP/2'den HTTP/3'e
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
- Giriş
- Adres Çubuğundan İsteğe
- Paket Anahtarlamalı Yol
- İsimden Adrese
- Önbellek, Tazelik ve Güven
- Port Numarası ve Soket
- Bağlantısız Taşıma: UDP
- Üç Adımlı El Sıkışma
- Simetrik Şifreleme
- Açık Anahtarlı Kriptografi
- TLS El Sıkışması
- Sertifika Zinciri
- HTTP Mesajının Anatomisi
- Durum, Çerez ve Oturum
- HTTP/2'den HTTP/3'e
- İçerik Dağıtım Ağları
- Diğer Uygulama Protokolleri
- Katmanlı Mimari
- Kapsülleme ve Başlıklar
- Paket Yakalama ve Çözümleme
- Ağ Arayüz Kartı
- Fiziksel Adresleme
- Hata Sezme ve CRC
- Adres Çözümleme: ARP
- Anahtarlama ve Ortam Erişimi
- Ağ Topolojileri
- Sanal Yerel Ağlar
- Kablosuz Erişim: 802.11
- Radyo Kanalı ve Kapasite
- Kablosuz Ağ Güvenliği
- Hücresel Ağlar
- Spektrum ve Kuşaklar
- Hareketlilik ve Aktarma
- Adres Kiralama: DHCP
- Adresin Anatomisi
- Adres Tükenmesi ve IPv6
- Yönlendirici Mimarisi
- Kuyruk Yönetimi
- Cihaz Yapılandırma
- Yönlendirme Protokolleri
- Kontrol Düzlemi ve SDN
- Ağ İzleme ve Yönetimi
- Alanlar Arası Yönlendirme
- Adres Çevirisi
- Ara Kutular
- Tünelleme ve IPsec
- Güvenlik Duvarları
- Operatör ve Metro Ağları
- Bitten Sinyale
- Çoklama Teknikleri
- İletim Ortamları
- Kablo Sonlandırma
- Fiber Optik İletim
- Bakır Erişim Ağı: DSL
- Pasif Optik Ağlar
- Kablo Erişim Ağları
- Denizaltı Kabloları
- Veri Merkezi Ağları
- Kapsülleme Çözme
- Güvenilir Aktarım
- Tıkanıklık Denetimi
- Trafik Çözümleme
- Gecikme Bütçesi
- 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 bekledi2. 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:
B hazır olsa bile A bitene kadar bekliyor.- 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.
Bhazır olsa bileAbitene kadar bekliyor. - 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 —
BartıkAyı beklemiyor. - 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 bileAveBde duruyor. Taşıma katmanı akışların varlığından habersiz. - 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;
AileBakmayı 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 0x07Gö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=86400Sonra 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ı