Протокол HTTP/2 и его реализация

20 май 2014 11:58


Работа над новым протоколом для всемирной сети находится на завершающем этапе. HTTP/2 должен решить многие застарелые проблемы HTTP/1.1 и дать возможность Интернету развиваться дальше. Есть довольно хорошая статья «http2 explained» , посвященная новому протоколу HTTP/2, разъясняющая предпосылки его создания и наглядно демонстрирующая его возможности и принципы работы. Статья, тем не менее, не содержит какой-либо детальной технической спецификации протокола. Есть также отличная книга Ilya Grigorik «High-Performance Browser Networking» , веб-версия которой свободно доступна , и которая также содержит интересные детали о прародителе HTTP/2 - SPDY и самом HTTP/2 (правда уже немного устаревшие). После её прочтения я вдохновился создать реализацию HTTP/2-протокола и в процессе досконально изучить новый протокол. Эта статья - небольшой отчёт за последние два месяца убитого свободного времени.

↓↓↓↓↓↓↓↓

Спецификация HTTP/2

Разработкой протокола занимается рабочая группа IETF HTTPbis. Спецификация имеет свой веб-сайт , в соответствии с современной традицией свободной разработки, сайт и репозиторий исходных текстов хостятся на Github .

Спецификация в свою очередь разделена на три документа:

 • Спецификация протокола HTTP/2. Текущий черновик - draft-12

 • Спецификация сжатия заголовков HPACK. Текущий черновик - draft-07

 • Спецификация альтернативных сервисов. Текущий черновик - draft-01

Самые важные факты о HTTP/2

 • HTTP/2 не меняет парадигмы HTTP/1.1, т.е. с точки зрения веб-приложения ничего не меняется: остаются всё те же методы GET, POST и т.д., по-прежнему присутствуют HTTP-заголовки и тело HTTP-запроса/ответа. Просто по сети это передаётся в другом формате.

 • HTTP/2, в отличии от HTTP/1.1, сохраняет своё состояние между запросами (является stateful), поэтому порядок отправки/приёма фреймов важен.

 • HTTP/2 - это бинарный протокол. Поток делится на фреймы, имеющие фиксированную структуру и размер. Нет необходимости в парсинге для поиска границ сообщений.

 • HTTP/2 - это двусторонний поток данных, в котором мультиплексированы фреймы от разных запросов/ответов. Т.о. нет необходимости более чем в одном tcp-соединении для одновременной передачи различных данных.

 • HTTP/2 может управлять приоритетом, зависимостями и скоростью передачи запрашиваемых ресурсов, т.е. клиент может указать серверу какие ресурсы он хочет получить в первую очередь и регулировать объём доступной полосы персонально для каждого ресурса.

 • HTTP/2 может сжимать HTTP-заголовки и данные

 • В HTTP/2 сервер имеет возможность инициировать передачу данных клиенту (Server Push)

Известные программные реализации протокола HTTP/2

Ведётся учёт известных реализаций протокола, который отражается на wiki-странице проекта группы HTTPbis.

Наиболее совершенную, на мой взгляд, реализацию имеет проект nghttp2 -- это C-библиотека, реализующая спецификацию протокола. Именно её используют некоторые другие проекты, например libcurl и mruby-http2. Также стоит отметить реализацию Mozilla, которая в данный момент используется в свежих сборках Firefox.

Есть в списке и реализация на языке Perl http2-perl, но последний коммит был сделан в августе 2013 и анонсировал неполную поддержку draft-04, т.о. проект безнадёжно устарел.

Protocol::HTTP2

Мне показалось, что наиболее подходящим именем для реализации протокола на языке Perl будет имя Protocol::HTTP2 . По аналогии с модулем Protocol::WebSocket , API которого, кстати, стало прототипом для нового модуля (хотя в итоге я ещё больше его упростил).

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

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

Например, так может выглядеть код приложения (упрощённо):


# вычитать данные из сокета
while ( sysread $socket, my $data, 4096 ) {

    # передать данные обработчику протокола
    $http2_client->feed($data);
}

# последовательно отправлять генерируемые данные в сокет
while (my $frame = $http2_client->next_frame ) {
    syswrite $socket, $frame;
}

Приложению требуется лишь предварительно зарегистрировать свои callback-функции, на определённые события, например, получение HTTP-запроса или HTTP-ответа.

Такой принцип делает возможным создание клиента/сервера на основе любой реализации цикла событий (AnyEvent, IO::Async), а также на основе форков/тредов или любой другой модели сетевых серверов/клиентов.

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

Protocol::HTTP2::Client

Модуль для реализации клиента протокола HTTP/2.

Для понимания начнём с примера:

use Protocol::HTTP2::Client;
 
# Создать объект клиента
my $client = Protocol::HTTP2::Client->new;
 
# Подготовить запрос
$client->request(
 
    # HTTP/2 заголовки
    ':scheme'    => 'http',
    ':authority' => 'localhost:8000',
    ':path'      => '/',
    ':method'    => 'GET',
 
    # HTTP/1.1 заголовки
    headers      => [
        'accept'     => '*/*',
        'user-agent' => 'perl-Protocol-HTTP2/0.08',
    ],
 
    # Функция-callback вызываемая после получения ответа от сервера
    on_done => sub {
        my ( $headers, $data ) = @_;
        ...
    },
);
# Дальше идёт работа с сокетом (отправка/приём)
...

В HTTP/2 есть специальные заголовки, начинающиеся с двоеточия. В примере выше представлены все заголовки необходимые для запроса (они обязательны). В случае ответа сервера, присутствует заголовок :status. Отдельно указываются заголовки HTTP/1.1, которые увидит веб-приложение при получении запроса. Функция-callback on_done будет вызвана после получения данных от сервера: ссылка на массив с заголовками и данные.

Метод request возвращает сам объект $client, поэтому можно объединить в цепочку несколько запросов. Они будут отправлены последовательно, но отправка запросов будет происходить до получения ответа сервера. Т.е. можно сделать запрос на тысячи ресурсов и потом неспешно собирать их из ответа сервера. Сервер будет отправлять потоки в произвольном порядке, мультиплексируя все части в одном соединении.

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

Protocol::HTTP2::Server

Модуль для реализации сервера протокола HTTP/2.

Есть достаточно большой пример из документации модуля , но нас больше интересует именно получение запроса.

# Создать объект сервера (персонально для каждого клиента)
my $server;
$server = Protocol::HTTP2::Server->new(

    # Функция-callback при получении запроса
    on_request => sub {
        my ( $stream_id, $headers, $data ) = @_;
        my $message = "hello, world!";
 
        # Ответ клиенту
        $server->response(
            ':status' => 200,
            stream_id => $stream_id,
 
            # HTTP/1.1 Headers
            headers   => [
                'server'         => 'perl-Protocol-HTTP2/0.08',
                'content-length' => length($message),
                'cache-control'  => 'max-age=3600',
                'date'           => 'Fri, 18 Apr 2014 07:27:11 GMT',
                'last-modified'  => 'Thu, 27 Feb 2014 10:30:37 GMT',
            ],
 
            # Content
            data => $message,
        );
    },
);

Каждый запрос в HTTP/2 порождаёт новый поток - stream. Потоки имеют свои номера stream_id. Поэтому функция-callback получает помимо заголовков и тела запроса ещё и информацию о номере потока, чтобы знать в какой поток направить ответ.

Шувгей

Знаете ли вы, что такое Шувгей ? В легендах из тех мест, где я обитаю, это ветер нечисти. Именно такое название я решил дать пробной реализации HTTP/2 веб-сервера на основе AnyEvent и Protocol::HTTP2. С одной стороны это своеобразный ответ питоновскому веб-серверу Торнадо, а с другой стороны - дань карго-культу странных названий из локального фольклора. Вон японцы дают имена Годзилла, Хероку и получаются вполне успешные проекты, может дело именно в названиях? ;)

Проект Shuvgey выложен на github и на CPAN . Веб сервер следует спецификации Plack/PSGI, поэтому его можно также запускать и через plackup:

plackup -s Shuvgey --no_tls app.psgi

или непосредственно:

shuvgey --tls_crt path/to/cert --tls_key path/to/key app.psgi

Как видно веб-сервер может работать как без TLS на чистом протоколе HTTP/2, так и с использованием шифрования. Кроме того есть опция --upgrade, которая позволяет веб-серверу апгрейдить соединение с HTTP/1.1 до HTTP/2, но это возможно только для соединений без шифрования.

Firefox + Shuvgey

Чтобы увидеть веб-сайте по протоколу HTTP/2 надо использовать браузер, который поддерживает последний черновик HTTP/2. Можно загрузить «ночные» сборки Firefox 32.0a. Для активации HTTP/2 потребуется установить опцию на странице настроек about:config:

