Skip to content

Reduce repeated work in HTTP header parsing - #13376

Open
moonchen wants to merge 6 commits into
apache:masterfrom
moonchen:header-parse-optimization
Open

moonchen wants to merge 6 commits into
apache:masterfrom
moonchen:header-parse-optimization

Conversation

@moonchen

@moonchen moonchen commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Reduce repeated work in HTTP header parsing.

  1. Replace per-byte libc calls in URL compliance validation with a vectorizable, locale-independent ASCII range check.
  2. Simplify request-target validation using the stored URL fields.
  3. Combine field-name colon scanning, hashing, and character validation into one pass, reusing the existing well-known-string table.
  4. Skip duplicate lookup for well-known fields when their presence bit is clear.
  5. Append adjacent duplicate fields in constant time instead of searching the duplicate chain.

Includes a header parsing benchmark harness and regression tests for validation, field-name scanning, duplicate attachment, and parser reuse.

@moonchen moonchen self-assigned this Jul 13, 2026
@moonchen
moonchen force-pushed the header-parse-optimization branch from dd3ee23 to 0442cf1 Compare July 13, 2026 20:38
@moonchen moonchen added this to the 11.0.0 milestone Jul 13, 2026
@moonchen
moonchen force-pushed the header-parse-optimization branch from 0442cf1 to 656735c Compare July 13, 2026 22:18
@moonchen
moonchen marked this pull request as ready for review August 17, 2026 16:07
Copilot AI lite review requested due to automatic review settings August 17, 2026 16:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Suppressed comments (3)

tools/benchmark/benchmark_HdrParse.cc:797

  • The --profile unknown-target error message omits the supported "wks-lower"/"wkslower" target even though parse_target() accepts it, which can mislead users.
    Target t = parse_target(profile_target);
    if (t == Target::Unknown) {
      std::fprintf(stderr, "unknown target '%s' (want: request|response|mime|url|wks)\n", profile_target.c_str());
      return 2;

src/proxy/hdrs/HdrToken.cc:606

  • hdrtoken_tokenize_prehashed()’s comment says it does an “exact ASCII-case-insensitive byte compare”, but the implementation currently only checks (hash, length) and returns the bucket token without validating the bytes. Either adjust the comment or add the byte-compare; adding the compare also prevents accidental or crafted hash/length collisions from being misclassified as a WKS token.
// WKS lookup for a name whose FNV-1a hash the caller has already computed
// (e.g. fused into the field-name scan). Does the slot/length narrowing plus
// the exact ASCII-case-insensitive byte compare, but no hashing and no
// interned-pointer test, so it is only valid for a non-interned `string`.
int
hdrtoken_tokenize_prehashed(const char *string, int string_len, uint32_t hash, const char **wks_string_out)
{
  uint32_t            slot   = hash_to_slot(hash);
  HdrTokenHashBucket *bucket = &(hdrtoken_hash_table[slot]);

  if ((bucket->wks != nullptr) && (bucket->hash == hash) && (hdrtoken_wks_to_length(bucket->wks) == string_len)) {
    int wks_idx = hdrtoken_wks_to_index(bucket->wks);
    if (wks_string_out) {
      *wks_string_out = bucket->wks;
    }
    return wks_idx;
  }

src/proxy/hdrs/URL.cc:1210

  • url_is_mostly_compliant() no longer emits the debug message that previously identified the first offending byte (whitespace/non-printable). That makes troubleshooting strict_uri_parsing=2 failures harder. You can keep the vectorized scan and only do a second scalar scan when an invalid byte was detected, to log the first bad value.
  // Mode 2 accepts exactly the printable, non-space ASCII range 0x21..0x7E --
  // equivalent to the previous isspace()/isprint() pair, but locale-independent
  // and call-free. This runs on every request target under the default
  // strict_uri_parsing=2. OR-reducing an out-of-range flag over the whole target
  // (no early exit, no data-dependent branch) lets the compiler auto-vectorize
  // the scan to the build's SIMD; ATS builds -O3, where clang and GCC both do.
  unsigned char bad = 0;
  for (const char *i = start; i < end; ++i) {
    unsigned char const c  = static_cast<unsigned char>(*i);
    bad                   |= static_cast<unsigned char>((c < 0x21) | (c > 0x7E));
  }
  return bad == 0;

Comment thread tools/benchmark/benchmark_HdrParse.cc
@moonchen
moonchen marked this pull request as draft August 20, 2026 18:47
@moonchen
moonchen force-pushed the header-parse-optimization branch from 25227f3 to c0d1671 Compare September 9, 2026 14:50
@moonchen moonchen changed the title Header Parsing Optimizations Reduce repeated work in HTTP header parsing Sep 9, 2026
@moonchen
moonchen marked this pull request as ready for review September 9, 2026 14:50
Copilot AI review requested due to automatic review settings September 9, 2026 14:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Comment thread src/proxy/hdrs/HdrToken.cc
Comment on lines +729 to +747
int
hdrtoken_field_name_scan(const char *string, int maxlen, uint32_t *hash_out, bool *all_valid_out)
{
uint32_t hval = HDRTOKEN_HASH_SEED; // same FNV-1a name hash as hdrtoken_hash
bool all_valid = true;
int i = 0;

for (; i < maxlen; ++i) {
unsigned char const uc = static_cast<unsigned char>(string[i]);
if (uc == ':') {
break;
}
hval = hdrtoken_hash_step(hval, hdrtoken_ascii_toupper(uc));
all_valid &= (ParseRules::is_http_field_name(static_cast<char>(uc)) != 0);
}

*hash_out = hval;
*all_valid_out = all_valid;
return (i < maxlen) ? i : -1;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hash_out is gone from hdrtoken_tokenize — it was dead, its only caller wrote it and never read it back.

For hdrtoken_field_name_scan both out-params are now documented as required rather than made optional. It is a hot-path helper with two in-tree callers; a conditional store would hide a caller bug instead of surfacing it.

Comment thread tools/benchmark/benchmark_HdrParse.cc Outdated
Comment thread tools/benchmark/benchmark_HdrParse.cc
Comment thread tools/benchmark/benchmark_HdrParse.cc Outdated
if (!profile_target.empty()) {
Target t = parse_target(profile_target);
if (t == Target::Unknown) {
std::fprintf(stderr, "unknown target '%s' (want: request|response|mime|url|wks)\n", profile_target.c_str());

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added wks-lower to the list.

Header parsing needs repeatable measurements before its hot paths can
be optimized. Cover zero-copy parsing under the default strict URI mode,
copied inputs, modern browser headers, duplicate-heavy responses, and
canonical and lowercase WKS names in one harness. Include a profiling
loop and file-loaded corpora for investigating representative workloads.
Default URI validation pays for libc character classification on every
byte. Use a branchless ASCII range reduction to enable vectorization
and make acceptance locale-independent; rejection no longer logs the
offending byte. Exhaustive differential tests cover every byte value
across vector boundaries and scalar tails.
Request-target validation calls three URL getters and follows branches
that reduce to a direct test of the stored host and scheme fields.
Express that condition directly while preserving the existing treatment
of origin, asterisk, absolute, and authority forms.
Field names were walked separately to find the colon, hash the name,
and validate its characters. Fuse those passes and reuse the WKS lookup
through a prehashed entry point, preserving whitespace normalization
and the current table representation. Parity tests check the delimiter
position, character validation, and token lookup.
A clear presence bit already proves that a well-known field has no
duplicate in the header. Use that result when attaching parsed fields
to avoid redundant lookup work. Well-known names without a presence
mask and non-well-known names retain the normal duplicate search.
Consecutive fields such as Set-Cookie repeatedly search an existing
duplicate chain even though their predecessor is already its tail.
Derive that predecessor from the current header block and append
directly, avoiding parser-held pointers that could outlive the header.
Tests compare duplicate chains with normal attachment and cover parser
reuse after a header is destroyed.
Copilot AI review requested due to automatic review settings September 9, 2026 15:45
@moonchen
moonchen force-pushed the header-parse-optimization branch from c0d1671 to 238fb0c Compare September 9, 2026 15:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.

Comment on lines +132 to +136
extern void hdrtoken_init();
extern int hdrtoken_tokenize(const char *string, int string_len, const char **wks_string_out = nullptr);
extern int hdrtoken_tokenize_prehashed(const char *string, int string_len, uint32_t hash, const char **wks_string_out = nullptr);
extern int hdrtoken_field_name_scan(const char *string, int maxlen, uint32_t *hash_out, bool *all_valid_out);
extern int hdrtoken_method_tokenize(const char *string, int string_len);
Comment on lines +736 to +754
hdrtoken_field_name_scan(const char *string, int maxlen, uint32_t *hash_out, bool *all_valid_out)
{
uint32_t hval = HDRTOKEN_HASH_SEED; // same FNV-1a name hash as hdrtoken_hash
bool all_valid = true;
int i = 0;

for (; i < maxlen; ++i) {
unsigned char const uc = static_cast<unsigned char>(string[i]);
if (uc == ':') {
break;
}
hval = hdrtoken_hash_step(hval, hdrtoken_ascii_toupper(uc));
all_valid &= (ParseRules::is_http_field_name(static_cast<char>(uc)) != 0);
}

*hash_out = hval;
*all_valid_out = all_valid;
return (i < maxlen) ? i : -1;
}
Comment thread src/proxy/hdrs/URL.cc
bad |= static_cast<unsigned char>((c < 0x21) | (c > 0x7E));
}
return true;
return bad == 0;
Comment on lines +594 to +597
auto t1 = std::chrono::steady_clock::now();
double ns = std::chrono::duration<double, std::nano>(t1 - t0).count();
double ns_per = ns / static_cast<double>(count);

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

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants