Już od ponad 20 lat przeglądamy strony internetowe za pomocą protokołu HTTP. Większość użytkowników w ogóle nie zastanawia się, co to jest i jak to działa. Inni wiedzą, że gdzieś pod HTTP znajduje się TLS, a pod nim TCP, a pod TCP IP i tak dalej. A trzeci – heretycy – uważają, że TCP to już przeszłość, chcą czegoś szybszego, bardziej niezawodnego i bezpiecznego. Jednak w swoich próbach wynalezienia nowego idealnego protokołu wrócili do technologii z lat 80. i próbują na nich zbudować swój wspaniały nowy świat.

Trochę historii: HTTP/1.1
W 1997 roku protokół wymiany informacji tekstowych HTTP w wersji 1.1 uzyskał swój RFC. W tamtym czasie protokół był używany przez przeglądarki już od kilku lat, a nowy standard przetrwał jeszcze piętnaście. Protokół działał tylko na zasadzie zapytanie-odpowiedź i był przeznaczony głównie do przesyłania informacji tekstowych.
HTTP został zaprojektowany do pracy na protokole TCP, który gwarantuje niezawodne dostarczenie pakietów do odbiorcy. Działanie TCP opiera się na ustanowieniu i utrzymaniu niezawodnego połączenia między punktami końcowymi oraz podziale ruchu na segmenty. Segmenty mają swój numer sekwencyjny i sumę kontrolną. Jeśli jakiś segment nie dotrze lub dotrze z nieprawidłową sumą kontrolną, to transmisja zostanie wstrzymana, aż do momentu przywrócenia utraconego segmentu.
W HTTP/1.0 połączenie TCP zamykano po każdym żądaniu. Było to niezwykle nieefektywne, ponieważ ustanowienie połączenia TCP (3-Way-Handshake) jest procesem nieprędko przebiegającym. W HTTP/1.1 wprowadzono mechanizm keep-alive, który pozwala na ponowne używanie jednego połączenia do wielu żądań. Jednak ponieważ może on łatwo stać się wąskim gardłem, w różnych implementacjach HTTP/1.1 dopuszcza się otwarcie kilku połączeń TCP do jednego hosta. Na przykład w Google Chrome i najnowszych wersjach Firefox dozwolone jest do sześciu połączeń.

Szyfrowanie również postanowiono powierzyć innym protokołom, dlatego na protokole TCP zaczął działać protokół TLS, który dość skutecznie chronił dane, ale jeszcze bardziej wydłużał czas wymagany do nawiązania połączenia. Ostatecznie proces uzgadniania wyglądał tak:

Ilustracja Cloudflare
Tak więc HTTP/1.1 miał szereg problemów:
- Wolne nawiązywanie połączenia.
- Dane są przesyłane w formie tekstowej, co sprawia, że przesyłanie obrazów, filmów i innych informacji nie tekstowych jest nieefektywne.
- Jedno połączenie TCP jest używane do jednego żądania, co oznacza, że pozostałe żądania muszą znaleźć inne połączenie lub czekać, aż aktualne żądanie je zwolni.
- Obsługiwana jest tylko model pull. W standardzie nie ma nic o server-push.
- Nagłówki są przesyłane w postaci tekstu.
Jeśli server-push jest w miarę wdrażany za pomocą protokołu WebSocket, to z pozostałymi problemami trzeba było sobie radzić bardziej radykalnie.
Trochę nowoczesności: HTTP/2
W 2012 roku w Google rozpoczęto prace nad protokołem SPDY (wymawia się „spidi”). Protokół miał na celu rozwiązanie głównych problemów HTTP/1.1, jednocześnie zachowując kompatybilność wsteczną. W 2015 roku grupa robocza IETF przedstawiła specyfikację HTTP/2, opartą na protokole SPDY. Oto jakie różnice wystąpiły w HTTP/2:
- Binarną serializację.
- MMultiplexowanie wielu żądań HTTP w jednym połączeniu TCP.
- Server-push w pudełku (bez WebSocket).
Protokół stał się dużym krokiem naprzód. Znacząco i nie wymaga tworzenia wielu połączeń TCP: wszystkie żądania do jednego hosta są multiplexowane w jedno. To znaczy, że w jednym połączeniu znajduje się kilka tzw. strumieni, z których każdy ma swój ID. Dodatkowym atutem jest fabryczny server-push.
Jednak multiplexowanie prowadzi do innego fundamentalnego problemu. Wyobraź sobie, że asynchronicznie wykonujemy 5 żądań do jednego serwera. Przy użyciu HTTP/2 wszystkie te żądania będą wykonywane w ramach jednego połączenia TCP, co oznacza, że jeśli jeden z segmentów któregokolwiek żądania zostanie utracony lub przyjdzie niepoprawnie, przesyłanie wszystkich żądań i odpowiedzi zostanie wstrzymane, aż zostanie odtworzony utracony segment. Oczywiście, im gorsza jakość połączenia, tym wolniej działa HTTP/2. , w warunkach, gdy utracone pakiety stanowią 2% wszystkich, HTTP/1.1 w przeglądarkach spisuje się lepiej niż HTTP/2, ponieważ otwiera 6 połączeń zamiast jednego.
Problem ten nosi nazwę „blocking head-of-line” i niestety rozwiązanie go podczas korzystania z TCP nie jest możliwe.

