Optimize response header handling for HTTP/2 (P2)#21
Conversation
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>
|
@sdkReviewAgent |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
🔴 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.
|
|
||
| Map<String, String> map = headers.toMap(); | ||
| assertThat(map.get(headerName)).isEqualTo(headerValue); | ||
| } |
There was a problem hiding this comment.
🟡 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
}| * @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) { |
There was a problem hiding this comment.
🔴 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.
|
/sdkReviewAgent |
1 similar comment
|
/sdkReviewAgent |
Option A: Skip redundant toLowerCase() for HTTP/2 headers