network.http.spdy.enabled.http2draft=true

Также придётся выключить опцию (вероятно это баг в Net::SSLeay):

network.http.spdy.enforce-tls-profile=false

Конечно не всё ещё работает гладко, мне приходилось наблюдать сбросы потоков, с этим предстоит разобраться, но в принципе прототип вполне рабочий.

Бенчмарки

Я попробовал сделать бенчмарк Shuvgey, чтобы оценить насколько всё ужасно сделано. Но оказалось всё не так уж плохо, даже на таком начальном этапе.

Для теста я взял утилиту h2load из проекта nghttp2:

# Вариант без TLS
$ h2load http://127.0.0.1:5000/ -n 10000
finished in 11 sec, 601 millisec and 151 microsec, 861 req/s, 24 kbytes/s

# Вариант с TLS
$ h2load https://127.0.0.1:5000/ -n 10000
finished in 13 sec, 790 millisec and 799 microsec, 725 req/s, 20 kbytes/s

Для сравнения c HTTP/1.1 взял веб-сервер Twiggy (как наиболее близкий по технологии):

# Вариант без TLS
$ ab2 -n 10000 -c 1 http://127.0.0.1:5000/
Requests per second:    1158.82 [#/sec] (mean)

Т.е. в варианте без TLS реализация HTTP/2 проигрывает реализации HTTP/1.1 примерно на четверть (861 против 1158 запросов в секунду).

В принципе это объяснимо, т.к. HTTP/2 достаточно интенсивно нагружает процессор, т.к. требуется обработка поступающих фреймов и декодирование заголовков. В однопоточном приложении это становится критическим фактором.

Но не стоит думать, что HTTP/2 плох, ведь перед ним не ставилась цель упростить протокол, а ставилась задача снижения задержки и уменьшения количества соединений при одновременном увеличении количества загружаемых ресурсов.

Итог

Работа над модулем протокола и веб-сервером продолжится. Думаю, что продолжу писать небольшие заметки о протоколе HTTP/2. Например, в следующей статье можно будет рассказать о сжатии заголовков HPACK. Это довольно интересный алгоритм и обеспечивает эффективное сжатие заголовков, особенно при большой серии однообразных запросов. Текущая реализация содержится в модуле Protocol::HTTP2::HeaderCompression

Теги: perl HTTP2 Shuvgey

Обновлён: 2014-05-20 12:15


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

Денис Зотов

2015-03-22 17:06

Хорошо рассказал, даже с наглядными фрагментами кода. Хоть мало имею практики с программными реализациями в последнее время, статья помогла получить более детальное представление о новом протоколе. Автору спасибо за сводную информацию.


Константин

2015-06-04 22:02

Было бы здорово иметь так же prefork-HTTP2-сервер, типа Starlet. В некоторых задачах блокирующая обработка запросов предпочтительнее, при этом от "внешнего мира" защищает реверс-прокси.


crux

2015-06-15 22:50

> Было бы здорово иметь так же prefork-HTTP2-сервер, типа Starlet.
> В некоторых задачах блокирующая обработка запросов предпочтительнее,
> при этом от "внешнего мира" защищает реверс-прокси.

В этом случае реверс-прокси должен понимать HTTP2-протокол. Такие прокси уже существуют и они могут проксировать из HTTP2 в HTTP/1.1, поэтому можно оставить Starlet или любой другой HTTP/1.1 бэкенд-сервер.


anonymous

2015-06-18 13:21

>В этом случае реверс-прокси должен понимать HTTP2-протокол.

Естественно. Идея была в том, чтобы использовать для внутренних коммуникаций стандартизованный бинарный протокол вместо uwsgi или fastcgi


Алеша

2015-10-29 02:21

А вообще какой смысл переезжать на HTTP2? Я нагуглил один пример с результатом перезда https://intsystem.org/server/pereexal-na-https-vklyuchil-http2/ чувак пишет что абстрактная страница загружается чуть ли не в два раза быстрее. Это реально? Чет я не верю.


crux

2015-10-30 19:18

Пример со сравнением загрузки 180 кусочков изображения по http/1.1 против http/2 https://http2.golang.org


1informer.com

2016-12-23 01:36

На сколько влияет http/2 на скорость сайта ?

Есть ли какой то плюс при ранжирование сайта?


Оставить комментарий