6. Протокол http2
Достаточно сказано о предпосылках, истории и политике, и теперь мы здесь. Давайте погрузимся в специфику протокола. Те части и концепции, которые слагают http2.
6.1. Бинарный протокол
http2 – это бинарный протокол.
Давайте попробуем осознать это на минутку. Если вы были знакомы с интернет-протоколами до этого, то велика вероятность, что инстинктивно вы сильно воспротивитесь этому факту и приготовите аргументы о том, что протоколы, использующие текст/ascii, лучше, поскольку люди могут вручную писать запросы через телнет.
http2 – бинарный для того, чтобы сделать формирование пакетов проще. Определение начала и конца пакета – одна из самых сложных задач в HTTP 1.1 и во всех текстовых протоколах в принципе. Уходя от опциональных пробелов и всевозможных способов записи одних и тех же вещей, мы делаем реализацию проще.
Кроме того, это позволяет гораздо проще разделять части связанные с самим протоколом и пакетом данных, что в HTTP1 беспорядочно перемешано.
Тот факт, что протокол позволяет использовать сжатие и часто работает поверх TLS также снижает ценность текста, так как вы в любом случае больше не увидите открытого текста в проводах. Мы просто должны придти к пониманию, что надо использовать анализатор Wireshark или что-то похожее, чтобы выяснить что происходит на уровне протокола в http2.
Отладка этого протокола, скорее будет выполняться такими утилитами, как curl, или путём анализа сетевого потока http2-диссектором Wireshark или чего-то подобного.
6.2. Бинарный формат
http2 отправляет бинарные фреймы. Существует несколько различных типов фреймов, но все они имеют одинаковое строение:
Тип, длина, флаги, идентификатор потока и полезная нагрузка фрейма.
Существует десять различных типов фреймов определённых в спецификации http2 и, в том числе, два, возможно наиболее важных, которые связывают с HTTP 1.1: DATA (данные) и HEADERS (заголовки). Я опишу некоторые из фреймов более подробно дальше.
6.3. Мультиплексирование потоков
Идентификатор потока, упомянутый в предыдущей секции, описывающей формат фреймов, привязывает каждый фрейм, передаваемый поверх http2, к так называемому «потоку». Поток – это логическая ассоциация. Независимая двухсторонняя последовательность фреймов, которыми обмениваются клиент с сервером внутри http2-соединения.
Одно http2-соединение может содержать множество одновременных открытых потоков от любой из сторон, обменивающихся фреймами множества потоков. Потоки могут быть установлены и использованы в одностороннем порядке или совместно использованы как клиентом, так и сервером, и могут быть закрыты любой из сторон. Важен порядок, в котором отправляются фреймы. Получатель обрабатывает фреймы в порядке их получения.
Мультиплексирование потоков означает, что пакеты множества потоков смешаны в рамках одного соединения. Два (или больше) отдельных поезда данных собираются в один состав, а затем разделяются на другой стороне. Здесь два поезда:
Они собираются вместе по одному соединению в смешанном режиме:
6.4. Приоритеты и зависимости
Каждый поток имеет приоритет (также известный, как «вес»), используемый для того, чтобы показать другому участнику обмена, какие потоки считать более важными в случае, если есть ограничения в ресурсах, которые требуют от сервера выбирать какие потоки отправлять в первую очередь.
Используя фрейм PRIORITY клиент может также указать серверу от какого другого потока зависит данный поток. Это позволяет клиенту построить «дерево» приоритетов, где несколько «потоков-потомков» могут зависеть от завершения «родительских потоков».
Веса приоритетов и зависимости могут динамически меняться при обмене, что позволит браузеру быть уверенным, что когда пользователь проматывает страницу заполненную картинками, он сможет указать какие изображения являются наиболее важными, или когда вы переключаете вкладки, он может повысить приоритет потокам, которые неожиданно попали в фокус.
6.5. Сжатие заголовков
HTTP – это протокол без состояния. Вкратце, это означает, что каждый запрос должен содержать максимальное число деталей, которые требуются серверу, чтобы выполнить запрос без необходимости сохранять множества метаданных от предыдущего запроса. Так как http2 не меняет ни одну из подобных парадигм, ему приходится делать также.
Это делает HTTP повторяющимся. Когда клиент запрашивает множество ресурсов с одного сервера, например, изображения веб-страницы, это превращается в большую серию запросов, выглядящих почти одинаково. Для серии чего-то почти одинакового само собой напрашивается сжатие.
Как я уже упоминал, в то время как число объектов на странице увеличивается, использование cookie и размер запросов также продолжают расти. Cookie также должны быть включены во все запросы, практически всегда одинаковые на протяжение множества запросов.
Размер HTTP 1.1 запроса стал настолько велик со временем, что иногда он становился больше чем первоначальный размер TCP-окна, что делало его чрезвычайно медленным в отправке, требуя полного цикла отправки-приёма для получения подтверждения ACK от сервера перед тем, как полный запрос будет отправлен. Ещё один аргумент для сжатия.
6.5.1. Сжатие это непростая тема
Сжатие HTTPS и SPDY оказались уязвимыми к атакам BREACH и CRIME. Путём вставки известного текста в поток и наблюдения за тем, как меняется зашифрованный вывод, атакующий мог выяснить, что было отправлено.
Выполнение сжатия для динамического контента в протоколе без риска быть подверженным одной из известных атак, требует серьёзного обдумывания и внимательности. То, что команда HTTPbis и пытается делать.
Так появился HPACK, Сжатие заголовков для HTTP/2, который, как и подсказывает название, формат сжатия, предназначенный специально для http2-заголовков и, строго говоря, это отдельный интернет черновик спецификации. Новый формат совместно с другими контр-мерами, такими как специальные флаги, которые просят посредников не сжимать определённые заголовки и опционально добавлять в фреймы лишние пустые данные, чтобы усложнить атаку на сжатие.
Со слов Роберто Пиона (один из создателей HPACK):
«HPACK был разработан так, чтобы для соответствующей реализации была затруднена утечка информации, чтобы сделать процесс кодирования и декодирования быстрым и дешёвым, предоставить получателю контроль над размером контекста сжатия, позволить прокси реиндексацию (т. е. Разделяемое состояние между фронтендом и бекендом внутри прокси) и для быстрого сравнения huffman-кодированных строк».
6.6. Reset (сброс) — передумал
Один из недостатков HTTP 1.1, когда HTTP-сообщение отправлено с заголовком Content-Length определённой длины, вы не можете так просто его остановить. Конечно, зачастую вы можете (но не всегда) разорвать TCP-соединение, но ценой повторного согласования нового TCP-соединения.
Гораздо лучше просто отменить отправку и начать новое сообщение. Это может быть достигнуто отправкой http2-фрейма RST_STREAM, который таким образом предотвратит растрату полосы пропускания и необходимость разрыва каких-либо соединений.
6.7. Server push (посылка сервера)
Эта возможность также известна как «посылка в кэш». Идея в том, что если клиент запрашивает ресурс X, а сервер предполагает, что клиент наверняка затем попросит ресурс Z, отправляет этот ресурс клиенту без просьбы с его стороны. Это помогает клиенту поместить Z в свой кэш, и он будет на месте, когда потребуется.
Посылка сервера – это то, что клиент явно должен разрешить серверу, и даже если он разрешил, он может по своему выбору быстро отменить посланный поток с помощью RST_STREAM, если он ему оказался не нужен.
6.8. Управление потоком
Каждый индивидуальный поток в http2 имеет своё объявленное окно потока, которое другая сторона разрешила для передачи данных. Если вы представляете как работает SSH, то это очень похоже и выполнено в том же духе и стиле.
Для каждого потока оба конца сообщают друг другу, что у них есть ещё место для принятия входных данных, и противоположному концу дозволено отправить только указанное количество данных до тех пор, пока окно не будет расширено.