Skip to content

Optimize response header handling for HTTP/2 (P2)#48463

Draft
xinlian12 wants to merge 8 commits intoAzure:mainfrom
xinlian12:perf/p1-fix-http2-toLowerCase
Draft

Optimize response header handling for HTTP/2 (P2)#48463
xinlian12 wants to merge 8 commits intoAzure:mainfrom
xinlian12:perf/p1-fix-http2-toLowerCase

Conversation

@xinlian12
Copy link
Member

Option A: Skip redundant toLowerCase() for HTTP/2 headers

  • Add keysAlreadyLowerCase flag to Cosmos HttpHeaders
  • Detect HTTP/2 in ReactorNettyHttpResponse.headers() via version check
  • Saves ~15-25 toLowerCase calls + String allocations per response

Option C: Eliminate toLowerCaseMap() as redundant

  • Change set() to store lowercased key in HttpHeader (not original case)
  • toMap() now returns lowercase keys, making toLowerCaseMap() redundant
  • Delete toLowerCaseMap() and revert RxGatewayStoreModel callers to toMap()
  • Also fixes direct-connect mode (ResponseUtils, HttpClientUtils) which still used toMap() with original casing (latent Fabric-class bug)

Description

Please add an informative description that covers that changes made by the pull request and link all relevant issues.

If an SDK is being regenerated based on a new swagger spec, a link to the pull request containing these swagger spec changes has been included above.

All SDK Contribution checklist:

  • The pull request does not introduce [breaking changes]
  • CHANGELOG is updated for new features, bug fixes or other significant changes.
  • I have read the contribution guidelines.

General Guidelines and Best Practices

  • Title of the pull request is clear and informative.
  • There are a small number of commits, each of which have an informative message. This means that previously merged commits do not appear in the history of the PR. For more information on cleaning up the commits in your PR, see this page.

Testing Guidelines

  • Pull request includes test coverage for the included changes.

@xinlian12 xinlian12 force-pushed the perf/p1-fix-http2-toLowerCase branch from beb48da to 8ba91eb Compare March 18, 2026 18:19
Option A: Skip redundant toLowerCase() for HTTP/2 headers
- Add keysAlreadyLowerCase flag to Cosmos HttpHeaders
- When true, set() skips the toLowerCase() call on header names
- Detect HTTP/2 in ReactorNettyHttpResponse.headers() via version check
- Saves ~15-25 toLowerCase calls + String allocations per response
- HTTP/2 headers are guaranteed lowercase per RFC 7540 §8.1.2

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@xinlian12 xinlian12 force-pushed the perf/p1-fix-http2-toLowerCase branch from 8ba91eb to 63c1521 Compare March 18, 2026 19:02
@xinlian12
Copy link
Member Author

@sdkReviewAgent

@xinlian12
Copy link
Member Author

sdkReviewAgent | Status: ⏳ Queued

Review requested by @xinlian12. I'll start shortly.

@xinlian12
Copy link
Member Author

sdkReviewAgent | Status: 🔍 Reviewing

I'm reviewing this PR now. I'll post my findings as comments when done.

