Skip to content

transport/http: direct header insertion on HTTP/2 outbound - #2484

Open
bananacocodrilo wants to merge 1 commit into
yarpc:mainfrom
bananacocodrilo:http2-direct-header-injection
Open

transport/http: direct header insertion on HTTP/2 outbound#2484
bananacocodrilo wants to merge 1 commit into
yarpc:mainfrom
bananacocodrilo:http2-direct-header-injection

Conversation

@bananacocodrilo

Copy link
Copy Markdown
Collaborator

RELEASE NOTES:

On HTTP/2, header field names are always lowercased on the wire (RFC 7540 §8.1.2), so the canonicalization that net/http performs via textproto.CanonicalMIMEHeaderKey on every http.Header.Set/Add call is redundant and only adds CPU and allocations.

This PR introduces a fast path on the HTTP/2 outbound that writes directly into the underlying http.Header map, bypassing canonicalization in three hot spots:

  • application header conversion — headerMapper.ToHTTPHeadersPreserveCase
  • core RPC header writes — withCoreHeaders (gated by o.useHTTP2)
  • OpenTracing header injection — directHTTPHeadersCarrier

The HTTP/1.1 path is unchanged and continues to canonicalize header names, preserving backward compatibility with peers that perform case-sensitive header matching.

Why it's safe

Path Behavior Reason
HTTP/2 outbound New: keys written as stored (no canonicalization) HTTP/2 wire format mandates lowercase header names; map casing is irrelevant on the wire
HTTP/1.1 outbound Unchanged Still uses http.Header.Set/Add and opentracing.HTTPHeadersCarrier
AddHeader (setup-time) Unchanged Still uses http.Header.Add; not a hot path
Core RPC header constants (CallerHeader, ServiceHeader, …) Already canonical (Rpc-Caller, Rpc-Service, …) Stored casing matches what canonicalization would produce

Wire-format implications

  • HTTP/2: header names appear lowercase on the wire (HTTP/2 requirement) regardless of this change. No observable difference for peers.
  • HTTP/1.1: no change.
  • Tracing headers on HTTP/2 are now stored with whatever casing the tracer supplies (typically lowercase, e.g. uber-trace-id) instead of the canonicalized form (Uber-Trace-Id). On the wire it's still lowercase per HTTP/2 spec.

Performance

Benchmarks on Apple M2 Max, count=6, full createRequest + withCoreHeaders path. Median of 6 runs.

Scenario http1_canonical http2_direct Δ time Δ allocs
4 headers, mixed case (typical) 2440 ns / 37 allocs 1819 ns / 32 allocs −25% −14%
24 headers, all lowercase 5716 ns / 96 allocs 3275 ns / 71 allocs −43% −26%
24 headers, mixed case 5677 ns / 96 allocs 3478 ns / 71 allocs −39% −26%

Bytes allocated drop 4–11% across scenarios.

@bananacocodrilo
bananacocodrilo force-pushed the http2-direct-header-injection branch from 9c73877 to 9af0395 Compare May 11, 2026 14:35
@bananacocodrilo
bananacocodrilo force-pushed the http2-direct-header-injection branch from 9af0395 to d436036 Compare July 14, 2026 16:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant