Привет! Я Ивасюта Алексей, техлид команды Bricks в Авито в кластере Architecture, а это мой цикл статей о протоколе HTTP. В предыдущей части я рассказал о работе Cookies и механизме CORS. Теперь мы узнаем, какие нововведения в протокол привнесла версия HTTP/1.1 и чем она отличается от версии HTTP/2.

Нововведения в HTTP/1.1

Версия протокола HTTP/1.1 появилась в 1999 году. Это была первая стандартизированная версия. С тех пор работать с протоколом стало намного удобнее.

Новые методы в этой версии:

  • PUT заменяет все текущие представления сущности данными запроса. Например, в блоге можно заменить данные поста, который ранее был опубликован;

  • DELETE удаляет указанную сущность;

  • OPTIONS описывает параметры соединения с ресурсом;

  • TRACE проверяет обратную связь по пути к целевому ресурсу, предоставляет полезный механизм для отладки запроса.

Позже добавили ещё два метода: 

  • CONNECT устанавливает «туннель» к серверу, определённому по ресурсу.

  • PATCH частично изменяет указанную сущность данными из запроса.

Еще одним нововведением стало кеширование запрашиваемых ресурсов на стороне клиента. При следующем запросе они отдаются сразу из кеша, а время на загрузку сокращается.

Виртуальные хосты

До появления этого механизма на одном IP-адресе можно было располагать только один сайт. Стандартизированного способа разместить несколько сайтов на одной машине не было. Теперь появился обязательный заголовок Host. Он указывает на домен, с которого пришел запрос на веб-сервер. Сервер определяет хост по этому заголовку и решает, какие данные отправить клиенту.

Например, если запрос уходит с домена example.com, нужно добавить заголовок Host: example.com.

POST /path HTTP/1.1
Host: example.com
Content-Type: text/plain; charset=utf-8
Content-Length: 4

test

keep-alive

Чтобы передать данные по протоколу HTTP, надо установить TCP/IP1 соединение между клиентом и сервером. Оно устанавливается перед каждым запросом, а после выполнения запроса — закрывается. Эта операция требует ресурсов процессора на сервере и занимает время. Если сайтом пользуется несколько сотен человек или на странице загружаются несколько ресурсов — это незаметное явление. Но если сайтом начнут пользоваться миллионы пользователей, сервер не справится с нагрузкой, надо будет ставить железо помощнее. То же самое произойдет, если сайт сложный, с сотнями ресурсов. Пользователи будут долго ждать загрузку страниц.

В HTTP/1.1 появились постоянные соединения keep-alive. Они не закрываются после первого запроса и остаются открытыми для нескольких последующих. Так за одно соединение можно запросить несколько ресурсов, не перегрузить сервер и ускорить загрузку страниц. Соединение закрывает запрос с заголовком Connection: close.

Пересылка частями (chunked)

Иногда серверу нужно передать много данных или данные, неизвестного размера (например, при потоковом воспроизведении аудио или видео). Кажется, что самый простой вариант — дождаться полной загрузки данных в оперативную память сервера и после этого отправить их клиенту. Но тут мы сталкиваемся с двумя проблемами:

  1. Данные можно будет отправить только после их полной загрузки. Это ломает концепцию потокового воспроизведения.

  2. Запросы к серверу от каждого клиента будут задействовать определённый размер оперативной памяти и быстро израсходуют физические лимиты на сервере.

В новой версии протокола проблему решили: ввели механизм потоковой передачи данных небольшими частями. При стандартной схеме работы сервер присылает клиенту весь ответ целиком, который упрощенно выглядит примерно так:

HTTP/1.1 200 OK

test

Если включить потоковую передачу, данные передаются небольшими кусочками — чанками. Длина чанка зависит от настроек сервера. В ответе на запрос сервер присылает заголовок Transfer-Encoding со значением chunked. При этом в теле ответа перед каждым чанком указывается его длина в байтах, а с новой строки — данные чанка. Когда передача данных заканчивается, последним присылается чанк нулевой длины. Упрощенный ответ от сервера может выглядеть так:

HTTP/1.1 200 OK
Transfer-Encoding: chunked

10
Long chunk
19
One more chunk data
0

Нововведения в HTTP/2

История второй версии протокола началась в 2009 году. Компания Google решила разрабатывать собственную надстройку над HTTP, чтобы повысить скорость работы и безопасности. Протокол назвали SPDY и развивали его 6 лет, а браузеры постепенно реализовывали его поддержку. В 2015 году в Google решили объединить SPDY и HTTP. Так появился HTTP/2.

Бинарные данные

В HTTP данные передаются серверу в формате текста. Это удобно для людей, нам легко воспринимать текстовую информацию. Можно легко просматривать содержимое запросов и ответов, вести отладку. Например, вручную писать текст запроса и отправлять его через telnet. Но в наше время нет необходимости в ручной отправке, а передача данных текстом — далеко не самый производительный вариант. 

HTTP/2 использует бинарный формат. Бинарные данные меньше размером, поэтому в таком формате скорость передачи и парсинга выше, а нагрузка на сеть — ниже. Браузеры с поддержкой HTTP/2 кодируют запросы в бинарный формат перед отправкой, а сервер обрабатывает запрос и декодирует.

Мультиплексирование

Современный сайт обычно работает так: отправляется первый GET-запрос → он возвращает клиенту HTML-страницу → она подключает дополнительные ресурсы: js-файлы, css-таблицы, картинки, шрифты. После загрузки всех (или почти всех) ресурсов сайт становится интерактивным для пользователя.

В HTTP/1.0 для загрузки каждого ресурса устанавливалось новое TCP/IP соединение. HTTP/1.1 принёс keep-alive, который позволил загружать множество ресурсов в рамках одного соединения. Это значительно увеличило производительность протокола. Однако, ресурсы загружаются последовательно (waterfall): нельзя получить следующий ресурс, пока не получен предыдущий. 

Когда запрос к ресурсу подвисает из-за сложностей с его получением, возникает проблема Head-Of-Line Blocking. Это означает, что зависший запрос блокирует отправку всех остальных, так как обработка идёт в порядке очереди. Новая структура передачи данных в бинарном формате дала возможность загружать несколько ресурсов параллельно.

Теперь вся информация при передаче бьётся на небольшие бинарные кусочки — фреймы. Они собираются в потоки, для каждого запрашиваемого ресурса создаётся отдельный. При этом все фреймы от разных потоков могут передаваться вперемешку в одном TCP/IP соединении. Принимающая сторона самостоятельно соберёт все фреймы из одного потока и обработает их. Также клиент теперь может прервать получение одного потока, не закрывая соединение.

Сжатие заголовков через HPACK

Раньше сжатие тела и заголовков происходило на уровне TLS. Это более низкий уровень по отношению к HTTP (микс транспортного уровня и уровня приложения). Он не знает, какой тип данных сжимает, и жмёт все через gzip при помощи алгоритма DEFLATE2

Позже в SPDY появился отдельный алгоритм сжатия заголовков, но он по-прежнему использовал DEFLATE внутри. Из-за него оба алгоритма были подвержены атакам типа CRIME3 и в результате злоумышленники получали авторизационные куки из сжатых заголовков. Поэтому многие пограничные сети, типа Cloudflare, блокировали сжатие заголовков, чтобы защитить пользователей от атак.

Во второй версии HTTP представили новый алгоритм сжатия заголовков HPACK4. Он не использует DEFLATE и заточен под эффективную защиту от CRIME-атак. С ним новая версия HTTP стала производительнее и безопаснее.

Приоритизация потоков

Механизм приоритизации потоков позволяет управлять порядком обработки и передачи потоков данных на одном соединении. Он улучшает производительность, делает управление ресурсами эффективнее, позволяет гибко контролировать рендеринг страницы.

При отправке параллельных запросов на сервер клиент может расставить приоритеты запрашиваемых им ответов. Это делается присвоением каждому потоку веса от 1 до 256. Чем выше вес, тем выше приоритет. Потоки могут быть связаны с ресурсами на сервере: изображениями, CSS-файлами, HTML-страницами и другими. 

