Wprowadzenie do HTTP/2

HTTP/2 sprawi, że nasze aplikacje będą szybsze, prostsze i bardziej niezawodne – co jest rzadką kombinacją – dzięki temu możemy cofnąć wiele obejścia problemów z HTTP/1.1, które były stosowane wcześniej w naszych aplikacjach, i rozwiązywać te problemy bezpośrednio w warstwie transportu. Co więcej, otwiera to też szereg zupełnie nowych możliwości optymalizacji aplikacji i zwiększania wydajności.

Głównymi celami HTTP/2 są skrócenie czasu oczekiwania przez włączenie multipleksowania żądań i odpowiedzi, zminimalizowanie obciążenia wynikającego z protokołu dzięki wydajnej kompresji pól nagłówka HTTP oraz dodanie obsługi ustalania priorytetów żądań i wypychania z serwera. Aby spełnić te wymagania, przygotowaliśmy wiele innych ulepszeń protokołów, takich jak kontrola przepływu, obsługa błędów i mechanizmy uaktualniania. Są to jednak najważniejsze funkcje, które każdy programista stron internetowych powinien znać i wykorzystywać w swoich aplikacjach.

HTTP/2 w żaden sposób nie zmienia semantyki aplikacji HTTP/2. Wszystkie podstawowe pojęcia, takie jak metody HTTP, kody stanu, identyfikatory URI i pola nagłówka, pozostają niezmienione. Zamiast tego HTTP/2 modyfikuje sposób formatowania (ramki) danych i przenosi je między klientem a serwerem. Oba te rozwiązania zarządzają całym procesem i ukrywają złożoność przed naszymi aplikacjami w nowej warstwie ramki. W rezultacie wszystkie istniejące aplikacje można dostarczać bez modyfikacji.

Dlaczego nie HTTP/1.2?

Aby osiągnąć cele związane z wydajnością wyznaczone przez grupę roboczą HTTP/2, wprowadzamy nową warstwę ramek kadrowych, która nie jest zgodna wstecznie z poprzednimi serwerami i klientami HTTP/1.x. Dlatego wersja głównego protokołu jest zwiększona do HTTP/2.

Jednak jeśli nie wdrażasz serwera WWW (lub klienta niestandardowego) przez pracę z nieprzetworzonymi gniazdami TCP, nie zauważycie żadnej różnicy: wszystkie nowe, niskopoziomowe kadrowanie są wykonywane w Twoim imieniu przez klienta i serwer. Jedynymi zaobserwowanymi różnicami będzie większa wydajność i dostępność nowych możliwości, takich jak ustalanie priorytetów żądań, kontrola przepływu i wypychanie z serwera.

Krótka historia SPDY i HTTP/2

SPDY to protokół eksperymentalny, opracowany przez Google i ogłoszony w połowie 2009 r., którego głównym celem było skrócenie czasu oczekiwania na wczytanie stron internetowych przez rozwiązanie niektórych znanych ograniczeń wydajności HTTP/1.1. W szczególności określono następujące cele projektu:

  • Zmniejsz o 50% czas wczytywania strony (PLT).
  • Dzięki temu nie musisz wprowadzać zmian w treściach przez autorów witryn.
  • Zminimalizować złożoność wdrażania i uniknąć zmian w infrastrukturze sieciowej.
  • Opracuj ten nowy protokół we współpracy ze społecznością open source.
  • Zbierz rzeczywiste dane dotyczące wydajności, aby zweryfikować protokół eksperymentalny.

Niedługo po ogłoszeniu pierwszego ogłoszenia Mike Belshe i Robert Peon, inżynierzy oprogramowania w Google, opublikowali swoje pierwsze wyniki, dokumentację i kod źródłowy dotyczący eksperymentalnej implementacji nowego protokołu SPDY:

Do tej pory przetestowaliśmy SPDY tylko w warunkach laboratoryjnych. Wstępne wyniki są bardzo zachęcające: gdy pobierzemy 25 najpopularniejszych witryn za pomocą symulowanych połączeń z siecią domową, odnotowujemy znaczną poprawę wydajności – strony wczytują się nawet o 55% szybciej. (blog Chromium)

W 2012 r. zaczęliśmy korzystać z nowego, eksperymentalnego protokołu, który był obsługiwany w Chrome, Firefoksie i Operze. Szybko rosnąca liczba witryn, zarówno dużych (np. Google, Twitter, Facebook), jak i małych, wdrażała SPDY w swojej infrastrukturze. Dzięki coraz większej liczbie użytkowników SPDY stało się standardem w branży.

Zaobserwując ten trend, grupa robocza HTTP (HTTP-WG) zainicjowała nowe starania, aby wykorzystać wnioski z protokołu SPDY, rozbudowywać i ulepszać je oraz dostarczać oficjalny standard „HTTP/2”. Opracowano nowy statut, otwarto zaproszenie na propozycje HTTP/2 i po wielu dyskusji w grupie roboczej specyfikacja SPDY została przyjęta jako punkt wyjścia dla nowego protokołu HTTP/2.

Przez kilka kolejnych lat SPDY i HTTP/2 rozwijały się równolegle, a SPDY funkcjonowało jako gałąź eksperymentalna, która służyła do testowania nowych funkcji i propozycji dla standardu HTTP/2. To, co wygląda dobrze na papierze, może nie sprawdzić się w praktyce i na odwrót. SPDY oferowało możliwość przetestowania i oceny każdej oferty przed jej uwzględnieniem w standardzie HTTP/2. Ostatecznie ten proces trwał 3 lata i w efekcie powstało ponad kilkanaście wersji roboczych:

  • Marzec 2012 r.: generowanie ofert pakietowych dla HTTP/2
  • Listopad 2012 r.: pierwsza wersja robocza protokołu HTTP/2 (na podstawie SPDY)
  • Sierpień 2014 roku: opublikowanie wersji roboczej 17 protokołu HTTP/2 i HPACK w wersji roboczej 12
  • Sierpień 2014 r.: ostatnie wywołanie HTTP/2 przez grupę roboczą
  • Luty 2015 roku: zatwierdzone przez IESG wersje robocze HTTP/2 i HPACK
  • Maj 2015 r.: opublikowano dokumenty RFC 7540 (HTTP/2) i RFC 7541 (HPACK)

Na początku 2015 r. IEG przeanalizował i zatwierdził nowy standard HTTP/2 dla publikacji. Niedługo potem zespół Google Chrome ogłosił harmonogram wycofania rozszerzeń SPDY i NPN dla protokołu TLS:

Główne zmiany w HTTP/2 w stosunku do HTTP/1.1 związane z większą wydajnością. Niektóre kluczowe funkcje, takie jak multipleksowanie, kompresja nagłówków, ustalanie priorytetów i negocjowanie protokołu, rozwinęły się z pracy wykonanej wcześniej w otwartym, ale niestandardowym protokole o nazwie SPDY. Chrome obsługuje SPDY od wersji 6, ale większość korzyści jest dostępna dzięki HTTP/2, więc czas się pożegnać. Na początku 2016 r. planujemy wycofać obsługę SPDY i jednocześnie usunąć obsługę rozszerzenia TLS o nazwie NPN na rzecz ALPN w Chrome. Zdecydowanie zachęcamy deweloperów serwerów do przejścia na HTTP/2 i ALPN.

Cieszymy się, że możemy uczestniczyć w procesie tworzenia standardów otwartych, który doprowadził do protokołu HTTP/2. Mamy nadzieję, że ze względu na duże zaangażowanie branży w standaryzację i wdrażanie tego rozwiązania uda nam się osiągnąć powszechne zastosowanie. (Blog Chromium)

Koewolucja SPDY i HTTP/2 dla programistów serwerów, przeglądarek i witryn, w których mógł działać w praktyce podczas opracowywania nowego protokołu. W rezultacie standard HTTP/2 należy do najlepszych i najbardziej szeroko testowanych standardów. Do czasu zatwierdzenia przez IESG protokół HTTP/2 miał już do czynienia z dziesiątkami dokładnie przetestowanych i gotowych do wykorzystania w środowisku produkcyjnym implementacji klientów i serwerów. Zaledwie kilka tygodni po zatwierdzeniu ostatecznego protokołu wielu użytkowników korzystało już z jego zalet, ponieważ kilka popularnych przeglądarek (i wiele witryn) wdrożyło pełną obsługę HTTP/2.

Cele projektowe i techniczne

Wcześniejsze wersje protokołu HTTP zostały celowo zaprojektowane z myślą o uproszczeniu implementacji: HTTP/0.9 był jednowierszowym protokołem do wczytywania stron internetowych. HTTP/1.0 udokumentował popularne rozszerzenia HTTP/0.9 w standardzie informacyjnym, a HTTP/1.1 wprowadził oficjalny standard IETF. Zobacz Krótka historia HTTP. W związku z tym protokół HTTP/0.9-1.x dostarczał dokładnie to, co zakładano: HTTP to jeden z najpopularniejszych protokołów aplikacji w internecie.

Niestety prostota wdrożenia wiąże się również z kosztem wydajności aplikacji: klienty HTTP/1.x muszą używać wielu połączeń, aby uzyskać równoczesność i zmniejszyć czas oczekiwania; HTTP/1.x nie kompresuje nagłówków żądań i odpowiedzi, powodując niepotrzebny ruch sieciowy; HTTP/1.x nie umożliwia efektywnego ustalania priorytetów zasobów, co skutkuje słabym wykorzystaniem bazowego połączenia TCP itd.

Ograniczenia te nie były śmiertelne, ale w miarę jak zakres, złożoność i znaczenie aplikacji internetowych wciąż rosło w codziennym życiu, nakładały one coraz większe obciążenie zarówno na programistów, jak i użytkowników internetu. Właśnie dlatego protokół HTTP/2 został zaprojektowany tak, aby:

HTTP/2 umożliwia wydajniejsze wykorzystanie zasobów sieciowych i zmniejszenie czasu oczekiwania dzięki wprowadzeniu kompresji pola nagłówka i umożliwieniu wielu równoczesnych wymian wiadomości w tym samym połączeniu. W szczególności umożliwia przeplatanie wiadomości z żądaniami i odpowiedziami w tym samym połączeniu oraz wydajne kodowanie pól nagłówka HTTP. Umożliwia też ustalanie priorytetów żądań, dzięki czemu ważniejsze żądania są realizowane szybciej, co jeszcze bardziej zwiększa wydajność.

Powstały w ten sposób protokół jest bardziej przyjazny dla sieci, ponieważ mniej połączeń TCP można wykorzystać niż HTTP/1.x. Oznacza to mniejszą konkurencję z innymi przepływami danych i dłuższe połączenia, co z kolei pozwala lepiej wykorzystać dostępną przepustowość sieci. HTTP/2 umożliwia też bardziej wydajne przetwarzanie wiadomości dzięki umieszczaniu ich w ramkach w postaci binarnej. (Hypertext Transfer Protocol w wersji 2, wersja robocza 17)

Warto zauważyć, że protokół HTTP/2 rozszerza, a nie zastępuje poprzednie standardy HTTP. Semantyka aplikacji HTTP jest taka sama, nie wprowadziliśmy żadnych zmian w oferowanych funkcjach ani podstawowych pojęciach, takich jak metody HTTP, kody stanu, identyfikatory URI czy pola nagłówka. Te zmiany wyraźnie wykraczały poza zakres działań HTTP/2. Ogólny interfejs API pozostaje bez zmian, ale ważne jest, aby zrozumieć, w jaki sposób zmiany na niskim poziomie odnoszą się do ograniczenia wydajności poprzednich protokołów. Omówmy pokrótce warstwę z ramkami binarnymi i jej funkcje.

Warstwa kadrowania binarnego

Podstawą wszystkich funkcji zwiększających wydajność HTTP/2 jest nowa warstwa ramek binarnego, która określa, w jaki sposób wiadomości HTTP są zabezpieczane i przesyłane między klientem a serwerem.

Warstwa binarnego kadrowania HTTP/2

„Warstwa” odnosi się do wyboru projektu w celu wprowadzenia nowego zoptymalizowanego mechanizmu kodowania między interfejsem gniazda a wyższym interfejsem API HTTP dostępnym dla naszych aplikacji. semantyka HTTP, np. czasowniki, metody i nagłówki, nie wpływa na sposób kodowania w ruchu. W odróżnieniu od protokołu HTTP/1.x jawnego tekstu rozdzielanego znakami nowego wiersza cała komunikacja w protokole HTTP/2 jest dzielona na mniejsze wiadomości i ramki. Każda z nich jest zakodowana w formacie binarnym.

W związku z tym klient i serwer muszą używać nowego mechanizmu kodowania binarnego, aby porozumiewać się ze sobą – klient HTTP/1.x nie będzie w stanie zrozumieć serwera, który obsługuje tylko HTTP/2, i odwrotnie. Na szczęście nasze aplikacje cały czas nie są świadome tych zmian, ponieważ klient i serwer wykonują wszystkie niezbędne operacje kadrowania w naszym imieniu.

Strumienie, wiadomości i ramki

Wprowadzenie nowego mechanizmu binarnego kadrowania zmienia sposób wymiany danych między klientem a serwerem. Aby opisać ten proces, zapoznaj się z terminologią HTTP/2:

  • Strumień: dwukierunkowy przepływ bajtów w ramach nawiązanego połączenia, który może zawierać co najmniej 1 komunikat.
  • Wiadomość: pełna sekwencja ramek, które mapują się na logiczne żądanie lub wiadomość z odpowiedzią.
  • Frame: najmniejsza jednostka komunikacji w HTTP/2. Każda z nich zawiera nagłówek ramki, który co najmniej identyfikuje strumień, do którego należy ramka.

