如何要求客户端将 HTTP2 连接降级为 HTTP1.1
How to ask the client to downgrade a HTTP2 connection to HTTP1.1
这主要是出于好奇;说我有一个 HTTP1.1 兼容的服务器。我没有资源/无法向该服务器添加完整的 HTTP2 支持。
我仍然希望能够通过告诉他们改用 HTTP1.1 来为 clients which open a connection with a HTTP2 connection preface (PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
) 提供服务。请注意,这是关于 不 使用 Upgrade
header.
HTTP1.1 请求启动连接的客户端
这是我想要实现的行为:
CLIENT SERVER
| ------ http2 GET --> |
| <----need http1 ---- |
| ------ http1 GET --> |
... (normal http1 processing)
该规范包含一个错误代码 HTTP_1_1_REQUIRED (0xd)
,这听起来正是我所需要的。此外,从周围阅读看来,此错误代码似乎用于在必要时降级连接(显然是 TLS re-negotiation?)。但是我似乎无法让它工作...
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 Version not supported) 也无济于事...
What response do I need to send to a client initiating a http2 connection for them to downgrade the connection to HTTP1.1?
我认为您的尝试没有意义。你为什么首先接受 HTTP/2 连接?直接拒绝就行了。为什么实施 HTTP/2 只是为了说您不支持 HTTP/2?
但是如果你真的想这样做,那么你需要关闭一个 GOAWAY
帧和 HTTP_1_1_REQUIRED
错误代码的连接。您无法降级连接。
为了给这个答案提供更多的背景知识,已经进行了很多思考以确保在不支持 HTTP/2 的情况下不建立 HTTP/2 连接。基本上有 3 种建立 HTTP/2 连接的方法:
- HTTPS - 作为使用 ALPN 扩展的 TLS 协商的一部分。只有在服务器实际宣传 HTTP/2 支持时才会成功。
- HTTP - 作为升级舞蹈的一部分 - 只有在服务器实际通告 HTTP/2 支持时才会成功。
- HTTP - 作为直接连接的一部分。这似乎是你的建议。这假设 HTTP/2 支持,所以风险更大,所以只有在知道服务器支持 HTTP/2 的可能性很高(例如,您同时拥有客户端和服务器)和客户端时才应该这样做应准备好处理前言连接失败的情况,以防知识证明是错误的。
HTTPS 是 HTTP/2 用例的绝大部分(当然在不使用 HTTP 时来自不支持 HTTP/2 的浏览器),但是所有三种方法都有显式检查作为连接建立的一部分.所以应该不可能有一个只支持 HTTP/1.1 的 HTTP/2 连接(在这一点上,我很难理解我写的最后一句话有什么意义!)。但是嘿,奇怪的事情发生了......
因此,忽略这一点,暂时与您一起,在 RST_STREAM
帧上使用错误代码 HTTP_1_1_REQUIRED
旨在拒绝单个流,而不是整个连接。因此,需要客户端证书(在 HTTP/2 - though a proposal exists to add it) of websockets (originally not supported under HTTP/2 but since added 下不受支持)的请求应该拒绝具有此错误代码、RST_STREAM
和该错误代码的请求。应保留连接,以便可以发出任何其他 HTTP/2 兼容请求。理论上,您可以以相同的方式拒绝每个流请求,但似乎有点愚蠢且效率低下,仅仅为了说“请使用 HTTP/1.1”而费心建立 HTTP/2 连接它发送的每个请求。
如果你想拒绝整个连接,那么你应该不使用RST_STREAM
框架([=28=])。此 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
首先,您不能在流 0 上使用 RST_STREAM
,因此您应该只在流 ID > 0:
上发送它
RST_STREAM frames MUST be associated with a stream. If a RST_STREAM
frame is received with a stream identifier of 0x0, the recipient MUST
treat this as a connection error (Section 5.4.1) of type
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 连接上重试。这不会降级连接,而只是按照上述拒绝一个流。
这主要是出于好奇;说我有一个 HTTP1.1 兼容的服务器。我没有资源/无法向该服务器添加完整的 HTTP2 支持。
我仍然希望能够通过告诉他们改用 HTTP1.1 来为 clients which open a connection with a HTTP2 connection preface (PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
) 提供服务。请注意,这是关于 不 使用 Upgrade
header.
这是我想要实现的行为:
CLIENT SERVER
| ------ http2 GET --> |
| <----need http1 ---- |
| ------ http1 GET --> |
... (normal http1 processing)
该规范包含一个错误代码 HTTP_1_1_REQUIRED (0xd)
,这听起来正是我所需要的。此外,从周围阅读看来,此错误代码似乎用于在必要时降级连接(显然是 TLS re-negotiation?)。但是我似乎无法让它工作...
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 Version not supported) 也无济于事...
What response do I need to send to a client initiating a http2 connection for them to downgrade the connection to HTTP1.1?
我认为您的尝试没有意义。你为什么首先接受 HTTP/2 连接?直接拒绝就行了。为什么实施 HTTP/2 只是为了说您不支持 HTTP/2?
但是如果你真的想这样做,那么你需要关闭一个 GOAWAY
帧和 HTTP_1_1_REQUIRED
错误代码的连接。您无法降级连接。
为了给这个答案提供更多的背景知识,已经进行了很多思考以确保在不支持 HTTP/2 的情况下不建立 HTTP/2 连接。基本上有 3 种建立 HTTP/2 连接的方法:
- HTTPS - 作为使用 ALPN 扩展的 TLS 协商的一部分。只有在服务器实际宣传 HTTP/2 支持时才会成功。
- HTTP - 作为升级舞蹈的一部分 - 只有在服务器实际通告 HTTP/2 支持时才会成功。
- HTTP - 作为直接连接的一部分。这似乎是你的建议。这假设 HTTP/2 支持,所以风险更大,所以只有在知道服务器支持 HTTP/2 的可能性很高(例如,您同时拥有客户端和服务器)和客户端时才应该这样做应准备好处理前言连接失败的情况,以防知识证明是错误的。
HTTPS 是 HTTP/2 用例的绝大部分(当然在不使用 HTTP 时来自不支持 HTTP/2 的浏览器),但是所有三种方法都有显式检查作为连接建立的一部分.所以应该不可能有一个只支持 HTTP/1.1 的 HTTP/2 连接(在这一点上,我很难理解我写的最后一句话有什么意义!)。但是嘿,奇怪的事情发生了......
因此,忽略这一点,暂时与您一起,在 RST_STREAM
帧上使用错误代码 HTTP_1_1_REQUIRED
旨在拒绝单个流,而不是整个连接。因此,需要客户端证书(在 HTTP/2 - though a proposal exists to add it) of websockets (originally not supported under HTTP/2 but since added 下不受支持)的请求应该拒绝具有此错误代码、RST_STREAM
和该错误代码的请求。应保留连接,以便可以发出任何其他 HTTP/2 兼容请求。理论上,您可以以相同的方式拒绝每个流请求,但似乎有点愚蠢且效率低下,仅仅为了说“请使用 HTTP/1.1”而费心建立 HTTP/2 连接它发送的每个请求。
如果你想拒绝整个连接,那么你应该不使用RST_STREAM
框架([=28=])。此 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
首先,您不能在流 0 上使用 RST_STREAM
,因此您应该只在流 ID > 0:
RST_STREAM frames MUST be associated with a stream. If a RST_STREAM frame is received with a stream identifier of 0x0, the recipient MUST treat this as a connection error (Section 5.4.1) of type 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 连接上重试。这不会降级连接,而只是按照上述拒绝一个流。