Сервер использует эту информацию, управляет потоками данных и определяет порядок их обработки. Например, он может начать обработку потока с более высоким приоритетом, даже если это не первый поток, который был отправлен. Помимо этого клиент может указывать зависимости между потоками. Тогда сервер создаёт дерево зависимостей, которое определяет порядок получения данных.

Server push

Это функция, которая позволяет серверу начать передачу ресурсов на клиент ещё до запроса.

Как это работает:

  1. Клиент отправляет запрос на сервер для загрузки страницы. Например, GET /index.html.

  2. Сервер анализирует запрос и определяет, какие ресурсы будут нужны для полной загрузки страницы. Например, CSS, JS и изображения.

  3. Сервер создаёт множество потоков данных и начинает отправлять эти ресурсы через Server Push на клиент, даже если он ещё не запросил их.

  4. Клиент получает ресурсы и может начать их загрузку, даже если они не запрошены явно.

Для эффективной передачи ресурсов Server Push использует механизмы мультиплексирования и приоритизации потоков. Это уменьшает задержки при загрузке страницы, увеличивает скорость загрузки, улучшает пользовательский опыт.

Отмечу, что Server Push эффективен, только если сервер точно знает, какие ресурсы нужны для загрузки страницы. Иначе случится ненужная передача ресурсов и ухудшение производительности.

Насколько HTTP/2 лучше HTTP/1.1

Выход HTTP/2 кардинально изменил протокол и повысил его производительность и безопасность. Использование HTTP/2 может ускорить загрузку страницы на 30-50% по сравнению с HTTP/1.1. Но это может зависеть от многих факторов: размера файлов, их количества и скорости соединения.

Полезные ссылки

  1. Основы TCP/IP

  2. Алгоритм сжатия DEFLATE

  3. CRIME атаки

  4. Спецификация RFC 7541 про HPACK

Предыдущая статья: Как заонбордиться тимлиду — первые 90 дней на новой работе

Комментарии (7)


  1. Myclass
    25.05.2023 11:04
    +2

    Спасибо за статью. Было интересно узнать детали разницы между HTTP и HTTP/2.

    Интерессно было это потому, что например в различных системах как например Kafka тоже есть сжатие, перед тем как отправить пакет. В протоколе это тоже предусмотренно. Думаю, что двойное сжатие не всегда оптимально для процесса. Это можно в HTTP/2 отключить? Или отключение ни на что не повлияет?


    1. avivasyuta Автор
      25.05.2023 11:04

      Здравствуйте. Спасибо за приятный отзыв.

      Компрессию заголовков можно принудительно выключить в HTTP/2. К сожалению, не могу сказать как это будет работать в связке с Kafka.


  1. atsv
    25.05.2023 11:04
    +1

    http://www.http2demo.io/ показывает увеличение скорости загрузки по HTTP 2 в 4-10 раз, что гораздо выше 30-50%, указанных в статье.

    При просмотре панели Network в браузере вижу, что 170 тайлов картинки грузятся по HTTP 1.1 в 6 потоков, запрос следующих тайлов ожидает получения ответа предыдущих 6. По HTTP 2 тайлы загружаются одновременно в большом количестве потоков, хотя спецификация требует, чтобы соединение к одному хосту было только одно. Вопрос - как тайлы пролезают одновременно целой пачкой по одному соединению?


    1. avivasyuta Автор
      25.05.2023 11:04
      +1

      Это синтетическое демо возможностей протокола и прироста производительности в 4-10 раз вы никогда не увидите на более или менее реальном примере. Посмотрите хотябы на результаты на моем не очень быстром интернете.

      Так что прирост на 30-50% — это то, на что вы можете рассчитывать в реальности.

      С чего вы взяли, что спецификация требует устанавливать только одно соединение с хостом? Их может быть множество, и в каждом соединении может существовать несколько потоков, которые грузят ресурсы одновременно.


      1. atsv
        25.05.2023 11:04

        HTTP/2 standard commands to open not more than 1 connection per server while handling concurrent requests via streams.

        Несколько потоков могут существовать, но в одном соединении они всё равно должны передавать данные по очереди, а не одновременно.



  1. Kenya
    25.05.2023 11:04

    Добавлю, что большинство браузеров не будут использовать HTTP2 без наличия SSL-сертификата