Podsumowanie relacji między tymi terminami wygląda tak:

  • Cała komunikacja jest realizowana za pomocą jednego połączenia TCP, które może przesyłać dowolną liczbę strumieni dwukierunkowych.
  • Każdy strumień ma unikalny identyfikator i opcjonalne informacje o priorytecie, które są używane do przesyłania komunikatów dwukierunkowych.
  • Każda wiadomość to logiczny komunikat HTTP, taki jak żądanie lub odpowiedź, która składa się z co najmniej 1 ramki.
  • Ramka to najmniejsza jednostka komunikacji, która zawiera określony typ danych, np. nagłówków HTTP, ładunek wiadomości itd. Ramki z różnych strumieni mogą być przeplatane, a następnie ponownie łączone za pomocą identyfikatora strumienia umieszczonego w nagłówku każdej ramki.

Strumienie HTTP/2, wiadomości i ramki

Krótko mówiąc, protokół HTTP/2 dzieli komunikację za pomocą protokołu HTTP na wymianę ramek zakodowanych binarnie, które są następnie mapowane na wiadomości należące do określonego strumienia, z których wszystkie są multipleksowane w ramach pojedynczego połączenia TCP. Jest to podstawa, która pozwala włączyć wszystkie inne funkcje i optymalizacje wydajności zapewniane przez protokół HTTP/2.

Multipleksowanie żądań i odpowiedzi

W przypadku HTTP/1.x, jeśli klient chce wysyłać wiele równoległych żądań, by zwiększyć wydajność, konieczne jest użycie wielu połączeń TCP (zobacz artykuł na temat używania wielu połączeń TCP). Jest to bezpośrednia konsekwencja modelu dostarczania HTTP/1.x, który zapewnia, że w danym momencie na każde połączenie może zostać dostarczona tylko jedna odpowiedź (kolejkowanie odpowiedzi). Co gorsza, powoduje to również blokowanie głównej linii i nieefektywne wykorzystanie bazowego połączenia TCP.

Nowa warstwa binarnego kadrowania w HTTP/2 eliminuje te ograniczenia i umożliwia pełne multipleksowanie żądań i odpowiedzi, umożliwiając klientowi i serwerowi podział wiadomości HTTP na niezależne ramki, ich przeplatanie, a potem ponowne złączenie tych elementów po drugiej stronie.

Multipleksowanie żądań i odpowiedzi HTTP/2 w ramach połączenia współdzielonego

Zrzut zawiera wiele strumieni podczas lotu w ramach tego samego połączenia. Klient przesyła do serwera ramkę DATA (strumień 5), podczas gdy serwer przesyła do klienta przeplataną sekwencję ramek w przypadku strumieni 1 i 3. W wyniku tego istnieją 3 równoległe strumienie.

