Как попросить клиента понизить соединение HTTP2 до HTTP1.1 - PullRequest
1 голос
/ 19 октября 2019

Это в основном из любопытства;скажем, у меня есть HTTP1.1-совместимый сервер. У меня нет ресурсов / невозможно добавить полную поддержку HTTP2 на этот сервер.

Тем не менее я хочу иметь возможность обслуживать клиентов, которые открывают соединение с предисловием соединения HTTP2 (PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n), сказав им использовать HTTP1.1 вместо этого. Обратите внимание, что речь идет о клиентах, которые не запускают соединение с запросом HTTP1.1 с заголовком Upgrade.

Я бы хотел добиться такого поведения:

CLIENT                 SERVER
  | ------ http2 GET --> |
  | <----need http1 ---- |
  | ------ http1 GET --> |
  ... (normal http1 processing)

В спецификации содержится код ошибки HTTP_1_1_REQUIRED (0xd), который звучит примерно так, как мне нужно. Более того, из прочтения кажется, что этот код ошибки используется для понижения уровня соединений при необходимости (очевидно, повторное согласование TLS?). Тем не менее, я просто не могу заставить это работать ...

char const Http2EmptySettingsFrame[] =
  {
   // Frame Header
   0, 0, 0,
   0x4,
   0,
   0 & 0x7F, 0, 0, 0
  };

char const Http2RstStreamFrame[] =
  {
   // Frame Header
   0, 0, 4, // Length of frame content
   0x3, // Type: RST_STREAM
   0, // Flags
   0 & 0x7F, 0, 0, 1, // Stream identifier 1 ; also tried with 0

   // Frame content
   0, 0, 0, 0xD // Error code (RST_STREAM frame)
  };

char const Http2GoAwayFrame[] =
  {
   // Frame Header
   0, 0, 8,
   0x7,
   0,
   0 & 0x7F, 0, 0, 0,

   0 & 0x7F, 0, 0, 0, // GO_AWAY stream id
   0, 0, 0, 0xD // GO_AWAY error code: HTTP_1_1_REQUIRED
  };

Используя вышеизложенное и fwrite(frame, sizeof(frame), 1, stdout) в небольшой тестовой программе, я создаю кадры для отправки клиенту (./a.out > frame.bin). Затем я запускаю свой «сервер»:

cat emptysettings.bin rststream.bin goaway.bin - | netcat -l -p 8888 -s 127.0.0.1

Используя клиент http2, я получаю ...

# this was when just sending empty SETTINGS and RST_STREAM frame
nghttp -v http://127.0.0.1:8888
[  0.000] Connected
[  0.000] recv SETTINGS frame <length=0, flags=0x00, stream_id=0>
          (niv=0)
[  0.000] [INVALID; error=Protocol error] recv RST_STREAM frame <length=4, flags=0x00, stream_id=1>
          (error_code=HTTP_1_1_REQUIRED(0x0d))
[  0.000] send SETTINGS frame <length=12, flags=0x00, stream_id=0>
          (niv=2)
          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
[  0.000] send GOAWAY frame <length=34, flags=0x00, stream_id=0>
          (last_stream_id=0, error_code=PROTOCOL_ERROR(0x01), opaque_data(26)=[RST_STREAM: stream in idle])
[ERROR] request http://127.0.0.1:8888 failed: request HEADERS is not allowed
Some requests were not processed. total=1, processed=0

... или используя curl ...