Comment on lines +49 to 53
public HttpHeaders(int size, boolean isHttp2Headers) {
this.headers = new HashMap<>(size);
this.isHttp2Headers = isHttp2Headers;
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Recommendation
isHttp2Headers optimization is not active — no production caller

This constructor and the conditional toLowerCase() skip in set() are added, but ReactorNettyClient.java:412 — the sole production site where HTTP response headers are constructed — still calls:
new HttpHeaders(reactorNettyResponse.responseHeaders().size())
which defaults isHttp2Headers to false. Grepping for the new constructor across sdk/cosmos/ returns zero production matches.

This means the PR's claimed saving of ~15-25 toLowerCase() calls per HTTP/2 response is not realized. The flag and constructor are dead code.

Suggestion: Either wire the flag into ReactorNettyHttpResponse.headers() in this PR (e.g. checking reactorNettyResponse.version() for HTTP/2), or defer this infrastructure to the PR that includes the wiring. Shipping dead infrastructure creates maintenance burden without delivering value.

Powered by AI

@xinlian12
Copy link
Member Author

@sdkReviewBot

@xinlian12
Copy link
Member Author

@sdkReviewAgent

@xinlian12
Copy link
Member Author

sdkReviewAgent | Status: ⏳ Queued

Review requested by @xinlian12. I'll start shortly.

@xinlian12
Copy link
Member Author

sdkReviewAgent | Status: 🔍 Reviewing

I'm reviewing this PR now. I'll post my findings as comments when done.

*/
public class HttpHeaders implements Iterable<HttpHeader>, JsonSerializable {
private Map<String, HttpHeader> headers;
private final boolean isHttp2Headers;
Copy link
Member Author

Choose a reason for hiding this comment

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

Observation
PR Review Summary

What this PR does: Two changes: (1) refactors Http2ResponseHeaderCleanerHandler from O(n) forEach to O(1) AsciiString lookup — clean and correct; (2) adds this isHttp2Headers flag to skip toLowerCase() in set() for HTTP/2 responses.

Handler changes are likely already merged from PR #48455 (P1). The branch may need rebasing — after rebase, those handler changes should disappear from the diff.

Option C is described but not implemented. The PR description mentions "Eliminate toLowerCaseMap() as redundant", "revert RxGatewayStoreModel to toMap()", and "fix direct-connect mode (ResponseUtils, HttpClientUtils)". None of these changes are in the diff. Consider updating the description to reflect only the actual changes.

Architecture note: This flag introduces transport-protocol awareness into HttpHeaders, which is otherwise a generic data container. The existing pattern isolates HTTP/2 concerns in pipeline handlers, not in data structures that flow through business logic. If the flag stays, document that it must only be set by the reactor-netty response path.

Powered by AI

*/
public HttpHeaders set(String name, String value) {
final String headerKey = name.toLowerCase(Locale.ROOT);
final String headerKey = isHttp2Headers ? name : name.toLowerCase(Locale.ROOT);
Copy link
Member Author

Choose a reason for hiding this comment

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

Recommendation
set() / getHeader() asymmetry creates a latent footgun

When isHttp2Headers=true, set() stores the map key without lowercasing, but getHeader() (line 102) always lowercases the lookup key:

// In set():
final String headerKey = isHttp2Headers ? name : name.toLowerCase(Locale.ROOT);
// In getHeader():
final String headerKey = headerName.toLowerCase(Locale.ROOT);  // always lowercases

If anyone calls set("Content-Type", value) on an isHttp2Headers=true instance, the header is stored under key "Content-Type" but value("Content-Type") looks up "content-type"silent miss. The header appears set but is unretrievable.

This is safe today because HTTP/2 guarantees lowercase and the constructor has no production callers. But when it gets wired up, a future developer could easily misuse it. Consider adding a defensive assertion:

assert !isHttp2Headers || name.equals(name.toLowerCase(Locale.ROOT))
    : "isHttp2Headers requires lowercase names, got: " + name;

Or, make getHeader() also honor the flag to keep the two paths symmetric.

Powered by AI

Comment on lines +31 to +40
public void keysAlreadyLowerCaseSkipsNormalization() {
String headerName = "etag";
String headerValue = "456";

// Simulates HTTP/2 where header names are already lowercase
HttpHeaders headers = new HttpHeaders(4, true);
headers.set(headerName, headerValue);

Map<String, String> map = headers.toMap();
assertThat(map.get(headerName)).isEqualTo(headerValue);
Copy link
Member Author

Choose a reason for hiding this comment

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

Suggestion
Test doesn't prove the optimization is exercised, and misses value() coverage

Two gaps:

  1. The test uses "etag" (already lowercase), so "etag".toLowerCase() == "etag" — the result is identical whether or not the flag is true. Changing new HttpHeaders(4, true) to new HttpHeaders(4) would still pass. The test proves correctness but not that the optimization path is actually different.

  2. value() lookup is not tested with isHttp2Headers=true. The primary consumer API (value() / getHeader()) is uncovered for this flag. Add assertions like:

assertThat(headers.value("etag")).isEqualTo(headerValue);
assertThat(headers.value("ETag")).isEqualTo(headerValue);  // verifies getHeader() lowercases lookup

This would also serve as a regression guard against the set()/getHeader() asymmetry noted in HttpHeaders.java.

Powered by AI

Annie Liang and others added 4 commits March 19, 2026 13:16
Imports the shared review pipeline from xinlian12/sdk-auto-pr-review.
Triggers on PR open/push/ready_for_review. Posts inline review comments
with severity tags and AI disclaimer.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add sdkReviewAgent agentic workflow for automated PR review
@xinlian12
Copy link
Member Author

@sdkReviewAgent

@xinlian12
Copy link
Member Author

sdkReviewAgent | Status: ⏳ Queued

Review requested by @xinlian12. I'll start shortly.

@xinlian12
Copy link
Member Author

sdkReviewAgent | Status: 🔍 Reviewing

I'm reviewing this PR now. I'll post my findings as comments when done.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant