Skip to content

Commit 5ea0674

Browse files
snicollphilwebb
andcommitted
Migrate to new HttpHeaders API
See gh-45487 Co-authored-by: Phillip Webb <phil.webb@broadcom.com>
1 parent 6fceab2 commit 5ea0674

File tree

17 files changed

+73
-64
lines changed

17 files changed

+73
-64
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/HttpSender.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,16 +19,16 @@
1919
import java.io.ByteArrayOutputStream;
2020
import java.io.IOException;
2121
import java.net.URI;
22+
import java.util.LinkedHashMap;
2223
import java.util.List;
24+
import java.util.Map;
2325
import java.util.zip.GZIPOutputStream;
2426

2527
import zipkin2.reporter.BaseHttpSender;
2628
import zipkin2.reporter.BytesMessageSender;
2729
import zipkin2.reporter.Encoding;
2830
import zipkin2.reporter.HttpEndpointSupplier.Factory;
2931

30-
import org.springframework.util.LinkedMultiValueMap;
31-
import org.springframework.util.MultiValueMap;
3232
import org.springframework.util.unit.DataSize;
3333

3434
/**
@@ -61,20 +61,20 @@ protected byte[] newBody(List<byte[]> list) {
6161

6262
@Override
6363
protected void postSpans(URI endpoint, byte[] body) throws IOException {
64-
MultiValueMap<String, String> headers = getDefaultHeaders();
64+
Map<String, String> headers = getDefaultHeaders();
6565
if (needsCompression(body)) {
6666
body = compress(body);
67-
headers.add("Content-Encoding", "gzip");
67+
headers.put("Content-Encoding", "gzip");
6868
}
6969
postSpans(endpoint, headers, body);
7070
}
7171

72-
abstract void postSpans(URI endpoint, MultiValueMap<String, String> headers, byte[] body) throws IOException;
72+
abstract void postSpans(URI endpoint, Map<String, String> headers, byte[] body) throws IOException;
7373

74-
MultiValueMap<String, String> getDefaultHeaders() {
75-
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
76-
headers.add("b3", "0");
77-
headers.add("Content-Type", this.encoding.mediaType());
74+
Map<String, String> getDefaultHeaders() {
75+
Map<String, String> headers = new LinkedHashMap<>();
76+
headers.put("b3", "0");
77+
headers.put("Content-Type", this.encoding.mediaType());
7878
return headers;
7979
}
8080

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/zipkin/ZipkinHttpClientSender.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,12 +25,11 @@
2525
import java.net.http.HttpResponse;
2626
import java.net.http.HttpResponse.BodyHandlers;
2727
import java.time.Duration;
28+
import java.util.Map;
2829

2930
import zipkin2.reporter.Encoding;
3031
import zipkin2.reporter.HttpEndpointSupplier.Factory;
3132

32-
import org.springframework.util.MultiValueMap;
33-
3433
/**
3534
* A {@link HttpSender} which uses the JDK {@link HttpClient} for HTTP communication.
3635
*
@@ -50,12 +49,12 @@ class ZipkinHttpClientSender extends HttpSender {
5049
}
5150

5251
@Override
53-
void postSpans(URI endpoint, MultiValueMap<String, String> headers, byte[] body) throws IOException {
52+
void postSpans(URI endpoint, Map<String, String> headers, byte[] body) throws IOException {
5453
Builder request = HttpRequest.newBuilder()
5554
.POST(BodyPublishers.ofByteArray(body))
5655
.uri(endpoint)
5756
.timeout(this.readTimeout);
58-
headers.forEach((name, values) -> values.forEach((value) -> request.header(name, value)));
57+
headers.forEach((name, value) -> request.header(name, value));
5958
try {
6059
HttpResponse<Void> response = this.httpClient.send(request.build(), BodyHandlers.discarding());
6160
if (response.statusCode() / 100 != 2) {

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/exchanges/reactive/RecordableServerHttpRequest.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2022 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,11 +19,13 @@
1919
import java.net.InetAddress;
2020
import java.net.InetSocketAddress;
2121
import java.net.URI;
22+
import java.util.Collections;
2223
import java.util.LinkedHashMap;
2324
import java.util.List;
2425
import java.util.Map;
2526

2627
import org.springframework.boot.actuate.web.exchanges.RecordableHttpRequest;
28+
import org.springframework.http.HttpHeaders;
2729
import org.springframework.http.server.reactive.ServerHttpRequest;
2830

2931
/**
@@ -35,7 +37,7 @@ class RecordableServerHttpRequest implements RecordableHttpRequest {
3537

3638
private final String method;
3739

38-
private final Map<String, List<String>> headers;
40+
private final HttpHeaders headers;
3941

4042
private final URI uri;
4143

@@ -66,7 +68,9 @@ public URI getUri() {
6668

6769
@Override
6870
public Map<String, List<String>> getHeaders() {
69-
return new LinkedHashMap<>(this.headers);
71+
Map<String, List<String>> headers = new LinkedHashMap<>();
72+
this.headers.forEach(headers::put);
73+
return Collections.unmodifiableMap(headers);
7074
}
7175

7276
@Override

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/exchanges/reactive/RecordableServerHttpResponse.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2022 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.boot.actuate.web.exchanges.reactive;
1818

19+
import java.util.Collections;
1920
import java.util.LinkedHashMap;
2021
import java.util.List;
2122
import java.util.Map;
@@ -38,7 +39,9 @@ class RecordableServerHttpResponse implements RecordableHttpResponse {
3839

3940
RecordableServerHttpResponse(ServerHttpResponse response) {
4041
this.status = (response.getStatusCode() != null) ? response.getStatusCode().value() : HttpStatus.OK.value();
41-
this.headers = new LinkedHashMap<>(response.getHeaders());
42+
Map<String, List<String>> headers = new LinkedHashMap<>();
43+
response.getHeaders().forEach(headers::put);
44+
this.headers = Collections.unmodifiableMap(headers);
4245
}
4346

4447
@Override

spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/exchanges/HttpExchangeTests.java

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,11 +23,8 @@
2323
import java.time.Instant;
2424
import java.time.ZoneId;
2525
import java.util.Arrays;
26-
import java.util.Collections;
2726
import java.util.HashMap;
28-
import java.util.LinkedHashMap;
2927
import java.util.List;
30-
import java.util.Map;
3128
import java.util.function.Supplier;
3229

3330
import org.junit.jupiter.api.Test;
@@ -47,14 +44,11 @@
4744
*/
4845
class HttpExchangeTests {
4946

50-
private static final Map<String, List<String>> AUTHORIZATION_HEADER = Map.of(HttpHeaders.AUTHORIZATION,
51-
Arrays.asList("secret"));
47+
private static final HttpHeaders AUTHORIZATION_HEADER = ofSingleHttpHeader(HttpHeaders.AUTHORIZATION, "secret");
5248

53-
private static final Map<String, List<String>> COOKIE_HEADER = Map.of(HttpHeaders.COOKIE,
54-
Arrays.asList("test=test"));
49+
private static final HttpHeaders COOKIE_HEADER = ofSingleHttpHeader(HttpHeaders.COOKIE, "test=test");
5550

56-
private static final Map<String, List<String>> SET_COOKIE_HEADER = Map.of(HttpHeaders.SET_COOKIE,
57-
Arrays.asList("test=test"));
51+
private static final HttpHeaders SET_COOKIE_HEADER = ofSingleHttpHeader(HttpHeaders.SET_COOKIE, "test=test");
5852

5953
private static final Supplier<Principal> NO_PRINCIPAL = () -> null;
6054

@@ -298,31 +292,33 @@ void defaultIncludes() {
298292
}
299293

300294
private RecordableHttpRequest createRequest() {
301-
return createRequest(Collections.singletonMap(HttpHeaders.ACCEPT, Arrays.asList("application/json")));
295+
return createRequest(ofSingleHttpHeader(HttpHeaders.ACCEPT, "application/json"));
302296
}
303297

304-
private RecordableHttpRequest createRequest(Map<String, List<String>> headers) {
298+
@SuppressWarnings("removal")
299+
private RecordableHttpRequest createRequest(HttpHeaders headers) {
305300
RecordableHttpRequest request = mock(RecordableHttpRequest.class);
306301
given(request.getMethod()).willReturn("GET");
307302
given(request.getUri()).willReturn(URI.create("https://api.example.com"));
308-
given(request.getHeaders()).willReturn(new HashMap<>(headers));
303+
given(request.getHeaders()).willReturn(new HashMap<>(headers.asMultiValueMap()));
309304
given(request.getRemoteAddress()).willReturn("127.0.0.1");
310305
return request;
311306
}
312307

313308
private RecordableHttpResponse createResponse() {
314-
return createResponse(Collections.singletonMap(HttpHeaders.CONTENT_TYPE, Arrays.asList("application/json")));
309+
return createResponse(ofSingleHttpHeader(HttpHeaders.CONTENT_TYPE, "application/json"));
315310
}
316311

317-
private RecordableHttpResponse createResponse(Map<String, List<String>> headers) {
312+
@SuppressWarnings("removal")
313+
private RecordableHttpResponse createResponse(HttpHeaders headers) {
318314
RecordableHttpResponse response = mock(RecordableHttpResponse.class);
319315
given(response.getStatus()).willReturn(204);
320-
given(response.getHeaders()).willReturn(new HashMap<>(headers));
316+
given(response.getHeaders()).willReturn(new HashMap<>(headers.asMultiValueMap()));
321317
return response;
322318
}
323319

324-
private Map<String, List<String>> mixedCase(Map<String, List<String>> headers) {
325-
Map<String, List<String>> result = new LinkedHashMap<>();
320+
private HttpHeaders mixedCase(HttpHeaders headers) {
321+
HttpHeaders result = new HttpHeaders();
326322
headers.forEach((key, value) -> result.put(mixedCase(key), value));
327323
return result;
328324
}
@@ -336,4 +332,10 @@ private String mixedCase(String input) {
336332
return output.toString();
337333
}
338334

335+
private static HttpHeaders ofSingleHttpHeader(String header, String... values) {
336+
HttpHeaders httpHeaders = new HttpHeaders();
337+
httpHeaders.put(header, List.of(values));
338+
return httpHeaders;
339+
}
340+
339341
}

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/client/RestTemplateAutoConfigurationTests.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -45,6 +45,7 @@
4545
import org.springframework.web.client.RestTemplate;
4646

4747
import static org.assertj.core.api.Assertions.assertThat;
48+
import static org.assertj.core.api.Assertions.entry;
4849
import static org.mockito.ArgumentMatchers.any;
4950
import static org.mockito.BDDMockito.given;
5051
import static org.mockito.BDDMockito.then;
@@ -168,7 +169,7 @@ void restTemplateShouldApplyRequestCustomizer() {
168169
given(requestFactory.createRequest(any(), any())).willReturn(request);
169170
RestTemplate restTemplate = builder.requestFactory(() -> requestFactory).build();
170171
restTemplate.getForEntity("http://localhost:8080/test", String.class);
171-
assertThat(request.getHeaders()).containsEntry("spring", Collections.singletonList("boot"));
172+
assertThat(request.getHeaders().headerSet()).contains(entry("spring", Collections.singletonList("boot")));
172173
});
173174
}
174175

spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/livereload/LiveReloadServerTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -342,7 +342,7 @@ public void beforeRequest(Map<String, List<String>> requestHeaders) {
342342
requestHeaders.forEach((key, value) -> uppercaseRequestHeaders.put(key.toUpperCase(Locale.ROOT), value));
343343
requestHeaders.clear();
344344
requestHeaders.putAll(uppercaseRequestHeaders);
345-
requestHeaders.putAll(this.headers);
345+
this.headers.forEach(requestHeaders::put);
346346
}
347347

348348
@Override

spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/web/client/TestRestTemplateTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -481,10 +481,10 @@ private void assertBasicAuthorizationCredentials(TestRestTemplate testRestTempla
481481
ClientHttpRequest request = ReflectionTestUtils.invokeMethod(testRestTemplate.getRestTemplate(),
482482
"createRequest", URI.create("http://localhost"), HttpMethod.POST);
483483
if (username == null) {
484-
assertThat(request.getHeaders()).doesNotContainKey(HttpHeaders.AUTHORIZATION);
484+
assertThat(request.getHeaders().headerNames()).doesNotContain(HttpHeaders.AUTHORIZATION);
485485
}
486486
else {
487-
assertThat(request.getHeaders()).containsKeys(HttpHeaders.AUTHORIZATION);
487+
assertThat(request.getHeaders().headerNames()).contains(HttpHeaders.AUTHORIZATION);
488488
assertThat(request.getHeaders().get(HttpHeaders.AUTHORIZATION)).containsExactly("Basic "
489489
+ Base64.getEncoder().encodeToString(String.format("%s:%s", username, password).getBytes()));
490490
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/client/BasicAuthentication.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class BasicAuthentication {
4444
}
4545

4646
void applyTo(HttpHeaders headers) {
47-
if (!headers.containsKey(HttpHeaders.AUTHORIZATION)) {
47+
if (!headers.containsHeader(HttpHeaders.AUTHORIZATION)) {
4848
headers.setBasicAuth(this.username, this.password, this.charset);
4949
}
5050
}

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/client/RestTemplateBuilderTests.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -319,15 +319,15 @@ void errorHandlerShouldApply() {
319319
void basicAuthenticationShouldApply() {
320320
RestTemplate template = this.builder.basicAuthentication("spring", "boot", StandardCharsets.UTF_8).build();
321321
ClientHttpRequest request = createRequest(template);
322-
assertThat(request.getHeaders()).containsOnlyKeys(HttpHeaders.AUTHORIZATION);
322+
assertThat(request.getHeaders().headerNames()).containsOnly(HttpHeaders.AUTHORIZATION);
323323
assertThat(request.getHeaders().get(HttpHeaders.AUTHORIZATION)).containsExactly("Basic c3ByaW5nOmJvb3Q=");
324324
}
325325

326326
@Test
327327
void defaultHeaderAddsHeader() {
328328
RestTemplate template = this.builder.defaultHeader("spring", "boot").build();
329329
ClientHttpRequest request = createRequest(template);
330-
assertThat(request.getHeaders()).contains(entry("spring", Collections.singletonList("boot")));
330+
assertThat(request.getHeaders().headerSet()).contains(entry("spring", Collections.singletonList("boot")));
331331
}
332332

333333
@Test
@@ -336,15 +336,15 @@ void defaultHeaderAddsHeaderValues() {
336336
String[] values = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE };
337337
RestTemplate template = this.builder.defaultHeader(name, values).build();
338338
ClientHttpRequest request = createRequest(template);
339-
assertThat(request.getHeaders()).contains(entry(name, Arrays.asList(values)));
339+
assertThat(request.getHeaders().headerSet()).contains(entry(name, Arrays.asList(values)));
340340
}
341341

342342
@Test // gh-17885
343343
void defaultHeaderWhenUsingMockRestServiceServerAddsHeader() {
344344
RestTemplate template = this.builder.defaultHeader("spring", "boot").build();
345345
MockRestServiceServer.bindTo(template).build();
346346
ClientHttpRequest request = createRequest(template);
347-
assertThat(request.getHeaders()).contains(entry("spring", Collections.singletonList("boot")));
347+
assertThat(request.getHeaders().headerSet()).contains(entry("spring", Collections.singletonList("boot")));
348348
}
349349

350350
@Test
@@ -361,7 +361,7 @@ void requestCustomizersAddsCustomizers() {
361361
.requestCustomizers((request) -> request.getHeaders().add("spring", "framework"))
362362
.build();
363363
ClientHttpRequest request = createRequest(template);
364-
assertThat(request.getHeaders()).contains(entry("spring", Collections.singletonList("framework")));
364+
assertThat(request.getHeaders().headerSet()).contains(entry("spring", Collections.singletonList("framework")));
365365
}
366366

367367
@Test
@@ -371,7 +371,7 @@ void additionalRequestCustomizersAddsCustomizers() {
371371
.additionalRequestCustomizers((request) -> request.getHeaders().add("for", "java"))
372372
.build();
373373
ClientHttpRequest request = createRequest(template);
374-
assertThat(request.getHeaders()).contains(entry("spring", Collections.singletonList("framework")))
374+
assertThat(request.getHeaders().headerSet()).contains(entry("spring", Collections.singletonList("framework")))
375375
.contains(entry("for", Collections.singletonList("java")));
376376
}
377377

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/reactive/server/AbstractReactiveWebServerFactoryTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -662,7 +662,7 @@ protected void assertResponseIsCompressed(ResponseEntity<Void> response) {
662662

663663
protected void assertResponseIsNotCompressed(ResponseEntity<Void> response) {
664664
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
665-
assertThat(response.getHeaders().keySet()).doesNotContain("X-Test-Compressed");
665+
assertThat(response.getHeaders().headerNames()).doesNotContain("X-Test-Compressed");
666666
}
667667

668668
protected void assertForwardHeaderIsUsed(AbstractReactiveWebServerFactory factory) {

spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-actuator-custom-security/src/test/java/smoketest/actuator/customsecurity/AbstractSampleActuatorCustomSecurityTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -46,7 +46,7 @@ void homeIsSecure() {
4646
@SuppressWarnings("rawtypes")
4747
ResponseEntity<Map> entity = restTemplate().getForEntity(getPath() + "/", Map.class);
4848
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
49-
assertThat(entity.getHeaders()).doesNotContainKey("Set-Cookie");
49+
assertThat(entity.getHeaders().headerNames()).doesNotContain("Set-Cookie");
5050
}
5151

5252
@Test

0 commit comments

Comments
 (0)