Skip to content

Commit cd7bdc8

Browse files
committed
Further performance refinements in headers
1 parent 45a57dc commit cd7bdc8

File tree

2 files changed

+15
-28
lines changed

2 files changed

+15
-28
lines changed

src/workerd/api/headers.c++

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "headers.h"
22

3+
#include "simdutf.h"
34
#include "util.h"
45

56
#include <workerd/io/features.h>
@@ -70,14 +71,6 @@ constexpr size_t MAX_COMMON_HEADER_ID =
7071
// and must be kept in sync with the ordinal values defined in http-over-capnp.capnp). Since
7172
// it is extremely unlikely that those will change often, we hardcode them here for runtime
7273
// efficiency.
73-
//
74-
// TODO(perf): We can potentially optimize this further by using the mechanisms within
75-
// http-over-capnp, which also has a mapping of common header names to kj::HttpHeaderIds.
76-
// However, accessing that functionality requires some amount of new API to be added to
77-
// capnproto which needs to be carefully weighed. There's also the fact that, currently,
78-
// the HttpOverCapnpFactory is accessed via IoContext and the Headers object can be
79-
// created outside of an IoContext. Some amount of additional refactoring would be needed
80-
// to make it work. For now, this hardcoded table is sufficient and efficient enough.
8174
#define V(Name) Name##_kj,
8275
constexpr kj::StringPtr COMMON_HEADER_NAMES[] = {nullptr, // 0: invalid
8376
COMMON_HEADERS(V)};
@@ -164,16 +157,6 @@ static_assert(HEADER_HASH_TABLE.find("AcCePt-ChArSeT"_kj) == 1);
164157

165158
static_assert(std::size(COMMON_HEADER_NAMES) == (MAX_COMMON_HEADER_ID + 1));
166159

