Skip to content

http: resume kept-alive when no body allowed #52329

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

mweberxyz
Copy link

@mweberxyz mweberxyz commented Apr 2, 2024

In accordance with https://www.rfc-editor.org/rfc/rfc9112#name-message-body-length: HEAD, 1xx, 204, and 304 responses cannot contain a message body.

If a connection will be kept-alive, resume the socket during parsing so that it may be returned to the free pool, even if the caller has not consumed the known-expected-empty response.

Fixes #47228

Note: The original source of the issue reporter's issue was due to failure to consume the response body of a 3xx request, which this change will not resolve. As an option, 3xx with a Content-Length of 0 could receive the same auto-resume treatment. I am hesitant to propose that change, as some HTTP servers may return a message body (ie <a href=here>Moved Here</a>), which would then make the caller's responsibility to consume or resume dependent on the particular remote server's behavior.

@nodejs-github-bot
Copy link
Collaborator

Review requested:

  • @nodejs/http
  • @nodejs/net

@nodejs-github-bot nodejs-github-bot added http Issues or PRs related to the http subsystem. needs-ci PRs that need a full CI run. labels Apr 2, 2024
Copy link
Contributor

@ShogunPanda ShogunPanda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit. Rest LGTM.

if (!shouldKeepAlive) {
req.shouldKeepAlive = false;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this space.

@mweberxyz mweberxyz marked this pull request as draft April 5, 2024 15:53
@mweberxyz
Copy link
Author

Setting to draft, went to fix review nit and add tests and I am not convinced issue is fully resolved.

@mweberxyz mweberxyz force-pushed the http-keep-alive-auto-resume branch from 2de97af to be6b085 Compare April 5, 2024 17:52
@mweberxyz mweberxyz marked this pull request as ready for review April 5, 2024 17:52
@mweberxyz
Copy link
Author

@ShogunPanda I added tests and discovered that my approach putting the logic in in an else if following the !shouldKeepAlive condition will work for 204 and 304s, but not for HEAD, because at this point shouldKeepAlive is set to false for HEAD requests -- this is coming from llhttp.

@mweberxyz mweberxyz requested a review from ShogunPanda April 5, 2024 17:55
@mweberxyz mweberxyz force-pushed the http-keep-alive-auto-resume branch from be6b085 to 2dee82a Compare April 5, 2024 19:06
Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@mcollina mcollina added the request-ci Add this label to start a Jenkins CI on a PR. label Apr 9, 2024
@ronag
Copy link
Member

ronag commented Apr 9, 2024

This is potentially unsafe. Some servers don't properly end HEAD requests and there is no way to detect it. Such a case would potentially corrupt following responses from the same connection. Which is why in undici we always reset the connection after a HEAD request, unless explicitly told otherwise (reset: false option).

@mweberxyz
Copy link
Author

@ronag re-using a socket following a HEAD request is the existing logic.

I checked with the following replication:

const http = require("http");

const noKeepalive = process.argv.at(-1) === "no-keepalive";

const opts = {};

if (noKeepalive) {
  opts.agent = new http.Agent({ keepAlive: false });
}

console.log({
  version: process.version,
  keepAlive: !noKeepalive,
});

http.get("http://www.example.com", { method: "HEAD", ...opts }, (res) => {
  res.resume();
  const headSocket = res.socket;
  setTimeout(() => {
    http.get("http://www.example.com", { method: "GET", ...opts }, (res2) => {
      console.log({ sameSocket: headSocket === res2.socket });
    });
  }, 500);
});

gives:

% node index.js keepalive
{ version: 'v20.12.1', keepAlive: true }
{ sameSocket: true }

and

% node index.js no-keepalive
{ version: 'v20.12.1', keepAlive: false }
{ sameSocket: false }

Should I take a look at that first before returning to this PR?

@ronag
Copy link
Member

ronag commented Apr 9, 2024

What does this PR do then?

@ronag
Copy link
Member

ronag commented Apr 9, 2024

I think the problem might apply to all expected empty responses. I need to check that for undici as well.

@mweberxyz
Copy link
Author

What does this PR do then?

Makes it so the res.resume() following a HEAD request, a request that returns a 204, or a request that returns a 304 is implicit.

@ronag
Copy link
Member

ronag commented Apr 9, 2024

Makes it so the res.resume() following a HEAD request, a request that returns a 204, or a request that returns a 304 is implicit.

Sorry, I don't understand.

Copy link
Member

@ronag ronag left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I get it. LGTM.

@mweberxyz
Copy link
Author

mweberxyz commented Apr 9, 2024

FWIW what lead me here is a package that was using node:https and not consuming the response on a request that returned a 302, which prevented node from exiting until the keepAlive timed out. I can't fix that because a 302 could return a response, so it should be up to the caller to avoid keeping node from exiting, but figured in these three cases where an http request could not possibly return a response, per spec, we can safely do this automatically.

Will push changes for lint errors shortly.

@mweberxyz mweberxyz force-pushed the http-keep-alive-auto-resume branch from 2dee82a to 8c4db34 Compare April 9, 2024 16:41
@mweberxyz
Copy link
Author

In a twist of irony, my test appears to have left a socket open. Will take a look and push fix.

@mweberxyz mweberxyz force-pushed the http-keep-alive-auto-resume branch from 8c4db34 to cf6677a Compare April 9, 2024 20:03
@mweberxyz
Copy link
Author

Ready for ci: the server in the test needed to be unref'd.

Copy link
Contributor

@ShogunPanda ShogunPanda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@mweberxyz
Copy link
Author

I can't replicate that final test-asan failure locally - let me know if there's anything else I need to do to get this ready to land 👍

@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Apr 10, 2024
@nodejs-github-bot

This comment was marked as outdated.

@nodejs-github-bot

This comment was marked as outdated.

@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Oct 15, 2024
@nodejs-github-bot
Copy link
Collaborator

@mcollina
Copy link
Member

mcollina commented Dec 7, 2024

@mweberxyz would you mind to rebase this on top of main? Thanks. We should try to get this landed.

@hui-an-yang
Copy link

Hello all

Would like to check if there is any update on this?
Still having socket hang up error with node 19 and above

According to RFC9112 section 6.3.1: HEAD requests, and responses
with status 204 and 304 cannot contain a message body,

If a socket will be kept-alive, resume the socket during parsing
so that it may be returned to the free pool.

Fixes nodejs#47228
@mweberxyz mweberxyz force-pushed the http-keep-alive-auto-resume branch from 563fed7 to e21ef89 Compare January 24, 2025 16:29
@mweberxyz
Copy link
Author

@mcollina ready to go

@mcollina mcollina added the request-ci Add this label to start a Jenkins CI on a PR. label Feb 3, 2025
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Feb 3, 2025
@nodejs-github-bot
Copy link
Collaborator

Copy link

codecov bot commented Feb 3, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 89.20%. Comparing base (c752615) to head (e21ef89).
Report is 565 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #52329      +/-   ##
==========================================
- Coverage   89.21%   89.20%   -0.01%     
==========================================
  Files         662      662              
  Lines      191976   191990      +14     
  Branches    36950    36957       +7     
==========================================
+ Hits       171271   171273       +2     
- Misses      13545    13552       +7     
- Partials     7160     7165       +5     
Files with missing lines Coverage Δ
lib/_http_client.js 98.01% <100.00%> (+0.02%) ⬆️

... and 29 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@mcollina mcollina added commit-queue Add this label to land a pull request using GitHub Actions. semver-minor PRs that contain new features and should be released in the next minor version. labels Feb 21, 2025
@nodejs-github-bot nodejs-github-bot added commit-queue-failed An error occurred while landing this pull request using GitHub Actions. and removed commit-queue Add this label to land a pull request using GitHub Actions. labels Mar 12, 2025
@nodejs-github-bot
Copy link
Collaborator

Commit Queue failed
- Loading data for nodejs/node/pull/52329
✔  Done loading data for nodejs/node/pull/52329
----------------------------------- PR info ------------------------------------
Title      http: resume kept-alive when no body allowed (#52329)
Author     Matt Weber <hello@mweber.xyz> (@mweberxyz, first-time contributor)
Branch     mweberxyz:http-keep-alive-auto-resume -> nodejs:main
Labels     http, semver-minor, needs-ci
Commits    1
 - http: resume kept-alive when no body allowed
Committers 1
 - Matt Weber <hello@mweber.xyz>
PR-URL: https://github.com/nodejs/node/pull/52329
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Robert Nagy <ronagy@icloud.com>
Reviewed-By: Minwoo Jung <nodecorelab@gmail.com>
------------------------------ Generated metadata ------------------------------
PR-URL: https://github.com/nodejs/node/pull/52329
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Robert Nagy <ronagy@icloud.com>
Reviewed-By: Minwoo Jung <nodecorelab@gmail.com>
--------------------------------------------------------------------------------
   ℹ  This PR was created on Tue, 02 Apr 2024 17:41:06 GMT
   ✔  Approvals: 4
   ✔  - Paolo Insogna (@ShogunPanda) (TSC): https://github.com/nodejs/node/pull/52329#pullrequestreview-1991621837
   ✔  - Matteo Collina (@mcollina) (TSC): https://github.com/nodejs/node/pull/52329#pullrequestreview-2632844122
   ✔  - Robert Nagy (@ronag) (TSC): https://github.com/nodejs/node/pull/52329#pullrequestreview-1989549614
   ✔  - Minwoo Jung (@JungMinu): https://github.com/nodejs/node/pull/52329#pullrequestreview-2228985328
   ✘  Last GitHub CI failed
   ℹ  Last Full PR CI on 2025-02-03T09:29:31Z: https://ci.nodejs.org/job/node-test-pull-request/64942/
- Querying data for job/node-test-pull-request/64942/
   ✔  Last Jenkins CI successful
--------------------------------------------------------------------------------
   ✔  Aborted `git node land` session in /home/runner/work/node/node/.ncu
https://github.com/nodejs/node/actions/runs/13814439471

Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@mcollina mcollina added commit-queue Add this label to land a pull request using GitHub Actions. and removed commit-queue-failed An error occurred while landing this pull request using GitHub Actions. labels Mar 27, 2025
@aduh95 aduh95 added author ready PRs that have at least one approval, no pending requests for changes, and a CI started. request-ci Add this label to start a Jenkins CI on a PR. and removed commit-queue Add this label to land a pull request using GitHub Actions. labels Mar 27, 2025
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Apr 1, 2025
@nodejs-github-bot
Copy link
Collaborator

@nodejs-github-bot
Copy link
Collaborator

res.end();
}
});

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: A comment explaining what this test is asserting for would be helpful.

@nodejs-github-bot
Copy link
Collaborator

@mcollina mcollina added the commit-queue Add this label to land a pull request using GitHub Actions. label Apr 7, 2025
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. commit-queue Add this label to land a pull request using GitHub Actions. http Issues or PRs related to the http subsystem. needs-ci PRs that need a full CI run. semver-minor PRs that contain new features and should be released in the next minor version.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Process does not exit after https request with keepAlive enabled (now default in Node 19)
9 participants