如何要求客户端将 HTTP2 连接降级到 HTTP1.1

Dan*_*our 2 http http2

这主要是出于好奇;说我有一个 HTTP1.1 兼容的服务器。我没有资源/无法向该服务器添加完整的 HTTP2 支持。

我仍然希望能够通过告诉客户端PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n使用 HTTP1.1 来为使用HTTP2 连接序言( )打开连接的客户端提供服务。请注意,这是关于使用带有Upgrade标头的 HTTP1.1 请求启动连接的客户端。

这是我想要实现的行为:

CLIENT                 SERVER
  | ------ http2 GET --> |
  | <----need http1 ---- |
  | ------ http1 GET --> |
  ... (normal http1 processing)
Run Code Online (Sandbox Code Playgroud)

该规范包含一个错误代码HTTP_1_1_REQUIRED (0xd),听起来正是我所需要的。此外,从阅读周围来看,这个错误代码似乎用于在必要时降级连接(显然是 TLS 重新协商?)。然而,我似乎无法让它发挥作用......

CLIENT                 SERVER
  | ------ http2 GET --> |
  | <----need http1 ---- |
  | ------ http1 GET --> |
  ... (normal http1 processing)
Run Code Online (Sandbox Code Playgroud)

使用上述内容并fwrite(frame, sizeof(frame), 1, stdout)在一个小型测试程序中,我创建了要发送到客户端的帧 ( ./a.out > frame.bin)。然后我启动我的“服务器”:

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
  };

Run Code Online (Sandbox Code Playgroud)

使用http2客户端然后我得到......

cat emptysettings.bin rststream.bin goaway.bin - | netcat -l -p 8888 -s 127.0.0.1
Run Code Online (Sandbox Code Playgroud)

...或使用 curl ...

# 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
Run Code Online (Sandbox Code Playgroud)

...因此:我需要向启动 http2 连接的客户端发送什么响应,以便他们将连接降级到 HTTP1.1?

发送HTTP/1.1 505 hmmm\n\n(505 =不支持 HTTP 版本)也无济于事......

Bar*_*ard 6

我需要向启动 http2 连接的客户端发送什么响应,以便他们将连接降级到 HTTP1.1?

我不认为你试图做的事情有道理。为什么首先要接受 HTTP/2 连接?直接拒绝就好了。为什么要实现 HTTP/2 就说你不支持 HTTP/2?

但是,如果您真的想这样做,那么您需要使用GOAWAY框架和HTTP_1_1_REQUIRED错误代码关闭连接。您无法降级连接。

为了给这个答案提供更多的背景知识,如果不支持 HTTP/2,确保您不建立 HTTP/2 连接已经考虑了很多。基本上有 3 种建立 HTTP/2 连接的方法:

  1. HTTPS - 作为使用 ALPN 扩展的 TLS 协商的一部分。只有在服务器实际宣传 HTTP/2 支持时才应该成功。
  2. HTTP - 作为升级舞蹈的一部分 - 只有在服务器实际宣传 HTTP/2 支持时才会成功。
  3. HTTP - 作为直接连接的一部分。这似乎是你的建议。这假设支持 HTTP/2,因此风险更大,因此只有在很有可能知道服务器支持 HTTP/2(例如您拥有客户端和服务器)并且客户端应该准备好时才应该这样做处理前言连接失败的情况,以防知识证明是错误的。

HTTPS 是绝大多数 HTTP/2 用例(当然来自不使用 HTTP 时不支持 HTTP/2 的浏览器),但所有三种方法都具有显式检查作为连接建立的一部分。所以应该不可能有一个只支持 HTTP/1.1 的 HTTP/2 连接(此时我很难看出我写的最后一句话是如何有意义的!)。但是,嘿,奇怪的事情发生了......

所以,忽略这一点,现在和你一起,HTTP_1_1_REQUIREDRST_STREAM帧上使用错误代码旨在拒绝单个流,而不是整个连接。这样就要求客户端证书的请求(下不支持HTTP / 2 -虽然提议存在,将其添加)的WebSockets的(最初在HTTP / 2,但不支持自加)应该拒绝此错误代码的请求,与RST_STREAM和错误代码。连接应该保持原状,以便可以发出任何其他 HTTP/2 兼容请求。理论上,您可以仅以相同的方式拒绝每个流请求,但为了对它发送的每个请求说“请使用 HTTP/1.1”的目的而费心建立 HTTP/2 连接,这似乎有点愚蠢且效率低下。

如果你要拒绝整个连接,那么你应该使用RST_STREAM框架(这是不允许的流ID 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
Run Code Online (Sandbox Code Playgroud)

好吧,首先你不能RST_STREAM在流 0 上使用,所以你应该只在流 id > 0 上发送它:

RST_STREAM 帧必须与流相关联。如果 RST_STREAM 帧接收到的流标识符为 0x0,则接收者必须将此视为 PROTOCOL_ERROR 类型的连接错误(第 5.4.1 节)。

但是,如果您愿意,您可以使用它来拒绝每个进来的流。

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

在我看来,您RST_STREAM在客户端甚至有机会建立该流之前就发送了一个!客户端甚至还没有发送它的第一SETTINGS帧(HTTP/2 协议要求作为第一条消息)。您无法在流建立之前重置流。等到 GET 请求进来,然后用 a 拒绝它,RST_STREAM它应该在单独的 HTTP/1.1 连接上重试。这不会降级连接,而只是拒绝如上所述的一个流。