Skip to content

Fix handling of HTTP upgrades with bodies#60016

Merged
nodejs-github-bot merged 1 commit intonodejs:mainfrom
pimterry:upgrade-with-body
Nov 3, 2025
Merged

Fix handling of HTTP upgrades with bodies#60016
nodejs-github-bot merged 1 commit intonodejs:mainfrom
pimterry:upgrade-with-body

Conversation

@pimterry
Copy link
Member

@pimterry pimterry commented Sep 25, 2025

Fixes #58394

Previously, when processing and accepting a Upgrade request, we ignored all indicators of a body in the request (content-length or transfer-encoding headers) and treated any information following the headers as part of the upgraded stream itself.

This was not correct. An HTTP request to upgrade can have a body, and it will always indicate this with standard headers to do so. If the request had a valid HTTP body, you shouldn't treat it as part of the new protocol stream you've upgraded to.

With this change, we now fully process the requests bodies separately instead, allowing us to automatically handle correct parsing of the body like any other HTTP request.

Fixing this is a matter of persuading llhttp to parse the body like normal. The llhttp return values for on_headers_complete (i.e. this parserOnIncoming function) are (docs):

  • 0: Proceed normally.
  • 1: Assume that request/response has no body, and proceed to parsing the next message.
  • 2: Assume absence of body (as above) and make llhttp_execute() return HPE_PAUSED_UPGRADE.

The current Node code for this basically assumes that 2 is required for to finish parsing an accepted upgrade or CONNECT, but as far as I can tell (based on this) that's not true. In this case, the upgrade flag is already set (that's req.upgrade here) and that's what controls whether the upgrade happens after the message is completed (here). Setting 2 would set the upgrade flag to true (but it's already true) and set SKIPBODY to true (which is what we're trying to fix here, and isn't required because this condition skips the body for CONNECT or no-body-headers-present for upgrades anyway).

This is a breaking change if you are currently accepting HTTP Upgrade requests with request bodies successfully, or if you use socket-specific fields & methods on the upgraded stream argument.

In the former case, before now you will have received the request body and then the upgraded data on the same stream without any distinction or HTTP parsing applied. Now, you will need to separately read the request body from the request (the 1st argument) and the upgraded data from the upgrade stream (the 2nd argument). If you're not interested in request bodies, you can continue to just read from the upgrade stream directly.

In the latter case, if you want to access the raw socket, you should do so via request.socket, instead of expecting the 2nd argument to be a socket.


Separately, it's debatable whether we should also actually support bodies on CONNECT requests. Even with this change, we currently don't. That would require a small change to llhttp. The latest HTTP RFCs say:

Request message framing is independent of method semantics

but also

A CONNECT request message does not have content. The interpretation of data sent after the header section of the CONNECT request message is specific to the version of HTTP in use.

so to my mind it's somewhat unspecified (as opposed to Upgrade requests, where this isn't ambiguous at all imo). Opinions welcome on whether to modify llhttp to support that.

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

author ready PRs that have at least one approval, no pending requests for changes, and a CI started. http Issues or PRs related to the http subsystem. semver-major PRs that contain breaking changes and should be released in the next major version.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HTTP Upgrade IncomingMessage does not parse body

7 participants