HTTP/1.1 connection upgraded to HTTP/2 has old cowboy_req:version/1
okeuday opened this issue · comments
When using cowboy 2.10.0, a HTTP/1.1 connection that gets upgraded to HTTP/2 doesn't have its cowboy_req:version/1 data updated after the upgrade (i.e., 'HTTP/1.1'
is returned instead of the expected 'HTTP/2'
). Certain response headers are invalid for HTTP/2 use (based on curl use) like X-Content-Type-Options
and X-XSS-Protection
, so it is best to be aware of the current protocol used for the HTTP response headers. The version could be changed to 'HTTP/2'
in cowboy_http:http2_upgrade/4
or cowboy_http2:init/12
.
A way to replicate this problem is with CloudI/CloudI@f279724 using:
$ curl -v -v -v --http2 http://localhost:6464/cloudi/api/rpc/logging_status.erl
* Trying ::1:6464...
* TCP_NODELAY set
* connect to ::1 port 6464 failed: Connection refused
* Trying 127.0.0.1:6464...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 6464 (#0)
> GET /cloudi/api/rpc/logging_status.erl HTTP/1.1
> Host: localhost:6464
> User-Agent: curl/7.68.0
> Accept: */*
> Connection: Upgrade, HTTP2-Settings
> Upgrade: h2c
> HTTP2-Settings: AAMAAABkAARAAAAAAAIAAAAA
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 101 Switching Protocols
< connection: Upgrade
< upgrade: h2c
* Received 101
* 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
* Connection state changed (MAX_CONCURRENT_STREAMS == 4294967295)!
* http2 error: Invalid HTTP header field was received: frame type: 1, stream: 1, name: [X-Content-Type-Options], value: [nosniff]
* HTTP/2 stream 0 was not closed cleanly: PROTOCOL_ERROR (err 1)
* stopped the pause stream!
* Connection #0 to host localhost left intact
curl: (92) HTTP/2 stream 0 was not closed cleanly: PROTOCOL_ERROR (err 1)
Using the curl argument --http2-prior-knowledge
instead of --http2
doesn't cause this error, due to initiating the connection as HTTP/2 (no HTTP/1.1 upgrade occurs).
Good catch, but tough one. The request is HTTP/1.1 so I think we want to keep that around (for example for logs). Only the response is HTTP/2. Perhaps we need a token 'HTTP/1.1 -> HTTP/2'
or something to properly reflect the HTTP/1.1 request HTTP/2 response.
As a workaround for the time being you can check the Connection/Upgrade/HTTP2-Settings header to identify that the upgrade was requested.
@essen I think 'HTTP/1.1 -> HTTP/2'
is an idea separate from the cowboy_req:version/1
value that is closer to a state machine transition, so something that could be returned from a function that could trace the protocol's state machine as it progresses in the function call path. The reason I say that, is because if you want 'HTTP/1.1 -> HTTP/2'
as a return value, you likely want a websocket related return value too and when you have both, you are really representing the protocol's state machine's execution path.
I interpret the cowboy_req:version/1
function as the request's current version and you could always have a function like cowboy_req:version_initial/1
(your naming may prefer cowboy_req:initial_version/1
) to return 'HTTP/1.1'
while cowboy_req:version/1
could return 'HTTP/2'
after the upgrade. My view of:
GET /cloudi/api/rpc/logging_status.erl HTTP/1.1
...
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings: AAMAAABkAARAAAAAAAIAAAAA
is that it is 'HTTP/1.1'
which really isn't sure it wants 'HTTP/2'
because it isn't started as a 'HTTP/2'
request, until the server says its ok to be a 'HTTP/2'
request based on its logic because it is able to support the newer protocol (a "'HTTP/1.1'
XOR 'HTTP/2'
request"). So, I still think it is better to have cowboy_req:version/1
return 'HTTP/2'
.
What you decide is fine. I am not going to work-around it (don't like extra parsing of the Connection/Upgrade/HTTP2-Settings header stuff) because no one has complained about the problem yet. I will just wait for what exists in the future, unless someone complains.
Well the request's version is HTTP/1.1, it is only the response that is HTTP/2. The request's version is HTTP/1.1, it's not changing afterwards. So either we have a special token, or a second field identifying the protocol used in the response. A separate field likely makes more sense. But I need to think about it for a while.
The problem doesn't occur in Websocket because we don't provide the Req after the Websocket upgrade, only before when we are still HTTP/1.1.