HTTPS/2
Text vychází z návrhů draft-ietf-httpbis-http2–17 a draft-ietf-httpbis-header-compression-12, které byly odeslány editorovi RFC k vydání. Jejich základní principy se vám pokusíme přiblížit. Pro začátek zdůrazněme, že zmiňované specifikace se zabývají jen transportními záležitostmi. Základní myšlenky, účastníci HTTP komunikace (včetně případných mezilehlých proxy) či složení a význam hlaviček zůstávají beze změny. Čili obsah se příliš nemění, ale bude po síti přenášen dost odlišně.
Dědictví minulosti
V HTTP/2 najdete celkem zajímavou směs radikálních novinek a návratů k úplným kořenům. Předchozí verze HTTP byly textové a ve formátu vyměňovaných zpráv byla znatelná inspirace elektronickou poštou. Klient navázal TCP spojení a položil dotaz, kde nejprve oznámil, co a jak po serveru chce. Následující hlavičky pak umožňovaly předat doprovodné informace. Dotaz vypadá nějak takto:
GET / HTTP/1.1
Host: www.root.cz
Accept: text/html,application/xhtml+xml,application/xml
Connection: keep-alive
...
Server odpovídá v podobném duchu – první řádek poskytuje základní informaci o výsledku zpracování dotazu, následují hlavičky a prázdným řádkem oddělené vlastní tělo odpovědi.
Ve verzi 1.0 se pro každý dotaz a odpověď navazovalo samostatné TCP spojení. To je sice z ideologického pohledu krásně čistý přístup, ale není moc praktický. Když se navazuje TCP spojení, musí si obě strany nejprve oznámit a odsouhlasit pořadová čísla, která budou používat pro potvrzování. To vyžaduje výměnu tří paketů, která stojí čas. A jelikož stránka typicky potřebuje řadu součástek, je výsledné zdržení znatelné.
Čtěte: Protokol HTTP/2 byl dokončen. Prohlížeče už ho podporují
Verze 1.1 přišla s permanentním spojením. TCP spojení se po vyřízení dotazu nezavírá, ale klient po něm může položit další dotaz. Ovšem ne před dokončením odpovědi. Pokud se zadrhla, musí klient čekat na její dokončení, než může položit další dotaz. Aby vše běželo rychleji, navazuje klient se serverem několik paralelních TCP spojení, mezi něž své dotazy rozkládá a může jich tak řešit několik současně. Cenou je samozřejmě větší režie, která pálí především správce velkých serverů.
Proudy
Cílem HTTP/2 bylo zrychlit vzájemnou komunikaci mezi klientem a serverem a zároveň pokud možno minimalizovat zátěž obou stran. Přichází s koncepcí, podle níž klient bude navazovat k danému serveru jen jedno TCP spojení, kterým budou přepravovány všechny jeho požadavky paralelně prostřednictvím takzvaných proudů (stream).
Vrací se tak trochu k přístupu s HTTP/1.0, protože každý dotaz bude mít svůj vlastní proud. Ten vznikne položením dotazu a jakmile je odeslána celá odpověď, uzavře se. Na rozdíl od TCP spojení však založení a ukončení proudu nezahrnuje prakticky žádnou režii ani zpoždění. Klient jednoduše položí dotaz s novým identifikátorem proudu, a tím je proud založen. Server pak poslednímu paketu odpovědi nastaví příznak END_STREAM, čímž jej ukončí.
Jednotlivé proudy jsou navzájem nezávislé a jejich pakety se mohou v TCP spojení libovolně míchat. Dojde-li při vyřizování některého dotazu ke zpoždění, nijak to nebrzdí provoz v ostatních proudech. Čili pro přenos hromady menších souborů je tento přístup ideální. Proto také test HTTP vs. HTTPS vychází pro HTTPS (a za ním skryté SPDY) tak dobře. Mělo by to vést k vymizení současných praktik, kdy se několik obrázků spojuje do jednoho nebo se vkládají přímo do CSS, jen aby se zmenšil počet souborů potřebných pro zobrazení stránky.
Jak už bylo naznačeno, proudy jsou rozlišeny pomocí jednoznačných identifikátorů. Každý paket HTTP/2 obsahuje identifikátor proudu, ke kterému náleží, nebo nulu, pokud se jedná o zprávu s dopadem na celé TCP spojení (např. nastavení parametrů komunikace). Aby to bylo jednoduché, proudy založené klientem mají liché identifikátory, zatímco proudy zakládané serverem sudé. Identifikátory se stále zvětšují a pokud by došly, naváže se nové TCP spojení a začne se znovu. Ovšem identifikátor proudu je 31bitové číslo, takže každá ze stran jich má k dispozici něco přes miliardu.
Lze také signalizovat důležitost jednotlivých proudů. Každý z nich lze učinit závislým na jiném proudu a přidělit mu váhu. Proudu by měly být přidělovány zdroje, jen když proud, na němž je závislý (jeho rodič), je už uzavřen, nebo z nějakého důvodu nemůže pokračovat. Mezi proudy závislými na stejném rodiči by se měly zdroje alokovat v poměru podle jejich vah. Implicitně je každý proud závislý na proudu 0 (čili de facto nezávislý) a má váhu 16. Tyto parametry se nastavují při založení proudu a lze je později průběžně měnit podle potřeby. Díky tomu je možné některým přenášeným datům dávat přednost před jinými.
Formát zpráv
HTTP/2 je – na rozdíl od svých předchůdců – binární. Vedla se kolem toho řada diskusí, protože se narušuje kontinuita a internetové protokoly tradičně inklinovaly spíše k textové podobě. Hlavním důvodem byla snaha o zjednodušení implementací. Textové protokoly bývají tolerantní vůči zápisu (nepovinné mezery, několik variant pro zápis stejného čísla a podobě), což ovšem komplikuje zpracování.
Po TCP spojení bude v HTTP/2 přenášena dost pestrá směsice zpráv v obou směrech, kterou bude snadnější zvládat v podobě binárních paketů s pevně definovanými hlavičkami. Kromě toho se předpokládá, že HTTP/2 bude typicky nasazeno v kombinaci s TLS, tudíž stejně nepřipadá v úvahu, že by někdo s Telnetem simuloval WWW klienta a testoval odpovědi serveru.
Data jsou v HTTP/2 přenášena v podobě tak zvaných rámců (frames). Existuje jich deset typů, jejichž přehled najdete v tabulce 1. Nosné jsou samozřejmě HEADERS pro hlavičky dotazů a odpovědí a DATA, kterými se přenáší vlastní obsah. Typická HTTP/2 zpráva se skládá z jednoho rámce HEADERS a příslušného počtu rámců DATA.
HEADERS hlavičky dotazu/odpovědi
CONTINUATION pokračování hlaviček, pokud 1 rámec nestačí
DATA přenášená data (tělo dotazu/odpovědi)
SETTINGS nastavení parametrů spojení
PRIORITY změna váhy a závislosti proudu
RST_STREAM okamžité ukončení proudu
PUSH_PROMISE avízo dat iniciovaných serverem
WINDOW_UPDATE aktualizace okna pro řízení toku dat
PING test spojení a jeho zpoždění
GOAWAY zakazuje protějšku zakládat další proudy
Tabulka 1: Typy rámců
Hlavičky jsou tvořeny klasickými dvojicemi jméno–hodnota. Aby se odstranila nepravidelnost způsobená speciálním formátem prvního řádku v HTTP/1.x, zavádí HTTP/2 několik pseudohlaviček, v nichž přenáší informace ze stavového řádku. Jejich přehled najdete v tabulce 2. Výše uvedený dotaz by byl v HTTP/2 přenesen jedním rámcem HEADERS s následujícím obsahem (v závorkách jsou uvedeny nastavené příznaky):
HEADERS (END_HEADERS, END_STREAM)
:method GET
:scheme http
:path /
Host www.root.cz
Accept text/html,application/xhtml+xml,application/xml
...
V odpovědi by za rámcem HEADERS následoval alespoň jeden rámec DATA s vlastní stránkou. Vypadalo by to přibližně takto:
HEADERS (END_HEADERS)
:status 200
Content-Type text/html
Content-Length 19356
...
DATA (END_STREAM)
HTML kód stránky
dotaz
:method metoda dotazu
:scheme schéma z cílového URI
:authority autorita z cílového URI
:path cesta k poptávanému zdroji
odpověď
:status stavový kód odpovědi
Tabulka 2: Pseudohlavičky
Komprese hlaviček
Objem hlaviček není zanedbatelný a zejména u malých souborů (třeba ikony) mohou být hlavičky delší než vlastní data. Asi by bylo možné vymýšlet koncept společných hlaviček pro TCP spojení (Proč se má klient představovat u každého dotazu?) a jejich kombinování s hlavičkami dotazů, ale autoři zvolil konzervativnější přístup. Hlavičky ponechali u jednotlivých dotazů a odpovědí, ovšem zařadili do protokolu jejich kompresi.
Je to jedna z věcí, kde se HTTP/2 odlišuje od svého předchůdce SPDY. Ten používá kompresi ZLIB, pro kterou existuje známý typ útoku (CRIME útok). Proto byl pro HTTP/2 vyvinut nový kompresní algoritmus. Je definován v samostatném dokumentu.
Upřímně řečeno, je úplně triviální. Komunikující partneři si udržují tabulku hlaviček (header field table) a hlavičku lze nahradit odkazem do ní. Tabulky platí pro celé TCP spojení, jsou oddělené pro oba směry provozu a o jejich obsahu vždy rozhoduje odesilatel. Ten si pro každou hlavičku ve svých zprávách může vybrat jednu z následujících variant:
pošle odkaz do tabulky hlaviček, pokud se v ní vyskytuje celá hlavička
odkáže se na jméno hlavičky do tabulky a připojí k němu vlastní obsah
uvede celou hlavičku, jméno i obsah
Ve druhém a třetím případě navíc odesilatel rozhodne, zda si příjemce má hlavičku přidat do tabulky nebo ne, případě zcela zakáže její přidávání do tabulek pro případné mezilehlé stroje (proxy). Pokud by vložením do tabulky došlo k překročení její přípustné velikosti, bude nejprve odstraněno tolik nejstarších hlaviček, aby se nová vešla.
Autoři upozorňují, že za určitých podmínek lze obsah tabulky hlaviček zkoumat. Pokud útočník může vkládat do spojení své zprávy (například sdílí proxy se svou obětí, takže se jejich komunikace schází na jednom TCP spojení mezi proxy a WWW serverem) a vidí výsledné pakety, pozná podle jejich délky, zda hlavička byla komprimována nebo ne. Čili zda uhodl její obsah. TLS zde nepomůže, zpráva je sice šifrována, ale rozdíl v délce bude znatelný. Proto HTTP/2 umožňuje zakázat kompresi citlivých hlaviček.
Server Push
Zařazení konceptu Server Push může vyvolávat dojem, že HTTP/2 otevírá nové možnosti pro webové aplikace. Jeho ambice jsou však podstatně skromnější. Mechanismus má pouze urychlit vzájemnou komunikaci a umožnit serveru poslat klientovi data, která podle jeho názoru bude brzy poptávat, aniž by čekal na příchod HTTP dotazu. Například obrázky nebo CSS ze stránky, kterou klient právě poptal.
Funguje to tak, že server pošle klientovi rámec typu PUSH_PROMISE obsahující hlavičky (:path, :method a podobně) dotazu, jehož položení z klientovy strany očekává a na nějž hodlá poslat odpověď. Nese také identifikátor nového proudu, kterým server nevyžádanou odpověď odešle.
PUSH_PROMISE nevyžaduje žádnou reakci ze strany klienta. Jakmile server tento rámec odeslal, může začít posílat avizovaným proudem „tlačenou“ odpověď.
Klient na PUSH_PROMISE reaguje jen v případě, že nemá o data zájem. V tom případě rámcem RST_STREAM ukončí proud nevyžádané odpovědi. A samozřejmě může serveru úplně zakázat použití tohoto mechanismu. K tomu slouží parametr spojení SETTINGS_ENABLE_PUSH, kterým serveru sdělí, že pro dané TCP spojení nemá o nevyžádané odpovědi zájem.
Ochrana proti zahlcení
Tím se dostáváme k mechanismům, jimiž příjemce v HTTP/2 může brzdit příliš nedočkavého odesilatele a chránit své omezené kapacity. Řada z nich je obsažena v parametrech TCP spojení, které se nastavují pomocí rámce typu SETTINGS. Jím HTTP/2 komunikace povinně začíná. Tyto parametry se nedohadují. Každý směr spojení má svou sadu, jejíž hodnoty se mohou lišit od protisměru.
Pomocí parametrů lze omezit maximální počet paralelních proudů (SETTINGS_MAX_CONCURRENT_STREAMS), velikost tabulky hlaviček (SETTINGS_HEADER_TABLE_SIZE), maximální velikost jednoho rámce (SETTINGS_MAX_FRAME_SIZE) a podobně.
Kromě toho HTTP/2 obsahuje i mechanismus pro řízení toku dat. Používá osvědčený koncept okna, tedy počtu bajtů, které smí odesilatel poslat. Při odeslání rámce typu DATA (na ostatní se řízení nevztahuje) se jeho délka odečte od velikosti okna. Dojde-li do nuly, musí vysílající zastavit a počkat na jeho otevření. K němu slouží rámce typu WINDOW_UPDATE, jejichž hodnota se k oknu přičítá.
HTTP/2 obsahuje samostatná okna pro celé TCP spojení a pro každý jednotlivý proud. Data je povoleno odeslat, jen když je okno TCP spojení i příslušného proudu velké alespoň jako jejich délka.
Koexistence s HTTP/1.x
HTTP/2 samozřejmě musí fungovat v prostředí, kde se hojně vyskytují i jeho předchůdci. Vzhledem k existenci řady firewallů, které nepropouštějí prakticky nic jiného než porty 80 a 443, nebylo možné zvolit pro nový protokol jiný port nebo jej jiným způsobem zřetelně oddělit od HTTP/1.x. Klient z URL http://www.root.cz/ nepozná, jakou verzí HTTP server hovoří. Na případném použití nové verze se spolu musí domluvit.
Předpokládá se, že HTTP/2 bude typicky používáno v kombinaci s TLS. Existoval silný názorový proud, že TLS by mělo být pro HTTP/2 povinné. Nakonec zvítězil liberálnější přístup, nicméně pro některé klienty (Chrome, Firefox) není podpora nešifrovaného HTTP/2 v plánu.
Pro TLS byl definován mechanismus Application Layer Protocol Negotiation (ALPN), jímž se během zahájení TLS komunikace dohodne protokol aplikační vrstvy, který budou partneři používat. Pokud obě strany podporují HTTP/2, při použití https se na něm dohodnou ještě před zahájením HTTP komunikace. Ta pak celá proběhne v HTTP/2.
Bez šifrování je situace složitější, protože vnější dohoda není možná. Klient podporující HTTP/2 proto položí dotaz protokolem HTTP/1.1 a přidá do něj hlavičku Upgrade, kterou navrhne přechod na HTTP/2. Jestliže jej server nepodporuje, hlavičku ignoruje a pošle standardní odpověď. Pokud ale i server má zájem přejít na novou verzi, pošle po HTTP/1.1 odpověď s kódem 101 (Switching Protocols) a za ní už následuje komunikace v HTTP/2.
Ujme se?
Odpověď na otázku, zda HTTP/2 uspěje, je celkem snadná, protože v podstatě věštíme minulost. V podobě svého předchůdce SPDY už se ujal a je používán řadou velkých serverů (ne nadarmo stál u kolébky Google).
Z předchozí části je navíc patrné, že přechod na něj nemusí být skokový ani násilný – dokáže dobře koexistovat s HTTP/1.x. Klienti budou nepochybně starší verze protokolu podporovat ještě dlouhá léta a je čistě na rozhodnutí správců jednotlivých serverů, jestli budou chtít nasadit nový protokol nebo ne. Pokud to udělají, komunikace s většinou klientů přejde na novou verzi (podpora HTTP/2 v hlavních prohlížečích už je nebo se chystá), aniž by si toho jejich uživatelé explicitn