Możliwość podzielenia wiadomości HTTP na niezależne ramki, przeplatania ich i ponownego zmontowania z drugiej strony jest najważniejszym ulepszeniem protokołu HTTP/2. W rzeczywistości ta technologia ma ogromny wpływ na wydajność wszystkich technologii internetowych, co pozwala nam:

  • Spleć wiele żądań równolegle, nie blokując jednego z nich.
  • Spleć kilka odpowiedzi równolegle, nie blokując żadnej z nich.
  • Za pomocą jednego połączenia możesz dostarczać jednocześnie wiele żądań i odpowiedzi.
  • Usuń zbędne obejścia związane z protokołem HTTP/1.x (zobacz artykuł na temat optymalizacji pod kątem HTTP/1.x, takich jak połączone pliki, sprite'y obrazów i fragmentacja domeny).
  • Krótszy czas wczytywania stron dzięki eliminowaniu zbędnych opóźnień i poprawie wykorzystania dostępnej przepustowości sieci.
  • I wiele więcej...

Nowa warstwa binarnego kadrowania w HTTP/2 rozwiązuje problem związany z blokowaniem początku wiersza występujący w protokole HTTP/1.x i eliminuje potrzebę powtarzania wielu połączeń w celu umożliwienia równoległego przetwarzania oraz dostarczania żądań i odpowiedzi. W efekcie wdrożenie naszych aplikacji jest szybsze, prostsze i tańsze.

Priorytety transmisji

Gdy wiadomość HTTP można podzielić na wiele osobnych ramek i zezwolić na multipleksowanie ramek z wielu strumieni, kolejność przeplatania i dostarczenia ramek z wielu strumieni staje się kluczowym czynnikiem branym pod uwagę w kwestii wydajności. Aby to ułatwić, standard HTTP/2 zezwala każdemu strumieniowi na powiązanie wagi i zależności:

  • Każdemu strumieniowi można przypisać wagę całkowitą z zakresu od 1 do 256.
  • Każdy strumień może być uzależniony od innego.

Kombinacja zależności i wag strumienia pozwala klientowi utworzyć i przekazać „drzewo priorytetów”, które określa, jak preferowane jest otrzymywanie odpowiedzi. Z kolei na podstawie tych informacji serwer może nadawać priorytet przetwarzaniu strumieni, kontrolując przydział procesora, pamięci i innych zasobów. Gdy dane odpowiedzi będą dostępne, przydzielanie przepustowości w celu zapewnienia optymalnego dostarczania klientom odpowiedzi o wysokim priorytecie.

Zależności i wagi strumienia HTTP/2

Zależność strumienia w protokole HTTP/2 jest deklarowana, podając unikalny identyfikator innego strumienia jako jego element nadrzędny. W przypadku pominięcia identyfikatora strumień deklaruje, że jest zależny od „strumienia głównego”. Zadeklarowanie zależności strumienia wskazuje, że w miarę możliwości do strumienia nadrzędnego należy przydzielać zasoby przed zależnościami. Inaczej mówiąc „Prosimy o przetworzenie i dostarczenie odpowiedzi D przed odpowiedzią C”.

Strumieniom, które mają ten sam element nadrzędny (czyli strumienie równorzędne), należy przydzielić zasoby proporcjonalnie do swojej wagi. Jeśli np. strumień A ma wagę 12, a jedno z nich B ma wagę 4, to aby określić odsetek zasobów, jaki powinien otrzymać każdy z tych strumieni:

  1. Zsumuj wszystkie wagi: 4 + 12 = 16
  2. Podziel wagę każdego strumienia przez łączną wagę: A = 12/16, B = 4/16

Oznacza to, że strumień A powinien otrzymywać 3 czwarte, a strumień B – 1/4 dostępnych zasobów. Strumień B – 1/3 przydzielonych do strumienia A. Przyjrzyjmy się kilku praktycznym przykładom na grafice powyżej. Od lewej do prawej:

  1. Żaden strumień A ani B nie określa zależności nadrzędnej i nie mówi, że jest zależny od niejawnego „strumienia głównego”. Waga A to 12, a waga B to 4. Dlatego, na podstawie proporcjonalnych wag: strumień B powinien otrzymać jedną trzecią zasobów przydzielonych do strumienia A.
  2. Strumień D jest zależny od strumienia głównego; C – od strumienia D. W związku z tym podmiot D powinien otrzymać pełny przydział zasobów przed C. Wagi są nieistotne, ponieważ zależność C oznacza silniejszą preferencję.
  3. Strumień D powinien otrzymać pełny przydział zasobów wcześniej niż zasób C; strumień C powinien otrzymać pełny przydział zasobów wcześniej niż w przypadku strumienia A i B; strumień B powinien otrzymać jedną trzecią zasobów przydzielonych do strumienia A.
  4. Strumień D powinien otrzymać pełny przydział zasobów wcześniej niż E i C. Kanały E i C powinny otrzymać jednakowy przydział przed A i B. Źródła A i B powinny otrzymać proporcjonalny przydział na podstawie ich wagi.

Jak widać na powyższych przykładach, połączenie zależności i wag strumieni zapewnia ekspresyjny język określania priorytetów zasobów, co jest cechą krytyczną dla zwiększenia wydajności przeglądania, gdy mamy wiele typów zasobów o różnych zależnościach i wagach. Co więcej, protokół HTTP/2 umożliwia też klientowi aktualizowanie tych ustawień w dowolnym momencie, co umożliwia dalsze optymalizacje w przeglądarce. Inaczej mówiąc, możemy zmieniać zależności i zmieniać alokację wagi w zależności od interakcji użytkownika i innych sygnałów.

Jedno połączenie na punkt początkowy

Po wdrożeniu nowego mechanizmu binarnego kadrowania HTTP/2 nie potrzebuje już wielokrotnych połączeń TCP z równoległymi strumieniami multipleksu. Każdy strumień jest dzielony na wiele ramek, które można przeplatać i nadawać im priorytety. W rezultacie wszystkie połączenia HTTP/2 są trwałe i dla każdego źródła wymagane jest tylko jedno połączenie, co zapewnia wiele korzyści w zakresie wydajności.

Zarówno w przypadku SPDY, jak i HTTP/2 wyjątkową funkcją jest arbitralne multipleksowanie w jednym kanale, w którym ruch jest kontrolowany. To niesamowite, jak ważne jest to rozwiązanie i jak dobrze działa. Podoba mi się również odsetek utworzonych połączeń, które wykonują tylko jedną transakcję HTTP (dzięki czemu transakcja ponosi wszystkie koszty). W przypadku HTTP/1 74% naszych aktywnych połączeń realizuje tylko jedną transakcję – trwałe połączenia nie są tak przydatne, jak mogłoby się wydawać. Jednak w HTTP/2 ta liczba spada do 25%. To ogromna korzyść związana z ograniczeniem kosztów. (HTTP/2 jest w przeglądarce Firefox, PatrickMcManus)

Większość transferów HTTP jest krótka i imponująca, podczas gdy port TCP jest zoptymalizowany pod kątem długotrwałych masowych transferów danych. Dzięki ponownemu użyciu tego samego połączenia HTTP/2 może efektywniej korzystać z każdego połączenia TCP i znacznie zmniejszyć ogólne obciążenia związane z protokołem. Ponadto mniejsza liczba połączeń zmniejsza ilość pamięci i objętości przetwarzania na całej ścieżce połączenia (czyli na potrzeby klienta, serwerów pośrednich i serwerów źródłowych). Zmniejsza to ogólne koszty operacyjne oraz polepsza wykorzystanie i pojemność sieci. W rezultacie przejście na HTTP/2 powinno nie tylko zmniejszyć opóźnienia w sieci, ale także zwiększyć przepustowość i obniżyć koszty operacyjne.

Kontrola przepływu

Kontrola przepływu to mechanizm zapobiegający przeciążeniu odbiorcy danymi, których nie chce lub może przetworzyć. Odbiorca może być zajęty, nadmiernie obciążany lub może być skłonny przydzielić stałą ilość zasobów do konkretnego strumienia. Na przykład klient mógł zażądać dużego strumienia wideo o wysokim priorytecie, ale użytkownik wstrzymał odtwarzanie filmu i chce teraz wstrzymać lub ograniczyć przesyłanie strumieniowe z serwera, aby uniknąć pobierania i buforowania niepotrzebnych danych. Z kolei serwer proxy może mieć szybkie i wolne połączenia z góry i w górę, a także w podobny sposób regulować szybkość dostarczania danych przez serwer proxy tak, aby odpowiadała prędkości przesyłania danych z tego serwera, co pozwala kontrolować wykorzystanie zasobów itd.

Czy te wymagania przypominają o kontroli przepływu TCP? Problemy powinny być takie same, ponieważ problem jest w rzeczywistości identyczny (patrz Kontrola przepływu). Ponieważ jednak strumienie HTTP/2 są multipleksowane w ramach pojedynczego połączenia TCP, kontrola przepływu TCP nie jest wystarczająco szczegółowa i nie zapewnia interfejsów API na poziomie aplikacji niezbędnych do regulacji dostarczania poszczególnych strumieni. Aby rozwiązać ten problem, protokół HTTP/2 udostępnia zestaw prostych elementów składowych, które umożliwiają klientowi i serwerowi wdrożenie własnej kontroli przepływu na poziomie strumienia i połączenia:

  • Sterowanie przepływem jest kierunkowe. Każdy odbiornik może ustawić dowolny rozmiar okna dla każdego strumienia i całego połączenia.
  • Kontrola przepływu pracy jest oparta na kredytach. Każdy odbiornik rozgłasza swoje początkowe połączenie i okno sterowania przepływem strumienia (w bajtach), które jest zmniejszane za każdym razem, gdy nadawca wysyła ramkę DATA, a zwiększa się za pomocą ramki WINDOW_UPDATE wysyłanej przez odbiorcę.
  • Nie można wyłączyć kontroli przepływu. Po nawiązaniu połączenia HTTP/2 ramki SETTINGS klienta i serwera ustawiają rozmiary okien sterowania przepływem w obu kierunkach. Domyślna wartość okna sterowania przepływem to 65 535 bajtów, ale odbiorca może ustawić duży maksymalny rozmiar okna (2^31-1 B) i utrzymać go, wysyłając ramkę WINDOW_UPDATE za każdym razem, gdy odbieramy jakiekolwiek dane.
  • Kontrola przepływu pracy polega na każdym przeskoku, a nie o całości. Oznacza to, że pośrednik może za jego pomocą kontrolować wykorzystanie zasobów i wdrażać mechanizmy przydzielania zasobów na podstawie własnych kryteriów i metod heurystycznych.

HTTP/2 nie określa żadnego konkretnego algorytmu do implementowania kontroli przepływu. Zamiast tego udostępnia proste elementy składowe i opóźnia ich wdrożenie klientowi i serwerowi, które mogą go użyć do wdrożenia niestandardowych strategii regulujących wykorzystanie i przydzielanie zasobów, a także wdrożenie nowych możliwości dostarczania, które mogą poprawić zarówno rzeczywistą, jak i widoczną wydajność (patrz Szybkość, wydajność i postrzeganie przez człowieka) naszych aplikacji internetowych.

Na przykład sterowanie przepływem w warstwie aplikacji umożliwia przeglądarce pobranie tylko części konkretnego zasobu, wstrzymanie pobierania przez zmniejszenie okna sterowania przepływem strumienia do zera, a potem wznowienie go później. Inaczej mówiąc, przeglądarka może pobrać podgląd lub pierwsze skanowanie obrazu, wyświetlić go i kontynuować inne pobieranie o wysokim priorytecie, a także wznowić pobieranie, gdy zakończy się wczytywanie większej liczby zasobów krytycznych.

Push z serwera

Kolejną nową, potężną funkcją HTTP/2 jest możliwość wysyłania przez serwer wielu odpowiedzi na pojedyncze żądanie klienta. Oznacza to, że oprócz odpowiedzi na pierwotne żądanie serwer może przekazać klientowi dodatkowe zasoby (ilustracja 12–5), bez konieczności wysyłania przez klienta żądań o każdy z nich.

Serwer inicjuje nowe strumienie (obietnice) dla zasobów push

Po co nam taki mechanizm w przeglądarce? Typowa aplikacja internetowa składa się z dziesiątek zasobów, z których klient odkrywa podczas sprawdzania dokumentu dostarczonego przez serwer. Może warto więc wyeliminować dodatkowe opóźnienia i pozwolić serwerowi z wyprzedzeniem przekazać powiązane zasoby? Serwer wie już, których zasobów potrzebuje klient. To jest zadanie push z serwera.

Jeśli kiedykolwiek zdarzyło Ci się umieszczać kod CSS, JavaScript lub inny zasób za pomocą identyfikatora URI danych (patrz Wstawianie zasobów), masz już doświadczenie w korzystaniu z funkcji push z serwera. Ręcznie wbudowując zasób do dokumentu, przekazujemy go klientowi, nie czekając na jego żądanie. Dzięki protokołowi HTTP/2 możemy osiągnąć te same wyniki z dodatkowymi korzyściami dla wydajności. Zasoby push mogą być:

  • Pamięć podręczna przez klienta
  • Ponowne wykorzystanie na różnych stronach
  • Multipleksowanie obok innych zasobów
  • Priorytet przez serwer
  • Odrzucone przez klienta

Poradnik PUSH_PROMISE

Wszystkie strumienie push z serwera są inicjowane przez ramki PUSH_PROMISE, które sygnalizują intencje serwera dotyczące przekazania opisanych zasobów do klienta i muszą być dostarczone przed danymi odpowiedzi, które wysyłają żądanie przekazanych zasobów. To zamówienie dostawy ma kluczowe znaczenie: klient musi wiedzieć, które zasoby chce przekazać serwer, aby uniknąć tworzenia zduplikowanych żądań do tych zasobów. Najprostszą strategią spełnienia tego wymogu jest wysłanie wszystkich ramek PUSH_PROMISE, które zawierają tylko nagłówki HTTP obiecanego zasobu, przed odpowiedzią elementu nadrzędnego (czyli DATA klatek).

Gdy klient otrzyma ramkę PUSH_PROMISE, może w razie potrzeby odrzucić strumień (za pomocą ramki RST_STREAM). Może się tak zdarzyć, bo zasób jest już w pamięci podręcznej. To ważny wzrost w porównaniu z HTTP/1.x. Z kolei użycie funkcji wbudowywania zasobów, która jest popularną „optymalizacją” w przypadku HTTP/1.x, jest odpowiednikiem „wymuszonego przeniesienia” – klient nie może z nich zrezygnować, anulować ani przetworzyć wstawionego zasobu pojedynczo.

W przypadku HTTP/2 klient zachowuje pełną kontrolę nad sposobem wykorzystania funkcji push z serwera. Klient może ograniczyć liczbę równoczesnych strumieni przekazanych. Dostosuj początkowe okno sterowania przepływem, aby kontrolować ilość danych przesyłanych przy pierwszym uruchomieniu strumienia, lub całkowicie wyłącz przekazywanie z serwera. Preferencje są przekazywane za pomocą ramek SETTINGS na początku połączenia HTTP/2 i mogą być w każdej chwili aktualizowane.

Każdy przekazany zasób to strumień, który – w przeciwieństwie do zasobów wbudowanych – umożliwia indywidualne multipleksowanie, ustalanie ich priorytetów i przetwarzanie ich przez klienta. Jedynym ograniczeniem zabezpieczeń egzekwowanym przez przeglądarkę jest to, że przekazywane zasoby muszą być zgodne z zasadą dotyczącą tego samego pochodzenia – serwer musi być wiarygodny w przypadku dostarczanych treści.

Kompresja nagłówka

Każdy transfer HTTP zawiera zestaw nagłówków opisujących przeniesiony zasób i jego właściwości. W protokole HTTP/1.x metadane są zawsze wysyłane jako zwykły tekst i dodają od 500 do 800 bajtów zciążenia na transfer, a czasem o kilobajty więcej, jeśli używane są pliki cookie HTTP. (Patrz: Pomiar i kontrola nadmiarowości protokołu). Aby zmniejszyć koszty i zwiększyć wydajność, HTTP/2 kompresuje metadane żądań i odpowiedzi z wykorzystaniem formatu kompresji HPACK, w którym wykorzystano 2 proste, ale zaawansowane techniki:

  1. Umożliwia kodowanie przesyłanych pól nagłówka za pomocą statycznego kodu Huffmana, co zmniejsza rozmiar przesyłanych danych.
  2. Wymaga, aby zarówno klient, jak i serwer utrzymywał i aktualizował indeksowaną listę wcześniej widocznych pól nagłówka (czyli ustanawia wspólny kontekst kompresji), która jest następnie używana jako punkt odniesienia do efektywnego kodowania wcześniej przesyłanych wartości.

Kodowanie Huffmana umożliwia kompresowanie poszczególnych wartości podczas przenoszenia, a zindeksowana lista wcześniej przeniesionych wartości pozwala nam zakodować zduplikowane wartości przez przenoszenie wartości indeksu. Dzięki temu można skutecznie wyszukiwać i rekonstruować pełne klucze i wartości nagłówka.

HPACK: kompresja nagłówków HTTP/2

Kolejną optymalizację polega na tym, że kontekst kompresji HPACK składa się ze statycznej i dynamicznej tabeli: tabela statyczna jest zdefiniowana w specyfikacji i udostępnia listę typowych pól nagłówka HTTP, które mogą być używane przez wszystkie połączenia (np.prawidłowe nazwy nagłówków). Tabela dynamiczna jest początkowo pusta i aktualizowana na podstawie wartości wymienionych w ramach konkretnego połączenia. W rezultacie rozmiar każdego żądania jest zmniejszony przez zastosowanie statycznego kodowania Huffmana nad wartościami, które nie występowały wcześniej, oraz zastąpienie indeksów wartościami, które są już obecne w statycznych lub dynamicznych tabelach po każdej stronie.

Bezpieczeństwo i wydajność HPACK

Wczesne wersje protokołów HTTP/2 i SPDY używały biblioteki zlib z niestandardowym słownikiem do kompresowania wszystkich nagłówków HTTP. Pozwoliło to zmniejszyć rozmiar przesyłanych danych nagłówka o 85–88% i znacznie skrócić czas wczytywania strony:

W przypadku łącza DSL o niższej przepustowości, którego szybkość przesyłania wynosi tylko 375 kb/s, kompresja nagłówka żądania znacznie skróciła czas wczytywania strony w niektórych witrynach (czyli takich, które wysyłały dużą liczbę żądań zasobów). Wykryliśmy skrócenie czasu wczytywania strony o 45–1142 ms tylko ze względu na kompresję nagłówka. (dokument SPDY, chromium.org)

Jednak latem 2012 r. opublikowano atak typu „CRIME” na algorytmy kompresji TLS i SPDY, co mogło doprowadzić do przejęcia sesji. W rezultacie algorytm kompresji zlib został zastąpiony przez HPACK, który ma za zadanie rozwiązać wykryte problemy z bezpieczeństwem, być skutecznym i łatwym do wdrożenia oraz oczywiście zadbać o dobrą kompresję metadanych nagłówka HTTP.

Więcej informacji o algorytmie kompresji HPACK znajdziesz na stronie IETF HPACK – kompresja nagłówka dla HTTP/2.

Więcej informacji