Skip to content

http: reduce per-request allocations in the server - #65802

Open
mcollina wants to merge 1 commit into
nodejs:mainfrom
mcollina:http-reduce-request-allocations
Open

http: reduce per-request allocations in the server#65802
mcollina wants to merge 1 commit into
nodejs:mainfrom
mcollina:http-reduce-request-allocations

Conversation

@mcollina

Copy link
Copy Markdown
Member

Removes allocations and repeated work the HTTP server performs for every request:

  • Per-request bound functions: updateOutgoingData.bind() becomes one closure per connection reused by every response, and the per-request resOnFinish.bind() becomes a shared 'finish' listener that reaches everything through the connection state stored on the response.
  • req.headers materialization: the requireHostHeader check (req.headers.host), the Expect check, and optimizeEmptyRequests' body-header check now scan rawHeaders (over the same range the headers getter exposes) instead of building the whole headers object — with a toLowerCase() per header name — for every request even when the application never reads req.headers.
  • ServerResponse options object: cached per server; custom response classes keep receiving a fresh object since they may retain or mutate it.
  • Status line: cached per status code when the reason phrase is the default (also skipping its character validation, which cannot fail for the built-in phrases).
  • Date: header line: the complete line is cached alongside the existing one-second utcDate() cache.
  • Keep-alive header pair: Connection: keep-alive\r\nKeep-Alive: timeout=N is rendered once per server settings instead of per response.
  • Lenient-validation lookup: the 4-level option chain is computed once per message.

Responses are byte-identical to main (verified by diffing raw responses).

Benchmarks (i7-7700, server pinned to one core, wrk -t2 -c50 on separate physical cores, interleaved rounds, CPU from /proc/<pid>/stat):

workloadmainthis PRΔ
hello world, 1 request header26.0 µs/req24.2 µs/req−6.9% CPU, +7.5% req/s (8/8 rounds)
hello world, 8 request headers28.7 µs/req25.3 µs/req−11.8% CPU, +13% req/s (5/5 rounds)

Sampled heap allocation drops from 5206 to 4531 B/request (−13%); the req.headers build (~200 B + per-name toLowerCase per request) disappears from the profile entirely.

test/parallel/test-http-*, test-http2-*, test-stream-*, test-net-*, test-https-* and the http diagnostics-channel suites all pass (1171 tests).

Follow-up to #65732 and #65749.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TV8WVjiHJPR2hW4D6h1Xpr

Remove allocations and repeated work performed for every request:
- reuse a per-connection updateOutgoingData closure and a shared
'finish' listener instead of binding two functions per request,
reaching resOnFinish through the connection state stored on the
response
- cache the ServerResponse options object per server (custom response
classes keep receiving a fresh object)
- check for Host, Expect, Content-Length and Transfer-Encoding by
scanning rawHeaders instead of materializing req.headers, which was
built (with per-name toLowerCase calls) for every request even when
the application never reads it
- cache the rendered status line per status code when the reason
phrase is the default, skipping its character validation
- cache the complete 'Date: ...' header line in the utcDate cache and
the keep-alive header pair for the current server settings
- compute the lenient-validation option chain once per message
Signed-off-by: Matteo Collina <hello@matteocollina.com>
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/http
  • @nodejs/net

@nodejs-github-botnodejs-github-bot added http Issues and PRs related to the http subsystem. needs-ci PRs that need a full CI run. labels Sep 4, 2026
@mcollinamcollina added the request-ci Add this label to start a Jenkins CI on a PR. label Sep 4, 2026
@github-actionsgithub-actionsBot removed the request-ci Add this label to start a Jenkins CI on a PR. label Sep 4, 2026
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

@codecov

codecovBot commented Sep 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.15%. Comparing base (452c8c5) to head (ed051a3).
⚠️ Report is 133 commits behind head on main.

Additional details and impacted files
@@ Coverage Diff @@## main #65802 +/- ##
==========================================
+ Coverage 90.05% 90.15% +0.09% 
==========================================
Files 754 769 +15 Lines 255727 261721 +5994 Branches 48312 49688 +1376 ==========================================
+ Hits 230286 235944 +5658 - Misses 16564 16795 +231 - Partials 8877 8982 +105 
Files with missing linesCoverage Δ
lib/_http_incoming.js97.99% <100.00%> (+0.05%)⬆️
lib/_http_outgoing.js97.93% <100.00%> (+0.02%)⬆️
lib/_http_server.js96.82% <100.00%> (+0.20%)⬆️
lib/internal/http.js92.59% <100.00%> (+0.25%)⬆️

... and 158 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.

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

Labels

httpIssues and PRs related to the http subsystem.needs-ciPRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@mcollina@nodejs-github-bot@jasnell@ronag