Skip to content

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

Open
xinlian12 wants to merge 5 commits intomainfrom
perf/p1-fix-http2-toLowerCase
Open

Optimize response header handling for HTTP/2 (P2)#21
xinlian12 wants to merge 5 commits intomainfrom
perf/p1-fix-http2-toLowerCase

Conversation

@xinlian12
Copy link
Copy Markdown
Owner

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

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
Copy link
Copy Markdown
Owner Author

@sdkReviewAgent

Repository owner deleted a comment from github-actions Bot Mar 19, 2026
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Warning

⚠️ Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • releaseassets.githubusercontent.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "releaseassets.githubusercontent.com"

See Network Configuration for more information.

Generated by sdkReviewAgent for issue #21

*/
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
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Blocking · Correctness: set() skips normalization but getHeader() always normalizes — silent lookup miss

When isHttp2Headers=true, set() stores the key verbatim (e.g. "content-type"). However, getHeader() unconditionally calls headerName.toLowerCase(Locale.ROOT) before looking up in the map. This is symmetric and safe only as long as every caller passes lowercase names. The moment any caller does headers.set("Content-Type", ...) on an isHttp2Headers=true instance (perfectly valid via the public API), the key is stored as "Content-Type" but every lookup attempts "content-type" — a silent null return with no error.

Two complementary fixes are needed:

1. Guard in set() (fail-fast):

public HttpHeaders set(String name, String value) {
    if (isHttp2Headers && !name.equals(name.toLowerCase(Locale.ROOT))) {
        throw new IllegalArgumentException(
            "isHttp2Headers=true requires lowercase header names, got: " + name);
    }
    final String headerKey = isHttp2Headers ? name : name.toLowerCase(Locale.ROOT);
    ...
}

2. Skip normalization in getHeader() when flag is set (complete the optimization):

private HttpHeader getHeader(String headerName) {
    final String headerKey = isHttp2Headers ? headerName : headerName.toLowerCase(Locale.ROOT);
    return headers.get(headerKey);
}

These two changes together make the optimization correct and complete.


⚠️ AI-generated review — may be incorrect. Agree? → resolve the conversation. Disagree? → reply with your reasoning.


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

Choose a reason for hiding this comment

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

🟡 Recommendation · Test Coverage: Test bypasses the actual lookup path — bug in getHeader() would go undetected

toMap() iterates headers.values() directly and never calls getHeader(). This means the test does not exercise the set()getHeader() round-trip that the optimization affects. If getHeader() had a normalization bug when isHttp2Headers=true, this test would still pass.

Change the assertion to use value() (which goes through getHeader()):

// Before:
Map(String, String) map = headers.toMap();
assertThat(map.get(headerName)).isEqualTo(headerValue);

// After:
assertThat(headers.value(headerName)).isEqualTo(headerValue);

Also add a test for the mixed-case negative case once the guard from the set() fix is in place:

`@Test`(groups = "unit", expectedExceptions = IllegalArgumentException.class)
public void http2HeadersRejectsMixedCaseName() {
    HttpHeaders headers = new HttpHeaders(4, true);
    headers.set("Content-Type", "application/json"); // must throw
}

⚠️ AI-generated review — may be incorrect. Agree? → resolve the conversation. Disagree? → reply with your reasoning.

* @param size the initial capacity
* @param isHttp2Headers true if headers originate from an HTTP/2 response (names already lowercase)
*/
public HttpHeaders(int size, boolean isHttp2Headers) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Blocking · Correctness: Optimization is dead code — HTTP/2 detection never wired up

The new HttpHeaders(int size, boolean isHttp2Headers) constructor is never called from the response-handling code path. The PR description says "Detect HTTP/2 in ReactorNettyHttpResponse.headers() via version check", but that detection is absent from the diff. Every response — HTTP/1.1 or HTTP/2 — continues to use new HttpHeaders(reactorNettyResponse.responseHeaders().size()), paying the full toLowerCase() cost. The optimization is entirely bypassed in production.

The integration point should be ReactorNettyHttpResponse.headers(). Roughly:

`@Override`
public HttpHeaders headers() {
    boolean isHttp2 = /* check reactor-netty version API, e.g.: */
        reactorNettyResponse.version() == HttpVersion.HTTP_2_0;
    HttpHeaders headers = new HttpHeaders(reactorNettyResponse.responseHeaders().size(), isHttp2);
    reactorNettyResponse.responseHeaders().forEach(e -> headers.set(e.getKey(), e.getValue()));
    return headers;
}

Verify the exact version-detection API available on the HttpClientResponse type used in this module's reactor-netty version, as the API varies across releases.


⚠️ AI-generated review — may be incorrect. Agree? → resolve the conversation. Disagree? → reply with your reasoning.

Repository owner deleted a comment from github-actions Bot Mar 19, 2026
@xinlian12
Copy link
Copy Markdown
Owner Author

/sdkReviewAgent

1 similar comment
@xinlian12
Copy link
Copy Markdown
Owner Author

/sdkReviewAgent

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant