Conversation
|
Review requested:
|
fbe7035 to
1fda05a
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #66120 +/- ##
=======================================
Coverage 90.29% 90.29%
=======================================
Files 790 790
Lines 271882 271954 +72
Branches 51899 51937 +38
=======================================
+ Hits 245485 245559 +74
- Misses 16888 16889 +1
+ Partials 9509 9506 -3
🚀 New features to boost your workflow:
|
After nodejs#65802, Host/Expect/body checks still allocate toLowerCase() copies and Expect/HTTP/1.0 TE still read req.headers. Compare names without allocating, read those values from rawHeaders, intern parser header names, and skip Title-Case toLowerCase on common outgoing fields. Refs: nodejs#65802 Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com> Assisted-by: a closed-source coding agent
16a6107 to
9b786ff
Compare
|
Rebased onto #65802 (
The benchmark table in the description was measured against pre-#65802 |
| Local<String> ToHeaderNameString(Environment* env) const { | ||
| if (size_ == 0) return String::Empty(env->isolate()); | ||
| return OneByteString( | ||
| env->isolate(), str_, size_, v8::NewStringType::kInternalized); | ||
| } | ||
|
|
There was a problem hiding this comment.
Can't this cause a vulnerability? While header names are usually short and a limited set. An attacker could create lots of random fields over many requests causing a huge amount if internalized strings?
There was a problem hiding this comment.
Yes. Unique attacker-controlled names would stay in V8's interned string table for the isolate lifetime.
Dropped it in 534f13d. Header names are ordinary strings again; the leftover is the JS rawHeaders scan / lazy Expect-TE path.
|
Re-measured this leftover against current Official
40k sequential keep-alive requests, So the leftover after #65802 is mostly fewer |
Interning every parsed header name lets a client fill the V8 interned string table with unique tokens. Drop that and keep names as ordinary strings. Refs: nodejs#66120 Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com> Assisted-by: a closed-source coding agent
|
Jenkins run 77635 was started on
|
|
Jenkins 77639 failed only on windows-fanned 80517 ( I cannot read Jenkins consoles from here (403). GitHub Actions Coverage Windows on this SHA passed |
Failed to start CIFull Auto Start CI output |
|
@ronag — could you Resume 77639, or start a new The |
|
human in the loop is missing here... |
I'm in the loop. Agent was just baby sitting... |
What
#65802 already landed the status/Date/Keep-Alive caches and per-connection bound-function reuse. After that, the HTTP/1 server still allocates on every request for work most applications never need:
requireHostHeader(on by default) and the body-header checks stilltoLowerCase()every raw name while scanning.TEstill readreq.headers, which builds the whole headers object.matchHeader()lowercases Title-Case outgoing names (Content-Type,Content-Length, …) on every write.IncomingMessageallocated astreamOptionsobject just to sethighWaterMark.This change:
rawHeaderswith a non-allocating ASCII compare (asciiEqualIgnoreCase) for Host / Expect /Content-Length/Transfer-Encoding/TE.TEfromrawHeaderssoreq.headersstays lazy.toLowerCase().IncomingMessageHWM from the socket without allocatingstreamOptions.An earlier revision interned parser header names (
kInternalized). That is gone: unique attacker-controlled names would pin interned strings for the isolate lifetime (see review).Wire format is unchanged.
req.headersis still lazy and still correct when the application reads it, including unusualHOST/EXPECTcasing (test/parallel/test-http-server-raw-header-lookup.js).Benchmarks
Release build (
./configure --without-intl --without-npm) on one machine. Server pinned withtaskset -c 0where noted. Client iswrk.Combined series vs pre-#65802 (
dd5dfb5250)These numbers include both #65802 and an earlier revision of this leftover (including interned names):
http/simple.jsbytes,len=4,c=50http/incoming_headers.jsheaders=20wrkhello + 8 extra headers (median of 5×8s)req.headers.hostThis leftover vs current
main(357ba2f676/ #65802)Official
http/simple.jsandhttp/incoming_headers.js(repeated 5swrkruns) overlap withmain. I am not claiming a reliable official-bench win on top of #65802.wrk -t2 -c50hello + 8 extra headers, 5 interleaved 8s rounds:mainmedianres.end('hello')req.headers.hostSequential 40k keep-alive requests,
--expose-gc,heapUsedafter warmup:mainheap Δreq.headersreq.headers.hostAfter #65802 the leftover is mainly fewer
toLowerCase()copies when scanning extra headers for Host, and not buildingreq.headersfor Expect / HTTP/1.0TE.Tests
python3 tools/test.py --mode=release --shell <leftover-node>: focused HTTP files passed after the rebase (raw-header lookup, Expect, Host, server, multiheaders, content-length). Pre-rebase series ran 408test/parallel/test-http-*.js. GitHub Actions on534f13d7e3is the current matrix.Related
Complementary leftover after #65802. Not a rebase of #65332.
AI assistance
A closed-source coding agent helped write and measure this. The HTTP hot path (
parserOnIncoming,_storeHeader,CreateHeaders) was read and checked against the existing tests. Benchmark numbers above are from two release binaries on the same machine, not from a model.Refs: #65802
Refs: #65332