HTTP/1.1 vs HTTP/2
Last updated
Last updated
Hypertext Transfer Protocol, или HTTP – это протокол прикладного уровня, который с момента изобретения (в 1989 году) является стандартом для связи во Всемирной паутине. С момента выпуска HTTP/1.1 в 1997 году и до недавнего времени в протокол было внесено не так уж много изменений. Но в 2015 году появилась новая версия HTTP/2, в которой предлагается несколько способов снижения задержки, особенно при работе с мобильными платформами, а также с графикой и видео, интенсивно использующими сервер. С тех пор протокол HTTP/2 становится все более популярным: по некоторым оценкам, его поддерживают около трети всех веб-сайтов в мире. В этом переменчивом ландшафте веб-разработчики должны понимать технические различия между HTTP/1.1 и HTTP/2 и использовать их преимущества. Это позволяет принимать обоснованные и эффективные решения.
В этой статье мы расскажем о главных отличиях между HTTP/1.1 и HTTP/2, а также об основных технических изменениях в HTTP/2.
Чтобы лучше понимать контекст конкретных изменений, которые HTTP/2 внес в HTTP/1.1, давайте ознакомимся с историей разработки и основными принципами работы каждого из релизов протокола.
Разработанный Тимоти Бернерсом-Ли (Timothy Berners-Lee) в 1989 году в качестве стандарта связи для Всемирной паутины, HTTP – это протокол верхнего (прикладного) уровня, который обеспечивает обмен информацией между клиентским компьютером и локальным или удаленным веб-сервером. В этом процессе клиент отправляет текстовый запрос на сервер, вызывая метод (GET или POST). В ответ сервер отправляет клиенту ресурс, например, HTML-страницу.
Предположим, вы посещаете веб-сайт по домену www.example.com. При переходе по этому URL-адресу веб-браузер на вашем компьютере отправляет HTTP-запрос в виде текстового сообщения:
GET /index.html HTTP/1.1
Host: www.example.com
Этот запрос использует метод GET, который запрашивает данные с хост-сервера, указанного после Host:. В ответ на этот запрос веб-сервер example.com возвращает клиенту HTML-страницу вместе с изображениями, таблицами стилей или другими ресурсами, запрашиваемыми в HTML. Обратите внимание, что при первом обращении к данным клиенту возвращаются не все ресурсы. Запросы и ответы будут передаваться между сервером и клиентом до тех пор, пока веб-браузер не получит все ресурсы, необходимые для отображения содержимого HTML-страницы на вашем экране.
Этот обмен запросами и ответами можно объединить в единый прикладной уровень интернет-протоколов, расположенный над транспортным уровнем (обычно по протоколу TCP) и сетевым уровнем (по протоколу IP).
Можно еще долго обсуждать более низкие уровни этого стека, но чтобы получить общее представление о HTTP/2, достаточно знать только эту модель абстрактного и то, где в ней находится HTTP.
HTTP/2 появился как протокол SPDY, разработанный в основном в Google с целью снижения задержки загрузки веб-страниц такими методами, как сжатие, мультиплексирование и приоритизация. Этот протокол послужил шаблоном для HTTP/2, когда группа httpbis (это рабочая группа Hypertext Transfer Protocol) из IETF (Internet Engineering Task Force) объединила стандарт. Так в мае 2015 года случился релиз HTTP/2. С самого начала многие браузеры (включая Chrome, Opera, Internet Explorer и Safari) поддерживали эту попытку стандартизации. Частично благодаря этой поддержке с 2015 года наблюдается высокий уровень внедрения протокола, особенно среди новых сайтов.
С технической точки зрения, одной из наиболее важных особенностей, которые отличают HTTP/1.1 и HTTP/2, является двоичный уровень кадрирования, который можно рассматривать как часть прикладного уровня в стеке интернет-протоколов. В отличие от HTTP/1.1, в котором все запросы и ответы хранятся в простом текстовом формате, HTTP/2 использует двоичный уровень кадрирования для инкапсуляции всех сообщений в двоичном формате, при этом сохраняя семантику HTTP (методы, заголовки). API прикладного уровня по-прежнему создает сообщения в обычных форматах HTTP, но нижележащий уровень преобразовывает эти сообщения в двоичные. Благодаря этому веб-приложения, созданные до HTTP/2, могут продолжать работать как обычно при взаимодействии с новым протоколом.
Преобразование сообщений в двоичные позволяет HTTP/2 применять новые подходы к доставке данных, недоступные в HTTP/1.1.
В следующем разделе мы рассмотрим модель доставки HTTP/1.1, а также расскажем, какие новые модели стали возможны благодаря HTTP/2.
Как упоминалось в предыдущем разделе, HTTP/1.1 и HTTP/2 используют одну и ту же семантику, благодаря чему запросы и ответы, передаваемые между сервером и клиентом в обоих протоколах, достигают цели в виде традиционно отформатированных сообщений с заголовками и телом, используя знакомые методы (GET и POST). Но если HTTP/1.1 передает их в виде текстовых сообщений, то HTTP/2 кодирует их в двоичный файл, что позволяет ему применять другие модели доставки. В этом разделе мы сначала кратко рассмотрим, как HTTP/1.1 пытается оптимизировать эффективность с помощью своей модели доставки и какие при этом возникают проблемы, а затем ознакомимся с преимуществами двоичного уровня кадрирования HTTP/2 и его методами приоритизации запросов.
Первый ответ, который клиент получает на запрос GET, часто содержит не всю страницу, а ссылки на дополнительные ресурсы, необходимые для запрашиваемой страницы. Только после загрузки страницы клиент обнаруживает, что для полной визуализации от сервера требуются эти дополнительные ресурсы. Потому клиенту приходится делать дополнительные запросы для извлечения этих ресурсов. В HTTP/1.0 клиент должен был разрывать и снова создавать TCP-соединение для каждого нового запроса, а это довольно затратно с точки зрения времени и ресурсов.
HTTP/1.1 устраняет эту проблему через постоянные соединения и конвейерную обработку (pipelining). HTTP/1.1 предполагает, что TCP-соединение должно оставаться открытым, если закрытие не указано прямо. Это позволяет клиенту отправлять несколько запросов по одному и тому же соединению, не дожидаясь ответа на каждый запрос, что значительно повышает производительность по сравнению с HTTP/1.0.
К сожалению, в этой стратегии оптимизации есть узкое место. Поскольку при отправке в один и тот же пункт назначения несколько пакетов данных не могут проходить друг через друга, возникают ситуации, когда запрос в начале очереди, который не может извлечь требуемый ресурс, блокирует все запросы, находящиеся за ним. Это называется блокировкой очереди (head-of-line blocking, или HOL) и представляет собой серьезную проблему оптимизации эффективности соединения в HTTP/1.1. Добавление отдельных параллельных TCP-соединений может решить эту проблему, но в протоколе существуют ограничения на количество одновременных TCP-соединений между клиентом и сервером, и каждое новое соединение требует значительных ресурсов.
Эти проблемы были в центре внимания разработчиков HTTP/2, которые предложили использовать вышеупомянутый двоичный уровень кадрирования – мы поговорим об этом в следующем разделе.
В HTTP/2 двоичный уровень кадрирования кодирует запросы и ответы и разбивает их на более мелкие пакеты информации, что значительно повышает гибкость передачи данных.
Давайте подробнее рассмотрим, как это работает. В отличие от HTTP/1.1, который должен использовать несколько соединений TCP для снижения блокировки HOL, HTTP/2 устанавливает один объект соединения между двумя компьютерами. В этой связи есть несколько потоков данных. Каждый поток состоит из нескольких сообщений в привычном формате запрос-ответ. Наконец, каждое из этих сообщений разбивается на более мелкие блоки, называемые кадрами.
На самом детальном уровне канал связи состоит из набора двоично-кодированных кадров, каждый из которых помечен для определенного потока. Идентификационные теги позволяют соединению чередовать эти кадры во время передачи и повторно собирать их на другом конце. Чередующиеся запросы и ответы могут выполняться параллельно, не блокируя следующие сообщения в очереди. Этот процесс называется мультиплексированием. Мультиплексирование решает проблему блокировки очереди, присущую протоколу HTTP/1.1, гарантируя, что ни одно сообщение не будет ждать обработки другого. Это также означает, что серверы и клиенты могут отправлять параллельные запросы и ответы, что обеспечивает больший контроль и более эффективное управление соединениями.
Поскольку мультиплексирование позволяет клиенту создавать несколько потоков параллельно, этим потокам нужно только одно TCP-соединение. Наличие единого постоянного соединения для каждого источника уменьшает объем памяти и объем обработки по всей сети. Это оптимизирует использование сети и полосы пропускания и, таким образом, снижает общие расходы.
Единое соединение TCP также повышает производительность протокола HTTPS, поскольку клиент и сервер могут повторно использовать один и тот же защищенный сеанс для нескольких запросов/ответов. В HTTPS во время рукопожатия TLS или SSL обе стороны договариваются об использовании одного ключа в течение сеанса. Если соединение прерывается, начинается новый сеанс, для которого требуется новый сгенерированный ключ. Таким образом, поддержание одного соединения может значительно уменьшить ресурсы, необходимые для работы HTTPS. Обратите внимание: хотя спецификации HTTP/2 не требуют использовать уровень TLS, многие основные браузеры поддерживают только HTTP/2 с HTTPS.
Хотя мультиплексирование, свойственное двоичному уровню кадрирования, устраняет некоторые проблемы HTTP/1.1, проблемы с производительностью могут возникнуть из-за нескольких потоков, ожидающих одного и того же ресурса. Конструкция HTTP/2 учитывает и это, используя приоритезацию потоков.
Приоритизация потоков не только предотвращает потенциальную проблему с запросами, конкурирующими за один и тот же ресурс, но также позволяет разработчикам настраивать относительный вес запросов для лучшей оптимизации производительности приложений. В этом разделе мы разберем этот процесс, чтобы лучше понять, как можно использовать эту функцию HTTP/2.
Как вы теперь знаете, двоичный уровень кадрирования организует сообщения в параллельные потоки данных. Когда клиент отправляет параллельные запросы на сервер, он может расставить приоритеты запрашиваемых им ответов, присваивая вес от 1 до 256 каждому потоку. Чем выше вес, тем выше приоритет. Кроме того, клиент также определяет зависимости между потоками (указывая ID потока, от которого будет зависеть тот или иной поток). Если родительский идентификатор опущен, считается, что поток зависит от корневого потока.
На этой иллюстрации канал содержит шесть потоков, каждый из которых имеет уникальный идентификатор и связан с определенным весом. Поток 1 не имеет родительского ID, связанного с ним, и по умолчанию связан с корневым узлом. Все остальные потоки помечены родительским ID. Распределение ресурсов для каждого потока будет основываться на их весе и зависимостях, которые им требуются. Например, потоки 5 и 6, которым на рисунке назначен одинаковый вес и один и тот же родительский поток, при распределении ресурсов будут иметь одинаковую приоритетность.
Сервер использует эту информацию для создания дерева зависимостей, которое позволяет ему определять порядок, в котором запросы будут получать свои данные. Основываясь на потоках в предыдущей таблице, дерево зависимостей будет следующим:
В этом дереве зависимостей поток 1 зависит от корневого потока. Поскольку это единственный поток, порождаемый корневым потоком, все доступные ресурсы будут выделяться потоку 1 раньше других. Поскольку дерево указывает, что поток 2 зависит от завершения потока 1, поток 2 не будет обрабатываться, пока не будет завершена задача потока 1. Теперь давайте рассмотрим потоки 3 и 4. Оба они зависят от потока 2. Как и в случае с потоком 1, поток 2 получит все доступные ресурсы раньше, чем 3 и 4. После того, как поток 2 завершит свою задачу, потоки 3 и 4 получат ресурсы; они делятся в соотношении 2: 4, в соответствии с их весом, что передает большую часть ресурсов потоку 4. Наконец, когда поток 3 будет обработан, доступные ресурсы в равных частях получат потоки 5 и 6. Это может произойти до выполнения потока 4, даже если поток 4 получает большую часть ресурсов. Потоки более низкого уровня могут запускаться, как только завершатся потоки верхнего уровня, от которых они зависят.
Как разработчик приложения, вы можете устанавливать вес ваших запросов в зависимости от потребностей. Например, вы можете установить более низкий приоритет для загрузки изображения с высоким разрешением, предоставив миниатюру изображения на веб-странице. Давая возможность назначать вес, HTTP/2 позволяет разработчикам лучше контролировать рендеринг веб-страниц. Протокол также позволяет клиенту изменять зависимости и перераспределять вес во время выполнения в ответ на взаимодействие с пользователем. Однако важно отметить, что сервер может изменить назначенные приоритеты самостоятельно, если определенный поток заблокирован от доступа к конкретному ресурсу.
В любом TCP-соединении между двумя компьютерами и клиент, и сервер имеют определенный объем буферного пространства, доступного для хранения еще не обработанных входящих запросов. Эти буферы обеспечивают гибкий учет многочисленных или особенно больших запросов, поддерживая неравномерную скорость нисходящих и восходящих соединений.
Однако существуют ситуации, когда буфера недостаточно. Например, сервер может передавать большой объем данных со скоростью, с которой клиентское приложение не может справиться (из-за ограниченного размера буфера или меньшей пропускной способности). Аналогичным образом, когда клиент загружает на сервер огромное изображение или видео, буфер сервера может переполниться, что приведет к потере некоторых дополнительных пакетов.
Чтобы избежать переполнения буфера, механизм управления потоком не должен позволять отправителю перегружать получателя данными. В этом разделе мы рассмотрим, как HTTP/1.1 и HTTP/2 используют разные версии этого механизма для управления потоком данных в соответствии с различными моделями доставки.
В HTTP / 1.1 управление потоком основывается на базовом TCP-соединении. Когда это соединение инициируется, клиент и сервер устанавливают размеры буфера, используя системные настройки по умолчанию. Если буфер получателя частично заполнен данными, он сообщит отправителю свое окно приема, то есть количество доступного пространства, которое остается в буфере. Это окно приема объявляется в сигнале, известном как пакет ACK (это пакет данных, который отправляет приемник, чтобы подтвердить, что он принял сигнал открытия). Если этот объявленный размер окна приема равен нулю, отправитель не будет отправлять данные, пока клиент не очистит свой буфер и затем не запросит возобновить передачу данных. Здесь важно отметить, что использование окон приема, основанных на базовом TCP-соединении, может реализовать управление потоком на одном из концов соединения.
Поскольку HTTP/1.1 использует транспортный уровень, чтобы избежать переполнения буфера, каждое новое TCP-соединение требует отдельного механизма управления потоком. А HTTP/2 мультиплексирует потоки в одном TCP-соединении и должен реализовать управление потоками другим способом.
HTTP/2 мультиплексирует потоки данных в одном TCP-соединении. В результате для регулирования доставки отдельных потоков окон приема на уровне TCP-соединения недостаточно. HTTP/2 решает эту проблему, позволяя клиенту и серверу реализовать свои собственные средства управления потоком, а не полагаться на транспортный уровень. Прикладной уровень передает доступное буферное пространство, позволяя клиенту и серверу установить окно приема на уровне мультиплексированных потоков. Это мелкомасштабное управление потоком может быть изменено или сохранено после первоначального подключения через кадр WINDOW_UPDATE.
Поскольку этот метод управляет потоком данных на прикладном уровне, механизму управления потоком не нужно ждать, пока сигнал достигнет своего пункта назначения, прежде чем настраивать окно приема. Узлы-посредники могут использовать информацию о настройках управления потоком данных, чтобы определить собственное распределение ресурсов и соответственно изменить его. Таким образом, каждый сервер-посредник может реализовать свою стратегию использования ресурсов, что позволяет повысить эффективность соединения.
Такая гибкость в управлении потоком может быть полезной при создании соответствующих ресурсных стратегий. Например, клиент может извлечь первое сканирование изображения, отобразить его и позволить пользователю предварительно просмотреть его, извлекая при этом более важные ресурсы. Как только клиент извлечет эти ресурсы, браузер возобновит поиск оставшейся части изображения. Это может улучшить производительность веб-приложений.
С точки зрения управления потоком и приоритизации, упомянутых в предыдущем разделе, HTTP/2 обеспечивает более тонкий контроль, что открывает возможность большей оптимизации.
В типичном веб-приложении клиент отправляет GET-запрос и получает страницу в формате HTML. Обычно это индексная страница сайта. Исследуя содержимое страницы, клиент может обнаружить, что для полной визуализации страницы ему необходимо извлечь дополнительные ресурсы, такие как файлы CSS и JavaScript. Клиент определяет, что эти дополнительные ресурсы ему нужны, только после получения ответа от исходного GET-запроса. Таким образом, он должен сделать дополнительные запросы для извлечения этих ресурсов и завершения соединения страницы. Эти дополнительные запросы в конечном итоге увеличивают время загрузки.
Однако эту проблему можно решить: поскольку сервер заранее знает, что клиенту потребуются дополнительные файлы, сервер может сэкономить время клиента, отправляя клиенту эти ресурсы прежде, чем он их запросит. HTTP/1.1 и HTTP/2 имеют разные стратегии для достижения этой цели, каждая из которых описана в этом разделе.
В HTTP/1.1, если разработчик заранее знает, какие дополнительные ресурсы потребуется клиентскому компьютеру для отображения страницы, он может использовать метод встраивания ресурсов для включения требуемого ресурса непосредственно в документ HTML, который сервер отправляет в ответ на исходный запрос GET. Например, если клиенту нужен определенный файл CSS для визуализации страницы, встраивание этого файла предоставит клиенту необходимый ресурс, прежде чем он его запросит. Это уменьшает общее количество запросов, которые клиент должен отправить на сервер.
Но со встраиванием ресурсов есть несколько проблем. Встраивание в документ HTML – целесообразное решение для небольших текстовых ресурсов, но большие файлы в нетекстовых форматах могут значительно увеличить размер HTML документа, что в конечном итоге может снизить скорость соединения и вообще свести на нет исходное преимущество этой техники. Кроме того, поскольку встроенные ресурсы больше не отделены от HTML-документа, у клиента нет механизма для сокращения ресурсов или для размещения ресурса в кэше. Если ресурсу требуется несколько страниц, каждый новый HTML-документ будет содержать в своем коде один и тот же ресурс, что приведет к увеличению размера HTML-документов и времени загрузки (обработка займет больше времени, чем если бы ресурс просто кэшировался в начале).
Таким образом, основным недостатком встраивания является то, что клиент не может разделить ресурс и документ. Для оптимизации соединения необходим более высокий уровень контроля, который HTTP/2 стремится предоставить с помощью Server Push.
Поскольку HTTP/2 поддерживает множество одновременных ответов на первоначальный GET запрос клиента, сервер может отправить клиенту ресурс вместе с запрошенной HTML-страницей, предоставляя ресурс до того, как клиент запросит его. Этот процесс называется Server Push. Таким образом, HTTP/2-соединение может выполнить ту же задачу по встраиванию ресурсов, при этом сохраняя разделение между помещаемым ресурсом и документом. Это означает, что клиент может решить кэшировать или отклонить отправленный ресурс отдельно от основного HTML-документа. Так HTTP/2 устраняет основной недостаток встраивания ресурсов.
В HTTP/2 этот процесс начинается, когда сервер отправляет кадр PUSH_PROMISE, чтобы сообщить клиенту, что он собирается отправить ресурс. Этот кадр включает в себя только заголовок сообщения и позволяет клиенту заранее узнать, какой ресурс отправит сервер. Если ресурс уже кэширован, клиент может отклонить отправку, отправив в ответ кадр RST_STREAM. Кадр PUSH_PROMISE также предотвращает отправку дублированного запроса на сервер (поскольку клиент знает, какие ресурсы сервер собирается отправить).
Здесь важно отметить, что Server Push делает акцент на контроль клиента. Если клиенту необходимо отрегулировать приоритет Server Push или даже отключить его, он может в любое время отправить кадр SETTINGS для изменения этой функции HTTP/2.
Несмотря на то, что функция Server Push имеет большой потенциал, она не всегда оптимизирует работу веб-приложения. Например, некоторые веб-браузеры не всегда могут отменить отправленные запросы, даже если клиент уже кэшировал ресурс. Если клиент по ошибке разрешает серверу отправлять дублирующийся ресурс, Server Push может израсходовать соединение. В конце концов, принудительный Server Push должен использоваться по усмотрению разработчика. Больше о том, как использовать Server Push стратегически и оптимизировать веб-приложения, можно узнать из шаблона PRPL, разработанного Google. Чтобы узнать больше о возможных проблемах, связанных с принудительным Server Push, читайте этот пост.
Распространенным методом оптимизации веб-приложений является использование алгоритмов сжатия для уменьшения размера HTTP-сообщений, которые передаются между клиентом и сервером. HTTP/1.1 и HTTP/2 используют эту стратегию, но в первом протоколе существуют проблемы с реализацией, которые запрещают сжатие всего сообщения. В следующем разделе мы обсудим, почему это происходит и как HTTP/2 может решить эту проблему.
Такие программы, как gzip, давно используются для сжатия данных, отправляемых в сообщениях HTTP, особенно для уменьшения размера файлов CSS и JavaScript. Однако компонент заголовка сообщения всегда отправляется в виде простого текста. Несмотря на то, что каждый заголовок довольно мал, объем этих несжатых данных увеличивает нагрузку на соединение по мере того как клиент отправляет больше запросов. Особенно это касается сложных веб-приложений с тяжелым API, которые требуют много разных ресурсов и, следовательно, много разных запросов. Кроме того, использование файлов cookie иногда может значительно увеличить заголовки, что, в свою очередь, увеличивает потребность в сжатии.
Чтобы устранить это узкое место, HTTP/2 использует сжатие HPACK, которое позволяет уменьшить размер заголовков.
Одна из функций, которая постоянно всплывает при обсуждении HTTP/2 – это двоичный уровень кадрирования, который предоставляет больший контроль над мелкими деталями. Это касается и сжатия заголовков. HTTP/2 может отделить заголовки от остальных данных, в результате чего получаются кадр заголовка и кадр данных. Программа сжатия HPACK (специальная для HTTP/2) может затем сжать этот кадр заголовка. Этот алгоритм может кодировать метаданные заголовка с помощью кодирования Хаффмана, тем самым значительно уменьшая его размер. Кроме того, HPACK может отслеживать ранее переданные поля метаданных и дополнительно сжимать их в соответствии с динамически измененным индексом между клиентом и сервером. Например, рассмотрим следующие два запроса:
#запрос 1
method: GET
scheme: https
host: example.com
path: /academy
accept: /image/jpeg
user-agent: Mozilla/5.0 ...
#запрос 2
method: GET
scheme: https
host: example.com
path: /academy/images
accept: /image/jpeg
user-agent: Mozilla/5.0 ...
Поля в этих запросах (method, scheme, host, accept и user-agent) имеют одинаковые значения; только поле path использует другое значение. В результате при отправке запроса 2 клиент может использовать HPACK для отправки индексированных значений, необходимых для восстановления общих полей и нового кодирования поля path. Кадры заголовка будут выглядеть следующим образом:
#запрос 1
method: GET
scheme: https
host: example.com
path: /academy
accept: /image/jpeg
user-agent: Mozilla/5.0 ...
#запрос 2
path: /academy/images
Используя HPACK и другие методы сжатия, HTTP/2 предоставляет еще одну функцию, которая позволяет уменьшить задержку между клиентом и сервером.