Skip to content

Commit 0effd86

Browse files
Annie LiangCopilot
authored andcommitted
Skip redundant toLowerCase for HTTP/2 response headers
HTTP/2 header names are already lowercase per RFC 7540 §8.1.2. Add keysAlreadyLowerCased flag to HttpHeaders to skip toLowerCase() in set() and getHeader() for HTTP/2 responses (~25 scans saved per response). In RxGatewayStoreModel, use toMap() instead of toLowerCaseMap() when keys are already lowercase to avoid redundant HashMap allocation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 9ce646b commit 0effd86

3 files changed

Lines changed: 42 additions & 6 deletions

File tree

sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/RxGatewayStoreModel.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,11 @@ public StoreResponse unwrapToStoreResponse(
230230
// If there is any error in the header response this throws exception
231231
validateOrThrow(request, HttpResponseStatus.valueOf(statusCode), headers, retainedContent);
232232

233+
// For HTTP/2, header keys are already lowercase per RFC 7540 §8.1.2 and were stored
234+
// as-is (no toLowerCase) in HttpHeaders. Use toMap() directly to avoid redundant work.
235+
Map<String, String> headerMap = HttpUtils.unescape(
236+
headers.areKeysLowerCased() ? headers.toMap() : headers.toLowerCaseMap());
237+
233238
int size;
234239
if ((size = retainedContent.readableBytes()) > 0) {
235240
if (leakDetectionDebuggingEnabled) {
@@ -240,7 +245,7 @@ public StoreResponse unwrapToStoreResponse(
240245
return new StoreResponse(
241246
endpoint,
242247
statusCode,
243-
HttpUtils.unescape(headers.toLowerCaseMap()),
248+
headerMap,
244249
new ByteBufInputStream(retainedContent, true),
245250
size);
246251
} else {
@@ -250,7 +255,7 @@ public StoreResponse unwrapToStoreResponse(
250255
return new StoreResponse(
251256
endpoint,
252257
statusCode,
253-
HttpUtils.unescape(headers.toLowerCaseMap()),
258+
headerMap,
254259
null,
255260
0);
256261
}
@@ -731,7 +736,8 @@ private void validateOrThrow(RxDocumentServiceRequest request,
731736
String.format("%s, StatusCode: %s", cosmosError.getMessage(), statusCodeString),
732737
cosmosError.getPartitionedQueryExecutionInfo());
733738

734-
CosmosException dce = BridgeInternal.createCosmosException(request.requestContext.resourcePhysicalAddress, statusCode, cosmosError, headers.toLowerCaseMap());
739+
CosmosException dce = BridgeInternal.createCosmosException(request.requestContext.resourcePhysicalAddress, statusCode, cosmosError,
740+
headers.areKeysLowerCased() ? headers.toMap() : headers.toLowerCaseMap());
735741
BridgeInternal.setRequestHeaders(dce, request.getHeaders());
736742
throw dce;
737743
}

sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/http/HttpHeaders.java

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,36 @@
1919
*/
2020
public class HttpHeaders implements Iterable<HttpHeader>, JsonSerializable {
2121
private Map<String, HttpHeader> headers;
22+
private final boolean keysAlreadyLowerCased;
2223

2324
/**
2425
* Create an empty HttpHeaders instance.
2526
*/
2627
public HttpHeaders() {
2728
this.headers = new HashMap<>();
29+
this.keysAlreadyLowerCased = false;
2830
}
2931

3032
/**
3133
* Create an HttpHeaders instance with the given size.
3234
*/
3335
public HttpHeaders(int size) {
3436
this.headers = new HashMap<>(size);
37+
this.keysAlreadyLowerCased = false;
38+
}
39+
40+
/**
41+
* Create an HttpHeaders instance with the given size.
42+
* When {@code keysAlreadyLowerCased} is true, {@link #set(String, String)} skips
43+
* the {@code toLowerCase()} call on header names. This is an optimization for HTTP/2
44+
* responses where header names are guaranteed to be lowercase per RFC 7540 §8.1.2.
45+
*
46+
* @param size the initial capacity
47+
* @param keysAlreadyLowerCased true if header names are guaranteed to be lowercase (e.g. HTTP/2)
48+
*/
49+
public HttpHeaders(int size, boolean keysAlreadyLowerCased) {
50+
this.headers = new HashMap<>(size);
51+
this.keysAlreadyLowerCased = keysAlreadyLowerCased;
3552
}
3653

3754
/**
@@ -41,6 +58,7 @@ public HttpHeaders(int size) {
4158
*/
4259
public HttpHeaders(Map<String, String> headers) {
4360
this.headers = new HashMap<>(headers.size());
61+
this.keysAlreadyLowerCased = false;
4462
for (final Map.Entry<String, String> header : headers.entrySet()) {
4563
this.set(header.getKey(), header.getValue());
4664
}
@@ -66,7 +84,7 @@ public int size() {
6684
* @return this HttpHeaders
6785
*/
6886
public HttpHeaders set(String name, String value) {
69-
final String headerKey = name.toLowerCase(Locale.ROOT);
87+
final String headerKey = keysAlreadyLowerCased ? name : name.toLowerCase(Locale.ROOT);
7088
if (value == null) {
7189
headers.remove(headerKey);
7290
} else {
@@ -100,7 +118,7 @@ public String[] values(String name) {
100118
}
101119

102120
private HttpHeader getHeader(String headerName) {
103-
final String headerKey = headerName.toLowerCase(Locale.ROOT);
121+
final String headerKey = keysAlreadyLowerCased ? headerName : headerName.toLowerCase(Locale.ROOT);
104122
return headers.get(headerKey);
105123
}
106124

@@ -130,6 +148,14 @@ public Map<String, String> toLowerCaseMap() {
130148
return result;
131149
}
132150

151+
/**
152+
* Returns true if header keys are guaranteed to be lowercase,
153+
* meaning {@link #toLowerCaseMap()} and {@link #toMap()} produce equivalent keys.
154+
*/
155+
public boolean areKeysLowerCased() {
156+
return keysAlreadyLowerCased;
157+
}
158+
133159
@Override
134160
public Iterator<HttpHeader> iterator() {
135161
return headers.values().iterator();

sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/http/ReactorNettyClient.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,11 @@ public String headerValue(String name) {
409409

410410
@Override
411411
public HttpHeaders headers() {
412-
HttpHeaders headers = new HttpHeaders(reactorNettyResponse.responseHeaders().size());
412+
// HTTP/2 header names are guaranteed lowercase per RFC 7540 §8.1.2.
413+
// Detect directly from the response protocol version, not from the request record,
414+
// to handle HTTP/1.1 fallback correctly even when H2 was requested.
415+
boolean isHttp2 = reactorNettyResponse.version().majorVersion() == 2;
416+
HttpHeaders headers = new HttpHeaders(reactorNettyResponse.responseHeaders().size(), isHttp2);
413417
reactorNettyResponse.responseHeaders().forEach(e -> headers.set(e.getKey(), e.getValue()));
414418
return headers;
415419
}

0 commit comments

Comments
 (0)