curl -v --http2-prior-knowledge http://127.0.0.1:8888
*   Trying 127.0.0.1:8888...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8888 (#0)
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x16887f0)
> GET / HTTP/2
> Host: 127.0.0.1:8888
> User-Agent: curl/7.66.0
> Accept: */*
> 
* Connection state changed (MAX_CONCURRENT_STREAMS == 4294967295)!
* stopped the pause stream!
* Connection #0 to host 127.0.0.1 left intact
curl: (16) Error in the HTTP2 framing layer

... таким образом: Какой ответ мне нужно отправить клиенту, инициирующему соединение http2, чтобы он понизил соединение до HTTP1.1?

Отправка HTTP/1.1 505 hmmm\n\n(505 = HTTP-версия не поддерживается ) тоже не помогает ...

1 Ответ

2 голосов
/ 19 октября 2019

Какой ответ мне нужно отправить клиенту, инициирующему соединение http2, чтобы он понизил соединение до HTTP1.1?

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

Однако, если вы действительно хотите это сделать, вам нужно закрыть соединение с GOAWAY фреймом и HTTP_1_1_REQUIREDкод ошибки. Вы не можете понизить соединение.

Чтобы получить более подробные сведения об этом ответе, мы много думали о том, чтобы не устанавливать соединение HTTP / 2, если HTTP / 2 не поддерживается. Существует в основном 3 способа установления соединения HTTP / 2:

  1. HTTPS - как часть согласования TLS с использованием расширения ALPN. Который должен быть успешным, только если сервер фактически объявляет о поддержке HTTP / 2.
  2. HTTP - как часть танца обновления - который должен быть успешным, только если сервер фактически объявляет о поддержке HTTP / 2.
  3. HTTP- как часть прямой связи. Что, кажется, то, что вы предлагаете. Это предполагает поддержку HTTP / 2, поэтому это немного более рискованно, поэтому следует делать это только тогда, когда существует высокая вероятность того, что сервер поддерживает HTTP / 2 (например, у вас есть и клиент, и сервер), и клиент должен быть готов кобрабатывать случай, когда предисловие соединения не удается, в случае, если знание оказывается неправильным.

HTTPS - это подавляющее большинство случаев использования HTTP / 2 (и, конечно, из браузера, который не поддерживает HTTP / 2, когдане использует HTTP), но все три метода имеют явные проверки как часть установления соединения. Поэтому должно быть невозможным иметь соединение HTTP / 2, которое поддерживает только HTTP / 1.1 (и в этот момент я пытаюсь понять, как это последнее предложение, которое я написал, имеет смысл!). Но, эй, странные вещи произошли ...

Итак, игнорирование этого и дальнейшее использование с вами кода ошибки HTTP_1_1_REQUIRED в кадре RST_STREAM предназначались для отклонения одного потока, а невся связь. Поэтому запрос, который требует клиентских сертификатов (не поддерживается в HTTP / 2 - , хотя существует предложение добавить его ) веб-сокетов (изначально не поддерживается в HTTP / 2, но , так как добавлен ), долженотклонить запросы с этим кодом ошибки, с RST_STREAM и этим кодом ошибки. Соединение следует оставить на месте, чтобы можно было выполнить любой другой HTTP / 2-совместимый запрос. Теоретически вы могли бы просто отклонить каждый запрос потока таким же образом, но это кажется немного глупым и неэффективным, чтобы пытаться установить соединение HTTP / 2 исключительно с целью сказать «Пожалуйста, используйте HTTP / 1.1» для каждого отправляемого запроса.

Если вы хотите отклонить все соединение, вам следует , а не использовать RST_STREAM кадр (, который не разрешен для идентификатора потока 0 ), а вместо этого использоватьGOAWAY кадр (, который должен быть отправлен в потоке 0 ). В этом сообщении GOAWAY может использоваться код ошибки HTTP_1_1_REQUIRED. Это закроет все соединение, и клиенту потребуется переподключиться - в этот момент, как указано выше, вы не должны принимать соединение HTTP / 2. Невозможно понизить соединение. Технически, для этого он мог бы использовать методы обновления, но это предложение, а не команда.

Итак, оставив позади, давайте посмотрим на ваш код и ошибки:

   0x3, // Type: RST_STREAM
   0, // Flags
   0 & 0x7F, 0, 0, 1, // Stream identifier 1 ; also tried with 0

Ну, для начала вы не можете использовать RST_STREAM в потоке 0, поэтому вы должны отправлять это только в потоке с идентификатором> 0:

Кадры RST_STREAM ДОЛЖНЫ быть связаны с потоком. Если кадр RST_STREAM получен с идентификатором потока 0x0, получатель ДОЛЖЕН трактовать это как ошибку соединения (раздел 5.4.1) типа PROTOCOL_ERROR.

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

[INVALID; error=Protocol error] recv RST_STREAM frame <length=4, flags=0x00, stream_id=1>
      (error_code=HTTP_1_1_REQUIRED(0x0d))

Мне кажется, вы отправили RST_STREAM еще до того, как у клиента была возможность установить этот поток! Клиент даже не отправил свой первый SETTINGS кадр (требуется протоколом HTTP / 2 в качестве первого сообщения). Вы не можете сбросить поток, пока он не установлен. Подождите, пока не придет GET-запрос, а затем отклоните его с RST_STREAM, и он должен повторить попытку на отдельном соединении HTTP / 1.1. Это НЕ приведет к ухудшению соединения, а только к отклонению этого потока, как указано выше.

...