Ilustracja Daniel Steinberg
W rezultacie twórcy standardu HTTP/2 wykonali ogromną pracę i zrobili praktycznie wszystko, co dało się zrobić na poziomie aplikacyjnym modelu OSI. Nadszedł czas, aby zejść na poziom transportowy i wynaleźć nowy protokół transportowy.
Potrzebujemy nowego protokołu: UDP vs TCP
Szybko stało się jasne, że wdrożenie całkowicie nowego protokołu transportowego to w obecnych realiach zadanie nie do rozwiązania. Rzecz w tym, że o warstwie transportowej wiedzą urządzenia sieciowe lub urządzenia pośredniczące (routery, zapory ogniowe, serwery NAT...), a nauczenie ich czegokolwiek nowego jest niezwykle trudne. Ponadto wsparcie dla protokołów transportowych jest wbudowane w jądro systemów operacyjnych, a jądra też nie zmieniają się zbyt chętnie.
Można byłoby załamać ręce i powiedzieć: „Oczywiście wymyślimy nowy HTTP/3 z preferencjami i kurtyzanami, ale jego wdrożenie zajmie 10-15 lat (w przybliżeniu w tym czasie wymieniona zostanie większość sprzętu)”, ale istnieje jeszcze jeden mniej oczywisty wariant: użyć protokołu UDP. Tak, ten sam protokół, którym rzucaliśmy plikami w lokalnej sieci pod koniec lat 90-tych i na początku 2000-nych. Praktycznie wszystkie dzisiejsze urządzenia potrafią z nim pracować.
Jakie są zalety UDP w porównaniu do TCP? Przede wszystkim to, że nie mamy sesji na poziomie transportowym, o której wie sprzęt. Umożliwia to samodzielne definiowanie sesji na końcowych punktach i tam również rozwiązywanie powstających konfliktów. To znaczy, że nie jesteśmy ograniczeni do jednej lub kilku sesji (jak w TCP), a możemy stworzyć ich tyle, ile potrzebujemy. Po drugie, przesył danych przez UDP odbywa się szybciej niż przez TCP. W ten sposób, teoretycznie, możemy przebić dzisiejszy sufit prędkości osiągnięty w HTTP/2.
Jednak UDP nie gwarantuje niezawodności przesyłu danych. Właściwie po prostu wysyłamy pakiety, mając nadzieję, że po drugiej stronie je odbiorą. Nie odebrano? Cóż, pech... To wystarczyło do przesyłania filmów dla dorosłych, ale do bardziej poważnych rzeczy potrzebna jest niezawodność, co oznacza, że trzeba będzie coś jeszcze nawinąć na UDP.
Podobnie jak w przypadku HTTP/2, prace nad stworzeniem nowego protokołu rozpoczęły się w Google w 2012 roku, czyli mniej więcej w tym samym czasie, co rozpoczęcie prac nad SPDY. W 2013 roku Jim Roskind zaprezentował szerszej publiczności , a już w 2015 roku został wprowadzony Internet Draft w celu standaryzacji w IETF. Już wtedy protokół opracowany przez Roskinda w Google znacznie różnił się od wersji przedstawionej do standardu, dlatego wersja google'owska zaczęła być nazywana gQUIC.
Czym jest QUIC
Po pierwsze, jak już wspomniano, jest to nakładka na UDP. Na bazie UDP ustanawiane jest połączenie QUIC, w którym, analogicznie do HTTP/2, mogą istnieć różne strumienie. Strumienie te istnieją tylko na końcówkach i są obsługiwane niezależnie. Jeśli w jednym strumieniu dojdzie do utraty pakietu, inne nie są w żaden sposób dotknięte.

Ilustracja Daniel Steinberg
Po drugie, szyfrowanie jest teraz realizowane nie jako oddzielna warstwa, lecz jest włączone w protokół. To pozwala na nawiązywanie połączeń i wymianę kluczy publicznych w jednym uścisku ręki, a także umożliwia zastosowanie sprytnego mechanizmu 0-RTT handshake, eliminując opóźnienia przy handshake. Ponadto, teraz można szyfrować pojedyncze pakiety danych. To pozwala na nieczekanie na zakończenie odbioru danych ze strumienia, a na niezależne deszyfrowanie otrzymanych pakietów. Taki tryb pracy był całkowicie niemożliwy w TCP, ponieważ TLS i TCP działały niezależnie od siebie i TLS nie mógł wiedzieć, na jakie fragmenty TCP będzie dzielił dane. W związku z tym nie mógł przygotować swoich segmentów tak, aby odpowiadały segmentom TCP jeden do jednego i mogły być deszyfrowane niezależnie. Wszystkie te ulepszenia pozwalają QUIC zmniejszyć opóźnienia w porównaniu z TCP.

Po trzecie, koncepcja lekkich strumieni pozwala odłączyć połączenie od adresu IP klienta. To jest istotne, na przykład, gdy klient przełącza się z jednego punktu dostępu Wi-Fi na inny, zmieniając swój adres IP. W takim przypadku przy użyciu TCP następuje długi proces, w którym istniejące połączenia TCP rozłączają się z powodu przekroczenia czasu i tworzone są nowe połączenia z nowym IP. W przypadku QUIC klient po prostu kontynuuje wysyłanie pakietów do serwera z nowego IP z zachowanym starym ID strumienia. Ponieważ ID strumienia jest teraz unikalne i nie jest ponownie wykorzystywane, serwer rozumie, że klient zmienił adres IP, dosyła utracone pakiety i kontynuuje komunikację pod nowym adresem.
Po czwarte, QUIC jest realizowany na poziomie aplikacji, a nie systemu operacyjnego. Z jednej strony pozwala to na szybsze wprowadzanie zmian w protokole, ponieważ aby uzyskać aktualizację, wystarczy po prostu zaktualizować bibliotekę, a nie czekać na nową wersję systemu operacyjnego. Z drugiej strony prowadzi to do znacznego wzrostu zużycia procesora.
No i na koniec, nagłówki. Kompresja nagłówków odnosi się dokładnie do różnic między QUIC a gQUIC. Nie widzę sensu poświęcać temu dużo czasu, powiem tylko, że w wersji zgłoszonej do standaryzacji, kompresja nagłówków została maksymalnie zbliżona do kompresji nagłówków w HTTP/2. Więcej można przeczytać .
Jak szybko jest to?
To trudne pytanie. Chodzi o to, że póki co nie mamy standardu, więc trudno jest coś zmierzyć. Prawdopodobnie jedyne dane statystyczne, którymi dysponujemy, to dane z Google, który używa gQUIC od 2013 roku i w 2016 , że około 90% ruchu kierowanego do ich serwerów z przeglądarki Chrome teraz korzysta z QUIC. W tej samej prezentacji informują, że przy użyciu gQUIC strony ładują się około 5% szybciej, a w przypadku wideo strumieniowego występuje o 30% mniej zacięć w porównaniu do TCP.
W 2017 roku grupa badaczy pod przewodnictwem Arasha Molavi Kakhki opublikowała badanie wydajności gQUIC w porównaniu z TCP.
Badanie ujawniło kilka słabości gQUIC, takich jak niestabilność przy mieszaniu pakietów sieciowych, nieuczciwość w wykorzystaniu przepustowości oraz wolniejsze przesyłanie małych obiektów (do 10 kB). Ostatnie można jednak zrekompensować używając 0-RTT. We wszystkich pozostałych badanych przypadkach gQUIC wykazał wzrost szybkości w porównaniu do TCP. O konkretnych danych tutaj trudno mówić. Lepiej poczytać lub .
Tutaj trzeba powiedzieć, że to są dane dotyczące gQUIC i nie są aktualne dla rozwijanego standardu. Co będzie w przypadku QUIC: na razie to tajemnica, ale istnieje nadzieja, że słabości wykryte w gQUIC zostaną uwzględnione i naprawione.
Trochę o przyszłości: co z HTTP/3?
Tutaj wszystko jest jasne: API nie ulegnie zmianie. Wszystko pozostanie dokładnie tak, jak było w HTTP/2. Jeśli API pozostaje takie samo, przejście na HTTP/3 będzie wymagało użycia nowej wersji biblioteki na backendzie, wspierającej transport QUIC. Niestety, przez długi czas będziemy musieli zachować wsparcie dla starszych wersji HTTP, ponieważ internet obecnie nie jest gotowy na pełne przejście na UDP.
Kto już wspiera
Oto istniejących implementacji QUIC. Mimo braku standardu, lista jest całkiem przyzwoita.
Żaden z przeglądarek obecnie nie wspiera QUIC w stabilnej wersji. Niedawno pojawiła się informacja, że w Chrome włączono wsparcie HTTP/3, ale tylko w wersji Canary.
Spośród backendów HTTP/3 wspiera tylko i , ale na razie w wersji eksperymentalnej. NGINX na początku wiosny 2019 , ogłosił, że pracują nad wsparciem HTTP/3, ale jeszcze nie zakończyli.
Jakie są problemy
Żyjemy w realnym świecie, gdzie żadna duża technologia nie może przekroczyć granic mas, nie napotykając oporu, a QUIC nie jest wyjątkiem.
Najważniejsze, musimy jakoś wytłumaczyć przeglądarce, że "https://" niekoniecznie prowadzi teraz do portu TCP 443. TCP w ogóle może nie być obecne. W tym celu używany jest nagłówek Alt-Svc. Pozwala on poinformować przeglądarkę, że ta strona internetowa jest również dostępna na takim a takim protokole pod taką a taką adresem. Teoretycznie to powinno działać bez zarzutu, ale w praktyce napotykamy problemy, że UDP może być na przykład zablokowane w firewallu, aby uniknąć ataków DDoS.
Ale nawet jeśli UDP nie jest zablokowane, klient może znajdować się za routerem NAT, który jest skonfigurowany do utrzymywania sesji TCP pod adresem IP, a ponieważ używamy UDP, w którym nie ma sesji sprzętowej, NAT nie będzie utrzymywał połączenia, a sesja QUIC .
Wszystkie te problemy są związane z tym, że UDP wcześniej nie był używany do przesyłania treści internetowych, i producenci sprzętu nie mogli przewidzieć, że to kiedykolwiek się zdarzy. W podobny sposób administratorzy wciąż nie do końca rozumieją, jak prawidłowo skonfigurować swoje sieci do pracy z QUIC. Ta sytuacja powoli będzie się zmieniać, a w każdym razie tego typu zmiany zajmą mniej czasu niż wdrożenie nowego protokołu warstwy transportowej.
Ponadto, jak już opisano, QUIC znacznie zwiększa zużycie procesora. Daniel Stenberg wzrost zużycia procesora do trzech razy.
Kiedy nadejdzie HTTP/3
Standard Do maja 2020 roku, ale biorąc pod uwagę, że na chwilę obecną dokumenty planowane na lipiec 2019 roku wciąż nie zostały ukończone, można powiedzieć, że data najprawdopodobniej zostanie przesunięta.
Google korzysta ze swojej implementacji gQUIC od 2013 roku. Jeśli spojrzeć na zapytanie HTTP wysyłane do wyszukiwarki Google, można zobaczyć to:

Wnioski
QUIC obecnie wygląda na dość niedopracowaną, ale bardzo obiecującą technologię. Biorąc pod uwagę, że przez ostatnie 20 lat wszystkie optymalizacje protokołów warstwy transportowej dotyczyły głównie TCP, QUIC, w większości przypadków wygrywający pod względem wydajności, wygląda już teraz całkiem dobrze.
Jednak wciąż pozostają nierozwiązane problemy, z którymi trzeba będzie zmierzyć się w najbliższych latach. Proces może się przedłużyć ze względu na sprzęt, który nikt nie lubi aktualizować, jednak wszystkie problemy wydają się być do rozwiązania, i prędzej czy później wszyscy będziemy korzystać z HTTP/3.
Przyszłość jest tuż za rogiem!
Źródło: habr.com
