Browse Source
The `HttpHeaders#headerSet` method is intended as a drop-in replacement for `entrySet` that guarantees a single casing for all header names reported during the iteration, as the cost of some overhead but with support for iterator removal and entry value-setting. The `formatHeaders` static method is also altered to do a similar deduplication of casing variants, but now additionally mentions "with native header names [native name set]" if the native name set contains casing variants. Closes gh-33823pull/33850/head
9 changed files with 1623 additions and 52 deletions
@ -0,0 +1,124 @@
@@ -0,0 +1,124 @@
|
||||
/* |
||||
* Copyright 2002-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.http.support; |
||||
|
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
import java.util.function.Function; |
||||
|
||||
import io.netty.handler.codec.http.DefaultHttpHeaders; |
||||
import org.apache.hc.client5.http.classic.methods.HttpGet; |
||||
import org.eclipse.jetty.http.HttpFields; |
||||
import org.openjdk.jmh.annotations.Benchmark; |
||||
import org.openjdk.jmh.annotations.BenchmarkMode; |
||||
import org.openjdk.jmh.annotations.Level; |
||||
import org.openjdk.jmh.annotations.Mode; |
||||
import org.openjdk.jmh.annotations.Param; |
||||
import org.openjdk.jmh.annotations.Scope; |
||||
import org.openjdk.jmh.annotations.Setup; |
||||
import org.openjdk.jmh.annotations.State; |
||||
import org.openjdk.jmh.infra.Blackhole; |
||||
|
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.util.MultiValueMap; |
||||
|
||||
/** |
||||
* Benchmark for implementations of MultiValueMap adapters over native HTTP |
||||
* headers implementations. |
||||
* <p>Run JMH with {@code -p implementation=Netty,Netty5,HttpComponents,Jetty} |
||||
* to cover all implementations |
||||
* @author Simon Baslé |
||||
*/ |
||||
@BenchmarkMode(Mode.Throughput) |
||||
public class HeadersAdapterBenchmark { |
||||
|
||||
@Benchmark |
||||
public void iterateEntries(BenchmarkData data, Blackhole bh) { |
||||
for (Map.Entry<String, List<String>> entry : data.entriesProvider.apply(data.headers)) { |
||||
bh.consume(entry.getKey()); |
||||
for (String s : entry.getValue()) { |
||||
bh.consume(s); |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Benchmark |
||||
public void toString(BenchmarkData data, Blackhole bh) { |
||||
bh.consume(data.headers.toString()); |
||||
} |
||||
|
||||
@State(Scope.Benchmark) |
||||
public static class BenchmarkData { |
||||
|
||||
@Param({"NONE"}) |
||||
public String implementation; |
||||
|
||||
@Param({"true"}) |
||||
public boolean duplicate; |
||||
|
||||
public MultiValueMap<String, String> headers; |
||||
public Function<MultiValueMap<String, String>, Set<Map.Entry<String, List<String>>>> entriesProvider; |
||||
|
||||
//Uncomment the following line and comment the similar line for setupImplementationBaseline below
|
||||
//to benchmark current implementations
|
||||
@Setup(Level.Trial) |
||||
public void initImplementationNew() { |
||||
this.entriesProvider = map -> new HttpHeaders(map).headerSet(); |
||||
|
||||
this.headers = switch (this.implementation) { |
||||
case "Netty" -> new Netty4HeadersAdapter(new DefaultHttpHeaders()); |
||||
case "HttpComponents" -> new HttpComponentsHeadersAdapter(new HttpGet("https://example.com")); |
||||
case "Netty5" -> new Netty5HeadersAdapter(io.netty5.handler.codec.http.headers.HttpHeaders.newHeaders()); |
||||
case "Jetty" -> new JettyHeadersAdapter(HttpFields.build()); |
||||
//FIXME tomcat/undertow implementations (in another package)
|
||||
// case "Tomcat" -> new TomcatHeadersAdapter(new MimeHeaders());
|
||||
// case "Undertow" -> new UndertowHeadersAdapter(new HeaderMap());
|
||||
default -> throw new IllegalArgumentException("Unsupported implementation: " + this.implementation); |
||||
}; |
||||
initHeaders(); |
||||
} |
||||
|
||||
//Uncomment the following line and comment the similar line for setupImplementationNew above
|
||||
//to benchmark old implementations
|
||||
// @Setup(Level.Trial)
|
||||
public void setupImplementationBaseline() { |
||||
this.entriesProvider = MultiValueMap::entrySet; |
||||
|
||||
this.headers = switch (this.implementation) { |
||||
case "Netty" -> new HeadersAdaptersBaseline.Netty4(new DefaultHttpHeaders()); |
||||
case "HttpComponents" -> new HeadersAdaptersBaseline.HttpComponents(new HttpGet("https://example.com")); |
||||
case "Netty5" -> new HeadersAdaptersBaseline.Netty5(io.netty5.handler.codec.http.headers.HttpHeaders.newHeaders()); |
||||
case "Jetty" -> new HeadersAdaptersBaseline.Jetty(HttpFields.build()); |
||||
default -> throw new IllegalArgumentException("Unsupported implementation: " + this.implementation); |
||||
}; |
||||
initHeaders(); |
||||
} |
||||
|
||||
private void initHeaders() { |
||||
this.headers.add("TestHeader", "first"); |
||||
this.headers.add("SecondHeader", "value"); |
||||
if (this.duplicate) { |
||||
this.headers.add("TestHEADER", "second"); |
||||
} |
||||
else { |
||||
this.headers.add("TestHeader", "second"); |
||||
} |
||||
this.headers.add("TestHeader", "third"); |
||||
} |
||||
} |
||||
} |
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue