[Перевод] HTTP/3: развёртывание HTTP/3 на практике. Часть 3
Фото Wolfgang Rottmann, Unsplash.com
После почти пятилетних разработок протокол HTTP/3 наконец приближается к окончательному выпуску. Рассказываем, какие трудности могут возникнуть при развёртывании и тестировании HTTP/3 и как адаптировать к нему сайты и ресурсы.
Это третья и последняя часть серии о новых протоколах HTTP/3 и QUIC. Если, прочитав предыдущие две части об истории и основных концепциях и функциях производительности HTTP/3, вы решили, что новые протоколы срочно нужно внедрять (будем надеяться, что так вы и решили), здесь мы расскажем, как это сделать.
Начнём с того, что нужно изменить в страницах и ресурсах, чтобы оптимально использовать новые протоколы (это самая простая часть). Затем узнаем, как настроить серверы и клиенты (это сложно, если вы не используете CDN). Наконец, мы увидим, какие инструменты помогут оценить влияние новых протоколов на производительность (здесь пока всё совсем сложно).
Статьи серии:
- Часть 1: история и ключевые концепции HTTP/3
Эта часть для тех, кто в целом мало что знает об HTTP/3 и протоколах. Здесь мы говорим о самых основах. - Часть 2: характеристики производительности HTTP/3
Тут мы углубимся в технические детали. Если с основами вы уже знакомы, начинайте со второй части. - Часть 3: развёртывание HTTP/3 на практике
Здесь описываются трудности, связанные с самостоятельным развёртыванием и тестированием HTTP/3. Мы поговорим, как нужно изменить веб-страницы и ресурсы и нужно ли вообще.
Изменения для страниц и ресурсов
Начнём с хороших новостей: если вы уже используете HTTP/2, скорее всего, при переходе на HTTP/3 менять страницы и ресурсы не придётся совсем. В первой и во второй части мы уже объясняли, что HTTP/3, по сути, это HTTP/2-over-QUIC, и на прикладном уровне две версии не отличаются друг от друга. Все изменения и оптимизации под HTTP/2 будут работать для HTTP/3 и наоборот.
А вот если вы всё ещё держитесь за HTTP/1.1, забыли о переходе на HTTP/2 или ничего не настраивали для HTTP/2, вы можете задаваться вопросом, что и как нужно изменить, для того, чтобы быть готовым к HTTP/3. Даже сегодня сложно найти хорошую статью со всеми подробностями. Как я уже говорил в первой части, сначала все были слишком оптимистично настроены по поводу использования HTTP/2, поэтому многие статьи содержат серьёзные ошибки и плохие советы. К сожалению, многие заблуждения никуда не делись, поэтому я и написал эту серию — чтобы с HTTP/3 такое не повторилось.
Лучше всего об HTTP/2 написано в книге HTTP/2 in Action, автор Barry Pollard. Но она платная, так что я расскажу здесь о самых важных моментах и об их связи с HTTP/3:
1. Одно соединение
Главное отличие HTTP/2 от HTTP/1.1 — одно TCP-соединение вместо 6–30 параллельных. Во второй части я упомянул, что одно соединение может быть таким же быстрым, как несколько, потому что из-за контроля перегрузок может потеряться больше пакетов или потери начнутся раньше (что сведёт на нет преимущества в скорости из-за их одновременного старта). HTTP/3 придерживается такого же подхода, но с протоколом QUIC вместо TCP. Сама по себе разница несущественная (разве что издержки на стороне сервера меньше), но с ней связано много других факторов.
2. Шардинг между серверами и объединение соединений
На практике было очень сложно перейти на одно соединение, потому что многие страницы были разделены между именами хостов и даже серверами (например, img1.example.com
и img2.example.com
). Всё потому, что браузеры открывали не больше шести соединений для каждого имени хоста, так что если их было несколько, соединений могло быть больше. Без изменений HTTP/2 до сих пор открывал бы несколько соединений, мешая работать другим функциям, например, приоритизации (см. ниже).
Так что всем советовали отменить шардинг между серверами и по максимуму собрать ресурсы на одном сервере. У HTTP/2 даже была функция объединения соединений, которая упрощала переход с HTTP/1.1, — connection coalescing. В общих чертах, если два имени хоста соответствуют одному IP сервера (через DNS) и используют аналогичный сертификат TLS, браузер может использовать одно соединение даже между двумя именами хостов.
На практике объединение соединений сложно правильно организовать, например, из-за некоторых хитрых проблем безопасности, связанных с CORS. Даже если всё настроить правильно, соединений все равно может оказаться два. Правда, это не всегда плохо. Во-первых, из-за плохо реализованных приоритизации и мультиплексирования (см. ниже) одно соединение может быть медленнее, чем два и больше. Во-вторых, если соединений слишком много, пакеты начнут теряться раньше из-за состязающихся контролеров перегрузок. Если их будет немного (но больше одного), можно найти баланс между перегрузкой и улучшенной производительностью, особенно в быстрых сетях. Так что, по-моему, немного шардинга (где-то 2–4 соединения) не повредит даже для HTTP/2. Фактически большинство современных HTTP/2 ресурсов имеют такие скорости прогрузки, потому что у них всё ещё есть дополнительные соединения или загрузки сторонних ресурсов в «критическом пути».
3. Объединение и встраивание ресурсов
В HTTP/1.1 мог быть только один активный ресурс на соединение, что приводило к блокировке начала очереди (head-of-line, HoL) на уровне HTTP. Соединений было всего 6–30, так что долгое время все практиковали объединение ресурсов в бандл (resource bundle). Даже сегодня есть подобные инструменты, например, Webpack. Кроме того, одни ресурсы часто встраивались в другие (например, критический CSS встраивался в HTML).
Соединение HTTP/2 мультиплексирует ресурсы, поэтому невыполненных запросов для файлов может быть гораздо больше — один запрос больше не занимает соединение (которых и так немного) целиком. Сначала все решили, что в HTTP/2 больше не нужно объединять и встраивать ресурсы. Считалось, что каждый подресурс можно кэшировать по отдельности, и не нужно снова загружать весь бандл, если один из ресурсов изменился. Это правда, но применимо к ограниченному набору ситуаций.
Например, можно понизить коэффициент сжатия, потому что сжатие эффективнее работает с большими объёмами. Кроме того, каждый запрос или файл связан с издержками, потому что должен обрабатываться браузером или сервером. У нескольких сотен маленьких файлов эти издержки будут гораздо больше, чем у нескольких больших файлов. В наших ранних тестах я заметил ухудшение примерно на 40 файлах. Сейчас это число, наверное, немного больше, но файловые запросы до сих пор обходятся не так дёшево, как все изначально ждали от HTTP/2. Наконец, если не встраивать ресурсы, это увеличивает задержку, потому что файлы нужно запрашивать. Более того, из-за проблем с приоритизацией и server push (см. ниже) лучше всё же встраивать критические CSS. Может быть, однажды технология Resource Bundles поможет в этом, но пока этот момент ещё не настал.
Всё это относится и к HTTP/3. Говорят, что QUIC лучше справляется со множеством маленьких файлов, потому что чем больше независимых параллельных потоков, тем больше пользы от удаления блокировки HoL (мы говорили об этом во второй части). Может быть, отчасти это правда, но мы уже видели, что это очень сложная проблема с большим числом переменных. Не думаю, что преимущества перевесят остальные затраты, но нужно подробно изучить вопрос. (Просто мысли вслух: файлы могут быть такого размера, чтобы точно вписываться в один пакет QUIC, и тогда блокировка HoL вообще не будет нас беспокоить. Принимаю роялти от стартапа, который реализует такой бандлер.;))
4. Приоритизация
Чтобы загрузить несколько файлов по одному соединению, их нужно как-то мультиплексировать. Как мы говорили во второй части, в HTTP/2 это мультиплексирование управляется через систему приоритизации. Ещё и поэтому важно, чтобы в одном соединении было запрошено как можно больше ресурсов, — так их можно правильно приоритизировать. Мы видели, что система была очень сложной, часто ее неправильно использовали и реализовывали на практике (см. изображение ниже). Это означало, что некоторые рекомендации для HTTP/2 (объединение ресурсов, раз запросы все равно дешёвые, и отказ от шардинга, чтобы оптимально использовать одно соединение) в реальности не давали ожидаемых преимуществ.
В плохо реализованных стеках HTTP/2 ресурсы с высоким приоритетом (два нижних) загружаются после остальных ресурсов, у которых приоритет ниже. (Источник изображения: Andy Davies) (исходное изображение)
К сожалению, обычный веб-разработчик мало что может с этим сделать, потому что это проблема на стороне браузеров и серверов. Но вы хотя бы можете не использовать слишком много отдельных файлов (так будет сложнее расставить приоритеты) и применять шардинг, пусть и ограниченно. Ещё можно использовать методы расстановки приоритетов, например lazy loading, async
и defer
в JavaScript, и resource hints, например preload
. Все эти механизмы меняют приоритеты ресурсов, чтобы они отправлялись раньше или позже. Но и эти механизмы не без греха. Не думайте, что можно просто назначить preload
группе ресурсов и они сразу станут загружаться быстрее: если у всех ресурсов высокий приоритет, значит, у всех ресурсов одинаковый приоритет! Можно легко взять и отложить загрузку действительно критичных ресурсов, просто используя preload.
Во второй части мы видели, как сильно изменилась система приоритизации в HTTP/3. Мы надеемся, что багов и проблем на практике станет меньше, но пока ни в чём не уверены, потому что мало серверов и клиентов HTTP/3 полностью реализовали эту систему. В любом случае, фундаментальные концепции приоритизации не изменятся. Вы всё ещё не сможете использовать такие механизмы, как preload, не понимая, что происходит внутри, потому что можно случайно неверно расставить приоритеты.
5. Server push
Server push позволяет отправлять ответ, не дожидаясь запроса от клиента. В теории звучит интересно, и эту функцию можно использовать вместо встраивания. Во второй части мы уже говорили, что push сложно использовать правильно из-за проблем с контролем перегрузок, кешированием, приоритизацией и буферизацией. В целом, лучше не использовать server push для загрузки веб-страниц, разве что вы точно знаете, что делаете. Но и тогда большого преимущества вы не получите. Я уверен, что server push может найти применение в (REST) API, чтобы пушить подресурсы, на которые ссылается ответ (JSON), в заранее подготовленном соединении. Это относится как к HTTP/2, так и к HTTP/3.
Думаю, это также относится к возобновлению сеанса TLS и 0-RTT для TCP + TLS или QUIC. Как мы видели во второй части, 0-RTT похож на server push в том, что тоже пытается ускорить начальные этапы загрузки страницы. Но пока обе функции дают очень ограниченные преимущества (особенно в QUIC, где это связано с безопасностью). Чтобы получить пользу, нужно будет тщательно все настраивать на нижнем уровне. Поверить не могу, что раньше мне просто не терпелось объединить server push с 0-RTT.
Что всё это значит?
Можно сформулировать общее правило: применяйте большинство обычных рекомендаций для HTTP/2, но не переусердствуйте.
Вот, например, советы, которые подойдут для HTTP/2 и HTTP/3:
- Разделяйте ресурсы по 1–3 соединениям для критического пути (если только большинство ваших пользователей не находятся в сетях с маленькой полосой пропускания), применяя preconnect и dns-prefetch, где нужно.
- Объединяйте подресурсы по логическому принципу — по пути, функции или частоте изменений. 5–10 JavaScript и 5–10 ресурсов CSS на страницу — это хороший вариант. Встраивать критические CSS по-прежнему может быть полезно.
- Используйте сложные функции, например preload, без фанатизма.
- Используйте сервер, который корректно поддерживает приоритизацию HTTP/2. Для HTTP/2 рекомендую H2O. Apache и NGINX тоже ничего (хотя могло быть и лучше), а Node.js не особо подходит для HTTP/2. По HTTP/3 пока мало сведений (см. ниже).
- Убедитесь, что для веб-сервера HTTP/2 включен TLS 1.3.
Как видите, оптимизировать страницы для HTTP/3 (и HTTP/2) не просто, но и не слишком сложно. Сложнее будет правильно настроить серверы, клиенты и инструменты для HTTP/3.
Серверы и сети
Наверное, вы уже понимаете, что QUIC и HTTP/3 — довольно сложные протоколы. Чтобы реализовать их с нуля, придётся прочитать (и понять!) сотни страниц в семи и более документах. К счастью, сразу несколько компаний уже пять лет работают над опенсорс-реализациями QUIC и HTTP/3, так что у нас есть несколько зрелых и стабильных вариантов на выбор.
Лучшие из них:
Многие (или даже большинство) реализаций, в основном, решают вопросы HTTP/3 и QUIC, и это не полноценные веб-серверы. Если говорить об обычных серверах (вроде NGINX, Apache, Node.js), тут всё идёт не так быстро, и причин тому несколько. Во-первых, мало кто из их разработчиков участвовал в создании HTTP/3 с самого начала, и сейчас приходится нагонять. Многие просто берут какую-нибудь реализацию из списка выше и используют её как библиотеку, но даже интеграция требует много усилий.
Во-вторых, многие серверы используют сторонние библиотеки TLS, например OpenSSL, потому что протокол TLS очень сложный и должен быть хорошо защищён, так что лучше брать уже испытанные решения. Хотя QUIC интегрирован с TLS 1.3, он использует этот протокол совсем не так, как TCP. Это значит, что библиотеки TLS должны предоставлять API специально для QUIC, а их разработчики с этим не спешат. Особенно всё сложно с OpenSSL, которая откладывает поддержку QUIC, но при этом используется многими серверами. Akamai даже начали собственный форк OpenSSL для QUIC и назвали его quictls. Есть другие варианты и обходные пути, но поддержка TLS 1.3 для QUIC всё ещё мешает реализовать протокол на многих серверах, и в ближайшем будущем ничего не изменится.
Неполный список полноценных веб-серверов, которые можно использовать без дополнительной настройки, с указанием того, поддерживают ли они HTTP/3:
- Apache
Пока про поддержку ничего не известно. Никаких объявлений не было. Скорее всего, нужен OpenSSL. (Но есть реализация Apache Traffic Server.) - NGINX
Это кастомная реализация, относительно новая и пока очень экспериментальная. Слияние с основной веткой NGINX ожидается к концу 2021 года. Существует патч для запуска библиотеки Cloudflare quiche на NGINX, и, пожалуй, это пока что более стабильный вариант. - Node.js
Использует библиотеку ngtcp2, развитие которой блокируется развитием OpenSSL, хотя они планируют перейти на форк QUIC-TLS, чтобы поскорее хоть что-то наладить. - IIS
Пока про поддержку ничего не известно, никаких объявлений. Скорее всего, будет использоваться библиотека MsQuic. - Hypercorn
Интегрирует aioquic, экспериментальная поддержка. - Caddy
Использует quic-go, полная поддержка. - H2O
Использует quicly, полная поддержка. - Litespeed
Использует LSQUIC, полная поддержка.
Важные примечания:
- Даже «полная поддержка» предоставляется «как есть» и не означает готовность для продакшена. Например, многие реализации еще не полностью поддерживают миграцию соединения, 0-RTT, server push или приоритизацию в HTTP/3.
- Серверы, которых нет в списке, например Tomcat, пока, насколько мне известно, не делали никаких объявлений.
- Среди серверов из списка только Litespeed, патч Cloudflare для NGINX и H2O разрабатываются теми, кто активно участвовал в стандартизации QUIC и HTTP/3, так что пока, пожалуй, это лучшие варианты.
Как видите, вариантов немного, но что-то выбрать уже можно. Мало просто запустить сервер — куда сложнее настроить его и всю остальную сеть.
Конфигурация сети
Как мы говорили в первой части, QUIC работает поверх UDP, чтобы его было проще развёртывать, потому что большинство сетевых устройств понимает UDP. К сожалению, UDP разрешен не везде. Его часто используют для атак, и он не очень-то нужен в повседневной работе, кроме как для DNS, так что многие корпоративные сети и файрволы почти полностью его блокируют. Скорее всего, UDP нужно будет явно разрешить для входящего и исходящего трафика HTTP/3-серверов. QUIC может использовать любой порт UDP, но обычно это порт 443 (которые используется и для HTTPS-over-TCP).
Многие сетевые администраторы не захотят разрешать весь UDP-трафик, только QUIC-over-UDP. Проблема в том, что QUIC почти полностью зашифрован, включая метаданные, допустим номера пакетов, а также, например, сигналы о закрытии соединения. Для TCP файрволы активно отслеживают все метаданные, чтобы исключить неожиданное поведение. (До пакетов с данными выполнено полное рукопожатие? Пакеты соответствуют ожидаемым паттернам? Сколько соединений открыто?) В первой части мы говорили, что это как раз одна из причин, которые мешают развитию TCP. Но из-за шифрования QUIC файрволы не могут отслеживать почти ничего, а то, что могут, представляет сложности.
Сейчас многие вендоры файрволов рекомендуют блокировать QUIC, пока они не обновят софт. И даже после этого компании будут неохотно разрешать QUIC, потому что файрвол будет поддерживать гораздо меньше функций QUIC, чем дает TCP.
Функция миграции соединений только все усложняет. Мы видели, что она продолжает соединение по новому IP-адресу без дополнительных рукопожатий, с помощью идентификаторов соединений (CID). Файрвол же видит новое соединение без рукопожатия и подозревает, что это злоумышленник отправляет вредоносный трафик. Файрвол не может использовать QUIC CID, потому что из соображений приватности они время от времени меняются. Поэтому нужно будет, чтобы серверы сообщали файрволу ожидаемые CID, но пока таких механизмов не существует.
Те же опасения относятся к балансировщикам нагрузки в масштабных развёртываниях, где входящие соединения распределяются по большому числу бэкенд-серверов. Трафик для одного соединения нужно будет направить на тот же бэкенд-сервер, потому что остальные не будут знать, что с ним делать. В TCP это можно сделать с помощью четырёх параметров, которые никогда не меняются. Если использовать миграцию соединения в QUIC, это уже не поможет. Повторю: серверы и балансировщики нагрузки должны как-то договориться о выборе CID, чтобы разрешить детерминированную маршрутизацию. В отличие от конфигурации файрвола, здесь уже есть предложение по настройке (хотя оно пока мало где реализовано).
Наконец, есть и другие, более общие, соображения по безопасности, в основном, связанные с 0-RTT и DDoS-атаками. Во второй части мы видели, что в QUIC эти проблемы уже активно решаются, но в идеале не помешало бы ещё несколько линий обороны в сети. Например, прокси- или граничные серверы могут не пропускать некоторые запросы 0-RTT к бэкендам, чтобы не допустить атаки повторением (replay attack). Чтобы защититься от атак отражением (reflection attack) или DDoS-атак, которые отправляют первый пакет рукопожатия, а потом перестают отвечать (так называемый SYN-флуд в TCP), QUIC включает функцию повторных попыток. Это позволяет серверу убедиться в добросовестности клиента, не храня никакое состояние (эквивалент TCP SYN cookie). Процесс повторных попыток, конечно, лучше разместить где-нибудь до бэкенд-сервера. На балансировщике нагрузки, например. И снова для этого нужна дополнительная конфигурация и настройка коммуникаций.
Это только самые заметные проблемы, которые возникнут у сетевых и системных администраторов с QUIC и HTTP/3. Есть и другие, и я рассказывал о некоторых. Эти проблемы и частичные решения обсуждаются в двух отдельных документах (здесь и здесь) для QUIC RFC.
Что всё это значит?
HTTP/3 и QUIC — это сложные протоколы, использующие разные внутренние механизмы. Не все из них готовы на сто процентов, но у вас уже есть несколько вариантов развёртывания новых протоколов на бэкенде. Пройдёт несколько месяцев или даже лет, прежде чем основные серверы и базовые библиотеки (например, OpenSSL), будут обновлены.
И даже тогда в масштабных проектах сложно будет настроить серверы и другие сетевые устройства, чтобы новые протоколы можно было использовать безопасно и эффективно. Понадобится хорошая команда DevOps.
Особенно на начальных этапах будет лучше, если настройкой и конфигурированием протоколов за вас займется крупная хостинговая компания или CDN. Как мы говорили во второй части, именно здесь QUIC лучше всего проявит себя, а использование CDN позволяет серьёзно оптимизировать производительность. Лично я рекомендую Cloudflare или Fastly, потому что они активно участвуют в процессе стандартизации и смогут предложить самые продвинутые и продуманные реализации.
Клиенты и обнаружение QUIC
До сих пор мы говорили о поддержке новых протоколов на стороне сервера и в сети, но на стороне клиента тоже не всё так просто.
Начнем с хорошей новости: большинство браузеров уже поддерживают HTTP/3, пусть и в экспериментальном режиме. Вот как обстоят дела на момент написания статьи (см. также caniuse.com):
Браузеры активно поддерживают HTTP/3. (Источник: caniuse.com) (исходное изображение)
- Google Chrome (version 91+): включено по умолчанию.
- Mozilla Firefox (version 89+): включено по умолчанию.
- Microsoft Edge (version 90+): включено по умолчанию (использует Chromium).
- Opera (version 77+): включено по умолчанию (использует Chromium).
- Apple Safari (version 14): включается вручную. Будет включено по умолчанию в версии 15, которая сейчас на стадии technology preview.
- Другие браузеры: пока ничего неизвестно (хотя браузеры на Chromium, например Brave, в теории тоже могут поддерживать протокол).
Важные примечания:
- Большинство браузеров распространяют обновления постепенно, так что не все пользователи получат поддержку HTTP/3 по умолчанию с самого начала. Это делается для того, чтобы один пропущенный баг не повлиял сразу на всех пользователей и чтобы не перегрузить сервера обновлений. Есть небольшая вероятность, что даже в последних версиях браузера вы не получите HTTP/3 по умолчанию и придётся включать его вручную.
- Как и в случае с серверами, поддержка HTTP/3 не означает, что все функции реализованы или используются. Например, 0-RTT, миграция соединения, server push, динамическое сжатие заголовков QPACK и приоритизация HTTP/3 могут пока отсутствовать, использоваться осторожно или иметь неоптимальную конфигурацию.
- Если вы хотите использовать HTTP/3 на клиенте не в браузере (например, в нативном приложении), нужно интегрировать одну из библиотек из списка выше или использовать cURL. Apple скоро предоставит нативную поддержку HTTP/3 и QUIC во встроенных сетевых библиотеках на macOS и iOS, а Microsoft добавляет QUIC в ядро Windows и окружение .NET, но другие системы, например Android, пока не объявили о подобной поддержке.
ALT-SVC
Даже если у вас есть сервер, совместимый с HTTP/3, и обновлённый браузер, это не значит, что HTTP/3 будет работать стабильно. Чтобы понять причину, представьте себя на месте браузера. Пользователь просит вас открыть example.com
(сайт, где вы раньше никогда не были), и вы используете DNS, чтобы преобразовать его в IP-адрес. Вы отправляете один или несколько пакетов рукопожатия QUIC на этот IP. Тут может возникнуть сразу несколько проблем:
- Сервер не поддерживает QUIC.
- Одна из промежуточных сетей или файрволов полностью блокирует QUIC и/или UDP.
- Пакеты рукопожатия могут потеряться при передаче.
Как узнать, что именно случилось? Во всех трёх случаях вы просто не получите ответ на рукопожатие. Вы ждёте и надеетесь, что ответ придет. Когда время ожидания истечет, вы решите, что с HTTP/3 проблема. Тогда вы пытаетесь открыть TCP-соединение с сервером в надежде, что хотя бы HTTP/2 или HTTP/1.1 сработают.
Такой подход приводит к серьёзным задержкам, и особенно это будет проявляться в первые несколько лет, пока многие серверы и сети ещё не поддерживают QUIC. Простое, но наивное решение — открыть сразу два соединения (QUIC и TCP) и использовать то, по которому быстрее придёт ответ. Такой метод называется гонкой соединений, или happy eyeballs. Это возможно, но издержки будут большими. Даже если проигравшее соединение почти сразу закрывается, оно потребляет какое-то количество памяти и процессорных ресурсов на клиенте и сервере (особенно при использовании TLS). Есть и другие проблемы, связанные с IPv4 и IPv6 и атаками повторением (подробнее об этом я рассказываю в своем выступлении).
Так что браузеры предпочтут не рисковать и будут пытаться использовать QUIC, только если известно, что сервер поддерживает протокол. Если браузер впервые связывается с сервером, он будет использовать только HTTP/2 или HTTP/1.1 по TCP-соединению. Сервер может сообщить браузеру, что поддерживает HTTP/3, чтобы его можно было использовать для последующих соединений. Для этого в ответе, который отправляется по HTTP/2 или HTTP/1.1, задается специальный HTTP-заголовок. Заголовок называется Alt-Svc, что расшифровывается как alternative services (альтернативные сервисы). С помощью Alt-Svc можно сообщить браузеру, что определённый сервис доступен через другой сервер (IP и/или порт), а ещё он может указывать на альтернативные протоколы. См. рисунок 1.
Рис. 1: Facebook использует заголовок Alt-Svc, чтобы сообщить браузеру, что он доступен через HTTP/3 на порте UDP 443 (действует в течение 3600 с). Пока протокол называется h3–29 или h3–27 (29-я и 27-я черновые версии HTTP/3), но скоро останется только h3 (некоторые серверы, например, google.com, уже используют h3). (исходное изображение)
Получив заголовок Alt-Svc и узнав о поддержке HTTP/3, браузер кэширует его и с тех пор пытается установить QUIC-соединение. Некоторые клиенты делают это сразу (прямо во время начальной загрузки страницы, см. ниже), другие сначала ждут, пока закроются существующие TCP-соединения. Это означает, что браузер будет использовать HTTP/3 только после того, как уже загрузил хотя бы несколько ресурсов по HTTP/2 или HTTP/1.1. И даже тогда не всё идет гладко. Браузер уже знает, что сервер поддерживает HTTP/3, но про промежуточные сети ему ничего не известно. Так что на практике гонка соединений все ещё нужна. Придётся использовать HTTP/2, если рукопожатие QUIC застряло где-то в сети. Если QUIC-соединение не устанавливается несколько раз подряд, некоторые браузеры на какое-то время помещают кэшированную запись Alt-Svc в чёрный список и не пытаются использовать HTTP/3. Можно вручную очистить кэш браузера, если что-то идёт не так, чтобы удалить привязки Alt-Svc. Наконец, Alt-Svc, как оказалось, может представлять серьёзные риски для безопасности. Поэтому некоторые браузеры накладывают дополнительные ограничения, например, на доступные порты (в Chrome серверы HTTP/2 и HTTP/3 должны оба находиться либо ниже порта 1024 либо на порте 1024 и выше, иначе Alt-Svc будет игнорироваться). Логика у браузеров очень сильно различается, так что добиться стабильности HTTP/3-соединений сложно. Это затрудняет и тестирование новых площадок.
Работы по улучшению двухэтапного процесса Alt-Svc уже ведутся. Есть идея использовать новые записи DNS, SVCB и HTTPS, которые содержат примерно ту же информацию, что и Alt-Svc. С ними клиент может узнать, что сервер поддерживает HTTP/3, ещё на этапе разрешения DNS, а значит можно попробовать QUIC с первой загрузки страницы, не пытаясь сначала использовать HTTP/2 или HTTP/1.1. Больше об этом и об Alt-Svc см. в главе прошлогоднего Web Almanac об HTTP/2.
Как видите, Alt-Svc и обнаружение HTTP/3 добавляет сложности к и без того непростому развёртыванию сервера QUIC, потому что:
- Сервер HTTP/3 всегда нужно развёртывать рядом с сервером HTTP/2 и/или HTTP/1.1.
- Нужно настроить серверы HTTP/2 и HTTP/1.1 так, чтобы они задавали корректные заголовки Alt-Svc в ответах.
В продакшене это ещё можно настроить (например, один инстанс Apache или NGINX, скорее всего, будет поддерживать все три варианта HTTP одновременно), но в локальном тестовом окружении это будет очень хлопотно (лично я забываю добавлять заголовки Alt-Svc или допускаю ошибки в них). Проблема усугубляется тем, что в браузере сейчас недостаёт логов ошибок и индикаторов DevTools, то есть будет сложно понять, что именно пошло не так.
Другие проблемы
И это, к сожалению, не все сложности локального тестирования: Chrome очень затрудняет использование самоподписанных TLS-сертификатов для QUIC. Дело в том, что неофициальные TLS-сертификаты часто используются компаниями для дешифровки TLS-трафика сотрудников (чтобы файрволы, например, могли сканировать зашифрованный трафик). Если компании попытаются провернуть это с QUIC, мы опять получим кастомные реализации промежуточных устройств, которые строят собственные предположения о протоколе. Это может помешать поддержке протокола в будущем, а именно этого мы и пытались избежать, когда так старательно шифровали QUIC. У Chrome свой подход: если вы не используете официальный TLS-сертификат (подписанный центром сертификации или корневым сертификатом, которому Chrome доверяет, например Let«s Encrypt), то и QUIC вам использовать нельзя. К сожалению, это распространяется и на самоподписанные сертификаты, часто используемые для локальных тестовых окружений.
Можно обойти эту проблему с помощью навороченных флагов командной строки (потому что обычный --ignore-certificate-errors пока не работает для QUIC), используя сертификаты для каждого разработчика (хотя настраивать их долго) или установив реальный сертификат на ПК разработки (но это вряд ли подойдёт большим командам, потому что придётся предоставить закрытый ключ сертификата каждому разработчику). Наконец, мы можем установить кастомный корневой сертификат, но придется обойти флаги --origin-to-force-quic-on и --ignore-certificate-errors-spki-list при запуске Chrome (см. ниже). К счастью, сейчас такие строгие требования есть только в Chrome. Будем надеяться, его разработчики со временем передумают.
Если у вас возникают проблемы с настройкой QUIC в браузере, сначала проверьте их с помощью, например, cURL. cURL превосходно поддерживает HTTP/3 (можно даже выбирать из двух разных базовых библиотек), и с ним проще наблюдать за логикой кэширования Alt-Svc.
Что всё это значит?
Кроме сложностей с настройкой HTTP/3 и QUIC на стороне сервера, есть еще проблемы с согласованным использованием новых протоколов в браузерах. Они связаны с двухэтапным процессом обнаружения с HTTP-заголовком Alt-Svc и тем фактом, что HTTP/2-соединения нельзя просто проапгрейдить до HTTP/3, потому что последний использует UDP.
Даже если сервер поддерживает HTTP/3, клиенты (и владельцы сайтов!) ещё должны учитывать промежуточные сети, которые могут блокировать трафик UDP и/или QUIC. Поэтому HTTP/3 никогда полностью не заменит HTTP/2. На практике хорошо настроенная система HTTP/2 нужна будет при самом первом посещении и для клиентов в сетях, которые запрещают UDP. К счастью, и мы уже говорили об этом, страницы не придётся особо адаптировать под HTTP/3, так что хотя бы этой проблемы у нас не будет.
Зато проблемой может стать тестирование и проверка правильной конфигурации и ожидаемого использования протоколов. Это относится и к продакшену, но особенно к локальным развёртываниям. Я думаю, что большинство людей продолжат использовать серверы разработки с HTTP/2 (или даже HTTP/1.1) и перейдут на HTTP/3 только на более позднем этапе. И даже тогда тестировать производительность протокола с помощью инструментов текущего поколения будет сложно.
Инструменты и тестирование
Как и в случае с серверами, производители самых популярных инструментов тестирования веб-производительности не уследили за HTTP/3 с самого начала. В итоге на июль 2021 года у нас мало инструментов с поддержкой нового протокола, и даже эта поддержка неполная.
Google Lighthouse
Начнём с Google Lighthouse. В целом это превосходный инструмент тестирования веб-производительности, но мне всегда недоставало средств именно для протоколов. Проблема в том, что он симулирует медленные сети не очень правдоподобно (как и Chrome DevTools). Это удобный инструмент и обычно его достаточно, чтобы получить представление о медленной сети, но для тестирования различий низкоуровневых протоколов он мало что даёт. У браузера нет прямого доступа к стеку TCP, так что он загружает страницу по обычной сети, а потом искусственно задерживает передачу данных в логику браузера. То есть, например, Lighthouse симулирует только задержку и полосу пропускания, но не потерю пакетов (а мы уже видели, что это одно из главных различий между HTTP/3 и HTTP/2). Также Lighthouse использует продуманную модель симуляции, чтобы примерно оценить реальное влияние сети, потому что, допустим, Google Chrome использует сложную логику, чтобы корректировать некоторые аспекты загрузки страницы при обнаружении медленной сети. Эта модель, насколько я знаю (см. здесь и здесь) пока не адаптирована для IETF QUIC или HTTP/3. Так что если использовать Lighthouse только для сравнения производительности HTTP/2 и HTTP/3, можно получить ошибочные или слишком упрощённые результаты, по которым вы сделаете неверные выводы о возможностях HTTP/3 на практике. Можно надеяться, что в будущем инструмент будет усовершенствован, потому что у браузера есть полный доступ к стеку QUIC, так что Lighthouse может серьёзно улучшить симуляции (и даже учитывать потери пакетов!). Сейчас с помощью Lighthouse в теории можно тестировать загрузку страниц по HTTP/3, но я не рекомендую так делать.
Webpagetest
Ещё есть WebPageTest. Это отличный проект, который позволяет загружать страницы по реальным сетям с реальных устройств по всему миру и добавлять симуляцию на уровне пакетов, чтобы рассматривать сценарии с потерей пакетов. Поэтому WebPageTest, в принципе, может стать лучшим инструментом для сравнения производительности HTTP/2 и HTTP/3. Правда, хоть он и может уже загружать страницы по новому протоколу, HTTP/3 пока не полностью интегрирован в инструменты и визуализации. Например, сейчас н