From f1a205ddfc3e13ae07b7d8f7a47bb55cc5b320c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Ara=C3=BAjo?= Date: Fri, 3 Jul 2026 22:46:32 -0300 Subject: [PATCH 1/2] http: create header field names as internalized strings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Guilherme Araújo --- src/node_http_parser.cc | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc index d99b61780fad6c..358f6cbe85bd9e 100644 --- a/src/node_http_parser.cc +++ b/src/node_http_parser.cc @@ -65,6 +65,7 @@ using v8::Isolate; using v8::Local; using v8::LocalVector; using v8::MaybeLocal; +using v8::NewStringType; using v8::Number; using v8::Object; using v8::ObjectTemplate; @@ -233,6 +234,17 @@ struct StringPtr { return String::Empty(env->isolate()); } + Local ToInternalizedString(Environment* env) const { + if (size_ != 0) { + return String::NewFromOneByte(env->isolate(), + reinterpret_cast(str_), + NewStringType::kInternalized, + size_) + .ToLocalChecked(); + } + return String::Empty(env->isolate()); + } + // Strip trailing OWS (SPC or HTAB) from string. Local ToTrimmedString(Environment* env) { while (size_ > 0 && IsOWS(str_[size_ - 1])) { @@ -940,7 +952,8 @@ class Parser : public AsyncWrap, public StreamListener { Local headers_v[kMaxHeaderFieldsCount * 2]; for (size_t i = 0; i < num_values_; ++i) { - headers_v[i * 2] = fields_[i].ToString(env()); + // Field names repeat across requests, so internalize them. + headers_v[i * 2] = fields_[i].ToInternalizedString(env()); headers_v[i * 2 + 1] = values_[i].ToTrimmedString(env()); } From fff2fbc5dac69b63212c59fc620b51fb40087f2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Ara=C3=BAjo?= Date: Sat, 4 Jul 2026 12:50:16 -0300 Subject: [PATCH 2/2] http: only internalize short header field names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Guilherme Araújo --- src/node_http_common.h | 4 +++- src/node_http_parser.cc | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/node_http_common.h b/src/node_http_common.h index 5afb6b97becc4d..6836e0075b01ec 100644 --- a/src/node_http_common.h +++ b/src/node_http_common.h @@ -16,6 +16,8 @@ class Environment; #define DEFAULT_MAX_HEADER_LIST_PAIRS 128u #define DEFAULT_MAX_HEADER_LENGTH 8192 +constexpr size_t kMaxInternalizedHeaderNameLength = 64; + #define HTTP_SPECIAL_HEADERS(V) \ V(STATUS, ":status") \ V(METHOD, ":method") \ @@ -432,7 +434,7 @@ class NgRcBufPointer : public MemoryRetainer { return v8::String::Empty(env->isolate()); } - if (ptr.IsInternalizable() && len < 64) { + if (ptr.IsInternalizable() && len < kMaxInternalizedHeaderNameLength) { v8::MaybeLocal ret = GetInternalizedString(env, ptr); ptr.reset(); return ret; diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc index 358f6cbe85bd9e..f2a99a0c2f3bc2 100644 --- a/src/node_http_parser.cc +++ b/src/node_http_parser.cc @@ -28,6 +28,7 @@ #include "llhttp.h" #include "memory_tracker-inl.h" #include "node_external_reference.h" +#include "node_http_common.h" #include "stream_base-inl.h" #include "v8.h" @@ -235,14 +236,15 @@ struct StringPtr { } Local ToInternalizedString(Environment* env) const { - if (size_ != 0) { + // Only internalize short names to avoid pressuring the string table. + if (size_ != 0 && size_ < kMaxInternalizedHeaderNameLength) { return String::NewFromOneByte(env->isolate(), reinterpret_cast(str_), NewStringType::kInternalized, size_) .ToLocalChecked(); } - return String::Empty(env->isolate()); + return ToString(env); } // Strip trailing OWS (SPC or HTAB) from string.