Оригинальный адрес:блог Past and Wang/2018/05-H TT…
Введение Википедии в HTTP/2, вы можете увидеть определение и историю развития:
RFC 7540 определяет спецификацию протокола и детали HTTP/2. Подробности этой статьи в основном взяты из этого документа. Рекомендуется сначала прочитать эту статью, а затем вернуться и просмотреть RFC в соответствии с протоколом. Если вы хотите чтобы углубиться в некоторые детали, внимательно прочитайте RFC.
Why use it ?
Проблемы с HTTP/1.1:
1,Лимит TCP-подключения
Для одного и того же доменного имени браузер может одновременно создавать до 6–8 TCP-соединений (разные браузеры разные). Чтобы решить количественное ограничение,域名分片
Технология, по сути, и есть разделение ресурсов, размещение ресурсов под разными доменными именами (типа поддоменов второго уровня), чтобы можно было создавать соединения и запросы на разные доменные имена, и пробивать ограничения удобным способом, но злоупотребление этим Технология также вызывает много проблем, например, каждое TCP-соединение само по себе должно проходить через DNS-запрос, трехстороннее рукопожатие, медленный запуск и т. д., а также требует дополнительного процессора и памяти.Для сервера слишком много соединений могут легко вызвать перегрузка сети, перегрузка трафика и т. д. для мобильных устройств С другой стороны, проблема более очевидна, вы можете обратиться к этой статье:Why Domain Sharding is Bad News for Mobile Performance and Users
На рисунке видно, что создается шесть новых TCP-соединений, каждое новое соединение требует времени для разрешения DNS (от нескольких мс до нескольких сотен мс), медленный запуск TCP также требует времени, рукопожатие TLS требует времени и последующие запросы требуется время Ожидание планирования очереди
2,Блокировка начальника линиивопрос
Каждое TCP-соединение может одновременно обрабатывать только один запрос-ответ.Браузер обрабатывает запрос по принципу FIFO.Если предыдущий ответ не возвращается, последующий запрос-ответ будет заблокирован. Чтобы решить эту проблему,Конвейеризация - конвейеризациятехнологии, но есть много проблем в конвейере, например, медленный первый ответ блокирует последующие ответы, серверу необходимо кэшировать несколько ответов, чтобы занять больше ресурсов, чтобы вернуть соответствующий ответ по порядку, и браузер может отключиться и повторить попытку. сервер на полпути.Каждый запрос и необходимая клиент-прокси-сервер поддерживают конвейерную обработку
3. Содержимого заголовка много, и заголовок не будет слишком сильно меняться для каждого запроса, и нет соответствующей схемы оптимизации передачи сжатия
4. Чтобы максимально сократить количество запросов, необходимо провести работу по оптимизации, такую как слияние файлов, изображений спрайтов и встраивание ресурсов.Однако это, несомненно, вызывает проблему, заключающуюся в том, что содержимое одного запроса становится больше и задержка становится выше, и встроенные ресурсы не могут эффективно использовать кеш.
5. Небезопасная передача открытого текста
Преимущества HTTP2:
1. Слой бинарного кадрирования
Кадр — это наименьшая единица передачи данных. Исходная передача открытого текста заменяется двоичной передачей. Исходное сообщение делится на меньшие кадры данных:
Пакетное сравнение h1 и h2:
Сообщение h2 на рисунке реорганизовано и проанализировано, можно обнаружить, что некоторые поля заголовков изменились, и все поля заголовков в нижнем регистре.
strict-transport-security: max-age=63072000; includeSubdomains
поле включено на сервереПолитика HSTS, что заставляет браузер использовать HTTPS для связи, что снижает риск дополнительных запросов и перехвата сеанса, вызванного перенаправлениями
Способ включения HSTS на сервере такой: взять в качестве примера nginx, добавить его в серверный модуль соответствующего сайта
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains" always;
Просто
Можно открыть в Chrome
chrome://net-internals/#hsts
Войдите в интерфейс управления HSTS браузера, вы можете добавлять/удалять/запрашивать записи HSTS, как показано на следующем рисунке:
В течение срока действия HSTS и сертификата TLS браузер автоматически добавит https:// в blog.wangriyu.wang без запроса на перенаправление на https.
Дополнительные сведения о фреймах см.Как это работает? - Рамка
2. Мультиплексирование (MultiPlexing)
В TCP-соединении мы можем непрерывно отправлять кадры другой стороне, идентификатор потока каждого кадра указывает, к какому потоку принадлежит кадр, а затем, когда другая сторона его получает, все кадры каждого потока склеиваются в соответствии с идентификатор потока для формирования целого фрагмента данных. Каждый запрос HTTP/1.1 обрабатывается как поток, тогда несколько запросов становятся несколькими потоками, данные ответа на запрос делятся на несколько кадров, а кадры в разных потоках отправляются друг другу с чередованием.Это несколько потоков в HTTP/2. , мультиплексирование.
Концепция потока реализует параллелизм с несколькими запросами и ответами на одном соединении, решает проблему блокировки очереди и уменьшает количество соединений TCP и проблемы, вызванные медленным запуском соединений TCP.
Таким образом, http2 нужно создать только одно соединение для одного и того же доменного имени вместо создания 6-8 соединений, таких как http/1.1:
Для получения дополнительной информации о потоках см.Как это работает? - поток
3. Пуш-сервер
Браузер отправляет запрос, а сервер активно отправляет ресурсы, связанные с запросом, в браузер, так что браузеру не нужно инициировать последующие запросы.
Server-Push в основном оптимизирован для встраивания ресурсов.По сравнению с встраиванием ресурсов http/1.1 преимущества:
- Клиенты могут кэшировать выталкиваемые ресурсы
- Клиенты могут отклонять отправленные ресурсы
- Push-ресурсы могут быть общими для разных страниц
- Сервер может нажимать ресурсы по приоритету
Для получения дополнительной информации о передаче сервера см.:Как это работает? - Сервер-Push
4. Сжатие заголовков (HPACK)
использоватьHPACKалгоритм сжатия содержимого заголовка
Дополнительные сведения о HPACK см. в следующих разделах:Как это работает? - ХПАК
5. Соединение сброса прикладного уровня
Для HTTP/1 одноранговый конец уведомляется о закрытии соединения путем установки флага сброса в сегменте tcp. Этот метод будет напрямую разъединять соединение, и соединение должно быть восстановлено при следующей отправке запроса. HTTP/2 вводит фрейм типа RST_STREAM, который может отменить поток запроса без разрыва соединения, что лучше.
6. Запросить настройку приоритета
Для каждого потока в HTTP/2 можно задать зависимость и вес, а также назначить приоритет в соответствии с деревом зависимостей, что решает проблему блокирования ключевых запросов.
7. Управление потоком
Каждый поток http2 имеет свое собственное рекламное окно трафика, которое ограничивает отправку данных на другом конце. Для каждого потока оба конца должны сообщать другому, что у них достаточно места для обработки новых данных, а другому концу разрешено отправлять столько данных только до того, как окно будет увеличено.
Для получения дополнительной информации об управлении потоком см.:Как это работает? - управление потоком
8. Некоторые оптимизации HTTP/1 могут быть объявлены устаревшими.
Слияние файлов, встроенные ресурсы, изображения спрайтов и сегментация доменных имен не нужны для HTTP/2. Используйте h2, чтобы максимально детализировать ресурсы и разложить файлы как можно более разрозненно, чтобы вам не пришлось беспокоиться о количество запросов.
How does it work ?
Рамка - Рамка
каркасная конструкция
Все кадры имеют фиксированный 9-байтовый заголовок (перед полезной нагрузкой), за которым следует полезная нагрузка указанной длины:
+-----------------------------------------------+
| Length (24) |
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+---------------+-------------------------------+
|R| Stream Identifier (31) |
+=+=============================================================+
| Frame Payload (0...) ...
+---------------------------------------------------------------+
-
Length
Представляет длину всего кадра, представленного 24-разрядным целым числом без знака. Длина данных не должна превышать 2^14, если только получатель не установит большее значение в SETTINGS_MAX_FRAME_SIZE (размер может быть где угодно от 2^14(16384) байт до 2^24-1(16777215) байт) (16384) байт. 9 байтов заголовка не включены в эту длину. -
Type
Определите тип кадра, представленный 8 битами. Тип фрейма определяет формат и семантику тела фрейма, и его следует игнорировать или отбрасывать, если тип неизвестен. -
Flags
Логический флаг, зарезервированный для корреляции типа кадра. Флаги имеют разную семантику для разных типов фреймов. Если этот флаг не имеет определенной семантики для типа кадра, он ДОЛЖЕН игнорироваться и отправляться со значением (0x0) -
R
является зарезервированным битом. Семантика этого бита не определена, он ДОЛЖЕН быть установлен в (0x0) при отправке и игнорироваться при приеме. - Stream IdentifierИспользуется для управления потоком, представленным в виде 31-битного целого числа без знака. Sid, установленный клиентом, должен быть нечетным, sid, установленный сервером, должен быть четным, а значение (0x0) зарезервировано для кадров, связанных со всем соединением (сообщения управления соединением), а не отдельными потоками.
-
Frame Payload
основное содержимое, определяемое типом фрейма
Существует десять типов фреймов:
-
HEADERS
: Кадр заголовка (тип = 0x1), используемый для открытия потока или переноса фрагмента блока заголовка. -
DATA
: Фрейм данных (тип = 0x0), заполненный информацией тела, один или несколько кадров ДАННЫХ могут использоваться для возврата тела ответа запроса. -
PRIORITY
: Кадр приоритета (тип = 0x2), указывающий приоритет потока, предложенный отправителем, кадр ПРИОРИТЕТА может быть отправлен в любом состоянии потока, включая незанятые (бездействующие) и закрытые (закрытые) потоки. -
RST_STREAM
: Кадр завершения потока (тип = 0x3), используемый для запроса отмены потока или для указания того, что произошла ошибка, полезная нагрузка содержит код ошибки в виде 32-битного целого числа без знака (Error Codes), не может отправлять кадры RST_STREAM в потоке, который находится в состоянии ожидания -
SETTINGS
: установить кадр (тип = 0x4), установить это连接
параметр, который действует на все соединение -
PUSH_PROMISE
: Кадр отправки (тип = 0x5), сервер отправляет, клиент может вернуть кадр RST_STREAM, чтобы выбрать поток, который отказывается от отправки. -
PING
: Кадр PING (тип = 0x6), чтобы определить, доступно ли еще незанятое соединение, а также для измерения минимального времени приема-передачи (RTT). -
GOAWAY
: кадр GOWAY (тип = 0x7), используемый для инициации запроса на закрытие соединения или для предупреждения о серьезной ошибке. GOAWAY перестает получать новые потоки и обрабатывает ранее установленные потоки перед закрытием соединения -
WINDOW_UPDATE
: Кадр обновления окна (тип = 0x8), используемый для выполнения функции управления потоком, может воздействовать на один поток (указать конкретный идентификатор потока) или на все соединение (идентификатор потока равен 0x0), поток влияет только на кадры ДАННЫХ. контроль . После инициализации окна трафика окно трафика будет уменьшаться настолько, насколько будет отправлена нагрузка.Если окно трафика недостаточно, его нельзя отправить.Кадр WINDOW_UPDATE может увеличить размер окна трафика. -
CONTINUATION
: кадр продолжения (тип=0x9), используемый для продолжения передачи последовательности фрагментов блока заголовка, см.Сжатие и распаковка заголовков
Формат кадра ДАННЫХ
+---------------+
|Pad Length? (8)|
+---------------+-----------------------------------------------+
| Data (*) ...
+---------------------------------------------------------------+
| Padding (*) ...
+---------------------------------------------------------------+
-
Pad Length
: ?Указывает, что внешний вид этого поля является условным, необходимо установить соответствующий флаг (установить флаг) и указать длину отступа.Если он существует, устанавливается флаг PADDING -
Data
: Передаваемые данные, верхний предел которых равен длине полезной нагрузки кадра за вычетом длины других появляющихся полей. -
Padding
: байт заполнения, без особой семантики, при отправке должен быть установлен на 0, функция заключается в запутывании длины сообщения, аналогично шифрованию блоков CBC в TLS, подробности см.HTTP за пределами G.org/specs/RFC75…
Кадры DATA имеют следующие флаги:
- END_STREAM: бит 0 установлен в 1, чтобы представить последний кадр текущего потока
- PADDED: бит 3 установлен в 1 означает, что есть заполнение
пример:
Формат кадра ЗАГОЛОВКИ
+---------------+
|Pad Length? (8)|
+-+-------------+-----------------------------------------------+
|E| Stream Dependency? (31) |
+-+-------------+-----------------------------------------------+
| Weight? (8) |
+-+-------------+-----------------------------------------------+
| Header Block Fragment (*) ...
+---------------------------------------------------------------+
| Padding (*) ...
+---------------------------------------------------------------+
-
Pad Length
: указывает длину заполнения, если она существует, устанавливается флаг PADDING. -
E
: Бит, который объявляет, является ли зависимость потока исключительной, если она существует, устанавливается флаг ПРИОРИТЕТА -
Stream Dependency
: Укажите идентификатор потока, который представляет собой идентификатор потока, от которого зависит текущий поток.Если он существует, это означает, что установлен флаг ПРИОРИТЕТ -
Weight
: 8 без знака — это целое число, представляющее значение веса приоритета текущего потока (1~256), если оно существует, это означает, что флаг ПРИОРИТЕТ установлен. -
Header Block Fragment
: фрагмент блока заголовка -
Padding
: Заполнение байта, без особой семантики, функция такая же, как и заполнение DATA, если оно существует, это означает, что установлен флаг PADDING
Кадры HEADERS имеют следующие флаги:
- END_STREAM: когда бит 0 установлен в 1, это означает, что текущий блок заголовка является последним отправленным блоком, но за кадром HEADERS с флагом END_STREAM также может следовать кадр CONTINUATION (здесь CONTINUATION можно рассматривать как часть HEADERS). )
- END_HEADERS: бит 2 установлен в 1, чтобы указать конец блока заголовка
- PADDED: бит 3 установлен в 1 означает, что Pad установлен, есть длина Pad и заполнение.
- ПРИОРИТЕТ: бит 5, установленный в 1, означает наличие эксклюзивного флага (E), зависимости от потока и веса.
пример:
Сжатие и распаковка заголовков
Поле заголовка в HTTP/2 также является ключом с одним или несколькими значениями. Эти поля заголовков используются для HTTP-запросов и ответных сообщений, а также для операций push на стороне сервера.
Список заголовков — это набор из нуля или более полей заголовков. При передаче по соединению список заголовков сериализуется в блоки заголовков с помощью алгоритма сжатия (т.е. HPACK ниже). Затем сериализованный блок заголовка делится на одну или несколько последовательностей байтов, называемых фрагментами блока заголовка, и отправляется в качестве полезной нагрузки через кадры HEADERS, PUSH_PROMISE или CONTINUATION.
Поля заголовков файлов cookie требуют специальной обработки с помощью HTTP-сопоставления, см.8.1.2.5. Compressing the Cookie Header Field
Есть две возможности для полного блока заголовка
- Кадр HEADERS или кадр PUSH_PROMISE плюс установка флага END_HEADERS
- Кадр HEADERS или кадр PUSH_PROMISE без установленного флага END_HEADERS, а также несколько кадров CONTINUATION, где последний кадр CONTINUATION устанавливает флаг END_HEADERS.
Блок заголовка ДОЛЖЕН передаваться как непрерывная последовательность кадров без промежуточных кадров любого другого типа или другого потока. Конечный кадр устанавливает флаг END_HEADERS, чтобы указать конец блока заголовка, что делает блок заголовка логически эквивалентным одному кадру. Получатель объединяет фрагменты для повторной сборки блока заголовка, а затем распаковывает блок заголовка, чтобы восстановить список заголовков.
НАСТРОЙКИ формат кадра
HTTP за пределами G.org/specs/RFC75…
Полезная нагрузка кадра SETTINGS состоит из нуля или более параметров, каждый из которых имеет вид:
+-------------------------------+
| Identifier (16) |
+-------------------------------+-------------------------------+
| Value (32) |
+---------------------------------------------------------------+
-
Identifier
: представляет тип параметра, например, SETTINGS_HEADER_TABLE_SIZE равен 0x1. -
Value
: значение соответствующего параметра
Когда соединение установлено, обе стороны должны отправить кадр SETTINGS, чтобы указать конфигурацию, которую они ожидают от другой стороны.После того, как другая сторона согласится с параметрами конфигурации, она вернет пустой кадр SETTINGS с флагом ACK, чтобы указать подтверждение, и любая сторона в любое время после соединения также может быть. Затем отправьте кадр НАСТРОЙКИ для настройки, параметры в кадре НАСТРОЙКИ будут перезаписаны вновь полученными параметрами.
Кадр SETTINGS действует на все соединение, а не на поток, и идентификатор потока кадра SETTINGS должен быть 0x0, иначе получатель сочтет это ошибкой (PROTOCOL_ERROR).
Кадр НАСТРОЙКИ содержит следующие параметры:
- SETTINGS_HEADER_TABLE_SIZE (0x1): размер таблицы сжатия заголовков, используемой для анализа блока заголовков, начальное значение — 4096 байт.
- SETTINGS_ENABLE_PUSH (0x2): Server Push можно отключить, значение изначально равно 1, что означает, что функция server push разрешена.
- SETTINGS_MAX_CONCURRENT_STREAMS (0x3): представляет максимальное количество потоков, которое отправитель позволяет получателю создать.
- SETTINGS_INITIAL_WINDOW_SIZE (0x4): указывает начальный размер окна управления потоком всех потоков на отправителе, который повлияет на все потоки.Начальное значение – 2^16–1(65535) байт, максимальное значение – 2^31 – 1. Если максимальное значение превышено, значение вернет FLOW_CONTROL_ERROR.
- SETTINGS_MAX_FRAME_SIZE (0x5): указывает максимальное количество байт полезной нагрузки кадра, которое отправитель может получить. Начальное значение составляет 2 ^ 14 (16384) байт. Если значение не находится между начальным значением (2 ^ 14) и максимальное значение (2^24 - 1), вернуть PROTOCOL_ERROR
- SETTINGS_MAX_HEADER_LIST_SIZE (0x6): уведомить одноранговый узел о том, что отправитель готов получить максимальное количество байтов размера списка заголовков. Значение основано на размере несжатого поля заголовка, включая длину имени и значения в байтах, а также дополнительные 32 байта на каждое поле заголовка.
Фрейм НАСТРОЙКИ имеет следующие флаги:
- ACK: Когда бит 0 установлен в 1, это означает, что он получил запрос SETTINGS от другой стороны и согласился установить его Полезная нагрузка кадра SETTINGS, которая устанавливает этот флаг, должна быть пустой.
пример:
Фактический захват пакета обнаружит, что запрос HTTP2 создает соединение и отправляет кадр Magic до инициализации кадра SETTINGS (предисловие к установлению запроса HTTP/2).
В HTTP / 2 оба конца должны отправить преамбулу соединения в качестве окончательного подтверждения используемого протокола и определить начальные настройки соединения HTTP / 2. Клиент и сервер отправляют разные преамбулы соединения.
Содержимое предисловия клиента (соответствующее кадру с номером 23 на приведенном выше рисунке) содержит содержаниеPRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
Последовательность плюс кадр НАСТРОЙКИ, допускающий значение NULL, отправляемый после ответа 101 (Switching Protocols) (представляющий собой успешное обновление) или в качестве первых передаваемых данных приложения для подключения TLS. Если соединения HTTP/2 включены, когда известно, что сервер поддерживает HTTP/2 заранее, преамбула соединения клиента отправляется при установлении соединения.
Предисловие сервера (соответствующее кадру номер 26 на рисунке выше) содержит кадр SETTINGS, допускающий значение NULL, который отправляется в качестве первого кадра после установления соединения HTTP/2. смотрите подробностиHTTP/2 Connection Preface
После отправки преамбулы обе стороны должны отправить кадр SETTINGS с флагом ACK другой стороне, чтобы указать подтверждение, соответствующее кадрам с номерами 29 и 31 на приведенном выше рисунке.
Запросите всю последовательность кадров станции, число после кадра представляет собой идентификатор потока, которому она принадлежит, и, наконец, закройте соединение с помощью кадра GOAWAY:
Кадр GOAWAY имеет самый большой идентификатор потока (например, 29-й кадр на рисунке — самый большой поток), для отправителя он продолжит обрабатывать потоки меньше этого числа, а затем действительно закроет соединение
Поток - Поток
Поток — это просто логическое понятие, которое представляет собой последовательность независимых двунаправленных кадров, которыми обмениваются клиент и сервер в соединении HTTP/2, а поле идентификатора потока каждого кадра указывает, к какому потоку он принадлежит.
Потоки обладают следующими свойствами:
- Одно соединение h2 может содержать несколько одновременных потоков, а кадры разных потоков могут отправляться через два конца.
- Потоки могут создаваться и использоваться клиентами или серверами в одностороннем порядке или совместно
- Потоки могут быть закрыты любой стороной
- Порядок, в котором кадры отправляются в потоке, очень важен, и, наконец, получатель повторно соберет кадры одного и того же идентификатора потока (того же потока) в полный пакет сообщения.
состояние потока
Обратите внимание, что объекты send и recv на рисунке относятся к конечным точкам, а не к текущему потоку.
idle
Все потоки начинаются в состоянии «ожидания». В этом состоянии нет обмена никакими кадрами
Его переход состояния:
- Отправка или получение кадра HEADERS будет простаивать
idle
поток становится открытымopen
Статус, при котором поле идентификатора потока кадра HEADERS указывает идентификатор потока. Тот же кадр HEADERS (с END_STREAM ) также может сделать поток немедленно полузакрытым. - Сервер должен быть открыт в
open
или полузакрытый (дистанционный)half-closed(remote)
Кадр PUSH_PROMISE отправляется в потоке с отслеживанием состояния (инициированном клиентом), где поле идентификатора обещанного потока кадра PUSH_PROMISE указывает объявленный новый поток (инициированный сервером),- На стороне сервера новый поток будет простаивать через
idle
состояние введено зарезервировано (местное)reserved(local)
государство - На стороне клиента новый поток будет простаивать
idle
состояние введено зарезервировано (удаленно)reserved(remote)
государство
- На стороне сервера новый поток будет простаивать через
существует3.2 - Starting HTTP/2 for "http" URIsОсобый случай вводится в:
Клиент инициирует запрос HTTP/1.1 с механизмом обновления. Он хочет создать соединение h2c. Сервер соглашается на обновление и возвращает ответ 101. Запросам HTTP/1.1, отправленным до обновления, назначается идентификатор потока 0x1 и значение приоритета по умолчанию. Этот поток 1 от клиента к серверу неявно переходит в «полузакрытое» состояние, поскольку он завершен как запрос HTTP/1.1. После запуска соединения HTTP/2 для ответа используется поток 1. Подробный процесс можно увидеть нижеМеханизм согласования протокола HTTP/2
Кадры, отличные от HEADERS и PRIORITY, полученные в этом состоянии, обрабатываются как PROTOCOL_ERROR.
диаграмма состоянийsend PP
иrecv PP
Это означает, что обе конечные точки соединения отправили или получили PUSH_PROMISE, а не то, что бездействующий поток отправил или получил PUSH_PROMISE, это было появление PUSH_PROMISE, которое вызвало объявленный поток отidle
статус меняется наreserved
нижеServer-PushСодержимое, отправляемое сервером, и использование PUSH_PROMISE будут подробно описаны в
reserved (local) / reserved (remote)
PUSH_PROMISE, предвосхищенный потокомidle
Состояние входит в это состояние, что означает, что оно готово к серверу.
Его переход состояния:
- Ответ потока, предвосхищаемый фреймом PUSH_PROMISE, начинается с фрейма HEADERS, который немедленно переводит поток на сервер наполовину закрытым (удаленный конец).
half-closed(remote)
состояние, размещается полузакрытым на стороне клиента (локальное)half-closed(local)
состояние и заканчивается кадром, содержащим END_STREAM, который переводит поток в режим закрытия.closed
государство - Любая конечная точка МОЖЕТ отправить кадр RST_STREAM для завершения потока, статус которого определяется
reserved
Преобразовать вclosed
reserved(local)
Потоки в состоянии не могут отправлять кадры, отличные от HEADERS, RST_STREAM, PRIORITY, а получение кадров, кроме RST_STREAM, PRIORITY, WINDOW_UPDATE, рассматривается как PROTOCOL_ERROR.
reserved(remote)
Потоки в состоянии не могут отправлять кадры, отличные от RST_STREAM, WINDOW_UPDATE, PRIORITY, а полученные кадры, кроме HEADERS, RST_STREAM, PRIORITY, обрабатываются как PROTOCOL_ERROR.
open
вopen
Поток состояний может использоваться двумя одноранговыми узлами для отправки любого типа фрейма.
Его переход состояния:
- Любой конец может отправить кадр с флагом END_STREAM, отправитель
half-closed(local)
Статус; получатель перейдет вhalf-closed(remote)
государство - Любой конец может отправить кадр RST_STREAM, что приведет к немедленному поступлению потока.
closed
государство
half-closed (local)
Поток двунаправленный, полузакрытый означает, что поток закрыт в одном направлении, локальный означает, что направление от локального конца к противоположному концу закрыто, а удаленный означает, что направление от противоположного конца к локальному концу закрыто.
Потоки в этом состоянии не могут отправлять кадры, кроме WINDOW_UPDATE, PRIORITY, RST_STREAM.
Когда поток в этом состоянии получает кадр с флагом END_STREAM или любая из сторон отправляет кадр RST_STREAM, он будет преобразован вclosed
государство
Кадр PRIORITY, полученный потоком в этом состоянии, используется для настройки порядка зависимостей потока, вы можете увидеть приоритет потока ниже
half-closed (remote)
Поток в этом состоянии не будет использоваться партнером для отправки кадров, и конечная точка, осуществляющая управление потоком, больше не обязана поддерживать окно управления потоком получателя.
Конечная точка, получающая кадр, отличный от WINDOW_UPDATE, PRIORITY, RST_STREAM, в потоке в этом состоянии, ДОЛЖНА ответить с ошибкой потока STREAM_CLOSED.
Поток в этом состоянии может использоваться конечной точкой для отправки любого типа кадра, и в этом состоянии конечная точка по-прежнему соблюдает ограничения управления потоком на уровне потока.
Когда поток в этом состоянии отправляет кадр с флагом END_STREAM или любая из сторон отправляет кадр RST_STREAM, он превратится вclosed
государство
closed
Поток делегатов закрыт
Потоки в этом состоянии НЕ ДОЛЖНЫ отправлять кадры, отличные от PRIORITY. Отправка кадров PRIORITY регулирует приоритет тех потоков, которые зависят от закрытого потока. Конечные точки ДОЛЖНЫ обрабатывать кадры PRIORITY, хотя это нормально, если поток удален из дерева зависимостей Игнорировать кадры приоритета
В этом состоянии кадр WINDOW_UPDATE или RST_STREAM все еще может быть получен в течение короткого периода времени (периода) после получения кадра DATA или HEADERS с флагом END_STREAM, потому что до того, как удаленный партнер получит и обработает RST_STREAM или кадр с END_STREAM, он может отправлять кадры такого типа. Но конечные точки ДОЛЖНЫ игнорировать полученные WINDOW_UPDATE или RST_STREAM.
Если поток переходит в это состояние после отправки кадров RST_STREAM, а одноранговая сторона, возможно, уже отправила или находится в очереди на отправку при получении кадров RST_STREAM, эти кадры необратимы, и конечная точка, отправляющая кадры RST_STREAM, должна игнорировать эти кадры.
Конечная точка может ограничить продолжительность периода.Кадры, принятые в течение периода, игнорируются, а кадры, превышающие период, рассматриваются как ошибки.
После того, как конечная точка отправит кадр RST_STREAM и получит кадр управления потоком (например, DATA), он все равно будет учитываться в окне потока, даже если эти кадры будут проигнорированы, потому что противоположная сторона должна была отправить кадр управления потоком до получения. кадр RST_STREAM, а противоположный конец будет рассматривать управление потоком в действии
Конечная точка может получить кадр PUSH_PROMISE после отправки кадра RST_STREAM, даже если предикатный поток был сброшен, кадр PUSH_PROMISE позволяет предикативному потоку статьreserved
государство. Следовательно, RST_STREAM требуется, чтобы закрыть нежелательный предвосхищающий поток.
ПРИОРИТЕТНЫЕ кадры могут быть отправлены и получены потоками любого состояния, кадры неизвестного типа игнорируются
Переходы состояния потока
Давайте рассмотрим два примера, чтобы понять состояние потока:
(1) Сервер отправляет кадр PUSH_PROMISE в потоке, инициированном Клиентом, и его обещанный идентификатор потока указывает прогнозируемый поток для последующей отправки.После отправки PP прогнозируемый поток переходит из состояния ожидания в резервное (локальное) состояние в на стороне сервера и на стороне клиента. После получения PP поток переходит из состояния ожидания в резервное (удаленное) состояние.
(2) (3) В это время это указывает на то, что поток находится в зарезервированном состоянии.Если клиент решит отказаться принимать push, он может отправить кадр RST, чтобы закрыть поток; если на этом этапе есть проблема время сервер также может отправить кадр RST, чтобы отменить отправку. Это состояние меняется на «закрыто» независимо от того, какая сторона отправляет или получает RST.
(4) Если сброса нет, значит, пуш еще действителен, затем сервер начинает пуш, и сначала должен быть отправлен блок заголовка ответа HEADERS, в это время статус потока меняется на полузакрытый (удаленный); клиент получает ЗАГОЛОВКИ После изменения состояния потока на полузакрытый (локальный)
(5)(6) Поток в полузакрытом состоянии должен продолжать передавать кадры данных, такие как кадры ДАННЫХ и кадры ПРОДОЛЖЕНИЯ.Если этот процесс сталкивается со сбросом, инициированным какой-либо из сторон, поток будет закрыт и перейдет в закрытое состояние.
(7) Если все пойдет хорошо, ресурс ответит кадром данных, а последний кадр будет помечен END_STREAM, чтобы указать, что поток завершен, и поток будет в закрытом состоянии.
(1) Клиент инициирует запрос, сначала отправляет кадр HEADERS, его идентификатор потока создает новый поток, и поток переходит из состояния ожидания в открытое состояние.
(2) (3) Если клиент отменяет запрос, он может отправить кадр RST, и сервер также может отправить кадр RST в случае ошибки Независимо от того, какая сторона получает или отправляет RST, поток закрывается и переходит в закрытое состояние;
(4) Если запрос завершается (END_STREAM), поток переходит в полузакрытое состояние. Если это запрос GET, кадр HEADERS обычно является последним кадром, и поток переходит в полузакрытое состояние сразу после отправки H. Если это POST-запрос, после передачи данных последний кадр помечается END_STREAM, и поток наполовину закрывается.
(5) (6) После полузакрытия клиента сервер начинает возвращать ответ, в это время одна из сторон получает или отправляет RST, и поток закрывается;
(7), если все хорошо, дождитесь окончания ответа (END_STREAM), поток закрывается
идентификатор потока
Идентификатор потока представляет собой 31-битное целое число без знака. Поток, инициированный клиентом, должен быть нечетным числом, а поток, инициированный сервером, должен быть четным числом. 0x0 зарезервирован для сообщений управления соединением и не может использоваться для установления новых потоки.
Идентификатор потока ответа HTTP/1.1 Upgrade to HTTP/2 — 0x1.После завершения обновления поток 0x1 будет преобразован вhalf-closed (local)
состояние, поэтому клиент не может инициализировать поток с 0x1 в этом случае
Идентификатор вновь созданного потока должен быть больше, чем все используемые числа, и ответ PROTOCOL_ERROR должен быть возвращен, когда получен идентификатор неправильного размера.
При использовании нового потока идентификатор, инициированный одноранговым узлом, меньше, чем у текущего потока, и находится вidle
Поток состояния, например поток, который отправляет кадр HEADERS для открытия потока с идентификатором 7, но никогда не отправлял кадр в поток с идентификатором 5, тогда поток 0x5 будет преобразован в 0x7 после отправки или получения первого кадра.closed
государство
Идентификаторы потока в соединении нельзя использовать повторно
приоритет потока
Клиент может указать приоритет только что созданного потока через информацию о ПРИОРИТЕТЕ кадра HEADERS, а также может отправлять кадры ПРИОРИТЕТА в другие периоды для настройки приоритета потока.
Цель установки приоритетов состоит в том, чтобы конечная точка выразила ожидаемое поведение однорангового узла при распределении ресурсов между несколькими одновременными потоками. Что еще более важно, когда пропускная способность отправки ограничена, можно использовать приоритет для выбора потока для отправки кадров.
Потоки могут быть помечены как зависимые от других потоков, и текущий поток обрабатывается после завершения зависимого потока. За каждой зависимостью следует вес, число, используемое для определения относительной доли доступных ресурсов, которые могут быть выделены в зависимости от того же потока.
Потоковые зависимости
Каждый поток может явно зависеть от другого потока, и включение зависимостей означает, что ресурсы выделяются указанному потоку (верхнему узлу), а не зависимому потоку.
Поток, который не зависит от других потоков, будет указывать зависимость потока со значением 0x0, поскольку несуществующий поток 0x0 представляет собой корень дерева зависимостей.
Поток, зависящий от других потоков, называетсяпоток зависимостей, зависимый поток является родителем текущего потока. Если зависимый поток не находится в текущем дереве зависимостей (например, состояниеidle
поток), зависимый поток будет использовать приоритет по умолчанию
При использовании потока этот поток будет добавлен к родительской зависимости. Зависимые потоки, имеющие общий родительский элемент, не будут сортироваться относительно друг друга. Например, B и C зависят от A, и добавляется новый поток зависимостей D. , Порядок BCD не фиксирован:
A A
/ \ ==> /|\
B C B D C
Исключительный флаг (исключительный) позволяет вставить новый уровень (новое отношение зависимости), исключительный флаг заставляет поток стать единственным зависимым потоком родителя, в то время как другие зависимые потоки становятся его дочерними элементами, например, также вставка новой зависимости поток E (с эксклюзивом):
A
A |
/|\ ==> E
B D C /|\
B D C
В дереве зависимостей потоку зависимостей следует выделять ресурсы только тогда, когда все потоки, от которых он зависит (цепочки с родителем до 0x0 ), закрыты или не могут продолжать выполняться на нем.
Вес зависимости
Всем зависимым потокам присваивается значение веса от 1 до 256.
Зависимые потоки одного и того же родителя распределяют ресурсы пропорционально их весам.Например, поток B зависит от A со значением веса 4, а поток C зависит от A со значением веса 12. Когда A больше не выполняется, теоретически выделенные ресурсы для B составляют только одну треть от C
Приоритизация
Приоритет потока можно настроить с помощью фрейма PRIORITY.
Содержимое кадра PRIORITY такое же, как и модуль приоритета кадра HEADERS:
+-+-------------------------------------------------------------+
|E| Stream Dependency (31) |
+-+-------------+-----------------------------------------------+
| Weight (8) |
+-+-------------+
-
Если приоритет родителя изменен, зависимый поток перемещается вместе со своим родительским потоком. Если поток с измененным приоритетом имеет эксклюзивный флаг, это приведет к тому, что все дочерние элементы нового родительского потока будут зависеть от этого потока.
-
Если поток корректируется так, чтобы он зависел от одного из его собственных дочерних элементов, зависимый дочерний элемент сначала будет перемещен под родителем потока корректировки (то есть на тот же уровень), а затем будет перемещено все поддерево потока корректировки. , а перемещенная зависимость сохранит свой вес
Посмотрите на следующий пример: Первая диаграмма — исходное дерево отношений, и теперь A нужно настроить так, чтобы он зависел от D. Согласно второму пункту, D теперь перемещается ниже x, а затем A корректируется, чтобы быть поддеревом D (рис. 3). Если А настроен с эксклюзивной идентификацией, он также классифицируется как дочерний по первому пункту F (рис. 4).
x x x x
| / \ | |
A D A D D
/ \ / / \ / \ |
B C ==> F B C ==> F A OR A
/ \ | / \ /|\
D E E B C B C F
| | |
F E E
(intermediate) (non-exclusive) (exclusive)
Управление состоянием приоритета потока
Когда поток удаляется из дерева зависимостей, его дочерние элементы могут быть настроены так, чтобы они зависели от родителя закрытого потока (должен быть подключен к узлу верхнего уровня), а новый вес зависимости будет основан на весе закрытого потока. и вес самого потока.
Удаление потока из дерева зависимостей приводит к потере некоторой информации о приоритете. Ресурсы распределяются между потоками с одним и тем же родителем, а это означает, что если поток в этом наборе закрыт или заблокирован, любая свободная емкость будет выделена ближайшему соседнему потоку. Однако, если общие зависимости этого набора (т. е. родительские узлы) будут удалены из дерева, эти подпотоки будут совместно использовать ресурсы с потоками более высокого уровня.
Пример: поток A и поток B зависят от одного и того же родительского узла, а поток C и поток D оба зависят от A. Перед удалением потока A невозможно выполнить и A, и D (возможно, задача заблокирована), тогда C Все ресурсы будет отнесено к А; Если A удаляется из дерева, вес A пересчитывается и назначается пропорционально C и D. В это время D все еще заблокирован, а C выделяет меньше ресурсов, чем раньше. При том же начальном весе доступные ресурсы, полученные C, составляют одну треть вместо одной половины (почему одна треть? В документе не поясняются детали, и неясно, как перераспределяются веса. Далее следует по моему понимание объяснил)
Ресурс узла X равен 1, начальный вес узла ABCD равен 16, а знак * указывает на то, что узел в данный момент недоступен.На рисунке 1 узлы C и B занимают по половине ресурсов, а после удаления узла A вес узла CD становится равным 8, поэтому на рисунке 2 соотношение C и B становится равным 1:2, а R(C) становится равным 1/3.
X(v:1.0) X(v:1.0)
/ \ /|\
/ \ / | \
*A B ==> / | \
(w:16) (w:16) / | \
/ \ C *D B
/ \ (w:8)(w:8)(w:16)
C *D
(w:16) (w:16)
R(C)=16/(16+16)=1/2 ==> R(C)=8/(8+16)=1/3
Возможно, информация о приоритете для создания зависимости от потока все еще находится в пути, и этот поток закрыт. Если точка зависимости для зависимого потока не имеет соответствующей информации о приоритете (т. е. родительский узел недействителен), зависимому потоку будет назначен приоритет по умолчанию, что может привести к неоптимальным приоритетам, поскольку потоку назначается приоритет, который не ожидается. .
Чтобы избежать вышеуказанных проблем, конечной точке СЛЕДУЕТ сохранять информацию о состоянии приоритета потока в течение определенного периода времени после закрытия потока.Чем дольше сохраняется это состояние, тем меньше вероятность того, что потоку будет присвоен неправильный приоритет или приоритет по умолчанию.
Точно так же потокам в состоянии «ожидания» могут быть назначены приоритеты или они могут стать родительскими для других потоков. Это позволяет создавать узлы группировки в дереве зависимостей, обеспечивая более гибкие выражения приоритета. Бездействующие потоки начинаются с приоритетом по умолчанию
Сохранение информации о состоянии приоритета потока может увеличить нагрузку на терминал, поэтому это состояние может быть ограничено. Терминал может решить, сколько дополнительных состояний сохранять, в зависимости от нагрузки; при высокой нагрузке дополнительное состояние приоритета может быть отброшено, чтобы ограничить задачу ресурсов. В крайних случаях терминал может даже отбросить информацию о приоритете потока активного или зарезервированного состояния. Если используется фиксированный предел, терминал ДОЛЖЕН сохранять как минимум столько же состояний потока, сколько указано в настройке SETTINGS_MAX_CONCURRENT_STREAMS.
приоритет по умолчанию
Все потоки изначально включительно зависят от потока 0x0.
Передаваемые потоки изначально зависят от связанного с ними потока (см. Server-Push).
В обоих случаях вес потока указан как 16.
Server-Push
Формат кадра PUSH_PROMISE
+---------------+
|Pad Length? (8)|
+-+-------------+-----------------------------------------------+
|R| Promised Stream ID (31) |
+-+-----------------------------+-------------------------------+
| Header Block Fragment (*) ...
+---------------------------------------------------------------+
| Padding (*) ...
+---------------------------------------------------------------+
-
Pad Length
: указывает длину заполнения, если она существует, устанавливается флаг PADDING. -
R
: зарезервировано 1 бит -
Promised Stream ID
: 31-битное целое число без знака, представляющее поток, зарезервированный кадром PUSH_PROMISE, идентификатор потока должен быть допустимым значением, чтобы отправитель мог использовать его для следующего потока. -
Header Block Fragment
: Фрагмент блока заголовка, содержащий поля заголовка запроса. -
Padding
: Заполнение байта, без особой семантики, функция такая же, как и заполнение DATA, если оно существует, это означает, что установлен флаг PADDING
Фрейм PUSH_PROMISE имеет следующие флаги:
- END_HEADERS: бит 2 установлен в 1, чтобы указать конец блока заголовка
- PADDED: когда бит 3 установлен в 1, это означает, что пэд установлен, есть длина пэда и заполнение.
Нажмите процесс
В сочетании с переходом состояния потока Server-Push выше
Кадры PUSH_PROMISE могут отправляться только в потоках, инициированных одноранговым узлом (клиентом), чей статус потока открыт или полузакрыт (удаленно).
Кадр PUSH_PROMISE готов отправить ответ, всегда связанный с запросом от клиента. Сервер отправляет кадр PUSH_PROMISE в потоке, в котором сделан запрос. Кадр PUSH_PROMISE содержит идентификатор обещанного потока, выбранный из идентификаторов потока, доступных серверу.
Если сервер получает запрос на документ, который содержит встроенные ссылки на несколько файлов изображений, и сервер решает отправить эти дополнительные изображения клиенту, то перед отправкой кадра DATA, содержащего ссылки на изображения, отправьте PUSH_PROMISE. Фреймы гарантируют, что клиент знает, что ресурс должен быть отправлен до обнаружения встроенной ссылки. Аналогичным образом, если сервер готов передать ответ, на который ссылается блок заголовка (например, вПоле заголовка ссылки), отправка кадра PUSH_PROMISE перед отправкой блока заголовка гарантирует, что клиент больше не будет запрашивать эти ресурсы.
После того, как клиент получил кадр PUSH_PROMISE и решил получить отправленный ответ, клиент НЕ ДОЛЖЕН инициировать какие-либо запросы на отправленный ответ до тех пор, пока объявленный поток не будет закрыт.
Обратите внимание, что каждый из четырех ресурсов, переданных на рисунке, указывает на поток (обещанный идентификатор потока), а кадр PUSH_PROMISE отправляется в потоке запроса (идентификатор потока = 1), инициированном клиентом.Клиент получает кадр PUSH_PROMISE и выбирает получение К этим четырем ресурсам не будет делаться никаких запросов, после чего сервер инициирует заданный поток и отправляет ответ, связанный с ресурсом.
По какой-либо причине, если клиент решает больше не получать готовый к отправке ответ от сервера или если серверу требуется слишком много времени для подготовки к отправке предвосхищенного ответа, клиент МОЖЕТ отправить кадр RST_STREAM, который МОЖЕТ использовать ОТМЕНА. Или код REFUSED_STEAM и ссылка на передаваемый идентификатор потока.
Конфигурация nginx Server-Push
server-push требует настроек на стороне сервера, это не означает, что браузер инициирует запрос, и сервер ресурсов, связанный с этим запросом, будет автоматически пропушен.
Возьмем, к примеру, nginx, функция hppt2 serverpush официально поддерживается с версии 1.13.9.
Добавить на соответствующий сервер или модуль локацииhttp2_push
Поля плюс файл с относительным путем могут подталкивать связанные ресурсы при запросе ресурса.Например, мой блог настроен следующим образом.При доступе к домашней странице четыре файла будут активно подталкиваться сервером без запроса клиента:
server_name blog.wangriyu.wang;
root /blog;
index index.html index.htm;
location = /index.html {
http2_push /css/style.css;
http2_push /js/main.js;
http2_push /img/yule.jpg;
http2_push /img/avatar.jpg;
}
Просмотр через консоль браузераPush
отклик:
также можно использоватьnghttp
Протестируйте push-ответ (знак * означает, что он был отправлен сервером):
вышеhttp2_push
Настройки подходят для статических ресурсов, сервер заранее знает, какие файлы нужны клиенту, а затем выборочно проталкивает их.
Если это файл, динамически сгенерированный фоновым приложением (например, файл json), сервер не знает, что нажимать заранее, вы можете использоватьLink
Заголовки ответа для автоматической отправки
добавить в серверный модульhttp2_push_preload on;
server_name blog.wangriyu.wang;
root /blog;
index index.html index.htm;
http2_push_preload on;
Затем установите заголовок ответа (add_header) или добавьте тег ссылки заголовка ответа, когда фоновая программа генерирует файл данных, например
Link: </style.css>; as=style; rel=preload, </main.js>; as=script; rel=preload, </image.jpg>; as=image; rel=preload
nginx будет активно продвигать эти ресурсы в соответствии с заголовком ответа Link.
Более официальное введение в nginx см.Introducing HTTP/2 Server Push with NGINX 1.13.9
Потенциальные проблемы с сервером
прочитать эту статьюОбсуждение Server Push в HTTP/2, обнаружена потенциальная проблема с Server-Push
Server-Push инициирует отправку при выполнении условий, но клиент уже имеет кеш и хочет отправить RST, чтобы отклонить его, а сервер уже отправил ресурсы до получения RST.Хотя эта часть отправки недействительна , это определенно займет пропускную способность
Например, о конфигурации http2_push в моем блоге выше, каждый раз, когда я открываю сервер домашней страницы, я буду отправлять эти четыре файла, но на самом деле браузер знает, что у него есть кеш, и использует локальный кеш, то есть , в период, когда локальный кеш не инвалидный, Server-Push сервера просто играет роль занимающего пропускную способность
Конечно, это на самом деле не оказывает большого влияния на мой небольшой сайт, но если сайту нужно много нажимать, вам нужно рассмотреть и проверить, повлияет ли Server-Push на последующие посещения пользователя.
Кроме того, сервер может установить cookie или сеанс для записи времени доступа, а затем определить, требуется ли push для последующего доступа; кроме того, клиент может ограничить количество потоков PUSH или установить очень низкое окно трафика для ограничения размер данных, отправляемых PUSH.
Что касается того, какие ресурсы должны быть отправлены, в «Авторитетном руководстве по веб-производительности» упоминается несколько стратегий. Например, mod_spdy Apache может идентифицировать заголовок X-Associated-Content, в котором перечислены ресурсы, которые сервер хочет отправить; , некоторые люди в Интернете уже сделали, что Server-Push обрабатывается промежуточным программным обеспечением на основе заголовка Referer; или сервер может более разумно идентифицировать документы и решать, следует ли отправлять или отправлять эти ресурсы в соответствии с текущим трафиком. Я верю, что в будущем будет больше реализаций и приложений Server-Push.
управление потоком
Мультиплексированные потоки конкурируют за ресурсы TCP, что приводит к блокировке потоков. Механизм управления потоком гарантирует, что потоки в одном и том же соединении не мешают друг другу. Управление потоком действует на один поток или на все соединение. HTTP/2 обеспечивает управление потоком с помощью кадра WINDOW_UPDATE.
Управление потоком имеет следующие характеристики:
- Управление потоком зависит от соединения. Оба уровня управления потоком находятся между конечными точками в одном переходе, а не на всем сквозном пути. Например, если перед сервером есть внешний прокси-сервер, такой как Nginx, тогда будет два соединения: браузер-Nginx, Nginx-сервер и управление потоком, соответственно действующие на два соединения. Подробнее см.:How is HTTP/2 hop-by-hop flow control accomplished? - stackoverflow
- Управление потоком основано на кадре WINDOW_UPDATE. Получатель объявляет, сколько байтов он намеревается получить в каждом потоке и в соединении в целом. Это кредитная программа.
- Управление потоком является направленным и полностью контролируется приемником. Получатель может установить произвольный размер окна для каждого потока и всего соединения. Отправитель должен соблюдать ограничения управления потоком, установленные получателем. Когда клиент, сервер и промежуточный прокси действуют как получатели, все они независимо объявляют свои собственные окна управления потоком, а когда они действуют как отправители, все они подчиняются настройкам управления потоком партнера.
- Начальное значение окна управления потоком составляет 65535 байт, будь то для нового потока или всего соединения.
- Тип кадра определяет, применяется ли к кадру управление потоком. В настоящее время управление потоком затрагивает только кадры DATA, все остальные типы кадров не занимают место в окне управления потоком. Это гарантирует, что важные кадры управления не блокируются управлением потоком.
- Управление потоком нельзя отключить.
- HTTP/2 определяет только формат и семантику кадра WINDOW_UPDATE и не указывает, как получатель решает, когда отправлять кадр, какое значение отправлять или как отправитель решает отправлять пакеты. Конкретная реализация может выбрать любой алгоритм, отвечающий требованиям.
Формат кадра WINDOW_UPDATE
+-+-------------------------------------------------------------+
|R| Window Size Increment (31) |
+-+-------------------------------------------------------------+
Приращение размера окна указывает количество байтов, которое отправитель может передать в дополнение к существующему окну управления потоком. Диапазон значений от 1 до 2^31 - 1 байт.
Кадры WINDOW_UPDATE могут быть для потока или для всего соединения. В первом случае идентификатор потока кадра WINDOW_UPDATE указывает затронутый поток, во втором случае идентификатор потока 0 применяется ко всему соединению.
Функция управления потоком применяется только к идентифицированным кадрам, на которые влияет управление потоком. Из типов кадров, определенных в документе, управление потоком затрагивает только кадры DATA. Кадры, на которые не влияет управление потоком, должны быть получены и обработаны, если получатель больше не может выделять ресурсы для обработки этих кадров. Если получатель больше не может получать кадры, он МОЖЕТ ответить ошибкой потока или ошибкой соединения типа FLOW_CONTROL_ERROR.
WINDOW_UPDATE может быть отправлен узлом, отправившим кадр с флагом END_STREAM. Это означает, что получатель может получить кадр WINDOW_UPDATE в полузакрытом (удаленном) или закрытом потоке, который получатель НЕ ДОЛЖЕН рассматривать как ошибку.
Окно управления потоком
Окно управления потоком представляет собой простое целочисленное значение, указывающее количество байтов данных, которые отправителю разрешено передавать. Значение окна измеряет буферную емкость приемника.
Если это не рассматривается как ошибка соединения, когда получатель получает кадр DATA, он всегда должен вычитать его длину из окна управления потоком (за исключением длины заголовка кадра и обоих уровней окна управления). Это необходимо, даже если в кадре есть ошибки, потому что отправитель уже подсчитывает кадр в окне управления потоком, и если получатель этого не сделает, окна управления потоком отправителя и получателя будут несогласованными.
Отправитель НЕ ДОЛЖЕН отправлять кадры, затронутые управлением потоком, длина которых превышает доступное пространство в окне управления потоком для обоих уровней, объявленных получателем. Кадры длины 0 с установленным флагом END_STREAM (т. е. пустые кадры DATA) могут быть отправлены, даже если нет свободного места ни для одного из уровней окна управления потоком.
Кадр WINDOW_UPDATE может быть отправлен, когда получатель кадра израсходовал данные и освободил место в окне управления потоком. Для окон управления потоком на уровне потока и на уровне соединения кадры WINDOW_UPDATE необходимо отправлять отдельно.
При создании нового соединения начальный размер окна как для потоков, так и для соединений составляет 2^16 - 1(65535) байт. Начальный размер окна потока можно изменить, установив параметр SETTINGS_INITIAL_WINDOW_SIZE кадра SETTINGS в преамбуле соединения, который влияет на все потоки. и连接的初始窗口大小不能改,但可以用 WINDOW_UPDATE 帧来改变流量控制窗口
, поэтому преамбулы подключения часто содержат кадр WINDOW_UPDATE.
Помимо изменения окна управления потоком для неактивных потоков, фрейм SETTIGNS также может изменять размер начального окна управления потоком для активных потоков (потоков в открытом или полузакрытом (удаленном) состоянии). То есть, когда значение SETTINGS_INITIAL_WINDOW_SIZE изменяется, получатель должен настроить значение окна управления потоком для всех потоков, которые он поддерживает, независимо от того, является ли это ранее открытым потоком или потоком, который не был открыт.
Изменение SETTINGS_INITIAL_WINDOW_SIZE может привести к тому, что свободное пространство окна управления потоком станет отрицательным. Отправитель ДОЛЖЕН отслеживать отрицательные окна управления потоком и не может отправлять новые кадры DATA, пока не получит кадр WINDOW_UPDATE, который делает окно управления потоком положительным.
Например, если клиент отправляет 60 КБ данных, как только соединение установлено, но сервер устанавливает начальный размер окна равным 16 КБ, клиент пересчитает доступное окно управления потоком до -44 КБ, как только клиент получит кадр SETTINGS. . Клиент поддерживает отрицательное окно управления потоком до тех пор, пока кадр WINDOW_UPDATE не восстановит значение окна до положительного значения, после чего клиент сможет продолжить отправку данных.
Если изменение SETTINGS_INITIAL_WINDOW_SIZE приводит к тому, что окно управления потоком превышает максимальное значение, одна сторона ДОЛЖНА рассматривать это как ошибку соединения типа FLOW_CONTROL_ERROR.
Если получатель хочет использовать окно управления потоком меньше текущего значения, он может отправить новый кадр SETTINGS. Однако получатель должен быть готов к приему данных, превышающих это значение окна, поскольку отправитель мог отправить данные, превышающие это меньшее значение окна, до получения кадра SETTIGNS.
Добросовестное использование управления потоком
Определение управления потоком состоит в том, чтобы защитить работу конечных точек при ограничении ресурсов. Например, прокси-сервер, которому необходимо совместно использовать память между многими соединениями, может также иметь медленные исходящие соединения и быстрые нисходящие соединения. Управление потоком предназначено для ситуации, когда получатель не может обработать данные в одном потоке, но хочет продолжить обработку других потоков в том же соединении.
Развертывания, не требующие этой функции, могут объявить окно управления потоком максимального размера (2 ^ 31 - 1) и могут поддерживать этот размер окна неизменным, отправляя кадр WINDOW_UPDATE при получении любых данных. Это эффективно отключает управление потоком на приемнике. И наоборот, отправитель всегда связан окном управления потоком, объявленным получателем.
Планирование в условиях ограничений ресурсов (таких как память) может использовать трафик для ограничения объема памяти, который может потреблять одноранговый узел. Обратите внимание, что включение управления потоком без знания произведения полосы пропускания на задержку может привести к неоптимальному использованию доступных сетевых ресурсов (RFC1323).
Даже при хорошем понимании текущих продуктов сетевых задержек реализация управления потоком может быть сложной. При использовании управления потоком получатель должен своевременно считывать данные из приемного буфера TCP. Это может привести к взаимоблокировкам, когда некоторые ключевые кадры, такие как WINDOW_UPDATE, недоступны в HTTP/2. Но управление потоком может гарантировать, что ограниченные ресурсы могут быть защищены без снижения использования соединения.
Механизм согласования протокола HTTP/2
Переговоры без шифрования - h2c
Клиент использует механизм HTTP Upgrade для запроса обновления. Поле заголовка HTTP2-Settings — это поле заголовка, относящееся к конкретному соединению. Оно содержит параметры для управления соединениями HTTP/2 (с использованием кодировки Base64) при условии, что сервер примет запрос на обновление. .
GET / HTTP/1.1
Host: server.example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>
Если сервер поддерживает http/2 и согласен на обновление, сконвертируйте протокол, иначе игнорируйте
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: h2c
На данный момент существует потенциальный поток 0x1, и этот поток на клиенте будет преобразован в после выполнения запроса h1.half-closed
Статус, сервер будет использовать этот поток для возврата ответа
Обратите внимание, что поток, в котором находится первый ответ на рисунке, имеет адрес 0x1, что согласуется с приведенным выше
В настоящее время браузер поддерживает связь только по протоколу HTTP/2 с шифрованием TLS, поэтому описанная выше ситуация в настоящее время невозможна в браузере.На рисунке показан запрос, инициированный клиентом nghttp.
Зашифрованный механизм переговоров — h2
Передано во время Client-Hello и Server-Hello при шифровании TLSALPNЗаключить соглашение
Согласование протокола прикладного уровня В расширении первого шага рукопожатия TLS в Client Hello клиент указывает следующий протокол ALPN как h2 или http/1.1, чтобы указать протоколы, поддерживаемые клиентом.
Если сервер выбирает расширение h2 в Server Hello, это означает, что протокол согласования - h2, и соответствующий ответ на последующий запрос изменяется; если сервер не устанавливает http/2 или не поддерживает h2, он будет продолжать общаться по http /1.1
Пример анализа
196: первый шаг рукопожатия TLS — Client Hello, начинается согласование протокола, и сюда передается билет сеанса.
200: Server Hello соглашается использовать h2, и билет сеанса клиента действителен, сеанс возобновляется и рукопожатие успешно
202: Клиент также возобновляет сеанс и начинает шифровать последующие сообщения.
205: сервер инициирует префикс соединения (НАСТРОЙКИ), кадр НАСТРОЙКИ устанавливает максимальное количество параллельных потоков, начальный размер окна и максимальную длину кадра, а затем (WINDOW_UPDATE) расширяет размер окна
310:Клиент также отправляет предисловие к подключению Magic и инициализирует настройки (SETTINGS), во фрейме SETTINGS задается размер HEADER TABLE, начальный размер окна, максимальное количество параллельных потоков, а затем (WINDOW_UPDATE) расширяется размер окна
311: клиент может немедленно отправить запрос после отправки преамбулы соединения, GET / (HEADERS[1]), и этот кадр HEADERS также имеет END_STREAM, что приведет к немедленному переходу потока 1 из состояния ожидания в полузакрытое ( local ) состояние (open — это промежуточное состояние)
311: Это сообщение также содержит кадр SETTINGS с подтверждением, отправленным клиентом на сервер.
312: Сервер также ответил на фрейм SETTINGS с помощью ACK.
321: сервер отправляет четыре кадра PUSH_PROMISE в потоке 1 (состояние полузакрыто (удаленно) в это время), и они резервируют потоки 2, 4, 6 и 8 для последующих push-уведомлений соответственно.
321: Ответ на вышеуказанный запрос (HEADERS - DATA) также возвращается в этом сообщении, и, наконец, DATA сопровождается END_STREAM, а поток 1 изменяется с полузакрытого на закрытый.
329: Настройка приоритета потока, зависимости: 8 -> 6 -> 4 -> 2 -> 1 (все с эксклюзивными флагами и всеми весами 110)
342: После закрытия потока 1 потоку 2 выделяются ресурсы, сервер начинает отправлять данные, и данные возвращаются двумя кадрами DATA.
344: Конец потока 2, начать отправку потока 4
356: настроить зависимости
1 1 1 1(w: 110)
| | | |
2 2 2 2(w: 110)
| | | |
4 ==> 4 ==> 6 ==> 6(w: 147)
| | | |
6 8 4 8(w: 147)
| | | |
8 6 8 4(w: 110)
367, 369, 372: отправка данных потока для 6 и 8
377: инициировать запрос на открытие потока 3, где все запросы, инициированные клиентом, зависят от потока 0x0.
После этого та же процедура завершает запрос-ответ и, наконец, заканчивается кадром GOAWAY, закрывающим соединение.
Алгоритм HPACK
Изображение выше взято из PPT Ильи Григорика.HTTP/2 is here, let's optimize!
Хорошо видно, что заголовок HTTP2 также использует значения в виде пар ключ-значение, а строка запроса и строка состояния в HTTP1 также разбиты на пары ключ-значение, и все ключи строчные, в отличие от HTTP1. . Кроме того, существует индексное пространство, включающее статическую индексную таблицу и динамическую индексную таблицу. Таблица значений ключа заголовка будет сжата во время фактической передачи. Используемый алгоритм — HPACK. Принцип заключается в том, чтобы соответствовать индексному пространству, существующему в текущем соединении. Если значение ключа уже существует, соответствующий индекс будет использоваться для замены записи заголовка. Например, ":method: GET" может соответствовать индексу 2 в статическом индексе, и при передаче необходимо передать только один байт, содержащий 2; если пространство индекса Если он не существует в , то он передается с кодировкой символов.Кодировка символов может выбрать кодировку Хаффмана, а затем решить, нужно ли хранить ее в таблице динамических индексов в зависимости от ситуации.
диаграмма направлений
статический индекс
Таблица статических индексов является фиксированной и одинакова для клиента и сервера.Согласованный текущим протоколом статический индекс содержит 61 ключевое значение.Подробнее см.Static Table Definition - RFC 7541
Например, первые несколько таковы
показатель | значение поля | ключевое значение |
---|---|---|
index | Header Name | Header Value |
1 | :authority | |
2 | :method | GET |
3 | :method | POST |
4 | :path | / |
5 | :path | /index.html |
6 | :scheme | http |
7 | :scheme | https |
8 | :status | 200 |
динамический индекс
Таблица динамического индекса — это таблица с ограниченным пространством, поддерживаемая очередью FIFO, которая содержит индексы для нестатических таблиц. Таблица динамического индекса должна поддерживаться обеими сторонами соединения, и ее содержимое зависит от контекста соединения.Соединение HTTP2 имеет одну и только одну динамическую таблицу. Когда заголовок не соответствует индексу, вы можете вставить его в таблицу динамических индексов, и следующее значение с тем же именем может найти индекс в таблице и заменить его. Но не все значения ключей заголовков будут храниться в динамическом индексе, потому что таблица динамического индекса ограничена пространством, максимальное значение устанавливается параметром SETTINGS_HEADER_TABLE_SIZE (по умолчанию 4096 байт) во фрейме SETTING
- Как рассчитать размер таблицы динамического индекса (Table Size):
Размер указан в байтах, размер таблицы динамического индекса равен сумме размеров всех записей, размер каждой записи = длина поля + длина значения ключа + 32
Эти дополнительные 32 байта являются предполагаемыми служебными данными записи. Например, запись использует два 64-битных указателя для указания на поле и значение ключа соответственно и использует два 64-битных целых числа для записи количества ссылок на поле и значение ключа. .
Реализация golang также добавляет 32:golang.org/small/net/HTTP2…
Кадр SETTING указывает максимальный размер динамической таблицы, но кодировщик может дополнительно выбрать значение меньше SETTINGS_HEADER_TABLE_SIZE в качестве размера полезной нагрузки динамической таблицы.
- Как обновить максимальную емкость таблицы динамического индекса
Изменить максимальную емкость динамической таблицы, которую можно отправитьdynamic table size update
сигнал к изменению:
+---+---+---+---+---+---+---+---+
| 0 | 0 | 1 | Max size (5+) |
+---+---------------------------+
Префикс 001 означает, что этот байтdynamic table size update
сигнал, используемый позжеМетод целочисленного кодирования для N=5Указывает новую максимальную емкость динамической таблицы (не может превышать SETTINGS_HEADER_TABLE_SIZE), и метод ее расчета будет описан ниже.
Следует отметить, что этот сигнал должен быть отправлен до отправки блока заголовка или между двумя передачами блока заголовка.Вы можете очистить существующую динамическую таблицу, отправив сигнал обновления с максимальным размером 0.
- Когда таблица динамического индекса должна вытеснять запись
- Всякий раз, когда есть сигнал для обновления размера таблицы, необходимо оценивать и исключать запись в конце очереди, то есть старый индекс, до тех пор, пока текущий размер не станет меньше или равен новой емкости.
- Всякий раз, когда вставляется новая запись, необходимо оценить и исключить запись в конце очереди, пока текущий размер не станет меньше или равен емкости. Вставка новой записи большего размера, чем максимальный размер, в этом случае не считается ошибкой, но в результате таблица динамического индекса будет очищена.
Что касается управления динамической таблицей индексов, рекомендуется посмотреть реализацию golang:golang.org/small/net/HTTP2…, процесс можно лучше понять через код
адресное пространство индекса
Адресное пространство индекса может состоять из статической индексной таблицы и динамической индексной таблицы:
<---------- Index Address Space ---------->
<-- Static Table --> <-- Dynamic Table -->
+---+-----------+---+ +---+-----------+---+
| 1 | ... | s | |s+1| ... |s+k|
+---+-----------+---+ +---+-----------+---+
⍋ |
| ⍒
Insertion Point Dropping Point
В настоящее время s равно 61, и когда в таблицу динамического индекса будет вставлено новое значение ключа, оно будет вставлено в очередь из индекса 62, поэтому индекс в таблице динамического индекса хранит значения ключа из от нового к старому в порядке от меньшего к большему.
представление типа кодирования
Кодирование HPACK использует два примитивных типа: целые числа переменной длины без знака и строки, представленные октетами, и соответственно задаются следующие два метода кодирования.
Целочисленное кодирование
Целочисленное кодирование может использоваться для представления значений индекса поля, значений индекса записи заголовка или длины строки. Целочисленное кодирование состоит из двух частей: байта префикса и необязательной последовательности завершающих байтов.Заканчивающие байты требуются только в том случае, если байтов префикса недостаточно для выражения целочисленного значения,Доступные биты N в октете префикса являются параметром целочисленного кодирования.
Например, ниже приведено целочисленное кодирование с N=5 (первые три бита используются для других идентификаторов).Если целочисленное значение, которое мы хотим закодировать, меньше 2^N - 1, оно может быть непосредственно представлено префиксом байт, например 10. используйте???01010
выражать
+---+---+---+---+---+---+---+---+
| ? | ? | ? | Value |
+---+---+---+-------------------+
Если кодируемое целочисленное значение X больше или равно 2^N - 1, доступные биты байта префикса устанавливаются в 1, а затем значение R получается путем вычитания 2^N - 1 из X, что представлен одной или несколькими последовательностями байтов R, старший бит (msb) каждого байта в последовательности байтов используется для указания того, является ли конец,Когда msb установлен в 0, он представляет последний байт. Для конкретного кодирования см. следующий псевдокод и пример
+---+---+---+---+---+---+---+---+
| ? | ? | ? | 1 1 1 1 1 |
+---+---+---+-------------------+
| 1 | Value-(2^N-1) LSB |
+---+---------------------------+
...
+---+---------------------------+
| 0 | Value-(2^N-1) MSB |
+---+---------------------------+
кодирование:
if I < 2^N - 1, encode I on N bits
else
encode (2^N - 1) on N bits
I = I - (2^N - 1)
while I >= 128
encode (I % 128 + 128) on 8 bits
I = I / 128
encode I on 8 bits
расшифровка:
decode I from the next N bits
if I < 2^N - 1, return I
else
M = 0
repeat
B = next octet
I = I + (B & 127) * 2^M
M = M + 7
while B & 128 == 128
return I
Например, используйте целочисленное кодирование N=5 для представления 1337:
1337 больше 31 (2 ^ 5 - 1), заполните последние пять бит байта префикса 1
I = 1337 - (2^5 - 1) = 1306
I все еще больше 128, I % 128 = 26, 26 + 128 = 154
154 двоичный код: 10011010, это первый последующий байт
I = 1306 / 128 = 10, I меньше 128, петля заканчивается
Закодируйте I в двоичном формате: 00001010, который является последним байтом.
+---+---+---+---+---+---+---+---+
| X | X | X | 1 | 1 | 1 | 1 | 1 | Prefix = 31, I = 1306
| 1 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 1306 >= 128, encode(154), I=1306/128=10
| 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 10 < 128, encode(10), done
+---+---+---+---+---+---+---+---+
При декодировании прочитайте первый байт и обнаружите, что значение I, соответствующее последним пяти битам (11111), равно 31 (>= 2^N - 1), что указывает на наличие следующих байтов; пусть M=0, продолжайте чтобы прочитать следующее слово Раздел B, I = I + (B & 127) * 2 ^ M = 31 + 26 * 1 = 57, M = M + 7 = 7, старший бит равен 1, что указывает на то, что последовательность байтов не закончился, B указывает на следующий байт; I = I + (B & 127) * 2^M = 57 + 10 * 128 = 1337, старший бит равен 0, что указывает на конец байт-кода, вернуть I
1306 можно обрабатывать и так: 1306 = 0x51a = (0101 0001 1010)B, а битовая последовательность сгруппирована в группы по 7 от младшего к старшему, далее идет первая группа 001 1010, вторая группа 000 1010, плюс старшие значащие биты 0/1 соответствуют следующим байтам выше
Кодировка символов
Строка, которая может представлять поле или значение ключа записи заголовка. Кодировки символов представлены с помощью последовательностей байтов, либо непосредственно с использованием кода октета символа, либо с использованием кодировки Хаффмана.
+---+---+---+---+---+---+---+---+
| H | String Length (7+) |
+---+---------------------------+
| String Data (Length octets) |
+-------------------------------+
- H: бит, указывающий, следует ли использовать кодирование Хаффмана.
- Длина строки: представляет длину последовательности байтов, то есть длину строковых данных, представленную методом целочисленного кодирования N = 7.
- Строковые данные: Представление строки в виде последовательности октетов, если H равно 0, здесь представлено кодовое представление октетов исходного символа; если H равно 1, здесь представлена кодировка Хаффмана исходного символа.
RFC 7541 дает таблицу кодирования Хаффмана для символов:Huffman Code, который представляет собой кодировку Хаффмана, основанную на большом количестве данных заголовка HTTP.
- Первый столбец (sym) представляет кодируемый символ, а последний специальный символ «EOS» представляет собой конец строки.
- Второй столбец (код в виде битов) представляет собой двоичный код Хаффмана, выровненный по старшему биту.
- Третий столбец (шестнадцатеричный код) представляет собой шестнадцатеричный код Хаффмана, выровненный по младшему значащему биту.
- Последний столбец (len) представляет длину кодирования в битах.
При использовании кодировки Хаффмана могут быть кодировки, которые не являются целыми байтами и будут дополнены 1, чтобы сделать их целыми байтами.
Например следующий пример:
:authority: blog.wangriyu.wang
Соответствующая кодировка заголовка:
41 8e 8e 83 cc bf 81 d5 35 86 f5 6a fe 07 54 df
Literal Header Field with Incremental Indexing — Indexed Name
формат кодирования см. ниже
41 (0100 0001) указывает, что значение индекса поля равно 1, что соответствует первому элементу в статической таблице :authority
8e (1000 1110) Старший бит равен 1, чтобы указать, что значение ключа использует кодировку Хаффмана, 000 1110 указывает, что длина последовательности байтов равна 14.
позже8e 83 cc bf 81 d5 35 86 f5 6a fe 07 54 df
представляет собой кодирующую последовательность Хаффмана
Согласно таблице кодирования Хаффмана, 100011 -> «b», 101000 -> «l», 00111 -> «o», 100110 -> «g», 010111 -> «.», 1111000 -> «w», 00011 -> 'a', 101010 -> 'n', 100110 -> 'g', 101100 -> 'r', 00110 -> 'i', 1111010 -> 'y', 101101 -> 'u'
8e 83 cc bf 81 d5 35 86 f5 6a fe 07 54 df
|
⍒
1000 1110 1000 0011 1100 1100 1011 1111 1000 0001 1101 0101 0011 0101 1000 0110 1111 0101 0110 1010 1111 1110 0000 0111 0101 0100 1101 1111
|
⍒
100011 101000 00111 100110 010111 1111000 00011 101010 100110 101100 00110 1111010 101101 010111 1111000 00011 101010 100110 11111
|
⍒
blog.wangriyu.wang 最后 11111 用于填充
бинарный код
Теперь начинается истинная спецификация кодека HPACK.
Представление индексированного поля заголовка
Indexed Header Field
Начиная с 1 в качестве идентификатора, заголовок, который может соответствовать индексу в индексном пространстве, будет заменен этой формой, а следующий индекс использует указанный выше метод целочисленного кодирования и N = 7.
Например:method: GET
Может быть представлен 0x82, что равно 10000010.
+---+---+---+---+---+---+---+---+
| 1 | Index (7+) |
+---+---------------------------+
Представление неиндексированного литерального поля заголовка
Заголовок, который не проиндексирован, имеет три представления, первое будет добавлено в индекс, второе не будет добавлено в индекс для текущего перехода, а третье категорически запрещено добавлять в индекс
- Будет добавлено буквальное поле заголовка с добавочной индексацией
Начиная с 01, этот заголовок будет добавлен в список декодированных заголовков (Список заголовков) и будетВставить как новую запись в таблицу динамического индекса
Literal Header Field with Incremental Indexing — Indexed Name
Если у поля уже есть индекс, но значение ключа не индексируется, например заголовок:authority: blog.wangriyu.wang
поле:authority
Индекс уже существует, но значение ключаblog.wangriyu.wang
Если индекса нет, он будет заменен следующей формой (индекс представлен целочисленным кодированием N=6)
+---+---+---+---+---+---+---+---+
| 0 | 1 | Index (6+) |
+---+---+-----------------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length octets) |
+-------------------------------+
Literal Header Field with Incremental Indexing — New Name
Если ни поле, ни ключ не проиндексированы, напримерupgrade-insecure-requests: 1
, он будет заменен следующей формой
+---+---+---+---+---+---+---+---+
| 0 | 1 | 0 |
+---+---+-----------------------+
| H | Name Length (7+) |
+---+---------------------------+
| Name String (Length octets) |
+---+---------------------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length octets) |
+-------------------------------+
- Буквальное поле заголовка без индексации
Начиная с 0000, этот заголовок будет добавлен в список декодируемых заголовков, ноНе вставляется в таблицу динамического индекса
Literal Header Field without Indexing — Indexed Name
Если у поля уже есть индекс, но значение ключа не проиндексировано, оно будет заменено следующей формой (индекс представлен целочисленной кодировкой N=4)
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 0 | Index (4+) |
+---+---+-----------------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length octets) |
+-------------------------------+
Literal Header Field without Indexing — New Name
Если ни поле, ни значение ключа не проиндексированы, они будут заменены следующей формой. Напримерstrict-transport-security: max-age=63072000; includeSubdomains
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 0 | 0 |
+---+---+-----------------------+
| H | Name Length (7+) |
+---+---------------------------+
| Name String (Length octets) |
+---+---------------------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length octets) |
+-------------------------------+
- Поле буквального заголовка никогда не индексировалось
Это похоже на предыдущий заголовок, за исключением того, что он идентифицируется как 0001, и заголовок также будет добавлен в список декодированных заголовков, но не будет вставлен в таблицу динамического обновления.
Разница заключается в формате, в котором этот тип заголовка отправляется, и такой же формат используется для приема, который действует на каждом прыжке.Если прокси передается в середине, прокси должен быть отправлен как есть и не может быть кодируется отдельно.
Предыдущий заголовок действует только при текущем переходе и может быть перекодирован после прохождения через прокси.
использовать один в реализации golangSensitive
Укажите, какие поля никогда не следует индексировать:golang.org/small/net/HTTP2…
Причина этого подробно описана в документе RFC:Never-Indexed Literals
Представление такое же, как и в предыдущем заголовке, за исключением идентификатора:
Literal Header Field Never Indexed — Indexed Name
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 1 | Index (4+) |
+---+---+-----------------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length octets) |
+-------------------------------+
Literal Header Field Never Indexed — New Name
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 1 | 0 |
+---+---+-----------------------+
| H | Name Length (7+) |
+---+---------------------------+
| Name String (Length octets) |
+---+---------------------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length octets) |
+-------------------------------+
Динамическое обновление размера таблицы
Начиная с 001 в качестве логотипа, роль упоминалась ранее.
+---+---+---+---+---+---+---+---+
| 0 | 0 | 1 | Max size (5+) |
+---+---------------------------+
Обновления с максимальным размером 0 можно отправлять для очистки таблицы динамического индекса.
Пример
Многие примеры приведены в RFC.Examples - RFC 7541, рекомендуется прочитать его еще раз, чтобы углубить ваше понимание
What then ?
Демонстрация HTTP/2
До и после того, как веб-сайт включает h2, используйтеWebPageTestТест сделан, первый h1, второй h2:
Рекомендуется использовать HTTP/2
Чтобы включить HTTP2 в nginx, просто добавьте соответствующие настройки HTTPShttp2
Просто
listen [::]:443 ssl http2 ipv6only=on;
listen 443 ssl http2;
Следующие пункты одинаково применимы как к HTTP/1, так и к HTTP/2.
1. Включите сжатие
Настройка gzip и т. д. может сделать передачу меньше и быстрее.
Например, nginx может добавить в модуль http следующие поля, остальные поля и подробные пояснения можно погуглить
gzip on; // 开启
gzip_min_length 1k;
gzip_comp_level 1; // 压缩级别
gzip_types text/plain application/javascript application/x-javascript application/octet-stream application/json text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png font/ttf font/otf image/svg+xml; // 需要压缩的文件类型
gzip_vary on;
gzip_disable "MSIE [1-6]\.";
2. Используйте кеш
Очень важно установить период кэширования для статических ресурсов, см. другую запись в блоге о кэшировании.HTTP Message
Например, nginx может установить время кеша, добавив следующие поля в модуль сервера
location ~* ^.+\.(ico|gif|jpg|jpeg|png|moc|mtn|mp3|mp4|mov)$ {
access_log off;
expires 30d;
}
location ~* ^.+\.(css|js|txt|xml|swf|wav|json)$ {
access_log off;
expires 5d;
}
location ~* ^.+\.(html|htm)$ {
expires 24h;
}
location ~* ^.+\.(eot|ttf|otf|woff|svg)$ {
access_log off;
expires 30d;
}
3. Ускорение CDN
Преимущество CDN — ближайший доступ, низкая задержка и быстрый доступ
4. Уменьшите DNS-запросы
Для каждого доменного имени требуется DNS-запрос, который обычно занимает от нескольких миллисекунд до сотен миллисекунд, а в мобильной среде он будет медленнее. Запросы блокируются до завершения разрешения DNS. Сокращение DNS-запросов также является одной из оптимизаций.
браузерDNS PrefetchingТехнология также является средством оптимизации
5. Уменьшите перенаправление
Перенаправления могут привести к появлению новых DNS-запросов, новых TCP-соединений и новых HTTP-запросов, поэтому также важно сократить количество перенаправлений.
Браузеры в основном кэшируют переходы, указанные как 301 Moved Permanently, поэтому для постоянных переходов вы можете использовать код состояния 301. Для веб-сайтов с поддержкой HTTPS настройте политики HSTS, которые также могут уменьшить количество перенаправлений с HTTP на HTTPS.
Но следующие пункты не рекомендуется использовать в HTTP/2.
1. Шардинг домена
HTTP/2 использует одно TCP-соединение для одного и того же доменного имени, слишком много TCP-соединений тратят ресурсы впустую, и эффект не обязательно хороший.
Кроме того, сегментация ресурсов уничтожит функцию приоритета HTTP/2 и уменьшит эффект сжатия заголовков.
2. Консолидация ресурсов
Слияние ресурсов нанесет ущерб механизму кэширования, а слишком большой файл не годится для передачи по протоколу HTTP/2.
3. Встраивание ресурсов
HTTP/2 поддерживает Server-Push, который более эффективен, чем встроенный
И встроенные ресурсы не кэшируются эффективно
Если есть совместное использование, многостраничное встраивание также приведет к потерям.
Лучшие практики HTTP/2
Используйте HTTP/2, чтобы использовать как можно меньше соединений, потому что чем больше запросов и ответов генерируется по одному и тому же соединению, тем больше накапливается динамических словарей, тем лучше эффект сжатия заголовков и выше эффективность мультиплексирования. связи
Для этого необходимо отметить два момента:
- Ресурсы под одним и тем же доменным именем используют одно и то же соединение, что является особенностью HTTP/2.
- Для ресурсов под разными доменными именами, если они могут разрешаться в один и тот же IP-адрес или использовать один и тот же сертификат (например, общий сертификат доменного имени), HTTP/2 может объединять несколько подключений.
Таким образом, использование одного и того же IP-адреса и сертификата для развертывания веб-служб в настоящее время является наилучшим выбором, поскольку это позволяет терминалам, поддерживающим HTTP/2, повторно использовать одно и то же соединение и реализовать преимущества протокола HTTP/2, а поддерживает только HTTP/1.1. , Терминал будет устанавливать разные соединения с разными доменными именами для достижения цели одновременного выполнения большего количества одновременных запросов.
Например, ряд сайтов Google используют один и тот же сертификат:
Но это также, кажется, вызывает проблему.Веб-сервер, который я построил с помощью nginx, имеет три виртуальных хоста, и они имеют общий набор сертификатов.Два из них имеют явно настроенный http2, а другой я не настроил http2.В результате Я получаю доступ к сайтам, которые не настроены с http2, также становятся http2.
Проблемы, возникающие при передаче больших изображений
Сначала сравните время загрузки страницы h1 и h2.На рисунке зеленый цвет представляет время для инициирования запроса и получения ответа и ожидания загрузки, а синий цвет представляет время для загрузки нагрузки:
Можно обнаружить, что время загрузки h2 немного медленнее, чем у h1, особенно при столкновении с большими изображениями, разница более очевидна.
В этой статье тестируется загрузка изображений h1 и h2 в разных сценариях:Реальный HTTP/2: 400 ГБ изображений в день
результат:
-
Для типичного богатого изображения интерфейс с ограниченной задержкой. При высокоскоростном соединении с малой задержкой визуальное завершение выполняется в среднем на 5% быстрее.
-
Для страницы с большим количеством изображений и ограниченной пропускной способностью. При том же соединении визуальное завершение будет в среднем на 5–10% медленнее, но общее время загрузки страницы фактически сократится благодаря меньшей задержке соединения.
-
Низкоскоростное соединение с большой задержкой (например, медленный 3G на мобильных устройствах) может вызвать огромную задержку визуального завершения страницы, но h2 значительно лучше.
Во всех тестах видно: h2 ускоряет общую загрузку страницы и лучше справляется с начальным рендером, хотя во втором случае визуальная отделка немного падает, общий эффект все равно хороший
Причина снижения визуального завершения в том, что в HTTP/1.x нет ограничения на количество одновременных подключений, h2 может инициировать запросы на несколько картинок одновременно, а сервер может отвечать на загрузку картинок в то же время, как вы можете видеть из анимации ниже
Как только изображения будут загружены, браузер отобразит их, однако маленькие изображения будут отображаться быстрее после загрузки, но если большое изображение окажется начальным видом, загрузка займет больше времени, задерживая визуальную степень завершения.
chrome bug
Вышеприведенная анимация - результат теста в Safari, картинки в итоге успешно скачались, а часть картинок позади меня прямо зависла, когда я тестировал ее в Chrome, и все они сообщалиERR_SPDY_PROTOCOL_ERROR
ошибка, и она 100% воспроизводима
взглянулERR_SPDY_PROTOCOL_ERROR
Где это, я обнаружил, что это поток сброса сервера, который должен был пойти не так и привести к преждевременному завершению потока.
Затем я снова изучил последовательность кадров HTTP/2.Все отправленные запросы были успешно отвечены в сообщении № 629, но возвращенный кадр данных был только в потоке 15, а фактически полученные изображения были не только изображениями, соответствующими потоку 15. Это почему?
Позже я продолжил тестирование и обнаружил, что постоянно запрашивались несколько больших картинок.Хотя кадры HEADERS открывают разные потоки, возвращенные кадры HEADERS ответа также соответствуют идентификаторам предыдущего потока, но все кадры DATA ответа открываются из первый вернулся на стрим.
Если это маленькое изображение, поток закрывается после ответа на запрос, и следующее маленькое изображение возвращается в своем собственном соответствующем потоке. Вышеупомянутая ситуация возникает только с несколькими последовательными большими изображениями.Этот механизм очень странный, и я пока не нашел документа, объясняющего его.
Что касается того, почему chrome не так, если вы посмотрите на TCP-пакеты, вы обнаружите, что все данные отправляются по одному соединению, и позже будут различные проблемы с TCP-пакетами, такие как потеря пакета, повторная передача, выход из -порядок, переупаковка и т.д. Непонятно, так ли и сафари, т.к. wireshark умеет распаковывать только пакет хрома, но не пакет сафари
В Definitive Guide to Web Performance упоминаются проблемы, которые может вызвать TCP в HTTP/2: Хотя явление блокировки заголовка строки HTTP устранено, проблема блокировки заголовка строки на уровне TCP все еще существует; если масштабирование окна TCP отключено, тоЭффект продукта задержки полосы пропусканияМожет ограничивать пропускную способность соединения; окно перегрузки TCP уменьшается при потере пакетов;
С одной стороны, TCP является причиной.С другой стороны, это должна быть проблема политики браузера.По оценкам, это также ошибка хрома.Сравнивая две анимации, вы обнаружите, что сафари получает нагрузку по очереди. Пока все не будут приняты, а хром получен по порядку, и после этого приема наступает очередь получения следующего, в результате последующие картинки могут долго висеть не отвечая.
Используйте прогрессивные изображения
Прогрессивные jpg вместо обычных jpg хороши для визуального завершения и имеют меньший размер файла:
входитьconvert --version
посмотреть, установлен ли онImageMagic, если он не установлен первым: Mac может использоватьbrew install imagemagick
, Centos может использоватьyum install imagemagick
Определите, является ли это прогрессивным jpeg, если вывод None, это означает, что это не прогрессивный jpeg; если он выводит JPEG, это означает прогрессивный jpeg:
$ identify -verbose filename.jpg | grep Interlace
конвертировать базовый jpeg в прогрессивный jpeg,параметр чередования:
$ convert -strip -interlace Plane source.jpg destination.jpg // 还可以指定质量 -quality 90
// 批量处理
$ for i in ./*.jpg; do convert -strip -interlace Plane $i $i; done
Также конвертирует PNG и GIF, но я пробовалconvert -strip -interlace Plane source.png destination.png
Однако преобразованные изображения, как правило, имеют больший размер, что не рекомендуется.
ImageMagic имеет гораздо больше мощных функций
// 图片缩放
$ convert -resize 50%x50% source.jpg destination.jpg
// 图片格式转换
$ convert source.jpg destination.png
// 配合 find 命令,将当前目录下大于 100kb 的图片按 75% 质量进行压缩
$ find -E . -iregex '.*\.(jpg|png|bmp)' -size +100k -exec convert -strip +profile “*” -quality 75 {} {} \;
рекомендуется сжатие pngpngquant
Кроме того, фотошоп также может устанавливать прогрессивную или чересстрочную развертку при сохранении изображений:
Прогрессивное изображение: выберите формат изображения JPEG => установите флажок «Непрерывный».
Чередующееся изображение: выберите формат изображения PNG/GIF => установите флажок «Чередущее»
Связь между SPDY и HTTP2
SPDYЭто предшественник HTTP2, и большинство функций соответствуют HTTP2, включая отправку на стороне сервера, мультиплексирование и кадры как наименьшую единицу передачи. Однако SPDY и HTTP2 также имеют некоторые различия в реализации, например, SPDY использует алгоритм DEFLATE для сжатия заголовков, а HTTP2 использует алгоритм HPACK, который имеет более высокую степень сжатия.
QUIC-протокол
GoogleQUIC(Quick UDP Internet Connections)Протокол наследует характеристики SPDY. QUIC — это альтернативная реализация TCP + TLS + HTTP/2 через UDP.
QUIC может создавать соединения с меньшей задержкой и, как и HTTP/2, решает проблему потери пакетов, блокируя только часть потока, облегчая установление соединений в разных сетях — что именноMPTCPпроблема, которую вы хотите решить.
В настоящее время QUIC реализован только в Google Chrome и его внутреннем сервере.Хотя существует сторонняя библиотека libquic, эти коды по-прежнему сложно повторно использовать где-либо еще. Протокол также был представлен рабочей группой по коммуникациям IETF в качестве проекта.
Caddy: Веб-сервер разработан на основе языка Go, имеет хорошую поддержку HTTP/2 и HTTPS, а также поддерживает протокол QUIC (экспериментально).
Рекомендуемые инструменты
- Плагин Chrome:HTTP/2 and SPDY indicator
Если на посещаемом вами сайте включен протокол HTTP/2, значок загорится, и при нажатии откроется встроенный в Chrome инструмент мониторинга HTTP/2.
- Инструменты командной строки:nghttp2
HTTP/2 реализован на языке C, вы можете использовать его для отладки запросов HTTP/2.
непосредственныйbrew install nghttp2
Его можно установить, после установки введитеnghttp -nv https://nghttp2.org
вы можете просмотреть запрос h2
-
Вы также можете протестировать http2 с помощью h2i в дополнение к nghttp2:GitHub.com/golang/net/…
-
Вы также можете использовать wireshark для распаковки пакета h2, но вам необходимо установить симметричный ключ согласования, предоставленный браузером, или закрытый ключ, предоставленный сервером.Подробности см. в этой статье:Отладка трафика HTTP/2 с помощью Wireshark
Если не получается распаковать и посмотреть есть ли в файле sslkeylog.log записанные данные, если данных нет, то значит браузер открыт некорректно, для открытия браузера надо использовать командную строку, чтобы браузер мог прочитайте переменные среды, а затем запишите ключ в sslkeylog , и этот метод, похоже, поддерживает Google Chrome и Firefox, но не Safari
Если в sslkeylog.log есть данные, wireshark по-прежнему не может распаковать, откройте параметр SSL в настройках и повторно выберите файл, чтобы попробовать, если он по-прежнему не работает, также используйте командную строку, чтобы открыть Wireshark
Не один раз, попробуйте несколько раз
- h2o: Оптимизированный HTTP-сервер с улучшенной поддержкой HTTP/2.
References
- HTTP/2: новые возможности и вызовы
- HTTP2 is here, let's optimize!
- JerryQu's Blog
- http2 explained
- NGINX HTTP2 White Paper
- HTTP/2 Push: The details
- Полное руководство по веб-производительности