167-
inline constexpr void requireValidHeaderName(kj::StringPtr name) {
168-
if (HEADER_HASH_TABLE.find(name) != 0) {
169-
// Known common header, always valid
170-
return;
171-
}
172-
for (char c: name) {
173-
JSG_REQUIRE(util::isHttpTokenChar(c), TypeError, "Invalid header name.");
174-
}
175-
}
176-
177160
void maybeWarnIfBadHeaderString(kj::StringPtr str) {
178161
if (IoContext::hasCurrent()) {
179162
auto& context = IoContext::current();
@@ -241,6 +224,10 @@ constexpr Headers::HeaderKey getHeaderKeyFor(kj::StringPtr name) {
241224
return commonId;
242225
}
243226

227+
for (char c: name) {
228+
JSG_REQUIRE(util::isHttpTokenChar(c), TypeError, "Invalid header name.");
229+
}
230+
244231
// Not a common header, so allocate lowercase copy for uncommon header
245232
return toLower(name);
246233
}
@@ -336,6 +323,9 @@ Headers::Headers(jsg::Lock& js, const Headers& other): guard(Guard::NONE) {
336323

337324
Headers::Headers(jsg::Lock& js, const kj::HttpHeaders& other, Guard guard): guard(Guard::NONE) {
338325
headers.reserve(other.size());
326+
// TODO(perf): Once kj::HttpHeaders supports an API for getting the CommonHeaderName directly
327+
// from the headers, we can optimize this to avoid looking up the common header IDs again,
328+
// making this constructor more efficient when copying common headers from kj::HttpHeaders.
339329
other.forEach([this, &js](auto name, auto value) {
340330
// We have to copy the strings here but we can avoid normalizing and validating since
341331
// they presumably already went through that process when they were added to the
@@ -355,6 +345,8 @@ jsg::Ref<Headers> Headers::clone(jsg::Lock& js) const {
355345
// Fill in the given HttpHeaders with these headers. Note that strings are inserted by
356346
// reference, so the output must be consumed immediately.
357347
void Headers::shallowCopyTo(kj::HttpHeaders& out) {
348+
// TODO(perf): Once kj::HttpHeaders supports an API for setting headers by CommonHeaderName,
349+
// we can optimize this to avoid the additional lookup of the header name and use of addPtrPtr.
358350
for (auto& entry: headers) {
359351
for (auto& value: entry.values) {
360352
out.addPtrPtr(entry.getHeaderName(), value);
@@ -463,8 +455,7 @@ jsg::Ref<Headers> Headers::constructor(jsg::Lock& js, jsg::Optional<Initializer>
463455
}
464456

465457
kj::Maybe<kj::String> Headers::get(jsg::Lock& js, kj::String name) {
466-
requireValidHeaderName(name);
467-
return getUnguarded(js, name.asPtr());
458+
return getUnguarded(js, name);
468459
}
469460

470461
kj::Maybe<kj::String> Headers::getUnguarded(jsg::Lock&, kj::StringPtr name) {
@@ -490,8 +481,6 @@ kj::Array<kj::StringPtr> Headers::getSetCookie() {
490481
}
491482

492483
kj::Array<kj::StringPtr> Headers::getAll(kj::String name) {
493-
requireValidHeaderName(name);
494-
495484
if (!strcaseeq(name, "set-cookie"_kj)) {
496485
JSG_FAIL_REQUIRE(TypeError, "getAll() can only be used with the header name \"Set-Cookie\".");
497486
}
@@ -503,7 +492,6 @@ kj::Array<kj::StringPtr> Headers::getAll(kj::String name) {
503492
}
504493

505494
bool Headers::has(kj::String name) {
506-
requireValidHeaderName(name);
507495
return headers.find(getHeaderKeyFor(name)) != kj::none;
508496
}
509497

@@ -514,7 +502,6 @@ bool Headers::hasCommon(capnp::CommonHeaderName idx) {
514502

515503
void Headers::set(jsg::Lock& js, kj::String name, kj::String value) {
516504
checkGuard();
517-
requireValidHeaderName(name);
518505
setUnguarded(js, kj::mv(name), normalizeHeaderValue(kj::mv(value)));
519506
}
520507

@@ -542,7 +529,6 @@ void Headers::setCommon(capnp::CommonHeaderName idx, kj::String value) {
542529

543530
void Headers::append(jsg::Lock& js, kj::String name, kj::String value) {
544531
checkGuard();
545-
requireValidHeaderName(name);
546532
appendUnguarded(js, kj::mv(name), normalizeHeaderValue(kj::mv(value)));
547533
}
548534

@@ -561,7 +547,6 @@ void Headers::appendUnguarded(jsg::Lock& js, kj::String name, kj::String value)
561547

562548
void Headers::delete_(kj::String name) {
563549
checkGuard();
564-
requireValidHeaderName(name);
565550
headers.eraseMatch(getHeaderKeyFor(name));
566551
}
567552

src/workerd/api/headers.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,6 @@ class Headers final: public jsg::Object {
7171
static jsg::Ref<Headers> constructor(jsg::Lock& js, jsg::Optional<Initializer> init);
7272
kj::Maybe<kj::String> get(jsg::Lock& js, kj::String name);
7373

74-
kj::Maybe<kj::String> getUnguarded(jsg::Lock& js, kj::StringPtr name);
75-
7674
// getAll is a legacy non-standard extension API that we introduced before
7775
// getSetCookie() was defined. We continue to support it for backwards
7876
// compatibility but users really ought to be using getSetCookie() now.
@@ -88,6 +86,7 @@ class Headers final: public jsg::Object {
8886
void append(jsg::Lock& js, kj::String name, kj::String value);
8987
void delete_(kj::String name);
9088

89+
kj::Maybe<kj::String> getUnguarded(jsg::Lock& js, kj::StringPtr name);
9190
void setUnguarded(jsg::Lock& js, kj::String name, kj::String value);
9291
void appendUnguarded(jsg::Lock& js, kj::String name, kj::String value);
9392

@@ -161,6 +160,9 @@ class Headers final: public jsg::Object {
161160
// A header is identified by either a common header ID or an uncommon header name.
162161
// The header key name is always identifed in lower-case form, while the original
163162
// casing is preserved in the actual Header struct to support case-preserving display.
163+
// TODO(perf): We can likely optimize this further by interning uncommon header names
164+
// so that we avoid repeated allocations of the same uncommon header name. Unless
165+
// it proves to be a performance problem, however, we can leave that for future work.
164166
using HeaderKey = kj::OneOf<uint, kj::String>;
165167

166168
private:

0 commit comments

Comments
 (0)