Skip to content

Commit 3e413f7

Browse files
committed
Further performance refinements in headers
1 parent f6c970e commit 3e413f7

File tree

3 files changed

+23
-29
lines changed

3 files changed

+23
-29
lines changed

src/workerd/api/headers.c++

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
#include "headers.h"
2-
2+
// Turning off clang-tidy for this file due to issues it appears to have with
3+
// the simd intrinsics...
4+
// NOLINTBEGIN
5+
#include "simdutf.h"
36
#include "util.h"
47

58
#include <workerd/io/features.h>
@@ -70,14 +73,6 @@ constexpr size_t MAX_COMMON_HEADER_ID =
7073
// and must be kept in sync with the ordinal values defined in http-over-capnp.capnp). Since
7174
// it is extremely unlikely that those will change often, we hardcode them here for runtime
7275
// 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.
8176
#define V(Name) Name##_kj,
8277
constexpr kj::StringPtr COMMON_HEADER_NAMES[] = {nullptr, // 0: invalid
8378
COMMON_HEADERS(V)};
@@ -164,16 +159,6 @@ static_assert(HEADER_HASH_TABLE.find("AcCePt-ChArSeT"_kj) == 1);
164159

165160
static_assert(std::size(COMMON_HEADER_NAMES) == (MAX_COMMON_HEADER_ID + 1));
166161

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-
177162
void maybeWarnIfBadHeaderString(kj::StringPtr str) {
178163
if (IoContext::hasCurrent()) {
179164
auto& context = IoContext::current();
@@ -241,6 +226,10 @@ constexpr Headers::HeaderKey getHeaderKeyFor(kj::StringPtr name) {
241226
return commonId;
242227
}
243228

229+
for (char c: name) {
230+
JSG_REQUIRE(util::isHttpTokenChar(c), TypeError, "Invalid header name.");
231+
}
232+
244233
// Not a common header, so allocate lowercase copy for uncommon header
245234
return toLower(name);
246235
}
@@ -336,6 +325,9 @@ Headers::Headers(jsg::Lock& js, const Headers& other): guard(Guard::NONE) {
336325

337326
Headers::Headers(jsg::Lock& js, const kj::HttpHeaders& other, Guard guard): guard(Guard::NONE) {
338327
headers.reserve(other.size());
328+
// TODO(perf): Once kj::HttpHeaders supports an API for getting the CommonHeaderName directly
329+
// from the headers, we can optimize this to avoid looking up the common header IDs again,
330+
// making this constructor more efficient when copying common headers from kj::HttpHeaders.
339331
other.forEach([this, &js](auto name, auto value) {
340332
// We have to copy the strings here but we can avoid normalizing and validating since
341333
// they presumably already went through that process when they were added to the
@@ -355,6 +347,8 @@ jsg::Ref<Headers> Headers::clone(jsg::Lock& js) const {
355347
// Fill in the given HttpHeaders with these headers. Note that strings are inserted by
356348
// reference, so the output must be consumed immediately.
357349
void Headers::shallowCopyTo(kj::HttpHeaders& out) {
350+
// TODO(perf): Once kj::HttpHeaders supports an API for setting headers by CommonHeaderName,
351+
// we can optimize this to avoid the additional lookup of the header name and use of addPtrPtr.
358352
for (auto& entry: headers) {
359353
for (auto& value: entry.values) {
360354
out.addPtrPtr(entry.getHeaderName(), value);
@@ -463,8 +457,7 @@ jsg::Ref<Headers> Headers::constructor(jsg::Lock& js, jsg::Optional<Initializer>
463457
}
464458

465459
kj::Maybe<kj::String> Headers::get(jsg::Lock& js, kj::String name) {
466-
requireValidHeaderName(name);
467-
return getUnguarded(js, name.asPtr());
460+
return getUnguarded(js, name);
468461
}
469462

470463
kj::Maybe<kj::String> Headers::getUnguarded(jsg::Lock&, kj::StringPtr name) {
@@ -490,8 +483,6 @@ kj::Array<kj::StringPtr> Headers::getSetCookie() {
490483
}
491484

492485
kj::Array<kj::StringPtr> Headers::getAll(kj::String name) {
493-
requireValidHeaderName(name);
494-
495486
if (!strcaseeq(name, "set-cookie"_kj)) {
496487
JSG_FAIL_REQUIRE(TypeError, "getAll() can only be used with the header name \"Set-Cookie\".");
497488
}
@@ -503,7 +494,6 @@ kj::Array<kj::StringPtr> Headers::getAll(kj::String name) {
503494
}
504495

505496
bool Headers::has(kj::String name) {
506-
requireValidHeaderName(name);
507497
return headers.find(getHeaderKeyFor(name)) != kj::none;
508498
}
509499

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

515505
void Headers::set(jsg::Lock& js, kj::String name, kj::String value) {
516506
checkGuard();
517-
requireValidHeaderName(name);
518507
setUnguarded(js, kj::mv(name), normalizeHeaderValue(kj::mv(value)));
519508
}
520509

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

543532
void Headers::append(jsg::Lock& js, kj::String name, kj::String value) {
544533
checkGuard();
545-
requireValidHeaderName(name);
546534
appendUnguarded(js, kj::mv(name), normalizeHeaderValue(kj::mv(value)));
547535
}
548536

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

562550
void Headers::delete_(kj::String name) {
563551
checkGuard();
564-
requireValidHeaderName(name);
565552
headers.eraseMatch(getHeaderKeyFor(name));
566553
}
567554

@@ -741,3 +728,4 @@ jsg::Ref<Headers> Headers::deserialize(
741728
}
742729

743730
} // namespace workerd::api
731+
// NOLINTEND

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:

src/workerd/util/header-validation.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99

1010
#include <cstdint>
1111

12+
// clang-tidy doesn't like some of our use of intrinsics here.
13+
// NOLINTBEGIN
14+
1215
// Platform-specific intrinsics headers
1316
#if defined(__AVX2__)
1417
#include <immintrin.h>
@@ -261,3 +264,4 @@ inline constexpr bool isHttpTokenChar(char c) {
261264
static_assert(isHttpTokenChar('A'));
262265
static_assert(!isHttpTokenChar(' '));
263266
} // namespace workerd::util
267+
// NOLINTEND

0 commit comments

Comments
 (0)