这主要是出于好奇;说我有一个 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 版本)也无济于事......
我需要向启动 http2 连接的客户端发送什么响应,以便他们将连接降级到 HTTP1.1?
我不认为你试图做的事情有道理。为什么首先要接受 HTTP/2 连接?直接拒绝就好了。为什么要实现 HTTP/2 就说你不支持 HTTP/2?
但是,如果您真的想这样做,那么您需要使用GOAWAY框架和HTTP_1_1_REQUIRED错误代码关闭连接。您无法降级连接。
为了给这个答案提供更多的背景知识,如果不支持 HTTP/2,确保您不建立 HTTP/2 连接已经考虑了很多。基本上有 3 种建立 HTTP/2 连接的方法:
HTTPS 是绝大多数 HTTP/2 用例(当然来自不使用 HTTP 时不支持 HTTP/2 的浏览器),但所有三种方法都具有显式检查作为连接建立的一部分。所以应该不可能有一个只支持 HTTP/1.1 的 HTTP/2 连接(此时我很难看出我写的最后一句话是如何有意义的!)。但是,嘿,奇怪的事情发生了......
所以,忽略这一点,现在和你一起,HTTP_1_1_REQUIRED在RST_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 连接上重试。这不会降级连接,而只是拒绝如上所述的一个流。