-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Optimize header handling in JDK HttpClient (#38285)
Optimize header handling in JDK HttpClient
- Loading branch information
1 parent
12205a1
commit 48843d7
Showing
8 changed files
with
252 additions
and
104 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
101 changes: 101 additions & 0 deletions
101
.../src/main/java/com/azure/core/http/jdk/httpclient/implementation/AzureJdkHttpRequest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
package com.azure.core.http.jdk.httpclient.implementation; | ||
|
||
import com.azure.core.http.HttpMethod; | ||
import com.azure.core.implementation.util.HttpHeadersAccessHelper; | ||
import com.azure.core.util.Context; | ||
import com.azure.core.util.Contexts; | ||
import com.azure.core.util.ProgressReporter; | ||
import com.azure.core.util.logging.ClientLogger; | ||
import reactor.core.Exceptions; | ||
|
||
import java.net.URI; | ||
import java.net.URISyntaxException; | ||
import java.net.http.HttpClient; | ||
import java.net.http.HttpHeaders; | ||
import java.net.http.HttpRequest; | ||
import java.time.Duration; | ||
import java.util.Optional; | ||
import java.util.Set; | ||
import java.util.TreeMap; | ||
|
||
import static java.net.http.HttpRequest.BodyPublishers.noBody; | ||
|
||
/** | ||
* Implementation of the JDK {@link HttpRequest}. | ||
* <p> | ||
* Using this instead of {@link HttpRequest#newBuilder()} allows us to optimize some cases now allowed by the builder. | ||
* For example, setting headers requires each key-value for the same header to be set individually. This class allows | ||
* us to set all headers at once. And given that the headers are backed by a {@link TreeMap} it reduces the number of | ||
* String comparisons performed. | ||
*/ | ||
public final class AzureJdkHttpRequest extends HttpRequest { | ||
private final BodyPublisher bodyPublisher; | ||
private final String method; | ||
private final URI uri; | ||
private final HttpHeaders headers; | ||
|
||
/** | ||
* Creates a new instance of the JDK HttpRequest. | ||
* | ||
* @param azureCoreRequest The Azure Core request to create the JDK HttpRequest from. | ||
* @param context The context of the request. | ||
* @param restrictedHeaders The set of restricted headers. | ||
* @param logger The logger to log warnings to. | ||
*/ | ||
public AzureJdkHttpRequest(com.azure.core.http.HttpRequest azureCoreRequest, Context context, | ||
Set<String> restrictedHeaders, ClientLogger logger) { | ||
HttpMethod method = azureCoreRequest.getHttpMethod(); | ||
ProgressReporter progressReporter = Contexts.with(context).getHttpRequestProgressReporter(); | ||
|
||
this.method = method.toString(); | ||
this.bodyPublisher = (method == HttpMethod.GET || method == HttpMethod.HEAD) | ||
? noBody() : BodyPublisherUtils.toBodyPublisher(azureCoreRequest, progressReporter); | ||
|
||
try { | ||
uri = azureCoreRequest.getUrl().toURI(); | ||
} catch (URISyntaxException e) { | ||
throw logger.logExceptionAsError(Exceptions.propagate(e)); | ||
} | ||
|
||
this.headers = HttpHeaders.of(new HeaderFilteringMap( | ||
HttpHeadersAccessHelper.getRawHeaderMap(azureCoreRequest.getHeaders()), restrictedHeaders, logger), | ||
(ignored1, ignored2) -> true); | ||
} | ||
|
||
@Override | ||
public Optional<BodyPublisher> bodyPublisher() { | ||
return Optional.ofNullable(bodyPublisher); | ||
} | ||
|
||
@Override | ||
public String method() { | ||
return method; | ||
} | ||
|
||
@Override | ||
public Optional<Duration> timeout() { | ||
return Optional.empty(); | ||
} | ||
|
||
@Override | ||
public boolean expectContinue() { | ||
return false; | ||
} | ||
|
||
@Override | ||
public URI uri() { | ||
return uri; | ||
} | ||
|
||
@Override | ||
public Optional<HttpClient.Version> version() { | ||
return Optional.empty(); | ||
} | ||
|
||
@Override | ||
public HttpHeaders headers() { | ||
return headers; | ||
} | ||
} |
62 changes: 62 additions & 0 deletions
62
...t/src/main/java/com/azure/core/http/jdk/httpclient/implementation/HeaderFilteringMap.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
package com.azure.core.http.jdk.httpclient.implementation; | ||
|
||
import com.azure.core.http.HttpHeader; | ||
import com.azure.core.util.logging.ClientLogger; | ||
|
||
import java.util.AbstractMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import java.util.function.BiConsumer; | ||
import java.util.function.BiPredicate; | ||
|
||
/** | ||
* This class is a {@link Map} implementation that filters out headers that are not allowed to be set on an HTTP | ||
* request. | ||
* <p> | ||
* Based on logic used in {@link java.net.http.HttpHeaders#of(Map, BiPredicate)} it's known that the headers will be | ||
* accessed using {@link Map#forEach(BiConsumer)}. Give that, this class can be optimized to use the forEach method | ||
* on the raw HttpHeaders map used in azure-core which can filter out the headers that are not allowed to be set using | ||
* the known lowercase header names, then return the cased header name and values. | ||
*/ | ||
final class HeaderFilteringMap extends AbstractMap<String, List<String>> { | ||
private final Map<String, HttpHeader> rawHeaders; | ||
private final Set<String> restrictedHeaders; | ||
private final ClientLogger logger; | ||
|
||
/** | ||
* Creates a new HeaderFilteringMap. | ||
* | ||
* @param rawHeaders The raw headers map. | ||
* @param restrictedHeaders The header filter. | ||
* @param logger The logger to log any errors. | ||
*/ | ||
HeaderFilteringMap(Map<String, HttpHeader> rawHeaders, Set<String> restrictedHeaders, ClientLogger logger) { | ||
this.rawHeaders = rawHeaders; | ||
this.restrictedHeaders = restrictedHeaders; | ||
this.logger = logger; | ||
} | ||
|
||
@Override | ||
public Set<Entry<String, List<String>>> entrySet() { | ||
throw logger.logExceptionAsError( | ||
new UnsupportedOperationException("The only operation permitted by this Map is forEach.")); | ||
} | ||
|
||
@Override | ||
public void forEach(BiConsumer<? super String, ? super List<String>> action) { | ||
rawHeaders.forEach((headerName, header) -> { | ||
if (restrictedHeaders.contains(headerName)) { | ||
logger.atWarning() | ||
.addKeyValue("headerName", headerName) | ||
.log("The header is restricted by 'java.net.http.HttpClient' and will be ignored. To allow this " | ||
+ "header to be set on the request, configure 'jdk.httpclient.allowRestrictedHeaders' with the " | ||
+ "header added in the comma-separated list."); | ||
} else { | ||
action.accept(header.getName(), header.getValuesList()); | ||
} | ||
}); | ||
} | ||
} |
Oops, something went wrong.