- From: Willy Tarreau <w@1wt.eu>
- Date: Wed, 18 Dec 2024 11:28:34 +0100
- To: HTTP Working Group <ietf-http-wg@w3.org>
- Cc: mcmanus@ducksong.com
Hi! RFC8441 ("Bootstrapping WebSockets with HTTP/2") indicates how to deal with an Upgrade over HTTP/2. To make it short, HTTP/1's Connection+Upgrade->101 becomes a CONNECT+:protocol->200 in H2. The RFC specifies a :protocol pseudo-header that allows to use the mechanism beyond Websocket. A while ago, we've implemented the mechanism in haproxy and we support the upgrade from H1+Upgrade to H2+CONNECT. So far it works fine for our users. A few months ago we got a report of breakage[1] that unveiled something unexpected: given that CONNECT doesn't support a request body, we had simplified our support for Upgrade and completely rejected any request body at the H1 layer (so that we could fail early when not knowing if the output would be in H1 or H2). But doing so we accidentally broke support for the "Docker Engine API" protocol over H1. That's how we discovered that some protocols were relying on POST + Upgrade! (maybe it's the only one, I don't know, it was the first report in a few years of existence). Thus, for now we've worked around the problem so that our code now rejects the upgrade only on the outgoing H2 side when facing a body, but the problem remains that the mechanism proposed by RFC8441 doesn't offer provisions for completely transporting H1 over H2, nor replacing H1 with H2. Indeed, users of this docker engine protocol will need to limit themselves to H1 when talking to servers or other proxies. It's a bit sad, and I liked the idea of using the semantically-close CONNECT for this. To me, it looks like the limitations are shared among multiple causes: - the docker engine API relies on upgrades after a POST, which are not much common, and even never mentioned in any of the upgrade specs (2817, 7230, 9110), though common sense dictates that it's expected to work fine since the client cannot know whether the server will accept the upgrade or not. I.e. it's natural to think that the server is expected to consume the whole request body before upgrading, or at least drain it after rejecting the upgrade. - RFC8441 that proposes to use the CONNECT method for the tunnel, while CONNECT itself is defined as "not having content" (9110) or "not having semantics" (7231), which looked fine for websocket but not necessarily for others (that were not identified by then). - the use of RFC8441 for non-websocket protocols (but if not usable, what could be the point of ":protocol" ?) I really don't know what could reasonably be done at this point to address that incompatibility. Here are a few ideas: - we could decide that in the context of RFC8441, CONNECT works a bit like GET in that by default the request has no message body unless it is explicitly advertised, and in this case the request cannot succeed until it's entirely consumed. That sounds quite reasonable since this would remove exceptions, and not change anything for existing implementations that don't look for that message body. That does not solve the situation for request bodies sent using chunked encoding however. - we could imagine a new method that does like CONNECT but also covers the message body. Same as above, there are still no provisions in H2+ to transmit a chunked body and delimit the part that belongs to the request message and the part that is to be tunnelled. And I'm personally not convinced about the improved interoperability of a new method. - we could explicitly state that POST+Upgrade is strictly forbidden, but we now know it's already in use and working fine for at least one implementation. - or we could do nothing and consider that some parts of HTTP/1 will remain HTTP/1 forever and will not be transportable over newer versions. I'm not fundamentally against it but it would warrant some extra documentation (especially in the H1 related specs) to discourage such use so that we make sure no new protocol adopts them and stay stuck in a corner. I'd say that the first approach (tolerating content-length with CONNECT when :protocol is used) has my preference. It will not permit chunked requests but should cover the vast majority of use cases where the server expects a body before deciding to upgrade (after all not all HTTP/1 servers support chunked requests either). But I'm interesting in ideas and opinions others might have. Thanks! Willy [1] https://github.com/haproxy/haproxy/issues/2684
Received on Wednesday, 18 December 2024 10:28:41 UTC