Dlaczego potrzebny był nowy HTTP? Ograniczenia HTTP/1.1
Przez prawie dwie dekady protokół HTTP/1.1 stanowił podstawę rozwoju World Wide Web. Wprowadził on połączenia trwałe, pozwalając na wysyłanie wielu żądań i odpowiedzi przez jedno połączenie TCP, co stanowiło istotną zmianę w stosunku do jego poprzednika, HTTP/1.0. Jednak w miarę jak strony internetowe ewoluowały z prostych dokumentów tekstowych w złożone, bogate w zasoby aplikacje z dziesiątkami skryptów, arkuszy stylów i obrazów, ograniczenia HTTP/1.1 zaczęły ograniczać wydajność.
HTTP/1.0 pozwalał na jedno oczekujące żądanie w ramach połączenia. HTTP/1.1 dodał pipelining, lecz klienci często otwierali kilka połączeń, aby zwiększyć współbieżność. Połączenia te konkurowały o mechanizmy kontroli przeciążenia i zasoby gniazd.
Jednym z problemów HTTP/1.1 jest tak zwane . Trwałe połączenie nie zapewniało multipleksowania. Pipelining pozwalał wysłać kilka żądań przed otrzymaniem odpowiedzi, lecz odpowiedzi musiały zachować kolejność. Powolne żądanie, na przykład o duży obraz lub wolno działający punkt końcowy API, mogło więc opóźnić kolejne odpowiedzi i renderowanie strony.
Stosowano obejścia tego problemu, takie jak używanie wielu połączeń TCP do jednej nazwy hosta, fragmentacja domen czy łączenie plików. Te techniki dodawały złożoności i narzutu. Z czasem stało się jasne, że protokół wymaga przeprojektowania, aby sprostać wymaganiom nowoczesnej sieci. To doprowadziło do rozwoju HTTP/2, który w dużej mierze opierał się na eksperymentalnym protokole SPDY firmy Google i został oficjalnie ustandaryzowany w 2015 roku.
Koncepcja podstawowa 1: warstwa ramek binarnych
Jedną z najważniejszych zmian wprowadzonych w HTTP/2 jest . HTTP/1.1 używał tekstowej składni komunikatów. Linie żądań i pola były zapisywane jako tekst oddzielany znakami nowej linii, natomiast treść mogła zawierać dowolne oktety. Taki format ułatwiał debugowanie, lecz zwiększał złożoność parsowania.
HTTP/2 dzieli komunikację na mniejsze jednostki zwane ramkami i koduje je binarnie. Ta zmiana ułatwia parsowanie maszynowe oraz obsługę współbieżnych strumieni:
- Wydajność: format binarny ogranicza narzut parsowania.
- Jednoznaczność: struktura ramki ogranicza różnice interpretacji składni tekstowej.
- Obsługa strumieni: ramki można przeplatać na jednym połączeniu.
Struktura ramki HTTP/2
Każda ramka w HTTP/2 ma ściśle zdefiniowaną strukturę. Składa się ze stałego 9-bajtowego nagłówka, po którym następuje ładunek o zmiennej długości. Nagłówek zawiera długość ładunku, typ, flagi, bit zarezerwowany i identyfikator strumienia. Odbiorca ogłasza maksymalny rozmiar ładunku przezSETTINGS_MAX_FRAME_SIZE.
- Długość (
24bity) - Wskazuje długość ładunku ramki w bajtach.
- Typ (
8bitów) - Definiuje typ ramki i sposób interpretacji ładunku.
- Flagi (
8bitów) - Zestaw flag zależnych od typu ramki.
- Pole zarezerwowane (
1bit) - Bit bez zdefiniowanej semantyki, który nadawca pozostawia wyzerowany.
- Identyfikator strumienia (
31bitów) - Określa, do którego strumienia należy dana ramka.
- Ładunek ramki (zmienny)
- Właściwa zawartość ramki, zależna od jej typu.
Popularne typy ramek
Najczęściej spotkasz między innymi:
- DATA: ciało wiadomości.
- HEADERS: nagłówki HTTP.
- PRIORITY: starsza ramka priorytetu strumienia. Jej schemat sygnalizacji jest zdeprecjonowany.
- SETTINGS: konfiguracja połączenia.
- PUSH_PROMISE: zapowiedź server push.
Koncepcja podstawowa 2: strumienie i multipleksowanie
Warstwa ramek binarnych umożliwia podstawową funkcję HTTP/2: . Funkcja usuwa blokowanie na poziomie wymiany HTTP/1.1, lecz utrata pakietu TCP nadal może wstrzymać wszystkie strumienie.
Strumienie: wirtualne kanały
W ramach jednego połączenia HTTP/2 klient i serwer mogą ustanowić wiele niezależnych, dwukierunkowych sekwencji ramek zwanych strumieniami. Jedno połączenie TCP przenosi ramki wszystkich strumieni, które mogą dotyczyć HTML, arkusza stylów i obrazu oraz być obsługiwane współbieżnie.
- Każdy strumień ma własny identyfikator.
- Żądanie i odpowiedź odbywają się w tym samym strumieniu.
- Strumienie klienta mają identyfikatory nieparzyste, a serwera parzyste.
- Jedno połączenie może przenosić setki lub tysiące strumieni naraz.
Strumień 0 jest zarezerwowany dla ramek dotyczących całego połączenia, takich jakSETTINGS iGOAWAY. Ramki DATA podlegają kontroli przepływu dla strumienia i dla całego połączenia, dlatego nadawca musi respektować okna ogłoszone przez odbiorcę.
Jak multipleksowanie usuwa blokowanie HOL
Ponieważ komunikacja jest podzielona na małe ramki, serwer może przeplatać odpowiedzi z wielu strumieni na tym samym połączeniu. Na poziomie HTTP opóźnienie jednego dużego zasobu nie blokuje innych odpowiedzi, więc CSS, JS i małe zasoby mogą dotrzeć szybciej. Utrata pakietu TCP nadal wstrzymuje dostarczanie ramek wszystkich strumieni do czasu retransmisji.
W rezultacie zmniejsza się potrzeba stosowania obejść, takich jak wielokrotne połączenia do jednego hosta. Efektem może być szybsze ładowanie stron, mniejsze zużycie zasobów i lepsze wykorzystanie sieci.
Koncepcja podstawowa 3: wypychanie serwerowe (Server Push)
W klasycznym HTTP to klient inicjuje komunikację. Pobiera HTML, analizuje go i dopiero potem żąda CSS oraz JavaScriptu. To tworzy kaskadę żądań i opóźnień.
HTTP/2 dodał mechanizm , w którym serwer może przewidzieć potrzebne zasoby i wysłać je z wyprzedzeniem.
Jak działa Server Push
- Żądanie klienta: przeglądarka prosi o
index.html. - Analiza serwera: serwer wie, że zaraz potrzebne będą też
style.cssiapp.js. - PUSH_PROMISE: serwer zapowiada i wysyła te zasoby, zanim klient o nie poprosi.
- Oryginalna odpowiedź: klient nadal dostaje żądany HTML.
- Buforowanie po stronie klienta: przeglądarka może użyć zasobów od razu podczas renderu.
Server Push może skrócić czas ładowania, lecz wymaga kontroli stanu pamięci podręcznej klienta. Wypychanie zasobów już obecnych w cache zwiększa zużycie sieci, dlatego wdrożenia często korzystają z podpowiedzi preload.
Wypychana odpowiedź jest związana z syntetycznym żądaniem i podlega semantyce odpowiedzi HTTP, w tym regułom cache. Klient może anulować wypychanie, gdy zasób nie jest potrzebny.
Inne usprawnienia w HTTP/2
Poza trzema głównymi koncepcjami HTTP/2 wprowadził jeszcze kilka usprawnień wpływających na wydajność.
- Kompresja nagłówków (HPACK)
- HPACK używa tablicy statycznej i dynamicznej, aby zwięźlej kodować powtarzające się pola przy kolejnych żądaniach. Stan kompresji jest utrzymywany dla połączenia, a blok pól może obejmować ramki HEADERS i CONTINUATION.
- Priorytetyzacja strumieni
- Klient może zasugerować, które zasoby są ważniejsze dla renderowania. Starsze drzewo priorytetów jest zdeprecjonowane, a sygnał priorytetu pozostaje sugestią bez gwarancji kolejności dostarczenia.
- Jedno połączenie na domenę
- Multipleksowanie zmniejsza potrzebę otwierania wielu połączeń TCP do tego samego hosta. Klient może jednak utworzyć więcej niż jedno połączenie zależnie od konfiguracji, certyfikatu i warunków sieciowych.
- Domyślnie bezpieczny w praktyce
- Specyfikacja dopuszcza HTTP/2 bez TLS, lecz dla URI
httpsprotokół jest negocjowany przez TLS z identyfikatorem ALPNh2.