В ProxyMapService поддерживает трансляцию HTTP/1.1 → HTTP/2. Это особенно полезно, когда клиент поддерживает только HTTP/1.1, а сервер назначения требует HTTP/2. Клиент продолжает работать по HTTP/1.1, а прокси взаимодействует с сервером по h2 и преобразует ответы обратно в HTTP/1.1.
Зачем это нужно
Многие современные серверы (gRPC-шлюзы, Kestrel с h2-only, CDN) принимают или предпочитают HTTP/2. При этом клиентские программы, скрипты и старые библиотеки часто умеют только HTTP/1.1. Прокси закрывает этот разрыв: клиент ничего не меняет, а сервер получает современный протокол. Весь трафик по-прежнему проходит через прокси и логируется, в том числе h2-фреймы на стороне сервера.
Настройка
Трансляция HTTP/1.1 → HTTP/2 включена в ProxyMapService по умолчанию.
Параметр EnableTranslateH1ToH2 можно задать на трёх уровнях: глобально, для прослушиваемого порта или для отдельного правила хоста. Если необходимо отключить трансляцию, можно явно установить значение false.
Например, чтобы отключить трансляцию глобально:
{
"HTTP": {
"EnableTranslateH1ToH2": true
}
}
{
“HTTP”: {
“EnableTranslateH1ToH2”: false
}
}
При необходимости более гибкой настройки этот параметр также можно задать для конкретного прослушиваемого порта или правила хоста.
Трансляция работает только для HTTPS-соединений с включённой расшифровкой трафика (MITM): прокси должен анализировать запросы, чтобы преобразовывать их в HTTP/2-фреймы. Этот параметр не влияет на прозрачные туннели и обычный HTTP.
Важно: включение трансляции не гарантирует, что будет использоваться HTTP/2. Сервер назначения должен выбрать h2 во время TLS-рукопожатия, поэтому h2 должен быть включён в SslClientOptions.ApplicationProtocols (см. ниже).
Как это устроено и ограничения
Каждый запрос клиента парсится как HTTP/1.1, затем прокси отправляет на сервер HEADERS и DATA-фреймы, а ответ h2 собирает обратно в строку статуса, заголовки и тело. Правила преобразования:
- hop-by-hop заголовки (
Connection,Keep-Alive,Transfer-Encoding,Upgrade,Host) отбрасываются;Hostстановится псевдозаголовком:authority - метод, схема и путь уходят в
:method,:scheme,:path - ответ без
Content-Lengthотдаётся клиенту сTransfer-Encoding: chunked, трейлеры передаются - некорректные заголовки (CR/LF/NUL, конфликтующие
Content-Length, неоднозначныйTransfer-Encoding) отклоняются как защита от request smuggling
Ограничения:
- транслируется только направление h1 → h2; обратного h2 → h1 нет
- одновременно активен один h2-stream: pipelining клиента выполняется последовательно
CONNECTи WebSocket upgrade не транслируются и получают ответ501 Not Implemented- server push отключён (
ENABLE_PUSH=0) - если сервер присылает
RST_STREAM, клиент получит502
Роль ApplicationProtocols
Прокси делает два TLS-рукопожатия, и у каждого свой список ALPN. Для начала три обозначения:
- C —
SslClientOptions.ApplicationProtocols: что прокси предлагает upstream-серверу - S —
SslServerOptions.ApplicationProtocols: что прокси разрешает клиенту - K — ALPN из ClientHello самого клиента: что клиент реально умеет
Порядок рукопожатий обратный: сначала прокси договаривается с upstream, потом отвечает клиенту. Точнее, он заранее подглядывает ClientHello (SocketFlags.Peek) и уже потом выбирает, что предложить серверу.
Если список пуст (в K или в S), подразумевается только http/1.1. Пустой ALPN означает, что клиент ничего не согласовывает, и тогда поверх TLS идёт HTTP/1.1.
Решение принимается в три шага (T — включённая трансляция):
- Возможности клиента:
h1 = K ∩ S содержит http/1.1,h2 = K ∩ S содержит h2. - Что предложить upstream:
h2— если оно есть в C и (клиент умеет h2 или T включена и клиент умеет h1);http/1.1— если оно есть в C и клиент умеет h1. - После ответа upstream: h2 и клиент умеет h2 — прозрачный HTTP/2; h2 и клиент только h1 при включённой T — трансляция; upstream ответил http/1.1 и клиент умеет h1 — обычный HTTP/1.1. Всё остальное — ошибка согласования.
Результат прокси фиксирует: клиенту предлагается ровно один протокол, тот, на котором будет работать сессия.
Сценарии
Следующие сценарии иллюстрируют работу согласования ALPN при включённой и отключённой трансляции. Поскольку трансляция включена по умолчанию, сценарии с отключённой трансляцией применяются только в том случае, если для EnableTranslateH1ToH2 явно установлено значение false.
Обычно браузер предлагает оба протокола — http/1.1 и h2, если только это не изменено в его настройках. curl и аналогичные клиенты часто предлагают только http/1.1.
| C (SslClientOptions) | S (SslServerOptions) | K (клиент) | T | Результат |
|---|---|---|---|---|
| http/1.1 | http/1.1 | любой с h1 | любое | HTTP/1.1 с обеих сторон |
| http/1.1,h2 | http/1.1,h2 | http/1.1,h2 | любое | общается как сервер выбрал: h2 — прозрачный HTTP/2, иначе HTTP/1.1 |
| h2 | http/1.1 | http/1.1 | включена | трансляция h1 → h2 |
| h2 | http/1.1 | http/1.1 | выключена | ошибка согласования |
| h2 | http/1.1,h2 | http/1.1,h2 | любое | прозрачный HTTP/2, трансляция не нужна |
| h2 | http/1.1,h2 | http/1.1 (curl) | включена | трансляция h1 → h2 |
| h2 | http/1.1,h2 | http/1.1 (curl) | выключена | ошибка согласования |
| http/1.1,h2 | http/1.1 | http/1.1,h2 | включена | upstream выбрал h2 — трансляция; выбрал http/1.1 — обычный HTTP/1.1 |
| http/1.1,h2 | http/1.1 | http/1.1,h2 | выключена | HTTP/1.1 с обеих сторон (серверу h2 не предлагается) |
| http/1.1 | h2 | http/1.1,h2 | любое | ошибка: клиент умеет только h2, а upstream не предлагается h2 (обратной трансляции h2 → h1 нет) |
Последняя строка — частая ловушка: http/1.1 в C и жёсткое h2 в S не согласуются ни при каких настройках, потому что трансляция есть только в одну сторону.
Диагностика
При успешной трансляции в логе будет запись уровня Info:
HTTP/1.1 -> HTTP/2 translation: client 127.0.0.1:39461 speaks HTTP/1.1, upstream localhost:5112 negotiated h2
Хорошей отправной точкой для настройки будет SslClientOptions.ApplicationProtocols = "http/1.1,h2" и SslServerOptions.ApplicationProtocols = "http/1.1". Трансляция HTTP/1.1 → HTTP/2 включена по умолчанию, поэтому дополнительных настроек не требуется.
С такой конфигурацией прокси сможет согласовать использование HTTP/2 с сервером назначения, оставив для клиента HTTP/1.1.