diff --git a/spring-test/src/main/java/org/springframework/mock/http/server/reactive/MockServerHttpRequest.java b/spring-test/src/main/java/org/springframework/mock/http/server/reactive/MockServerHttpRequest.java index e9ca3ee6d1..f3e10eca52 100644 --- a/spring-test/src/main/java/org/springframework/mock/http/server/reactive/MockServerHttpRequest.java +++ b/spring-test/src/main/java/org/springframework/mock/http/server/reactive/MockServerHttpRequest.java @@ -298,9 +298,17 @@ public final class MockServerHttpRequest extends AbstractServerHttpRequest { /** * Add the given header values. * @param headers the header values + * @deprecated Use {@link #headers(HttpHeaders)} */ + @Deprecated B headers(MultiValueMap headers); + /** + * Add the given header values. + * @param headers the header values + */ + B headers(HttpHeaders headers); + /** * Set the list of acceptable {@linkplain MediaType media types}, as * specified by the {@code Accept} header. @@ -484,11 +492,18 @@ public final class MockServerHttpRequest extends AbstractServerHttpRequest { } @Override + @Deprecated public BodyBuilder headers(MultiValueMap headers) { this.headers.putAll(headers); return this; } + @Override + public BodyBuilder headers(HttpHeaders headers) { + this.headers.putAll(headers); + return this; + } + @Override public BodyBuilder accept(MediaType... acceptableMediaTypes) { this.headers.setAccept(Arrays.asList(acceptableMediaTypes)); diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockPart.java b/spring-test/src/main/java/org/springframework/mock/web/MockPart.java index 82b679b4b2..96b99e21df 100644 --- a/spring-test/src/main/java/org/springframework/mock/web/MockPart.java +++ b/spring-test/src/main/java/org/springframework/mock/web/MockPart.java @@ -129,7 +129,7 @@ public class MockPart implements Part { @Override public Collection getHeaderNames() { - return this.headers.keySet(); + return this.headers.headerNames(); } /** diff --git a/spring-test/src/main/java/org/springframework/test/http/HttpHeadersAssert.java b/spring-test/src/main/java/org/springframework/test/http/HttpHeadersAssert.java index a03bda4bc2..d0c2653300 100644 --- a/spring-test/src/main/java/org/springframework/test/http/HttpHeadersAssert.java +++ b/spring-test/src/main/java/org/springframework/test/http/HttpHeadersAssert.java @@ -20,10 +20,16 @@ import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; +import java.util.Arrays; +import java.util.Collection; import java.util.List; -import org.assertj.core.api.AbstractMapAssert; +import org.assertj.core.api.AbstractCollectionAssert; +import org.assertj.core.api.AbstractObjectAssert; import org.assertj.core.api.Assertions; +import org.assertj.core.api.ObjectAssert; +import org.assertj.core.presentation.Representation; +import org.assertj.core.presentation.StandardRepresentation; import org.springframework.http.HttpHeaders; @@ -34,54 +40,87 @@ import org.springframework.http.HttpHeaders; * @author Stephane Nicoll * @since 6.2 */ -public class HttpHeadersAssert extends AbstractMapAssert> { +public class HttpHeadersAssert extends AbstractObjectAssert { private static final ZoneId GMT = ZoneId.of("GMT"); + private final AbstractCollectionAssert, String, ObjectAssert> namesAssert; + public HttpHeadersAssert(HttpHeaders actual) { super(actual, HttpHeadersAssert.class); as("HTTP headers"); + withRepresentation(new Representation() { + @Override + public String toStringOf(Object object) { + if (object instanceof HttpHeaders headers) { + return headers.toString(); + } + return StandardRepresentation.STANDARD_REPRESENTATION.toStringOf(object); + } + }); + this.namesAssert = Assertions.assertThat(actual.headerNames()) + .as("HTTP header names"); } /** * Verify that the actual HTTP headers contain a header with the given * {@code name}. * @param name the name of an expected HTTP header - * @see #containsKey */ public HttpHeadersAssert containsHeader(String name) { - return containsKey(name); + this.namesAssert + .as("check headers contain HTTP header '%s'", name) + .contains(name); + return this.myself; } /** * Verify that the actual HTTP headers contain the headers with the given * {@code names}. * @param names the names of expected HTTP headers - * @see #containsKeys */ public HttpHeadersAssert containsHeaders(String... names) { - return containsKeys(names); + this.namesAssert + .as("check headers contain HTTP headers '%s'", Arrays.toString(names)) + .contains(names); + return this.myself; + } + + /** + * Verify that the actual HTTP headers contain only the headers with the + * given {@code names}, in any order and in a case-insensitive manner. + * @param names the names of expected HTTP headers + */ + public HttpHeadersAssert containsOnlyHeaders(String... names) { + this.namesAssert + .as("check headers contains only HTTP headers '%s'", Arrays.toString(names)) + .containsOnly(names); + return this.myself; } /** * Verify that the actual HTTP headers do not contain a header with the * given {@code name}. * @param name the name of an HTTP header that should not be present - * @see #doesNotContainKey */ public HttpHeadersAssert doesNotContainHeader(String name) { - return doesNotContainKey(name); + this.namesAssert + .as("check headers does not contain HTTP header '%s'", name) + .doesNotContain(name); + return this.myself; } /** * Verify that the actual HTTP headers do not contain any of the headers * with the given {@code names}. * @param names the names of HTTP headers that should not be present - * @see #doesNotContainKeys */ public HttpHeadersAssert doesNotContainsHeaders(String... names) { - return doesNotContainKeys(names); + this.namesAssert + .as("check headers does not contain HTTP headers '%s'", Arrays.toString(names)) + .doesNotContain(names); + return this.myself; } /** @@ -91,7 +130,7 @@ public class HttpHeadersAssert extends AbstractMapAssert values) { + containsHeader(name); + Assertions.assertThat(this.actual.get(name)) + .as("check all values of HTTP header '%s'", name) + .containsExactlyElementsOf(values); + return this.myself; + } + + /** + * Verify that the given header has a full list of values exactly equal to + * the given list of values, in any order. + * @param name the considered header name (case-insensitive) + * @param values the exhaustive list of expected values + */ + public HttpHeadersAssert hasExactlyValuesInAnyOrder(String name, List values) { + containsHeader(name); + Assertions.assertThat(this.actual.get(name)) + .as("check all values of HTTP header '%s' in any order", name) + .containsExactlyInAnyOrderElementsOf(values); + return this.myself; + } + + /** + * Verify that headers are empty and no headers are present. + */ + public HttpHeadersAssert isEmpty() { + this.namesAssert + .as("check headers are empty") + .isEmpty(); + return this.myself; + } + + /** + * Verify that headers are not empty and at least one header is present. + */ + public HttpHeadersAssert isNotEmpty() { + this.namesAssert + .as("check headers are not empty") + .isNotEmpty(); + return this.myself; + } + + /** + * Verify that there is exactly {@code expected} headers present, when + * considering header names in a case-insensitive manner. + * @param expected the expected number of headers + */ + public HttpHeadersAssert hasSize(int expected) { + this.namesAssert + .as("check headers have size '%i'", expected) + .hasSize(expected); + return this.myself; + } + + /** + * Verify that the number of headers present is strictly greater than the + * given boundary, when considering header names in a case-insensitive + * manner. + * @param boundary the given value to compare actual header size to + */ + public HttpHeadersAssert hasSizeGreaterThan(int boundary) { + this.namesAssert + .as("check headers have size > '%i'", boundary) + .hasSizeGreaterThan(boundary); + return this.myself; + } + + /** + * Verify that the number of headers present is greater or equal to the + * given boundary, when considering header names in a case-insensitive + * manner. + * @param boundary the given value to compare actual header size to + */ + public HttpHeadersAssert hasSizeGreaterThanOrEqualTo(int boundary) { + this.namesAssert + .as("check headers have size >= '%i'", boundary) + .hasSizeGreaterThanOrEqualTo(boundary); + return this.myself; + } + + /** + * Verify that the number of headers present is strictly less than the + * given boundary, when considering header names in a case-insensitive + * manner. + * @param boundary the given value to compare actual header size to + */ + public HttpHeadersAssert hasSizeLessThan(int boundary) { + this.namesAssert + .as("check headers have size < '%i'", boundary) + .hasSizeLessThan(boundary); + return this.myself; + } + + /** + * Verify that the number of headers present is less than or equal to the + * given boundary, when considering header names in a case-insensitive + * manner. + * @param boundary the given value to compare actual header size to + */ + public HttpHeadersAssert hasSizeLessThanOrEqualTo(int boundary) { + this.namesAssert + .as("check headers have size <= '%i'", boundary) + .hasSizeLessThanOrEqualTo(boundary); + return this.myself; + } + + /** + * Verify that the number of headers present is between the given boundaries + * (inclusive), when considering header names in a case-insensitive manner. + * @param lowerBoundary the lower boundary compared to which actual size + * should be greater than or equal to + * @param higherBoundary the higher boundary compared to which actual size + * should be less than or equal to + */ + public HttpHeadersAssert hasSizeBetween(int lowerBoundary, int higherBoundary) { + this.namesAssert + .as("check headers have size between '%i' and '%i'", lowerBoundary, higherBoundary) + .hasSizeBetween(lowerBoundary, higherBoundary); + return this.myself; + } + + /** + * Verify that the number actual headers is the same as in the given + * {@code HttpHeaders}. + * @param other the {@code HttpHeaders} to compare size with + */ + public HttpHeadersAssert hasSameSizeAs(HttpHeaders other) { + this.namesAssert + .as("check headers have same size as '%s'", other) + .hasSize(other.size()); + return this.myself; + } } diff --git a/spring-test/src/main/java/org/springframework/test/web/client/match/MockRestRequestMatchers.java b/spring-test/src/main/java/org/springframework/test/web/client/match/MockRestRequestMatchers.java index 203b4a8538..e68e169aa7 100644 --- a/spring-test/src/main/java/org/springframework/test/web/client/match/MockRestRequestMatchers.java +++ b/spring-test/src/main/java/org/springframework/test/web/client/match/MockRestRequestMatchers.java @@ -24,6 +24,7 @@ import javax.xml.xpath.XPathExpressionException; import org.hamcrest.Matcher; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.client.ClientHttpRequest; import org.springframework.test.web.client.MockRestServiceServer; @@ -162,7 +163,7 @@ public abstract class MockRestRequestMatchers { public static RequestMatcher queryParam(String name, Matcher... matchers) { return request -> { MultiValueMap params = getQueryParams(request); - assertValueCount("query param", name, params, matchers.length); + assertValueCount(name, params, matchers.length); for (int i = 0 ; i < matchers.length; i++) { assertThat("Query param", params.get(name).get(i), matchers[i]); } @@ -190,7 +191,7 @@ public abstract class MockRestRequestMatchers { public static RequestMatcher queryParam(String name, String... expectedValues) { return request -> { MultiValueMap params = getQueryParams(request); - assertValueCount("query param", name, params, expectedValues.length); + assertValueCount(name, params, expectedValues.length); for (int i = 0 ; i < expectedValues.length; i++) { assertEquals("Query param [" + name + "]", expectedValues[i], params.get(name).get(i)); } @@ -246,7 +247,7 @@ public abstract class MockRestRequestMatchers { @SafeVarargs public static RequestMatcher header(String name, Matcher... matchers) { return request -> { - assertValueCount("header", name, request.getHeaders(), matchers.length); + assertValueCount(name, request.getHeaders(), matchers.length); List headerValues = request.getHeaders().get(name); Assert.state(headerValues != null, "No header values"); for (int i = 0; i < matchers.length; i++) { @@ -273,7 +274,7 @@ public abstract class MockRestRequestMatchers { */ public static RequestMatcher header(String name, String... expectedValues) { return request -> { - assertValueCount("header", name, request.getHeaders(), expectedValues.length); + assertValueCount(name, request.getHeaders(), expectedValues.length); List headerValues = request.getHeaders().get(name); Assert.state(headerValues != null, "No header values"); for (int i = 0; i < expectedValues.length; i++) { @@ -356,11 +357,22 @@ public abstract class MockRestRequestMatchers { } - private static void assertValueCount( - String valueType, String name, MultiValueMap map, int count) { + private static void assertValueCount(String name, MultiValueMap map, int count) { List values = map.get(name); - String message = "Expected " + valueType + " <" + name + ">"; + String message = "Expected query param <" + name + ">"; + if (values == null) { + fail(message + " to exist but was null"); + } + else if (count > values.size()) { + fail(message + " to have at least <" + count + "> values but found " + values); + } + } + + private static void assertValueCount(String name, HttpHeaders headers, int count) { + + List values = headers.get(name); + String message = "Expected header <" + name + ">"; if (values == null) { fail(message + " to exist but was null"); } diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java index 476da5c5a0..7636b9b36b 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java @@ -373,10 +373,10 @@ class DefaultWebTestClient implements WebTestClient { private ClientRequest.Builder initRequestBuilder() { return ClientRequest.create(this.httpMethod, initUri()) .headers(headersToUse -> { - if (!CollectionUtils.isEmpty(DefaultWebTestClient.this.defaultHeaders)) { + if (!(DefaultWebTestClient.this.defaultHeaders == null || DefaultWebTestClient.this.defaultHeaders.isEmpty())) { headersToUse.putAll(DefaultWebTestClient.this.defaultHeaders); } - if (!CollectionUtils.isEmpty(this.headers)) { + if (!this.headers.isEmpty()) { headersToUse.putAll(this.headers); } }) diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/ExchangeResult.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/ExchangeResult.java index c68c349eeb..68a78ddbf0 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/ExchangeResult.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/ExchangeResult.java @@ -249,7 +249,7 @@ public class ExchangeResult { } private String formatHeaders(HttpHeaders headers, String delimiter) { - return headers.entrySet().stream() + return headers.headerSet().stream() .map(entry -> entry.getKey() + ": " + entry.getValue()) .collect(Collectors.joining(delimiter)); } diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/HeaderAssertions.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/HeaderAssertions.java index f1c0558edf..4c5548a936 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/HeaderAssertions.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/HeaderAssertions.java @@ -213,7 +213,7 @@ public class HeaderAssertions { * @since 5.0.3 */ public WebTestClient.ResponseSpec exists(String name) { - if (!getHeaders().containsKey(name)) { + if (!getHeaders().containsHeader(name)) { String message = getMessage(name) + " does not exist"; this.exchangeResult.assertWithDiagnostics(() -> fail(message)); } @@ -224,7 +224,7 @@ public class HeaderAssertions { * Expect that the header with the given name is not present. */ public WebTestClient.ResponseSpec doesNotExist(String name) { - if (getHeaders().containsKey(name)) { + if (getHeaders().containsHeader(name)) { String message = getMessage(name) + " exists with value=[" + getHeaders().getFirst(name) + "]"; this.exchangeResult.assertWithDiagnostics(() -> fail(message)); } diff --git a/spring-test/src/test/java/org/springframework/test/http/HttpHeadersAssertTests.java b/spring-test/src/test/java/org/springframework/test/http/HttpHeadersAssertTests.java index 36dc3c9253..2bb0b191df 100644 --- a/spring-test/src/test/java/org/springframework/test/http/HttpHeadersAssertTests.java +++ b/spring-test/src/test/java/org/springframework/test/http/HttpHeadersAssertTests.java @@ -45,7 +45,7 @@ class HttpHeadersAssertTests { Map map = Map.of("first", "1"); assertThatExceptionOfType(AssertionError.class) .isThrownBy(() -> assertThat(map).containsHeader("wrong-name")) - .withMessageContainingAll("HTTP headers", "first", "wrong-name"); + .withMessageContainingAll("HTTP header", "first", "wrong-name"); } @Test @@ -71,8 +71,8 @@ class HttpHeadersAssertTests { void doesNotContainHeaderWithNamePresent() { Map map = Map.of("first", "1"); assertThatExceptionOfType(AssertionError.class) - .isThrownBy(() -> assertThat(map).doesNotContainKey("first")) - .withMessageContainingAll("HTTP headers", "first"); + .isThrownBy(() -> assertThat(map).doesNotContainHeader("first")) + .withMessageContainingAll("HTTP header", "first"); } @Test @@ -112,7 +112,7 @@ class HttpHeadersAssertTests { headers.addAll("header", List.of("first", "second", "third")); assertThatExceptionOfType(AssertionError.class) .isThrownBy(() -> assertThat(headers).hasValue("wrong-name", "second")) - .withMessageContainingAll("HTTP headers", "header", "wrong-name"); + .withMessageContainingAll("HTTP header", "header", "wrong-name"); } @Test @@ -121,7 +121,7 @@ class HttpHeadersAssertTests { map.add("test-header", "a"); assertThatExceptionOfType(AssertionError.class) .isThrownBy(() -> assertThat(map).hasValue("wrong-name", "a")) - .withMessageContainingAll("HTTP headers", "test-header", "wrong-name"); + .withMessageContainingAll("HTTP header", "test-header", "wrong-name"); } @Test @@ -146,7 +146,7 @@ class HttpHeadersAssertTests { map.addAll("header", List.of("123", "456", "789")); assertThatExceptionOfType(AssertionError.class) .isThrownBy(() -> assertThat(map).hasValue("wrong-name", 456)) - .withMessageContainingAll("HTTP headers", "header", "wrong-name"); + .withMessageContainingAll("HTTP header", "header", "wrong-name"); } @Test @@ -164,7 +164,7 @@ class HttpHeadersAssertTests { map.setInstant("header", instant); assertThatExceptionOfType(AssertionError.class) .isThrownBy(() -> assertThat(map).hasValue("wrong-name", instant.minusSeconds(30))) - .withMessageContainingAll("HTTP headers", "header", "wrong-name"); + .withMessageContainingAll("HTTP header", "header", "wrong-name"); } @Test @@ -174,7 +174,7 @@ class HttpHeadersAssertTests { map.setInstant("header", instant); assertThatExceptionOfType(AssertionError.class) .isThrownBy(() -> assertThat(map).hasValue("wrong-name", instant.minusSeconds(1))) - .withMessageContainingAll("HTTP headers", "header", "wrong-name"); + .withMessageContainingAll("HTTP header", "header", "wrong-name"); } diff --git a/spring-test/src/test/java/org/springframework/test/web/client/response/ResponseCreatorsTests.java b/spring-test/src/test/java/org/springframework/test/web/client/response/ResponseCreatorsTests.java index 5a0ffe6625..7d7c31c03c 100644 --- a/spring-test/src/test/java/org/springframework/test/web/client/response/ResponseCreatorsTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/client/response/ResponseCreatorsTests.java @@ -45,7 +45,7 @@ class ResponseCreatorsTests { MockClientHttpResponse response = (MockClientHttpResponse) MockRestResponseCreators.withSuccess().createResponse(null); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(response.getHeaders()).isEmpty(); + assertThat(response.getHeaders().isEmpty()).as("isEmpty").isTrue(); assertThat(StreamUtils.copyToByteArray(response.getBody())).isEmpty(); } @@ -95,7 +95,7 @@ class ResponseCreatorsTests { MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT); - assertThat(response.getHeaders()).isEmpty(); + assertThat(response.getHeaders().isEmpty()).as("isEmpty").isTrue(); assertThat(StreamUtils.copyToByteArray(response.getBody())).isEmpty(); } @@ -105,7 +105,7 @@ class ResponseCreatorsTests { MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); - assertThat(response.getHeaders()).isEmpty(); + assertThat(response.getHeaders().isEmpty()).as("isEmpty").isTrue(); assertThat(StreamUtils.copyToByteArray(response.getBody())).isEmpty(); } @@ -115,7 +115,7 @@ class ResponseCreatorsTests { MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); - assertThat(response.getHeaders()).isEmpty(); + assertThat(response.getHeaders().isEmpty()).as("isEmpty").isTrue(); assertThat(StreamUtils.copyToByteArray(response.getBody())).isEmpty(); } @@ -152,7 +152,7 @@ class ResponseCreatorsTests { MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.TOO_MANY_REQUESTS); - assertThat(response.getHeaders()).doesNotContainKey(HttpHeaders.RETRY_AFTER); + assertThat(response.getHeaders().headerNames()).doesNotContain(HttpHeaders.RETRY_AFTER); assertThat(StreamUtils.copyToByteArray(response.getBody())).isEmpty(); } @@ -172,7 +172,7 @@ class ResponseCreatorsTests { MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); - assertThat(response.getHeaders()).isEmpty(); + assertThat(response.getHeaders().isEmpty()).as("isEmpty").isTrue(); assertThat(StreamUtils.copyToByteArray(response.getBody())).isEmpty(); } @@ -209,7 +209,7 @@ class ResponseCreatorsTests { MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); - assertThat(response.getHeaders()).isEmpty(); + assertThat(response.getHeaders().isEmpty()).as("isEmpty").isTrue(); assertThat(StreamUtils.copyToByteArray(response.getBody())).isEmpty(); } diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/MockServerTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/MockServerTests.java index 9bf980ce50..8e93c92baf 100644 --- a/spring-test/src/test/java/org/springframework/test/web/reactive/server/MockServerTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/MockServerTests.java @@ -100,15 +100,15 @@ public class MockServerTests { WebTestClient clientFromMutatedBuilder = mutatedBuilder.build(); client1.mutate().filters(filters -> assertThat(filters).hasSize(1)); - client1.mutate().defaultHeaders(headers -> assertThat(headers).hasSize(1)); + client1.mutate().defaultHeaders(headers -> assertThat(headers.size()).isOne()); client1.mutate().defaultCookies(cookies -> assertThat(cookies).hasSize(1)); client2.mutate().filters(filters -> assertThat(filters).hasSize(2)); - client2.mutate().defaultHeaders(headers -> assertThat(headers).hasSize(2)); + client2.mutate().defaultHeaders(headers -> assertThat(headers.size()).isEqualTo(2)); client2.mutate().defaultCookies(cookies -> assertThat(cookies).hasSize(2)); clientFromMutatedBuilder.mutate().filters(filters -> assertThat(filters).hasSize(2)); - clientFromMutatedBuilder.mutate().defaultHeaders(headers -> assertThat(headers).hasSize(2)); + clientFromMutatedBuilder.mutate().defaultHeaders(headers -> assertThat(headers.size()).isEqualTo(2)); clientFromMutatedBuilder.mutate().defaultCookies(cookies -> assertThat(cookies).hasSize(2)); } diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/MockMvcTesterIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/MockMvcTesterIntegrationTests.java index 2c0a8d45ad..bcb05345aa 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/MockMvcTesterIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/MockMvcTesterIntegrationTests.java @@ -322,8 +322,8 @@ public class MockMvcTesterIntegrationTests { } private Consumer textContent(String charset) { - return headers -> assertThat(headers).containsEntry( - "Content-Type", List.of("text/plain;charset=%s".formatted(charset))); + return headers -> assertThat(headers.hasHeaderValues("Content-Type", List.of("text/plain;charset=%s".formatted(charset)))) + .as("hasHeaderValues").isTrue(); } } diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilderTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilderTests.java index a38c09d52b..6854f4b761 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilderTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilderTests.java @@ -126,7 +126,7 @@ public class MockWebResponseBuilderTests { WebResponse webResponse = this.responseBuilder.build(); List responseHeaders = webResponse.getResponseHeaders(); - assertThat(responseHeaders).hasSize(1); + assertThat(responseHeaders.size()).as("size").isOne(); NameValuePair header = responseHeaders.get(0); assertThat(header.getName()).isEqualTo("Set-Cookie"); assertThat(header.getValue()).isEqualTo("cookieA=valueA"); diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client/standalone/FrameworkExtensionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client/standalone/FrameworkExtensionTests.java index bfbc8d6a10..ee3fcd6484 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client/standalone/FrameworkExtensionTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client/standalone/FrameworkExtensionTests.java @@ -130,7 +130,7 @@ public class FrameworkExtensionTests { @Override public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { - for (String headerName : this.headers.keySet()) { + for (String headerName : this.headers.headerNames()) { request.addHeader(headerName, this.headers.get(headerName)); } return request; diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/FrameworkExtensionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/FrameworkExtensionTests.java index 0af7bbccce..90609c22d6 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/FrameworkExtensionTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/FrameworkExtensionTests.java @@ -94,7 +94,7 @@ public class FrameworkExtensionTests { @Override public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { - for (String headerName : this.headers.keySet()) { + for (String headerName : this.headers.headerNames()) { request.addHeader(headerName, this.headers.get(headerName)); } return request; diff --git a/spring-web/src/main/java/org/springframework/http/HttpEntity.java b/spring-web/src/main/java/org/springframework/http/HttpEntity.java index ffb2bf4d25..05aeab7b87 100644 --- a/spring-web/src/main/java/org/springframework/http/HttpEntity.java +++ b/spring-web/src/main/java/org/springframework/http/HttpEntity.java @@ -73,7 +73,7 @@ public class HttpEntity { * Create a new, empty {@code HttpEntity}. */ protected HttpEntity() { - this(null, null); + this(null, (HttpHeaders) null); } /** @@ -81,13 +81,35 @@ public class HttpEntity { * @param body the entity body */ public HttpEntity(T body) { - this(body, null); + this(body, (HttpHeaders) null); } /** * Create a new {@code HttpEntity} with the given headers and no body. * @param headers the entity headers + * @since 7.0 */ + public HttpEntity(HttpHeaders headers) { + this(null, headers); + } + + /** + * Create a new {@code HttpEntity} with the given body and headers. + * @param body the entity body + * @param headers the entity headers + * @since 7.0 + */ + public HttpEntity(@Nullable T body, @Nullable HttpHeaders headers) { + this.body = body; + this.headers = HttpHeaders.readOnlyHttpHeaders(headers != null ? headers : new HttpHeaders()); + } + + /** + * Create a new {@code HttpEntity} with the given headers and no body. + * @param headers the entity headers + * @deprecated Use {@link #HttpEntity(HttpHeaders)} + */ + @Deprecated public HttpEntity(MultiValueMap headers) { this(null, headers); } @@ -96,10 +118,12 @@ public class HttpEntity { * Create a new {@code HttpEntity} with the given body and headers. * @param body the entity body * @param headers the entity headers + * @deprecated Use {@link #HttpEntity(Object, HttpHeaders)} */ + @Deprecated public HttpEntity(@Nullable T body, @Nullable MultiValueMap headers) { this.body = body; - this.headers = HttpHeaders.readOnlyHttpHeaders(headers != null ? headers : new HttpHeaders()); + this.headers = HttpHeaders.readOnlyHttpHeaders(headers != null ? new HttpHeaders(headers) : new HttpHeaders()); } diff --git a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java index 958d1012fa..4c68a7e1da 100644 --- a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java +++ b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java @@ -39,6 +39,7 @@ import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import java.util.StringJoiner; @@ -52,6 +53,7 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.LinkedCaseInsensitiveMap; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** @@ -71,23 +73,18 @@ import org.springframework.util.StringUtils; * {@link #HttpHeaders(MultiValueMap)} constructor like those instantiated * internally by the framework to adapt to existing HTTP headers data structures * do guarantee per-header get/set/add operations to be case-insensitive as - * mandated by the HTTP specification. However, it is not necessarily the case - * for operations that deal with the collection as a whole (like {@code size()}, - * {@code values()}, {@code keySet()} and {@code entrySet()}). Prefer using - * {@link #headerSet()} for these cases. - * - *

Some backing implementations can store header names in a case-sensitive - * manner, which will lead to duplicates during the entrySet() iteration where - * multiple occurrences of a header name can surface depending on letter casing - * but each such entry has the full {@code List} of values. — This can be - * problematic for example when copying headers into a new instance by iterating - * over the old instance's {@code entrySet()} and using - * {@link #addAll(String, List)} rather than {@link #put(String, List)}. + * mandated by the HTTP specification. However, it is not necessarily how + * entries are actually stored, and this can lead to the reported {@code size()} + * being inflated. Prefer using {@link #headerSet()} or {@link #headerNames()} + * to ensure a case-insensitive view. * *

This class is meant to reference "well-known" headers supported by Spring * Framework. If your application or library relies on other headers defined in RFCs, * please use methods that accept the header name as a parameter. * + *

Since 7.0, this class no longer implements the {@code MultiValueMap} + * contract. + * * @author Arjen Poutsma * @author Sebastien Deleuze * @author Brian Clozel @@ -97,7 +94,7 @@ import org.springframework.util.StringUtils; * @author Simon Baslé * @since 3.0 */ -public class HttpHeaders implements MultiValueMap, Serializable { +public class HttpHeaders implements Serializable { private static final long serialVersionUID = -8578554704772377436L; @@ -472,6 +469,28 @@ public class HttpHeaders implements MultiValueMap, Serializable } } + /** + * Construct a new {@code HttpHeaders} instance by removing any read-only + * wrapper that may have been previously applied around the given + * {@code HttpHeaders} via {@link #readOnlyHttpHeaders(HttpHeaders)}. + *

Once the writable instance is mutated, the read-only instance is + * likely to be out of sync and should be discarded. + * @param httpHeaders the headers to expose + * @since 7.0 + */ + public HttpHeaders(HttpHeaders httpHeaders) { + Assert.notNull(httpHeaders, "HttpHeaders must not be null"); + if (httpHeaders == EMPTY) { + this.headers = CollectionUtils.toMultiValueMap(new LinkedCaseInsensitiveMap<>(8, Locale.ENGLISH)); + } + else { + while (httpHeaders.headers instanceof HttpHeaders wrapped) { + httpHeaders = wrapped; + } + this.headers = httpHeaders.headers; + } + } + /** * Get the list of header values for the given header name, if any. @@ -479,9 +498,20 @@ public class HttpHeaders implements MultiValueMap, Serializable * @return the list of header values, or an empty list * @since 5.2 */ - public List getOrEmpty(Object headerName) { + public List getOrEmpty(String headerName) { + return getOrDefault(headerName, Collections.emptyList()); + } + + /** + * Get the list of header values for the given header name, or the given + * default list of values if the header is not present. + * @param headerName the header name + * @param defaultValue the fallback list if header is not present + * @return the list of header values, or a default list of values + */ + public List getOrDefault(String headerName, List defaultValue) { List values = get(headerName); - return (values != null ? values : Collections.emptyList()); + return (values != null ? values : defaultValue); } /** @@ -1723,14 +1753,13 @@ public class HttpHeaders implements MultiValueMap, Serializable } - // MultiValueMap implementation + // MultiValueMap-like methods /** * Return the first header value for the given header name, if any. * @param headerName the header name * @return the first header value, or {@code null} if none */ - @Override public @Nullable String getFirst(String headerName) { return this.headers.getFirst(headerName); } @@ -1743,19 +1772,33 @@ public class HttpHeaders implements MultiValueMap, Serializable * @see #put(String, List) * @see #set(String, String) */ - @Override public void add(String headerName, @Nullable String headerValue) { this.headers.add(headerName, headerValue); } - @Override - public void addAll(String key, List values) { - this.headers.addAll(key, values); + /** + * Add all the given values under the given name. + *

As values are represented as a {@code List}, duplicate values can be + * introduced. See {@link #put(String, List)} to replace the list of values + * instead. + * @param headerName the header name + * @param headerValues the values to add + * @see #put(String, List) + */ + public void addAll(String headerName, List headerValues) { + this.headers.addAll(headerName, headerValues); } - @Override - public void addAll(MultiValueMap values) { - this.headers.addAll(values); + /** + * Add all the values of the given {@code HttpHeaders} to the current header. + *

As values are represented as a {@code List}, duplicate values can be + * introduced. See {@link #putAll(HttpHeaders)} to replace the list of + * values of each individual header name instead. + * @param headers the headers to add + * @see #putAll(HttpHeaders) + */ + public void addAll(HttpHeaders headers) { + this.headers.addAll(headers.headers); } /** @@ -1766,132 +1809,202 @@ public class HttpHeaders implements MultiValueMap, Serializable * @see #put(String, List) * @see #add(String, String) */ - @Override public void set(String headerName, @Nullable String headerValue) { this.headers.set(headerName, headerValue); } - @Override + /** + * Set all single header value from the given Map under each of their + * corresponding name. + * @param values the name-single-value pairs + * @see #putAll(Map) + */ public void setAll(Map values) { this.headers.setAll(values); } - @Override + /** + * Return this HttpHeaders as a {@code Map} with the first values for each + * header name. + *

The difference between this method and {@link #asSingleValueMap()} is + * that this method returns a copy of the headers, whereas the latter + * returns a view. This copy also ensures that collection-iterating methods + * like {@code entrySet()} are case-insensitive. + * @return a single value representation of these headers + */ public Map toSingleValueMap() { return this.headers.toSingleValueMap(); } - @Override + /** + * Return this HttpHeaders as a {@code Map} with the first values for each + * header name. + *

The difference between this method and {@link #toSingleValueMap()} is + * that this method returns a view of the headers, whereas the latter + * returns a copy. This method is also susceptible to include multiple + * casing variants of a given header name, see {@link #asMultiValueMap()} + * javadoc. + * @return a single value representation of these headers + * @deprecated Use {@link #toSingleValueMap()} which performs a copy but + * ensures that collection-iterating methods like {@code entrySet()} are + * case-insensitive + */ + @Deprecated public Map asSingleValueMap() { return this.headers.asSingleValueMap(); } - // Map implementation + /** + * Return this HttpHeaders as a {@code MultiValueMap} with the full list + * of values for each header name. + *

Note that some backing server headers implementations can store header + * names in a case-sensitive manner, which will lead to duplicates during + * iteration in methods like {@code entrySet()}, where multiple occurrences + * of a header name can surface depending on letter casing but each such + * entry has the full {@code List} of values. + * @return a MultiValueMap representation of these headers + * @deprecated This method is provided for backward compatibility with APIs + * that would only accept maps. Generally avoid using HttpHeaders as a Map + * or MultiValueMap. + */ + @Deprecated + public MultiValueMap asMultiValueMap() { + return this.headers; + } - @Override + // Map-like implementation + + /** + * Returns {@code true} if this HttpHeaders contains no header entry. + */ public boolean isEmpty() { return this.headers.isEmpty(); } - @Override - public boolean containsKey(Object key) { - return this.headers.containsKey(key); + /** + * Returns {@code true} if this HttpHeaders contains an entry for the + * given header name. + * @param headerName the header name + * @since 7.0 + */ + public boolean containsHeader(String headerName) { + return this.headers.containsKey(headerName); } - @Override - public boolean containsValue(Object value) { - return this.headers.containsValue(value); + /** + * Returns {@code true} if this HttpHeaders contains exactly the given list + * of values for the given header name. + * @param headerName the header name + * @param values the expected list of values + * @since 7.0 + */ + public boolean hasHeaderValues(String headerName, List values) { + return ObjectUtils.nullSafeEquals(this.headers.get(headerName), values); } - @Override - public @Nullable List get(Object key) { - return this.headers.get(key); + /** + * Returns {@code true} if this HttpHeaders contains the given header and + * its list of values contains the given value. + * @param headerName the header name + * @param value the value expected to be in the list of values + * @since 7.0 + */ + public boolean containsHeaderValue(String headerName, String value) { + final List values = this.headers.get(headerName); + if (values == null) { + return false; + } + return values.contains(value); } - @Override - public List put(String key, List value) { - return this.headers.put(key, value); + /** + * Get the list of values associated with the given header name. + * @param headerName the header name + */ + @Nullable + public List get(String headerName) { + return this.headers.get(headerName); } - @Override - public List remove(Object key) { + /** + * Set the list of values associated with the given header name. Returns the + * previous list of values, or {@code null} if the header was not present. + * @param headerName the header name + * @param headerValues the new values + * @return the old values for the given header name + */ + public @Nullable List put(String headerName, List headerValues) { + return this.headers.put(headerName, headerValues); + } + + /** + * Set header values for the given header name if that header name isn't + * already present in this HttpHeaders and return {@code null}. If the + * header is already present, returns the associated value list instead. + * @param headerName the header name + * @param headerValues the header values to set if header is not present + * @return the previous value or {@code null} + */ + public @Nullable List putIfAbsent(String headerName, List headerValues) { + return this.headers.putIfAbsent(headerName, headerValues); + } + + /** + * Put all the entries from the given HttpHeaders into this HttpHeaders. + * @param headers the given headers + * @see #put(String, List) + */ + public void putAll(HttpHeaders headers) { + this.headers.putAll(headers.headers); + } + + /** + * Put all the entries from the given {@code MultiValueMap} into this + * HttpHeaders. + * @param headers the given headers + * @see #put(String, List) + */ + public void putAll(Map> headers) { + this.headers.putAll(headers); + } + + /** + * Remove a header from this HttpHeaders instance, and return the associated + * value list or {@code null} if that header wasn't present. + * @param key the name of the header to remove + * @return the value list associated with the removed header name + */ + public @Nullable List remove(String key) { return this.headers.remove(key); } - @Override - public void putAll(Map> map) { - this.headers.putAll(map); - } - - @Override + /** + * Remove all headers from this HttpHeaders instance. + */ public void clear() { this.headers.clear(); } - @Override - public List putIfAbsent(String key, List value) { - return this.headers.putIfAbsent(key, value); - } - - // Map/MultiValueMap methods that can have duplicate header names: size/keySet/values/entrySet/forEach - /** * Return the number of headers in the collection. This can be inflated, * see {@link HttpHeaders class level javadoc}. */ - @Override public int size() { return this.headers.size(); } - /** - * Return a {@link Set} view of header names. This can include multiple - * casing variants of a given header name, see - * {@link HttpHeaders class level javadoc}. - */ - @Override - public Set keySet() { - return this.headers.keySet(); - } - - /** - * Return a {@link Collection} view of all the header values, reconstructed - * from iterating over the {@link #keySet()}. This can include duplicates if - * multiple casing variants of a given header name are tracked, see - * {@link HttpHeaders class level javadoc}. - */ - @Override - public Collection> values() { - return this.headers.values(); - } - - /** - * Return a {@link Set} views of header entries, reconstructed from - * iterating over the {@link #keySet()}. This can include duplicate entries - * if multiple casing variants of a given header name are tracked, see - * {@link HttpHeaders class level javadoc}. - * @see #headerSet() - */ - @Override - public Set>> entrySet() { - return this.headers.entrySet(); - } - /** * Perform an action over each header, as when iterated via - * {@link #entrySet()}. This can include duplicate entries - * if multiple casing variants of a given header name are tracked, see - * {@link HttpHeaders class level javadoc}. + * {@link #headerSet()}. * @param action the action to be performed for each entry */ - @Override public void forEach(BiConsumer> action) { - this.headers.forEach(action); + this.headerSet().forEach(e -> action.accept(e.getKey(), e.getValue())); } /** * Return a view of the headers as an entry {@code Set} of key-list pairs. - * Both {@link Iterator#remove()} and {@link java.util.Map.Entry#setValue} + * Both {@link Iterator#remove()} and {@link Entry#setValue} * are supported and mutate the headers. *

This collection is guaranteed to contain one entry per header name * even if the backing structure stores multiple casing variants of names, @@ -1905,6 +2018,20 @@ public class HttpHeaders implements MultiValueMap, Serializable return new CaseInsensitiveEntrySet(this.headers); } + /** + * Return the set of header names. Both {@link Set#remove(Object)} and + * {@link Set#clear()} operations are supported and mutate the headers. + *

This collection is guaranteed to contain only one casing variant + * of each header name even if the backing structure stores multiple casing + * variants of names. The first encountered variant is the one that is + * retained. + * @return a {@code Set} of all the headers names + * @since 7.0 + */ + public Set headerNames() { + return new CaseInsensitiveHeaderNameSet(this.headers); + } + @Override public boolean equals(@Nullable Object other) { @@ -1959,7 +2086,7 @@ public class HttpHeaders implements MultiValueMap, Serializable * @since 5.1.4 */ public static String formatHeaders(MultiValueMap headers) { - Set headerNames = toCaseInsensitiveSet(headers.keySet()); + Set headerNames = new CaseInsensitiveHeaderNameSet(headers); String suffix = "]"; if (headerNames.size() != headers.size()) { suffix = "] with native header names " + headers.keySet(); @@ -2027,34 +2154,28 @@ public class HttpHeaders implements MultiValueMap, Serializable } - private static Set toCaseInsensitiveSet(Set originalSet) { - final Set deduplicatedSet = Collections.newSetFromMap( - new LinkedCaseInsensitiveMap<>(originalSet.size(), Locale.ROOT)); - // add/addAll (put/putAll in LinkedCaseInsensitiveMap) retain the casing of the last occurrence. - // Here we prefer the first. - for (String header : originalSet) { - //noinspection RedundantCollectionOperation - if (!deduplicatedSet.contains(header)) { - deduplicatedSet.add(header); - } - } - return deduplicatedSet; - } + private static final class CaseInsensitiveHeaderNameSet extends AbstractSet { - - private static final class CaseInsensitiveEntrySet extends AbstractSet>> { + private static final Object VALUE = new Object(); private final MultiValueMap headers; - private final Set deduplicatedNames; + private final Map deduplicatedNames; - public CaseInsensitiveEntrySet(MultiValueMap headers) { + public CaseInsensitiveHeaderNameSet(MultiValueMap headers) { this.headers = headers; - this.deduplicatedNames = toCaseInsensitiveSet(headers.keySet()); + this.deduplicatedNames = new LinkedCaseInsensitiveMap<>(headers.size(), Locale.ROOT); + // add/addAll (put/putAll in LinkedCaseInsensitiveMap) retain the casing of the last occurrence. + // Here we prefer the first. + for (String header : headers.keySet()) { + if (!this.deduplicatedNames.containsKey(header)) { + this.deduplicatedNames.put(header, VALUE); + } + } } @Override - public Iterator>> iterator() { - return new CaseInsensitiveIterator(this.deduplicatedNames.iterator()); + public Iterator iterator() { + return new HeaderNamesIterator(this.headers, this.deduplicatedNames); } @Override @@ -2062,15 +2183,87 @@ public class HttpHeaders implements MultiValueMap, Serializable return this.deduplicatedNames.size(); } - private final class CaseInsensitiveIterator implements Iterator>> { + @Override + public boolean contains(Object o) { + return this.headers.containsKey(o); + } + + @Override + public boolean remove(Object o) { + return this.headers.remove(o) != null && this.deduplicatedNames.remove(o) != null; + } + + @Override + public void clear() { + this.headers.clear(); + this.deduplicatedNames.clear(); + } + } + + private static class HeaderNamesIterator implements Iterator { + + + private @Nullable String currentName; + + private final MultiValueMap headers; + private final Iterator namesIterator; + + public HeaderNamesIterator (MultiValueMap headers, Map caseInsensitiveNames) { + this.headers = headers; + this.namesIterator = caseInsensitiveNames.keySet().iterator(); + this.currentName = null; + } + + @Override + public boolean hasNext() { + return this.namesIterator.hasNext(); + } + + @Override + public String next() { + this.currentName = this.namesIterator.next(); + return this.currentName; + } + + @Override + public void remove() { + if (this.currentName == null) { + throw new IllegalStateException("No current Header in iterator"); + } + if (!this.headers.containsKey(this.currentName)) { + throw new IllegalStateException("Header not present: " + this.currentName); + } + this.headers.remove(this.currentName); + } + } + + + private static final class CaseInsensitiveEntrySet extends AbstractSet>> { + + private final MultiValueMap headers; + private final CaseInsensitiveHeaderNameSet nameSet; + + public CaseInsensitiveEntrySet(MultiValueMap headers) { + this.headers = headers; + this.nameSet = new CaseInsensitiveHeaderNameSet(headers); + } + + @Override + public Iterator>> iterator() { + return new CaseInsensitiveIterator(this.nameSet.iterator()); + } + + @Override + public int size() { + return this.nameSet.size(); + } + + private final class CaseInsensitiveIterator implements Iterator>> { private final Iterator namesIterator; - private @Nullable String currentName; - - private CaseInsensitiveIterator(Iterator namesIterator) { + public CaseInsensitiveIterator(Iterator namesIterator) { this.namesIterator = namesIterator; - this.currentName = null; } @Override @@ -2079,24 +2272,17 @@ public class HttpHeaders implements MultiValueMap, Serializable } @Override - public Map.Entry> next() { - this.currentName = this.namesIterator.next(); - return new CaseInsensitiveEntry(this.currentName); + public Entry> next() { + return new CaseInsensitiveEntrySet.CaseInsensitiveEntry(this.namesIterator.next()); } @Override public void remove() { - if (this.currentName == null) { - throw new IllegalStateException("No current Header in iterator"); - } - if (!CaseInsensitiveEntrySet.this.headers.containsKey(this.currentName)) { - throw new IllegalStateException("Header not present: " + this.currentName); - } - CaseInsensitiveEntrySet.this.headers.remove(this.currentName); + this.namesIterator.remove(); } } - private final class CaseInsensitiveEntry implements Map.Entry> { + private final class CaseInsensitiveEntry implements Entry> { private final String key; @@ -2121,6 +2307,22 @@ public class HttpHeaders implements MultiValueMap, Serializable CaseInsensitiveEntrySet.this.headers.put(this.key, value); return previousValues; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Map.Entry that)) { + return false; + } + return ObjectUtils.nullSafeEquals(getKey(), that.getKey()) && ObjectUtils.nullSafeEquals(getValue(), that.getValue()); + } + + @Override + public int hashCode() { + return ObjectUtils.nullSafeHash(getKey(), getValue()); + } } } diff --git a/spring-web/src/main/java/org/springframework/http/ReadOnlyHttpHeaders.java b/spring-web/src/main/java/org/springframework/http/ReadOnlyHttpHeaders.java index e40118ad54..681e043e46 100644 --- a/spring-web/src/main/java/org/springframework/http/ReadOnlyHttpHeaders.java +++ b/spring-web/src/main/java/org/springframework/http/ReadOnlyHttpHeaders.java @@ -17,17 +17,18 @@ package org.springframework.http; import java.util.AbstractMap.SimpleImmutableEntry; -import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.function.BiConsumer; import java.util.stream.Collectors; import org.jspecify.annotations.Nullable; +import org.springframework.util.CollectionUtils; import org.springframework.util.MultiValueMap; /** @@ -84,8 +85,8 @@ class ReadOnlyHttpHeaders extends HttpHeaders { } @Override - public @Nullable List get(Object key) { - List values = this.headers.get(key); + public @Nullable List get(String headerName) { + List values = this.headers.get(headerName); return (values != null ? Collections.unmodifiableList(values) : null); } @@ -95,12 +96,12 @@ class ReadOnlyHttpHeaders extends HttpHeaders { } @Override - public void addAll(String key, List values) { + public void addAll(String key, List headerValues) { throw new UnsupportedOperationException(); } @Override - public void addAll(MultiValueMap values) { + public void addAll(HttpHeaders values) { throw new UnsupportedOperationException(); } @@ -120,27 +121,45 @@ class ReadOnlyHttpHeaders extends HttpHeaders { } @Override + @Deprecated public Map asSingleValueMap() { return Collections.unmodifiableMap(this.headers.asSingleValueMap()); } @Override - public Set keySet() { - return Collections.unmodifiableSet(this.headers.keySet()); + @Deprecated + public MultiValueMap asMultiValueMap() { + return CollectionUtils.unmodifiableMultiValueMap(this.headers); } + @Override + public Set headerNames() { + return Collections.unmodifiableSet(super.headerNames()); + } + + @Override public List put(String key, List value) { throw new UnsupportedOperationException(); } @Override - public List remove(Object key) { + public @Nullable List putIfAbsent(String headerName, List headerValues) { throw new UnsupportedOperationException(); } @Override - public void putAll(Map> map) { + public void putAll(@Nullable HttpHeaders values) { + throw new UnsupportedOperationException(); + } + + @Override + public void putAll(Map> headers) { + throw new UnsupportedOperationException(); + } + + @Override + public List remove(String key) { throw new UnsupportedOperationException(); } @@ -150,13 +169,8 @@ class ReadOnlyHttpHeaders extends HttpHeaders { } @Override - public Collection> values() { - return Collections.unmodifiableCollection(this.headers.values()); - } - - @Override - public Set>> entrySet() { - return this.headers.entrySet().stream().map(SimpleImmutableEntry::new) + public Set>> headerSet() { + return super.headerSet().stream().map(SimpleImmutableEntry::new) .collect(Collectors.collectingAndThen( Collectors.toCollection(LinkedHashSet::new), // Retain original ordering of entries Collections::unmodifiableSet)); diff --git a/spring-web/src/main/java/org/springframework/http/RequestEntity.java b/spring-web/src/main/java/org/springframework/http/RequestEntity.java index a31e78cc03..71dd7079da 100644 --- a/spring-web/src/main/java/org/springframework/http/RequestEntity.java +++ b/spring-web/src/main/java/org/springframework/http/RequestEntity.java @@ -79,7 +79,7 @@ public class RequestEntity extends HttpEntity { * @param url the URL */ public RequestEntity(HttpMethod method, URI url) { - this(null, null, method, url, null); + this(null, (HttpHeaders) null, method, url, null); } /** @@ -89,7 +89,7 @@ public class RequestEntity extends HttpEntity { * @param url the URL */ public RequestEntity(@Nullable T body, HttpMethod method, URI url) { - this(body, null, method, url, null); + this(body, (HttpHeaders) null, method, url, null); } /** @@ -101,7 +101,7 @@ public class RequestEntity extends HttpEntity { * @since 4.3 */ public RequestEntity(@Nullable T body, HttpMethod method, URI url, Type type) { - this(body, null, method, url, type); + this(body, (HttpHeaders) null, method, url, type); } /** @@ -109,7 +109,52 @@ public class RequestEntity extends HttpEntity { * @param headers the headers * @param method the method * @param url the URL + * @since 7.0 */ + public RequestEntity(HttpHeaders headers, HttpMethod method, URI url) { + this(null, headers, method, url, null); + } + + /** + * Constructor with method, URL, headers and body. + * @param body the body + * @param headers the headers + * @param method the method + * @param url the URL + * @since 7.0 + */ + public RequestEntity(@Nullable T body, @Nullable HttpHeaders headers, + @Nullable HttpMethod method, URI url) { + + this(body, headers, method, url, null); + } + + /** + * Constructor with method, URL, headers, body and type. + * @param body the body + * @param headers the headers + * @param method the method + * @param url the URL + * @param type the type used for generic type resolution + * @since 7.0 + */ + public RequestEntity(@Nullable T body, @Nullable HttpHeaders headers, + @Nullable HttpMethod method, @Nullable URI url, @Nullable Type type) { + + super(body, headers); + this.method = method; + this.url = url; + this.type = type; + } + + /** + * Constructor with method, URL and headers but without body. + * @param headers the headers + * @param method the method + * @param url the URL + * @deprecated Use {@link #RequestEntity(HttpHeaders, HttpMethod, URI)} + */ + @Deprecated public RequestEntity(MultiValueMap headers, HttpMethod method, URI url) { this(null, headers, method, url, null); } @@ -120,7 +165,9 @@ public class RequestEntity extends HttpEntity { * @param headers the headers * @param method the method * @param url the URL + * @deprecated Use {@link #RequestEntity(Object, HttpHeaders, HttpMethod, URI)} */ + @Deprecated public RequestEntity(@Nullable T body, @Nullable MultiValueMap headers, @Nullable HttpMethod method, URI url) { @@ -135,7 +182,9 @@ public class RequestEntity extends HttpEntity { * @param url the URL * @param type the type used for generic type resolution * @since 4.3 + * @deprecated Use {@link #RequestEntity(Object, HttpHeaders, HttpMethod, URI, Type)} */ + @Deprecated public RequestEntity(@Nullable T body, @Nullable MultiValueMap headers, @Nullable HttpMethod method, @Nullable URI url, @Nullable Type type) { @@ -694,7 +743,7 @@ public class RequestEntity extends HttpEntity { private final @Nullable Map uriVarsMap; UriTemplateRequestEntity( - @Nullable T body, @Nullable MultiValueMap headers, + @Nullable T body, @Nullable HttpHeaders headers, @Nullable HttpMethod method, @Nullable Type type, String uriTemplate, Object @Nullable [] uriVarsArray, @Nullable Map uriVarsMap) { diff --git a/spring-web/src/main/java/org/springframework/http/ResponseEntity.java b/spring-web/src/main/java/org/springframework/http/ResponseEntity.java index f5ea7cfc0d..e4e54c5a33 100644 --- a/spring-web/src/main/java/org/springframework/http/ResponseEntity.java +++ b/spring-web/src/main/java/org/springframework/http/ResponseEntity.java @@ -88,7 +88,7 @@ public class ResponseEntity extends HttpEntity { * @param status the status code */ public ResponseEntity(HttpStatusCode status) { - this(null, null, status); + this(null, (HttpHeaders) null, status); } /** @@ -97,14 +97,51 @@ public class ResponseEntity extends HttpEntity { * @param status the status code */ public ResponseEntity(@Nullable T body, HttpStatusCode status) { - this(body, null, status); + this(body, (HttpHeaders) null, status); } /** * Create a {@code ResponseEntity} with headers and a status code. * @param headers the entity headers * @param status the status code + * @since 7.0 */ + public ResponseEntity(HttpHeaders headers, HttpStatusCode status) { + this(null, headers, status); + } + + /** + * Create a {@code ResponseEntity} with a body, headers, and a raw status code. + * @param body the entity body + * @param headers the entity headers + * @param rawStatus the status code value + * @since 7.0 + */ + public ResponseEntity(@Nullable T body, @Nullable HttpHeaders headers, int rawStatus) { + this(body, headers, HttpStatusCode.valueOf(rawStatus)); + } + + /** + * Create a {@code ResponseEntity} with a body, headers, and a status code. + * @param body the entity body + * @param headers the entity headers + * @param statusCode the status code + * @since 7.0 + */ + public ResponseEntity(@Nullable T body, @Nullable HttpHeaders headers, HttpStatusCode statusCode) { + super(body, headers); + Assert.notNull(statusCode, "HttpStatusCode must not be null"); + + this.status = statusCode; + } + + /** + * Create a {@code ResponseEntity} with headers and a status code. + * @param headers the entity headers + * @param status the status code + * @deprecated Use {@link #ResponseEntity(HttpHeaders, HttpStatusCode)} + */ + @Deprecated public ResponseEntity(MultiValueMap headers, HttpStatusCode status) { this(null, headers, status); } @@ -115,7 +152,9 @@ public class ResponseEntity extends HttpEntity { * @param headers the entity headers * @param rawStatus the status code value * @since 5.3.2 + * @deprecated Use {@link #ResponseEntity(Object, HttpHeaders, int)} */ + @Deprecated public ResponseEntity(@Nullable T body, @Nullable MultiValueMap headers, int rawStatus) { this(body, headers, HttpStatusCode.valueOf(rawStatus)); } @@ -125,7 +164,9 @@ public class ResponseEntity extends HttpEntity { * @param body the entity body * @param headers the entity headers * @param statusCode the status code + * @deprecated Use {@link #ResponseEntity(Object, HttpHeaders, HttpStatusCode)} */ + @Deprecated public ResponseEntity(@Nullable T body, @Nullable MultiValueMap headers, HttpStatusCode statusCode) { super(body, headers); Assert.notNull(statusCode, "HttpStatusCode must not be null"); diff --git a/spring-web/src/main/java/org/springframework/http/client/MultipartBodyBuilder.java b/spring-web/src/main/java/org/springframework/http/client/MultipartBodyBuilder.java index 01223b32fa..cd4cd88960 100644 --- a/spring-web/src/main/java/org/springframework/http/client/MultipartBodyBuilder.java +++ b/spring-web/src/main/java/org/springframework/http/client/MultipartBodyBuilder.java @@ -366,7 +366,7 @@ public final class MultipartBodyBuilder { private final ResolvableType resolvableType; PublisherEntity( - @Nullable MultiValueMap headers, P publisher, ResolvableType resolvableType) { + @Nullable HttpHeaders headers, P publisher, ResolvableType resolvableType) { super(publisher, headers); Assert.notNull(publisher, "'publisher' must not be null"); diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpRequest.java index b2ac22eade..636912864a 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/HttpComponentsClientHttpRequest.java @@ -125,7 +125,7 @@ class HttpComponentsClientHttpRequest extends AbstractClientHttpRequest { protected void applyHeaders() { HttpHeaders headers = getHeaders(); - headers.entrySet() + headers.headerSet() .stream() .filter(entry -> !HttpHeaders.CONTENT_LENGTH.equals(entry.getKey())) .forEach(entry -> entry.getValue().forEach(v -> this.httpRequest.addHeader(entry.getKey(), v))); diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/JdkClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/reactive/JdkClientHttpRequest.java index 8130238fa7..678a2dcf19 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/JdkClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/JdkClientHttpRequest.java @@ -99,7 +99,7 @@ class JdkClientHttpRequest extends AbstractClientHttpRequest { @Override protected void applyHeaders() { - for (Map.Entry> entry : getHeaders().entrySet()) { + for (Map.Entry> entry : getHeaders().headerSet()) { if (entry.getKey().equalsIgnoreCase(HttpHeaders.CONTENT_LENGTH)) { // content-length is specified when writing continue; @@ -108,7 +108,7 @@ class JdkClientHttpRequest extends AbstractClientHttpRequest { this.builder.header(entry.getKey(), value); } } - if (!getHeaders().containsKey(HttpHeaders.ACCEPT)) { + if (!getHeaders().containsHeader(HttpHeaders.ACCEPT)) { this.builder.header(HttpHeaders.ACCEPT, "*/*"); } } diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpRequest.java index 573e34e4d6..89b8cef4ab 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/JettyClientHttpRequest.java @@ -143,7 +143,7 @@ class JettyClientHttpRequest extends AbstractClientHttpRequest { HttpHeaders headers = getHeaders(); this.jettyRequest.headers(fields -> { headers.forEach((key, value) -> value.forEach(v -> fields.add(key, v))); - if (!headers.containsKey(HttpHeaders.ACCEPT)) { + if (!headers.containsHeader(HttpHeaders.ACCEPT)) { fields.add(HttpHeaders.ACCEPT, "*/*"); } }); diff --git a/spring-web/src/main/java/org/springframework/http/client/support/BasicAuthenticationInterceptor.java b/spring-web/src/main/java/org/springframework/http/client/support/BasicAuthenticationInterceptor.java index d812a8d31e..e155d07dc1 100644 --- a/spring-web/src/main/java/org/springframework/http/client/support/BasicAuthenticationInterceptor.java +++ b/spring-web/src/main/java/org/springframework/http/client/support/BasicAuthenticationInterceptor.java @@ -74,7 +74,7 @@ public class BasicAuthenticationInterceptor implements ClientHttpRequestIntercep HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { HttpHeaders headers = request.getHeaders(); - if (!headers.containsKey(HttpHeaders.AUTHORIZATION)) { + if (!headers.containsHeader(HttpHeaders.AUTHORIZATION)) { headers.setBasicAuth(this.encodedCredentials); } return execution.execute(request, body); diff --git a/spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartHttpMessageWriter.java index cf7be2ea1b..5a12a77ba2 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartHttpMessageWriter.java @@ -261,7 +261,7 @@ public class MultipartHttpMessageWriter extends MultipartWriterSupport resolvableType = ResolvableType.forClass(body.getClass()); } - if (!headers.containsKey(HttpHeaders.CONTENT_DISPOSITION)) { + if (!headers.containsHeader(HttpHeaders.CONTENT_DISPOSITION)) { if (body instanceof Resource resource) { headers.setContentDispositionFormData(name, resource.getFilename()); } diff --git a/spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartWriterSupport.java b/spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartWriterSupport.java index dba5351a73..0f5658f215 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartWriterSupport.java +++ b/spring-web/src/main/java/org/springframework/http/codec/multipart/MultipartWriterSupport.java @@ -150,7 +150,7 @@ public class MultipartWriterSupport extends LoggingCodecSupport { return Mono.fromCallable(() -> { @SuppressWarnings("resource") FastByteArrayOutputStream bos = new FastByteArrayOutputStream(); - for (Map.Entry> entry : headers.entrySet()) { + for (Map.Entry> entry : headers.headerSet()) { byte[] headerName = entry.getKey().getBytes(getCharset()); for (String headerValueString : entry.getValue()) { byte[] headerValue = headerValueString.getBytes(getCharset()); diff --git a/spring-web/src/main/java/org/springframework/http/codec/multipart/PartHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/multipart/PartHttpMessageWriter.java index 76cd4b6751..fe33e3b211 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/multipart/PartHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/multipart/PartHttpMessageWriter.java @@ -94,7 +94,7 @@ public class PartHttpMessageWriter extends MultipartWriterSupport implements Htt HttpHeaders headers = new HttpHeaders(part.headers()); String name = part.name(); - if (!headers.containsKey(HttpHeaders.CONTENT_DISPOSITION)) { + if (!headers.containsHeader(HttpHeaders.CONTENT_DISPOSITION)) { headers.setContentDispositionFormData(name, (part instanceof FilePart filePart ? filePart.filename() : null)); } diff --git a/spring-web/src/main/java/org/springframework/http/converter/AbstractHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/AbstractHttpMessageConverter.java index 8c3c280d40..6289bd650f 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/AbstractHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/AbstractHttpMessageConverter.java @@ -262,7 +262,7 @@ public abstract class AbstractHttpMessageConverter implements HttpMessageConv headers.setContentType(contentTypeToUse); } } - if (headers.getContentLength() < 0 && !headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) { + if (headers.getContentLength() < 0 && !headers.containsHeader(HttpHeaders.TRANSFER_ENCODING)) { Long contentLength = getContentLength(t, headers.getContentType()); if (contentLength != null) { headers.setContentLength(contentLength); diff --git a/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java index be83a29f92..ca1e374011 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java @@ -645,7 +645,7 @@ public class FormHttpMessageConverter implements HttpMessageConverter> entry : this.headers.entrySet()) { + for (Map.Entry> entry : this.headers.headerSet()) { byte[] headerName = getBytes(entry.getKey()); for (String headerValueString : entry.getValue()) { byte[] headerValue = getBytes(headerValueString); diff --git a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java index 32da447629..a056a68a01 100644 --- a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java @@ -291,7 +291,7 @@ public class ServletServerHttpRequest implements ServerHttpRequest { writer.flush(); byte[] bytes = bos.toByteArray(); - if (bytes.length > 0 && getHeaders().containsKey(HttpHeaders.CONTENT_LENGTH)) { + if (bytes.length > 0 && getHeaders().containsHeader(HttpHeaders.CONTENT_LENGTH)) { getHeaders().setContentLength(bytes.length); } diff --git a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java index f09d8a9018..e0b9581adf 100644 --- a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java @@ -151,8 +151,8 @@ public class ServletServerHttpResponse implements ServerHttpResponse { private static final long serialVersionUID = 3410708522401046302L; @Override - public boolean containsKey(Object key) { - return (super.containsKey(key) || (get(key) != null)); + public boolean containsHeader(String key) { + return (super.containsHeader(key) || (get(key) != null)); } @Override @@ -169,10 +169,7 @@ public class ServletServerHttpResponse implements ServerHttpResponse { } @Override - public @Nullable List get(Object key) { - Assert.isInstanceOf(String.class, key, "Key must be a String-based header name"); - - String headerName = (String) key; + public @Nullable List get(String headerName) { if (headerName.equalsIgnoreCase(CONTENT_TYPE)) { // Content-Type is written as an override so don't merge String value = getFirst(headerName); @@ -185,7 +182,7 @@ public class ServletServerHttpResponse implements ServerHttpResponse { } boolean isEmpty1 = CollectionUtils.isEmpty(values1); - List values2 = super.get(key); + List values2 = super.get(headerName); boolean isEmpty2 = CollectionUtils.isEmpty(values2); if (isEmpty1 && isEmpty2) { diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpRequest.java index 19abe1646b..4cb0d82917 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpRequest.java @@ -79,9 +79,24 @@ public abstract class AbstractServerHttpRequest implements ServerHttpRequest { * @param contextPath the context path for the request * @param headers the headers for the request (as {@link MultiValueMap}) * @since 6.0.8 + * @deprecated Use {@link #AbstractServerHttpRequest(HttpMethod, URI, String, HttpHeaders)} */ + @Deprecated public AbstractServerHttpRequest(HttpMethod method, URI uri, @Nullable String contextPath, MultiValueMap headers) { + this(method, uri, contextPath, new HttpHeaders(headers)); + } + + /** + * Constructor with the method, URI and headers for the request. + * @param method the HTTP method for the request + * @param uri the URI for the request + * @param contextPath the context path for the request + * @param headers the headers for the request (as {@link MultiValueMap}) + * @since 7.0 + */ + public AbstractServerHttpRequest(HttpMethod method, URI uri, @Nullable String contextPath, + HttpHeaders headers) { Assert.notNull(method, "Method must not be null"); Assert.notNull(uri, "Uri must not be null"); diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpRequest.java index f9dadffcc9..833e67aaa0 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpRequest.java @@ -37,6 +37,7 @@ import reactor.netty5.http.server.HttpServerRequest; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.Netty5DataBufferFactory; import org.springframework.http.HttpCookie; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpLogging; import org.springframework.http.HttpMethod; import org.springframework.http.support.Netty5HeadersAdapter; @@ -70,7 +71,7 @@ class ReactorNetty2ServerHttpRequest extends AbstractServerHttpRequest { throws URISyntaxException { super(HttpMethod.valueOf(request.method().name()), initUri(request), "", - new Netty5HeadersAdapter(request.requestHeaders())); + new HttpHeaders(new Netty5HeadersAdapter(request.requestHeaders()))); Assert.notNull(bufferFactory, "DataBufferFactory must not be null"); this.request = request; this.bufferFactory = bufferFactory; diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpRequest.java index 6f71068a90..53715fb248 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpRequest.java @@ -35,6 +35,7 @@ import reactor.netty.http.server.HttpServerRequest; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.NettyDataBufferFactory; import org.springframework.http.HttpCookie; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpLogging; import org.springframework.http.HttpMethod; import org.springframework.http.support.Netty4HeadersAdapter; @@ -67,7 +68,7 @@ class ReactorServerHttpRequest extends AbstractServerHttpRequest { super(HttpMethod.valueOf(request.method().name()), ReactorUriHelper.createUri(request), request.forwardedPrefix(), - new Netty4HeadersAdapter(request.requestHeaders())); + new HttpHeaders(new Netty4HeadersAdapter(request.requestHeaders()))); Assert.notNull(bufferFactory, "DataBufferFactory must not be null"); this.request = request; this.bufferFactory = bufferFactory; diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpRequest.java index a5bd9df508..d205165ef8 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpRequest.java @@ -24,7 +24,6 @@ import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.security.cert.X509Certificate; import java.util.Enumeration; -import java.util.Locale; import java.util.Map; import jakarta.servlet.AsyncContext; @@ -47,7 +46,6 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; import org.springframework.util.LinkedCaseInsensitiveMap; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -89,7 +87,7 @@ class ServletServerHttpRequest extends AbstractServerHttpRequest { this(createDefaultHttpHeaders(request), request, asyncContext, servletPath, bufferFactory, bufferSize); } - public ServletServerHttpRequest(MultiValueMap headers, HttpServletRequest request, + public ServletServerHttpRequest(HttpHeaders headers, HttpServletRequest request, AsyncContext asyncContext, String servletPath, DataBufferFactory bufferFactory, int bufferSize) throws IOException, URISyntaxException { @@ -112,9 +110,8 @@ class ServletServerHttpRequest extends AbstractServerHttpRequest { } - private static MultiValueMap createDefaultHttpHeaders(HttpServletRequest request) { - MultiValueMap headers = - CollectionUtils.toMultiValueMap(new LinkedCaseInsensitiveMap<>(8, Locale.ROOT)); + private static HttpHeaders createDefaultHttpHeaders(HttpServletRequest request) { + HttpHeaders headers = new HttpHeaders(); for (Enumeration names = request.getHeaderNames(); names.hasMoreElements(); ) { String name = (String) names.nextElement(); for (Enumeration values = request.getHeaders(name); values.hasMoreElements(); ) { @@ -163,8 +160,7 @@ class ServletServerHttpRequest extends AbstractServerHttpRequest { } @SuppressWarnings("NullAway") - private static MultiValueMap initHeaders( - MultiValueMap headerValues, HttpServletRequest request) { + private static HttpHeaders initHeaders(HttpHeaders headerValues, HttpServletRequest request) { HttpHeaders headers = null; MediaType contentType = null; diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHttpHandlerAdapter.java b/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHttpHandlerAdapter.java index aaae97ba03..6fd3af0148 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHttpHandlerAdapter.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHttpHandlerAdapter.java @@ -34,7 +34,6 @@ import org.apache.coyote.Response; import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.http.HttpHeaders; import org.springframework.util.Assert; -import org.springframework.util.MultiValueMap; import org.springframework.util.ReflectionUtils; /** @@ -91,13 +90,13 @@ public class TomcatHttpHandlerAdapter extends ServletHttpHandlerAdapter { super(createTomcatHttpHeaders(request), request, context, servletPath, factory, bufferSize); } - private static MultiValueMap createTomcatHttpHeaders(HttpServletRequest request) { + private static HttpHeaders createTomcatHttpHeaders(HttpServletRequest request) { RequestFacade requestFacade = getRequestFacade(request); org.apache.catalina.connector.Request connectorRequest = (org.apache.catalina.connector.Request) ReflectionUtils.getField(COYOTE_REQUEST_FIELD, requestFacade); Assert.state(connectorRequest != null, "No Tomcat connector request"); Request tomcatRequest = connectorRequest.getCoyoteRequest(); - return new TomcatHeadersAdapter(tomcatRequest.getMimeHeaders()); + return new HttpHeaders(new TomcatHeadersAdapter(tomcatRequest.getMimeHeaders())); } private static RequestFacade getRequestFacade(HttpServletRequest request) { diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpRequest.java index 008023ee74..e1c6494fe6 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpRequest.java @@ -36,6 +36,7 @@ import reactor.core.publisher.Flux; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.http.HttpCookie; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; @@ -65,7 +66,7 @@ class UndertowServerHttpRequest extends AbstractServerHttpRequest { throws URISyntaxException { super(HttpMethod.valueOf(exchange.getRequestMethod().toString()), initUri(exchange), "", - new UndertowHeadersAdapter(exchange.getRequestHeaders())); + new HttpHeaders(new UndertowHeadersAdapter(exchange.getRequestHeaders()))); this.exchange = exchange; this.body = new RequestBodyPublisher(exchange, bufferFactory); this.body.registerListeners(exchange); diff --git a/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java b/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java index 3660a6f30b..d548c14eb5 100644 --- a/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java +++ b/spring-web/src/main/java/org/springframework/web/client/DefaultRestClient.java @@ -623,10 +623,10 @@ final class DefaultRestClient implements RestClient { private @Nullable HttpHeaders initHeaders() { HttpHeaders defaultHeaders = DefaultRestClient.this.defaultHeaders; - if (CollectionUtils.isEmpty(this.headers)) { + if (this.headers == null || this.headers.isEmpty()) { return defaultHeaders; } - else if (CollectionUtils.isEmpty(defaultHeaders)) { + else if (defaultHeaders == null || defaultHeaders.isEmpty()) { return this.headers; } else { diff --git a/spring-web/src/main/java/org/springframework/web/cors/DefaultCorsProcessor.java b/spring-web/src/main/java/org/springframework/web/cors/DefaultCorsProcessor.java index 2bbd283c09..f6cedf292a 100644 --- a/spring-web/src/main/java/org/springframework/web/cors/DefaultCorsProcessor.java +++ b/spring-web/src/main/java/org/springframework/web/cors/DefaultCorsProcessor.java @@ -220,7 +220,7 @@ public class DefaultCorsProcessor implements CorsProcessor { private List getHeadersToUse(ServerHttpRequest request, boolean isPreFlight) { HttpHeaders headers = request.getHeaders(); - return (isPreFlight ? headers.getAccessControlRequestHeaders() : new ArrayList<>(headers.keySet())); + return (isPreFlight ? headers.getAccessControlRequestHeaders() : new ArrayList<>(headers.headerNames())); } } diff --git a/spring-web/src/main/java/org/springframework/web/cors/reactive/CorsUtils.java b/spring-web/src/main/java/org/springframework/web/cors/reactive/CorsUtils.java index e50839398c..4961315566 100644 --- a/spring-web/src/main/java/org/springframework/web/cors/reactive/CorsUtils.java +++ b/spring-web/src/main/java/org/springframework/web/cors/reactive/CorsUtils.java @@ -42,7 +42,7 @@ public abstract class CorsUtils { */ @SuppressWarnings("deprecation") public static boolean isCorsRequest(ServerHttpRequest request) { - return request.getHeaders().containsKey(HttpHeaders.ORIGIN) && !isSameOrigin(request); + return request.getHeaders().containsHeader(HttpHeaders.ORIGIN) && !isSameOrigin(request); } /** @@ -52,8 +52,8 @@ public abstract class CorsUtils { public static boolean isPreFlightRequest(ServerHttpRequest request) { HttpHeaders headers = request.getHeaders(); return (request.getMethod() == HttpMethod.OPTIONS - && headers.containsKey(HttpHeaders.ORIGIN) - && headers.containsKey(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD)); + && headers.containsHeader(HttpHeaders.ORIGIN) + && headers.containsHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD)); } /** diff --git a/spring-web/src/main/java/org/springframework/web/cors/reactive/DefaultCorsProcessor.java b/spring-web/src/main/java/org/springframework/web/cors/reactive/DefaultCorsProcessor.java index 369a32fd93..87d273769c 100644 --- a/spring-web/src/main/java/org/springframework/web/cors/reactive/DefaultCorsProcessor.java +++ b/spring-web/src/main/java/org/springframework/web/cors/reactive/DefaultCorsProcessor.java @@ -217,7 +217,7 @@ public class DefaultCorsProcessor implements CorsProcessor { private List getHeadersToUse(ServerHttpRequest request, boolean isPreFlight) { HttpHeaders headers = request.getHeaders(); - return (isPreFlight ? headers.getAccessControlRequestHeaders() : new ArrayList<>(headers.keySet())); + return (isPreFlight ? headers.getAccessControlRequestHeaders() : new ArrayList<>(headers.headerNames())); } } diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/RequestHeaderMapMethodArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/method/annotation/RequestHeaderMapMethodArgumentResolver.java index 33ec1e7398..5ba3e1f207 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/RequestHeaderMapMethodArgumentResolver.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/RequestHeaderMapMethodArgumentResolver.java @@ -19,6 +19,7 @@ package org.springframework.web.method.annotation; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; +import java.util.function.BiConsumer; import org.jspecify.annotations.Nullable; @@ -33,13 +34,14 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; /** - * Resolves {@link Map} method arguments annotated with {@code @RequestHeader}. - * For individual header values annotated with {@code @RequestHeader} see + * Resolves {@link Map}, {@link MultiValueMap} and {@link HttpHeaders} method + * arguments annotated with {@code @RequestHeader}. For individual header values + * annotated with {@code @RequestHeader} see * {@link RequestHeaderMethodArgumentResolver} instead. * *

The created {@link Map} contains all request header name/value pairs. - * The method parameter type may be a {@link MultiValueMap} to receive all - * values for a header, not only the first one. + * The method parameter type may be a {@link HttpHeaders} or a {@link MultiValueMap} + * to receive all values for a header, not only the first one. * * @author Arjen Poutsma * @author Rossen Stoyanchev @@ -50,7 +52,8 @@ public class RequestHeaderMapMethodArgumentResolver implements HandlerMethodArgu @Override public boolean supportsParameter(MethodParameter parameter) { return (parameter.hasParameterAnnotation(RequestHeader.class) && - Map.class.isAssignableFrom(parameter.getParameterType())); + (Map.class.isAssignableFrom(parameter.getParameterType()) || + HttpHeaders.class.isAssignableFrom(parameter.getParameterType()))); } @Override @@ -58,23 +61,15 @@ public class RequestHeaderMapMethodArgumentResolver implements HandlerMethodArgu NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { Class paramType = parameter.getParameterType(); - if (MultiValueMap.class.isAssignableFrom(paramType)) { - MultiValueMap result; - if (HttpHeaders.class.isAssignableFrom(paramType)) { - result = new HttpHeaders(); - } - else { - result = new LinkedMultiValueMap<>(); - } - for (Iterator iterator = webRequest.getHeaderNames(); iterator.hasNext();) { - String headerName = iterator.next(); - String[] headerValues = webRequest.getHeaderValues(headerName); - if (headerValues != null) { - for (String headerValue : headerValues) { - result.add(headerName, headerValue); - } - } - } + + if (HttpHeaders.class.isAssignableFrom(paramType)) { + HttpHeaders result = new HttpHeaders(); + copyHeaderValues(webRequest, result::add); + return result; + } + else if (MultiValueMap.class.isAssignableFrom(paramType)) { + MultiValueMap result = new LinkedMultiValueMap<>(); + copyHeaderValues(webRequest, result::add); return result; } else { @@ -90,4 +85,16 @@ public class RequestHeaderMapMethodArgumentResolver implements HandlerMethodArgu } } + void copyHeaderValues(NativeWebRequest webRequest, BiConsumer consumer) { + for (Iterator iterator = webRequest.getHeaderNames(); iterator.hasNext();) { + String headerName = iterator.next(); + String[] headerValues = webRequest.getHeaderValues(headerName); + if (headerValues != null) { + for (String headerValue : headerValues) { + consumer.accept(headerName, headerValue); + } + } + } + } + } diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/RequestHeaderMethodArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/method/annotation/RequestHeaderMethodArgumentResolver.java index 45e9129ecd..f068f70990 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/RequestHeaderMethodArgumentResolver.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/RequestHeaderMethodArgumentResolver.java @@ -22,6 +22,7 @@ import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.core.MethodParameter; +import org.springframework.http.HttpHeaders; import org.springframework.util.Assert; import org.springframework.web.bind.MissingRequestHeaderException; import org.springframework.web.bind.ServletRequestBindingException; @@ -61,7 +62,8 @@ public class RequestHeaderMethodArgumentResolver extends AbstractNamedValueMetho @Override public boolean supportsParameter(MethodParameter parameter) { return (parameter.hasParameterAnnotation(RequestHeader.class) && - !Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())); + !Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) && + !HttpHeaders.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType()); } @Override diff --git a/spring-web/src/main/java/org/springframework/web/server/adapter/ForwardedHeaderTransformer.java b/spring-web/src/main/java/org/springframework/web/server/adapter/ForwardedHeaderTransformer.java index 0a00a58d28..9c0ef3913a 100644 --- a/spring-web/src/main/java/org/springframework/web/server/adapter/ForwardedHeaderTransformer.java +++ b/spring-web/src/main/java/org/springframework/web/server/adapter/ForwardedHeaderTransformer.java @@ -144,7 +144,7 @@ public class ForwardedHeaderTransformer implements Function entity = new HttpEntity<>(body); assertThat(entity.getBody()).isSameAs(body); - assertThat(entity.getHeaders()).isEmpty(); + assertThat(entity.getHeaders().isEmpty()).as("isEmpty").isTrue(); } @Test @@ -62,25 +62,25 @@ class HttpEntityTests { @Test void testEquals() { - MultiValueMap map1 = new LinkedMultiValueMap<>(); - map1.set("Content-Type", "text/plain"); + HttpHeaders headers1 = new HttpHeaders(); + headers1.set("Content-Type", "text/plain"); - MultiValueMap map2 = new LinkedMultiValueMap<>(); - map2.set("Content-Type", "application/json"); + HttpHeaders headers2 = new HttpHeaders(); + headers2.set("Content-Type", "application/json"); assertThat(new HttpEntity<>().equals(new HttpEntity<>())).isTrue(); - assertThat(new HttpEntity<>(map1).equals(new HttpEntity<>())).isFalse(); - assertThat(new HttpEntity<>().equals(new HttpEntity<>(map2))).isFalse(); + assertThat(new HttpEntity<>(headers1).equals(new HttpEntity<>())).isFalse(); + assertThat(new HttpEntity<>().equals(new HttpEntity<>(headers2))).isFalse(); - assertThat(new HttpEntity<>(map1).equals(new HttpEntity<>(map1))).isTrue(); - assertThat(new HttpEntity<>(map1).equals(new HttpEntity<>(map2))).isFalse(); + assertThat(new HttpEntity<>(headers1).equals(new HttpEntity<>(headers1))).isTrue(); + assertThat(new HttpEntity<>(headers1).equals(new HttpEntity<>(headers2))).isFalse(); - assertThat(new HttpEntity(null, null).equals(new HttpEntity<>(null, null))).isTrue(); - assertThat(new HttpEntity<>("foo", null).equals(new HttpEntity<>(null, null))).isFalse(); - assertThat(new HttpEntity(null, null).equals(new HttpEntity<>("bar", null))).isFalse(); + assertThat(new HttpEntity(null, (HttpHeaders) null).equals(new HttpEntity<>(null, (HttpHeaders) null))).isTrue(); + assertThat(new HttpEntity<>("foo", (HttpHeaders) null).equals(new HttpEntity<>(null, (HttpHeaders) null))).isFalse(); + assertThat(new HttpEntity(null, (HttpHeaders) null).equals(new HttpEntity<>("bar", (HttpHeaders) null))).isFalse(); - assertThat(new HttpEntity<>("foo", map1).equals(new HttpEntity<>("foo", map1))).isTrue(); - assertThat(new HttpEntity<>("foo", map1).equals(new HttpEntity<>("bar", map1))).isFalse(); + assertThat(new HttpEntity<>("foo", headers1).equals(new HttpEntity<>("foo", headers1))).isTrue(); + assertThat(new HttpEntity<>("foo", headers1).equals(new HttpEntity<>("bar", headers1))).isFalse(); } @Test diff --git a/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java b/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java index 2507b4e5d8..9045bcdfef 100644 --- a/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java +++ b/spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java @@ -645,7 +645,7 @@ class HttpHeadersTests { void keySetOperations() { headers.add("Alpha", "apple"); headers.add("Bravo", "banana"); - Set keySet = headers.keySet(); + Set keySet = headers.headerNames(); // Please DO NOT simplify the following with AssertJ's fluent API. // @@ -677,17 +677,17 @@ class HttpHeadersTests { // remove() assertThat(keySet.remove("Alpha")).isTrue(); assertThat(keySet).hasSize(1); - assertThat(headers).hasSize(1); + assertThat(headers.size()).isOne(); assertThat(keySet.remove("Alpha")).isFalse(); assertThat(keySet).hasSize(1); - assertThat(headers).hasSize(1); + assertThat(headers.size()).isOne(); // clear() keySet.clear(); assertThat(keySet).isEmpty(); assertThat(keySet).isEmpty(); - assertThat(headers).isEmpty(); - assertThat(headers).isEmpty(); + assertThat(headers.isEmpty()).as("isEmpty").isTrue(); + assertThat(headers.isEmpty()).as("isEmpty").isTrue(); // Unsupported operations assertThatExceptionOfType(UnsupportedOperationException.class) @@ -705,10 +705,10 @@ class HttpHeadersTests { // --- Given --- headers.add("Alpha", "apple"); headers.add("Bravo", "banana"); - assertThat(headers).containsOnlyKeys("Alpha", "Bravo"); + assertThat(headers.headerNames()).containsOnly("Alpha", "Bravo"); // --- When --- - boolean removed = headers.keySet().remove("Alpha"); + boolean removed = headers.headerNames().remove("Alpha"); // --- Then --- @@ -718,12 +718,12 @@ class HttpHeadersTests { // the behavior of the entire contract. assertThat(removed).isTrue(); - assertThat(headers.keySet().remove("Alpha")).isFalse(); - assertThat(headers).hasSize(1); - assertThat(headers.containsKey("Alpha")).as("Alpha should have been removed").isFalse(); - assertThat(headers.containsKey("Bravo")).as("Bravo should be present").isTrue(); - assertThat(headers.keySet()).containsOnly("Bravo"); - assertThat(headers.entrySet()).containsOnly(entry("Bravo", List.of("banana"))); + assertThat(headers.headerNames().remove("Alpha")).isFalse(); + assertThat(headers.size()).isOne(); + assertThat(headers.containsHeader("Alpha")).as("Alpha should have been removed").isFalse(); + assertThat(headers.containsHeader("Bravo")).as("Bravo should be present").isTrue(); + assertThat(headers.headerNames()).containsOnly("Bravo"); + assertThat(headers.headerSet()).containsOnly(entry("Bravo", List.of("banana"))); } @Test @@ -731,11 +731,11 @@ class HttpHeadersTests { String headerName = "MyHeader"; String headerValue = "value"; - assertThat(headers).isEmpty(); + assertThat(headers.isEmpty()).as("isEmpty").isTrue(); headers.add(headerName, headerValue); - assertThat(headers.containsKey(headerName)).isTrue(); - headers.keySet().removeIf(key -> key.equals(headerName)); - assertThat(headers).isEmpty(); + assertThat(headers.containsHeader(headerName)).isTrue(); + headers.headerNames().removeIf(key -> key.equals(headerName)); + assertThat(headers.isEmpty()).as("isEmpty").isTrue(); headers.add(headerName, headerValue); assertThat(headers.get(headerName)).containsExactly(headerValue); } @@ -745,11 +745,11 @@ class HttpHeadersTests { String headerName = "MyHeader"; String headerValue = "value"; - assertThat(headers).isEmpty(); + assertThat(headers.isEmpty()).as("isEmpty").isTrue(); headers.add(headerName, headerValue); - assertThat(headers.containsKey(headerName)).isTrue(); - headers.entrySet().removeIf(entry -> entry.getKey().equals(headerName)); - assertThat(headers).isEmpty(); + assertThat(headers.containsHeader(headerName)).isTrue(); + headers.headerSet().removeIf(entry -> entry.getKey().equals(headerName)); + assertThat(headers.isEmpty()).as("isEmpty").isTrue(); headers.add(headerName, headerValue); assertThat(headers.get(headerName)).containsExactly(headerValue); } @@ -764,10 +764,10 @@ class HttpHeadersTests { String[] expectedKeys = new String[] { "aardvark", "beaver", "cat", "dog", "elephant" }; - assertThat(headers.entrySet()).extracting(Entry::getKey).containsExactly(expectedKeys); + assertThat(headers.headerSet()).extracting(Entry::getKey).containsExactly(expectedKeys); HttpHeaders readOnlyHttpHeaders = HttpHeaders.readOnlyHttpHeaders(headers); - assertThat(readOnlyHttpHeaders.entrySet()).extracting(Entry::getKey).containsExactly(expectedKeys); + assertThat(readOnlyHttpHeaders.headerSet()).extracting(Entry::getKey).containsExactly(expectedKeys); } @Test @@ -784,15 +784,15 @@ class HttpHeadersTests { HttpHeaders forEachHeaders = new HttpHeaders(); readOnlyHttpHeaders.forEach(forEachHeaders::putIfAbsent); - assertThat(forEachHeaders.entrySet()).extracting(Entry::getKey).containsExactly(expectedKeys); + assertThat(forEachHeaders.headerSet()).extracting(Entry::getKey).containsExactly(expectedKeys); HttpHeaders putAllHeaders = new HttpHeaders(); putAllHeaders.putAll(readOnlyHttpHeaders); - assertThat(putAllHeaders.entrySet()).extracting(Entry::getKey).containsExactly(expectedKeys); + assertThat(putAllHeaders.headerSet()).extracting(Entry::getKey).containsExactly(expectedKeys); HttpHeaders addAllHeaders = new HttpHeaders(); addAllHeaders.addAll(readOnlyHttpHeaders); - assertThat(addAllHeaders.entrySet()).extracting(Entry::getKey).containsExactly(expectedKeys); + assertThat(addAllHeaders.headerSet()).extracting(Entry::getKey).containsExactly(expectedKeys); } @Test // gh-25034 diff --git a/spring-web/src/test/java/org/springframework/http/RequestEntityTests.java b/spring-web/src/test/java/org/springframework/http/RequestEntityTests.java index cda567b7fb..080c3e7dda 100644 --- a/spring-web/src/test/java/org/springframework/http/RequestEntityTests.java +++ b/spring-web/src/test/java/org/springframework/http/RequestEntityTests.java @@ -52,7 +52,7 @@ class RequestEntityTests { assertThat(requestEntity).isNotNull(); assertThat(requestEntity.getMethod()).isEqualTo(HttpMethod.GET); - assertThat(requestEntity.getHeaders().containsKey(headerName)).isTrue(); + assertThat(requestEntity.getHeaders().containsHeader(headerName)).isTrue(); assertThat(requestEntity.getHeaders().getFirst(headerName)).isEqualTo(headerValue); assertThat(requestEntity.getBody()).isEqualTo(entity); } @@ -100,7 +100,7 @@ class RequestEntityTests { assertThat(requestEntity).isNotNull(); assertThat(requestEntity.getMethod()).isEqualTo(HttpMethod.GET); - assertThat(requestEntity.getHeaders().containsKey(HttpHeaders.ACCEPT)).isTrue(); + assertThat(requestEntity.getHeaders().containsHeader(HttpHeaders.ACCEPT)).isTrue(); assertThat(requestEntity.getHeaders().getFirst(HttpHeaders.ACCEPT)).isEqualTo("image/gif, image/jpeg, image/png"); assertThat(requestEntity.getBody()).isNull(); } @@ -120,7 +120,7 @@ class RequestEntityTests { ifNoneMatch(ifNoneMatch). contentLength(contentLength). contentType(contentType). - headers(headers -> assertThat(headers).hasSize(6)). + headers(headers -> assertThat(headers.size()).isEqualTo(6)). build(); assertThat(responseEntity).isNotNull(); diff --git a/spring-web/src/test/java/org/springframework/http/ResponseEntityTests.java b/spring-web/src/test/java/org/springframework/http/ResponseEntityTests.java index 7c52e12edf..56659ce698 100644 --- a/spring-web/src/test/java/org/springframework/http/ResponseEntityTests.java +++ b/spring-web/src/test/java/org/springframework/http/ResponseEntityTests.java @@ -44,7 +44,7 @@ class ResponseEntityTests { assertThat(responseEntity).isNotNull(); assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(responseEntity.getHeaders().containsKey(headerName)).isTrue(); + assertThat(responseEntity.getHeaders().containsHeader(headerName)).isTrue(); assertThat(responseEntity.getHeaders().get(headerName)).containsExactly(headerValue1, headerValue2); assertThat(responseEntity.getBody()).isEqualTo(entity); } @@ -113,7 +113,7 @@ class ResponseEntityTests { assertThat(responseEntity).isNotNull(); assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.CREATED); - assertThat(responseEntity.getHeaders().containsKey(HttpHeaders.LOCATION)).isTrue(); + assertThat(responseEntity.getHeaders().containsHeader(HttpHeaders.LOCATION)).isTrue(); assertThat(responseEntity.getHeaders().getFirst(HttpHeaders.LOCATION)).isEqualTo(location.toString()); assertThat(responseEntity.getBody()).isNull(); @@ -195,7 +195,7 @@ class ResponseEntityTests { location(location). contentLength(contentLength). contentType(contentType). - headers(headers -> assertThat(headers).hasSize(5)). + headers(headers -> assertThat(headers.size()).isEqualTo(5)). build(); assertThat(responseEntity).isNotNull(); @@ -236,7 +236,7 @@ class ResponseEntityTests { HttpHeaders responseHeaders = responseEntity.getHeaders(); assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(responseHeaders).hasSize(1); + assertThat(responseHeaders.size()).isOne(); assertThat(responseHeaders.get("X-CustomHeader")).hasSize(1); assertThat(responseHeaders.getFirst("X-CustomHeader")).isEqualTo("vale"); @@ -250,7 +250,7 @@ class ResponseEntityTests { ResponseEntity.ok().headers((HttpHeaders) null).build(); assertThat(responseEntityWithEmptyHeaders.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(responseEntityWithEmptyHeaders.getHeaders()).isEmpty(); + assertThat(responseEntityWithEmptyHeaders.getHeaders().isEmpty()).isTrue(); assertThat(responseEntityWithNullHeaders.toString()).isEqualTo(responseEntityWithEmptyHeaders.toString()); } @@ -265,7 +265,7 @@ class ResponseEntityTests { assertThat(responseEntity).isNotNull(); assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(responseEntity.getHeaders().containsKey(HttpHeaders.CACHE_CONTROL)).isFalse(); + assertThat(responseEntity.getHeaders().containsHeader(HttpHeaders.CACHE_CONTROL)).isFalse(); assertThat(responseEntity.getBody()).isEqualTo(entity); } @@ -281,7 +281,7 @@ class ResponseEntityTests { assertThat(responseEntity).isNotNull(); assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(responseEntity.getHeaders().containsKey(HttpHeaders.CACHE_CONTROL)).isTrue(); + assertThat(responseEntity.getHeaders().containsHeader(HttpHeaders.CACHE_CONTROL)).isTrue(); assertThat(responseEntity.getBody()).isEqualTo(entity); String cacheControlHeader = responseEntity.getHeaders().getCacheControl(); assertThat(cacheControlHeader).isEqualTo( @@ -299,7 +299,7 @@ class ResponseEntityTests { assertThat(responseEntity).isNotNull(); assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(responseEntity.getHeaders().containsKey(HttpHeaders.CACHE_CONTROL)).isTrue(); + assertThat(responseEntity.getHeaders().containsHeader(HttpHeaders.CACHE_CONTROL)).isTrue(); assertThat(responseEntity.getBody()).isEqualTo(entity); String cacheControlHeader = responseEntity.getHeaders().getCacheControl(); diff --git a/spring-web/src/test/java/org/springframework/http/client/AbstractHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/AbstractHttpRequestFactoryTests.java index 22653ebca2..c9aa51bc70 100644 --- a/spring-web/src/test/java/org/springframework/http/client/AbstractHttpRequestFactoryTests.java +++ b/spring-web/src/test/java/org/springframework/http/client/AbstractHttpRequestFactoryTests.java @@ -98,9 +98,11 @@ abstract class AbstractHttpRequestFactoryTests extends AbstractMockWebServerTest try (ClientHttpResponse response = request.execute()) { assertThat(response.getStatusCode()).as("Invalid status code").isEqualTo(HttpStatus.OK); - assertThat(response.getHeaders()).as("Header not found").containsKey(headerName); - assertThat(response.getHeaders()).as("Header value not found") - .containsEntry(headerName, Arrays.asList(headerValue1, headerValue2)); + assertThat(response.getHeaders().containsHeader(headerName)) + .as("Header not found").isTrue(); + assertThat(response.getHeaders().hasHeaderValues(headerName, Arrays.asList(headerValue1, headerValue2))) + .as("Header value not found") + .isTrue(); byte[] result = FileCopyUtils.copyToByteArray(response.getBody()); assertThat(Arrays.equals(body, result)).as("Invalid body").isTrue(); } diff --git a/spring-web/src/test/java/org/springframework/http/client/BufferingClientHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/BufferingClientHttpRequestFactoryTests.java index 4716e351af..2355dc72c3 100644 --- a/spring-web/src/test/java/org/springframework/http/client/BufferingClientHttpRequestFactoryTests.java +++ b/spring-web/src/test/java/org/springframework/http/client/BufferingClientHttpRequestFactoryTests.java @@ -51,8 +51,8 @@ class BufferingClientHttpRequestFactoryTests extends AbstractHttpRequestFactoryT assertThat(response.getStatusCode()).as("Invalid status code").isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).as("Invalid status code").isEqualTo(HttpStatus.OK); - assertThat(response.getHeaders().containsKey(headerName)).as("Header not found").isTrue(); - assertThat(response.getHeaders().containsKey(headerName)).as("Header not found").isTrue(); + assertThat(response.getHeaders().containsHeader(headerName)).as("Header not found").isTrue(); + assertThat(response.getHeaders().containsHeader(headerName)).as("Header not found").isTrue(); assertThat(response.getHeaders().get(headerName)).as("Header value not found").isEqualTo(Arrays.asList(headerValue1, headerValue2)); assertThat(response.getHeaders().get(headerName)).as("Header value not found").isEqualTo(Arrays.asList(headerValue1, headerValue2)); diff --git a/spring-web/src/test/java/org/springframework/http/codec/multipart/DefaultPartHttpMessageReaderTests.java b/spring-web/src/test/java/org/springframework/http/codec/multipart/DefaultPartHttpMessageReaderTests.java index 692f06a108..2ba573dda4 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/multipart/DefaultPartHttpMessageReaderTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/multipart/DefaultPartHttpMessageReaderTests.java @@ -103,7 +103,7 @@ class DefaultPartHttpMessageReaderTests { StepVerifier.create(result) .consumeNextWith(part -> { - assertThat(part.headers()).isEmpty(); + assertThat(part.headers().isEmpty()).isTrue(); part.content().subscribe(DataBufferUtils::release); }) .verifyComplete(); @@ -266,7 +266,7 @@ class DefaultPartHttpMessageReaderTests { CountDownLatch latch = new CountDownLatch(1); StepVerifier.create(result) .consumeNextWith(part -> { - assertThat(part.headers()).containsEntry("Føø", Collections.singletonList("Bår")); + assertThat(part.headers().hasHeaderValues("Føø", Collections.singletonList("Bår"))).isTrue(); testPart(part, null, "This is plain ASCII text.", latch); }) .verifyComplete(); diff --git a/spring-web/src/test/java/org/springframework/http/codec/multipart/MultipartHttpMessageWriterTests.java b/spring-web/src/test/java/org/springframework/http/codec/multipart/MultipartHttpMessageWriterTests.java index 841926d968..b54413c306 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/multipart/MultipartHttpMessageWriterTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/multipart/MultipartHttpMessageWriterTests.java @@ -175,7 +175,7 @@ class MultipartHttpMessageWriterTests extends AbstractLeakCheckingTests { part = requestParts.getFirst("filePublisher"); assertThat(part).isNotNull(); assertThat(part.name()).isEqualTo("filePublisher"); - assertThat(part.headers()).containsEntry("foo", Collections.singletonList("bar")); + assertThat(part.headers().hasHeaderValues("foo", Collections.singletonList("bar"))).isTrue(); assertThat(((FilePart) part).filename()).isEqualTo("file.txt"); value = decodeToString(part); assertThat(value).isEqualTo("AaBbCc"); diff --git a/spring-web/src/test/java/org/springframework/http/codec/multipart/PartEventHttpMessageReaderTests.java b/spring-web/src/test/java/org/springframework/http/codec/multipart/PartEventHttpMessageReaderTests.java index 0e739d8307..4626002164 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/multipart/PartEventHttpMessageReaderTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/multipart/PartEventHttpMessageReaderTests.java @@ -42,7 +42,6 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.entry; import static org.springframework.core.ResolvableType.forClass; /** @@ -72,7 +71,7 @@ class PartEventHttpMessageReaderTests { Flux result = this.reader.read(forClass(PartEvent.class), request, emptyMap()); StepVerifier.create(result) - .assertNext(form(headers -> assertThat(headers).isEmpty(), "This is implicitly typed plain ASCII text.\r\nIt does NOT end with a linebreak.")) + .assertNext(form(headers -> assertThat(headers.isEmpty()).isTrue(), "This is implicitly typed plain ASCII text.\r\nIt does NOT end with a linebreak.")) .assertNext(form(headers -> assertThat(headers.getContentType()).isEqualTo(TEXT_PLAIN_ASCII), "This is explicitly typed plain ASCII text.\r\nIt DOES end with a linebreak.\r\n")) .verifyComplete(); @@ -85,7 +84,7 @@ class PartEventHttpMessageReaderTests { Flux result = this.reader.read(forClass(PartEvent.class), request, emptyMap()); StepVerifier.create(result) - .assertNext(data(headers -> assertThat(headers).isEmpty(), bodyText("a"), true)) + .assertNext(data(headers -> assertThat(headers.isEmpty()).isTrue(), bodyText("a"), true)) .verifyComplete(); } @@ -143,8 +142,8 @@ class PartEventHttpMessageReaderTests { Flux result = this.reader.read(forClass(PartEvent.class), request, emptyMap()); StepVerifier.create(result) - .assertNext(form(headers -> assertThat(headers).contains(entry("Part", List.of("1"))), "")) - .assertNext(data(headers -> assertThat(headers).contains(entry("Part", List.of("2"))), bodyText("a"), true)) + .assertNext(form(headers -> assertThat(headers.hasHeaderValues("Part", List.of("1"))).isTrue(), "")) + .assertNext(data(headers -> assertThat(headers.hasHeaderValues("Part", List.of("2"))).isTrue(), bodyText("a"), true)) .verifyComplete(); } @@ -156,7 +155,7 @@ class PartEventHttpMessageReaderTests { Flux result = this.reader.read(forClass(PartEvent.class), request, emptyMap()); StepVerifier.create(result, 3) - .assertNext(form(headers -> assertThat(headers).isEmpty(), + .assertNext(form(headers -> assertThat(headers.isEmpty()).isTrue(), "This is implicitly typed plain ASCII text.\r\nIt does NOT end with a linebreak.")) .thenCancel() .verify(); @@ -238,7 +237,7 @@ class PartEventHttpMessageReaderTests { Flux result = reader.read(forClass(PartEvent.class), request, emptyMap()); StepVerifier.create(result) - .assertNext(form(headers -> assertThat(headers).isEmpty(), "This is implicitly typed plain ASCII text.\r\nIt does NOT end with a linebreak.")) + .assertNext(form(headers -> assertThat(headers.isEmpty()).isTrue(), "This is implicitly typed plain ASCII text.\r\nIt does NOT end with a linebreak.")) .expectError(DecodingException.class) .verify(); } @@ -283,7 +282,7 @@ class PartEventHttpMessageReaderTests { Flux result = this.reader.read(forClass(PartEvent.class), request, emptyMap()); StepVerifier.create(result) - .assertNext(data(headers -> assertThat(headers).containsEntry("Føø", List.of("Bår")), + .assertNext(data(headers -> assertThat(headers.hasHeaderValues("Føø", List.of("Bår"))).isTrue(), bodyText("This is plain ASCII text."), true)) .verifyComplete(); } diff --git a/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java b/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java index 6dad7e7ab2..4215d95fc1 100644 --- a/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java @@ -129,7 +129,7 @@ class ServletServerHttpRequestTests { HttpHeaders headers = request.getHeaders(); assertThat(headers).as("No HttpHeaders returned").isNotNull(); - assertThat(headers.containsKey(headerName)).as("Invalid headers returned").isTrue(); + assertThat(headers.containsHeader(headerName)).as("Invalid headers returned").isTrue(); List headerValues = headers.get(headerName); assertThat(headerValues).as("No header values returned").isNotNull(); assertThat(headerValues.size()).as("Invalid header values returned").isEqualTo(2); @@ -150,7 +150,7 @@ class ServletServerHttpRequestTests { HttpHeaders headers = request.getHeaders(); assertThat(headers).as("No HttpHeaders returned").isNotNull(); - assertThat(headers.containsKey(headerName)).as("Invalid headers returned").isTrue(); + assertThat(headers.containsHeader(headerName)).as("Invalid headers returned").isTrue(); List headerValues = headers.get(headerName); assertThat(headerValues.size()).as("Invalid header values returned").isEqualTo(2); assertThat(headerValues.contains(headerValue1)).as("Invalid header values returned").isTrue(); @@ -162,7 +162,7 @@ class ServletServerHttpRequestTests { void getHeadersWithWildcardContentType() { mockRequest.setContentType("*/*"); mockRequest.removeHeader("Content-Type"); - assertThat(request.getHeaders()).as("Invalid content-type should not raise exception").isEmpty(); + assertThat(request.getHeaders().isEmpty()).as("Invalid content-type should not raise exception").isTrue(); } @Test diff --git a/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpResponseTests.java b/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpResponseTests.java index 34a40016ac..33c973fa6d 100644 --- a/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpResponseTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpResponseTests.java @@ -99,7 +99,7 @@ class ServletServerHttpResponseTests { assertThat(this.response.getHeaders().getFirst(headerName)).isEqualTo(headerValue); assertThat(this.response.getHeaders().get(headerName)).containsExactly(headerValue); - assertThat(this.response.getHeaders()).containsKey(headerName); + assertThat(this.response.getHeaders().containsHeader(headerName)).isTrue(); assertThat(this.response.getHeaders().getAccessControlAllowOrigin()).isEqualTo(headerValue); } diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/HeadersAdaptersTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/HeadersAdaptersTests.java index 823121e781..9067a092f4 100644 --- a/spring-web/src/test/java/org/springframework/http/server/reactive/HeadersAdaptersTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/HeadersAdaptersTests.java @@ -117,7 +117,7 @@ class HeadersAdaptersTests { assertThat(headers2.get("TestHeader")).as("TestHeader") .containsExactly("first", "second", "third"); // Using the headerSet approach, we keep the first encountered casing of any given key - assertThat(headers2.keySet()).as("first casing variant").containsExactlyInAnyOrder("TestHeader", "SecondHeader"); + assertThat(headers2.headerNames()).as("first casing variant").containsExactlyInAnyOrder("TestHeader", "SecondHeader"); assertThat(headers2.toString()).as("similar toString, no 'with native headers' dump") .isEqualTo(headers.toString().substring(0, headers.toString().indexOf(']') + 1)); } @@ -132,7 +132,7 @@ class HeadersAdaptersTests { assertThat(headers2.get("TestHeader")).as("TestHeader") .containsExactly("first", "second", "third"); // Ordering and casing are not guaranteed using the entrySet+put approach - assertThat(headers2).as("two keys") + assertThat(headers2.asMultiValueMap()).as("two keys") .containsKey("testheader") .containsKey("secondheader") .hasSize(2); @@ -148,7 +148,10 @@ class HeadersAdaptersTests { assertThat(headers2.get("TestHeader")).as("TestHeader") .containsExactly("first", "second", "third"); // Ordering and casing are not guaranteed using the putAll approach - assertThat(headers2).as("two keys").containsOnlyKeys("testheader", "secondheader"); + assertThat(headers2.asMultiValueMap()).as("two keys") + .containsKey("testheader") + .containsKey("secondheader") + .hasSize(2); assertThat(headers2.toString()).as("similar toString, no 'with native headers' dump") .isEqualToIgnoringCase(headers.toString().substring(0, headers.toString().indexOf(']') + 1)); } diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpResponseTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpResponseTests.java index 7e80029cac..40710368c1 100644 --- a/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpResponseTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpResponseTests.java @@ -110,7 +110,7 @@ class ServerHttpResponseTests { assertThat(response.statusCodeWritten).isFalse(); assertThat(response.headersWritten).isFalse(); assertThat(response.cookiesWritten).isFalse(); - assertThat(headers).doesNotContainKeys(HttpHeaders.CONTENT_TYPE, HttpHeaders.CONTENT_LENGTH, + assertThat(headers.headerNames()).doesNotContain(HttpHeaders.CONTENT_TYPE, HttpHeaders.CONTENT_LENGTH, HttpHeaders.CONTENT_ENCODING); assertThat(response.body).isEmpty(); } @@ -178,7 +178,7 @@ class ServerHttpResponseTests { assertThat(response.headersWritten).isFalse(); assertThat(response.cookiesWritten).isFalse(); assertThat(response.isCommitted()).isFalse(); - assertThat(response.getHeaders()).isEmpty(); + assertThat(response.getHeaders().isEmpty()).isTrue(); // Handle the error response.setStatusCode(HttpStatus.SERVICE_UNAVAILABLE); diff --git a/spring-web/src/test/java/org/springframework/web/ErrorResponseExceptionTests.java b/spring-web/src/test/java/org/springframework/web/ErrorResponseExceptionTests.java index fc39bff8a1..5513e59577 100644 --- a/spring-web/src/test/java/org/springframework/web/ErrorResponseExceptionTests.java +++ b/spring-web/src/test/java/org/springframework/web/ErrorResponseExceptionTests.java @@ -104,7 +104,7 @@ class ErrorResponseExceptionTests { assertDetail(ex, "Could not parse Content-Type."); assertDetailMessageCode(ex, "parseError", null); - assertThat(ex.getHeaders()).isEmpty(); + assertThat(ex.getHeaders().isEmpty()).isTrue(); } @Test @@ -117,7 +117,7 @@ class ErrorResponseExceptionTests { assertDetail(ex, "Acceptable representations: [application/json, application/cbor]."); assertDetailMessageCode(ex, null, new Object[] {ex.getSupportedMediaTypes()}); - assertThat(ex.getHeaders()).hasSize(1); + assertThat(ex.getHeaders().size()).isOne(); assertThat(ex.getHeaders().getAccept()).isEqualTo(mediaTypes); } @@ -131,7 +131,7 @@ class ErrorResponseExceptionTests { assertDetail(ex, "Could not parse Accept header."); assertDetailMessageCode(ex, "parseError", null); - assertThat(ex.getHeaders()).isEmpty(); + assertThat(ex.getHeaders().isEmpty()).isTrue(); } @Test @@ -143,7 +143,7 @@ class ErrorResponseExceptionTests { assertStatus(ex, HttpStatus.SERVICE_UNAVAILABLE); assertDetail(ex, null); - assertThat(ex.getHeaders()).isEmpty(); + assertThat(ex.getHeaders().isEmpty()).isTrue(); } @Test @@ -156,7 +156,7 @@ class ErrorResponseExceptionTests { assertDetail(ex, "Method 'PUT' is not supported."); assertDetailMessageCode(ex, null, new Object[] {ex.getMethod(), ex.getSupportedHttpMethods()}); - assertThat(ex.getHeaders()).hasSize(1); + assertThat(ex.getHeaders().size()).isOne(); assertThat(ex.getHeaders().getAllow()).containsExactly(HttpMethod.GET, HttpMethod.POST); } @@ -169,7 +169,7 @@ class ErrorResponseExceptionTests { assertDetail(ex, "Required header 'Authorization' is not present."); assertDetailMessageCode(ex, null, new Object[] {ex.getHeaderName()}); - assertThat(ex.getHeaders()).isEmpty(); + assertThat(ex.getHeaders().isEmpty()).isTrue(); } @Test @@ -181,7 +181,7 @@ class ErrorResponseExceptionTests { assertDetail(ex, "Required parameter 'query' is not present."); assertDetailMessageCode(ex, null, new Object[] {ex.getParameterName()}); - assertThat(ex.getHeaders()).isEmpty(); + assertThat(ex.getHeaders().isEmpty()).isTrue(); } @Test @@ -194,7 +194,7 @@ class ErrorResponseExceptionTests { assertDetail(ex, "Required path parameter 'region' is not present."); assertDetailMessageCode(ex, null, new Object[] {ex.getVariableName()}); - assertThat(ex.getHeaders()).isEmpty(); + assertThat(ex.getHeaders().isEmpty()).isTrue(); } @Test @@ -206,7 +206,7 @@ class ErrorResponseExceptionTests { assertDetail(ex, "Required path variable 'id' is not present."); assertDetailMessageCode(ex, null, new Object[] {ex.getVariableName()}); - assertThat(ex.getHeaders()).isEmpty(); + assertThat(ex.getHeaders().isEmpty()).isTrue(); } @Test @@ -218,7 +218,7 @@ class ErrorResponseExceptionTests { assertDetail(ex, "Required cookie 'oreo' is not present."); assertDetailMessageCode(ex, null, new Object[] {ex.getCookieName()}); - assertThat(ex.getHeaders()).isEmpty(); + assertThat(ex.getHeaders().isEmpty()).isTrue(); } @Test @@ -231,7 +231,7 @@ class ErrorResponseExceptionTests { assertDetail(ex, "Invalid request parameters."); assertDetailMessageCode(ex, null, new Object[] {List.of("\"foo=bar, bar=baz\"")}); - assertThat(ex.getHeaders()).isEmpty(); + assertThat(ex.getHeaders().isEmpty()).isTrue(); } @Test @@ -243,7 +243,7 @@ class ErrorResponseExceptionTests { assertDetail(ex, "Required part 'file' is not present."); assertDetailMessageCode(ex, null, new Object[] {ex.getRequestPartName()}); - assertThat(ex.getHeaders()).isEmpty(); + assertThat(ex.getHeaders().isEmpty()).isTrue(); } @Test @@ -258,7 +258,7 @@ class ErrorResponseExceptionTests { assertDetail(ex, "Invalid request content."); testHelper.assertMessages(ex, ex.getAllErrors()); - assertThat(ex.getHeaders()).isEmpty(); + assertThat(ex.getHeaders().isEmpty()).isTrue(); } @Test @@ -306,7 +306,7 @@ class ErrorResponseExceptionTests { assertDetail(ex, "Could not parse Content-Type."); assertDetailMessageCode(ex, "parseError", null); - assertThat(ex.getHeaders()).isEmpty(); + assertThat(ex.getHeaders().isEmpty()).isTrue(); } @Test @@ -319,7 +319,7 @@ class ErrorResponseExceptionTests { assertDetail(ex, "Acceptable representations: [application/json, application/cbor]."); assertDetailMessageCode(ex, null, new Object[] {ex.getSupportedMediaTypes()}); - assertThat(ex.getHeaders()).hasSize(1); + assertThat(ex.getHeaders().size()).isOne(); assertThat(ex.getHeaders().getAccept()).isEqualTo(mediaTypes); } @@ -333,7 +333,7 @@ class ErrorResponseExceptionTests { assertDetail(ex, "Could not parse Accept header."); assertDetailMessageCode(ex, "parseError", null); - assertThat(ex.getHeaders()).isEmpty(); + assertThat(ex.getHeaders().isEmpty()).isTrue(); } @Test @@ -345,7 +345,7 @@ class ErrorResponseExceptionTests { assertDetail(ex, "Failure"); assertDetailMessageCode(ex, null, new Object[] {ex.getReason()}); - assertThat(ex.getHeaders()).isEmpty(); + assertThat(ex.getHeaders().isEmpty()).isTrue(); } @Test @@ -358,7 +358,7 @@ class ErrorResponseExceptionTests { assertDetail(ex, "Required header 'foo' is not present."); assertDetailMessageCode(ex, null, new Object[] {ex.getLabel(), ex.getName()}); - assertThat(ex.getHeaders()).isEmpty(); + assertThat(ex.getHeaders().isEmpty()).isTrue(); } @Test @@ -373,7 +373,7 @@ class ErrorResponseExceptionTests { assertDetail(ex, "Invalid request parameters."); assertDetailMessageCode(ex, null, new Object[] {ex.getConditions()}); - assertThat(ex.getHeaders()).isEmpty(); + assertThat(ex.getHeaders().isEmpty()).isTrue(); } @Test @@ -388,7 +388,7 @@ class ErrorResponseExceptionTests { assertDetail(ex, "Invalid request content."); testHelper.assertMessages(ex, ex.getAllErrors()); - assertThat(ex.getHeaders()).isEmpty(); + assertThat(ex.getHeaders().isEmpty()).isTrue(); } @Test @@ -401,7 +401,7 @@ class ErrorResponseExceptionTests { assertDetail(ex, "Supported methods: [GET, POST]"); assertDetailMessageCode(ex, null, new Object[] {ex.getHttpMethod(), supportedMethods}); - assertThat(ex.getHeaders()).hasSize(1); + assertThat(ex.getHeaders().size()).isOne(); assertThat(ex.getHeaders().getAllow()).containsExactly(HttpMethod.GET, HttpMethod.POST); } @@ -414,7 +414,7 @@ class ErrorResponseExceptionTests { assertDetail(ex, "Request method 'PUT' is not supported."); assertDetailMessageCode(ex, null, new Object[] {ex.getHttpMethod(), Collections.emptyList()}); - assertThat(ex.getHeaders()).isEmpty(); + assertThat(ex.getHeaders().isEmpty()).isTrue(); } @Test // gh-30300 diff --git a/spring-web/src/test/java/org/springframework/web/ErrorResponseTests.java b/spring-web/src/test/java/org/springframework/web/ErrorResponseTests.java index f39afad7ad..43a4126a5b 100644 --- a/spring-web/src/test/java/org/springframework/web/ErrorResponseTests.java +++ b/spring-web/src/test/java/org/springframework/web/ErrorResponseTests.java @@ -36,7 +36,7 @@ class ErrorResponseTests { void createWithHttpHeader() { ErrorResponse response = ErrorResponse.builder(new IllegalStateException(), HttpStatus.BAD_REQUEST, "test") .header("header", "value").build(); - assertThat(response.getHeaders()).containsOnly(entry("header", List.of("value"))); + assertThat(response.getHeaders().asMultiValueMap()).containsOnly(entry("header", List.of("value"))); } @Test @@ -47,7 +47,7 @@ class ErrorResponseTests { headers.add("header", "value2"); headers.add("another", "value3"); }).build(); - assertThat(response.getHeaders()).containsOnly(entry("header", List.of("value", "value2")), + assertThat(response.getHeaders().asMultiValueMap()).containsOnly(entry("header", List.of("value", "value2")), entry("another", List.of("value3"))); } diff --git a/spring-web/src/test/java/org/springframework/web/client/RestClientResponseExceptionTests.java b/spring-web/src/test/java/org/springframework/web/client/RestClientResponseExceptionTests.java index cc13bb9ef0..7518739068 100644 --- a/spring-web/src/test/java/org/springframework/web/client/RestClientResponseExceptionTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/RestClientResponseExceptionTests.java @@ -36,7 +36,7 @@ class RestClientResponseExceptionTests { HttpHeaders responseHeaders = ex.getResponseHeaders(); assertThat(responseHeaders).isNotNull(); - assertThat(responseHeaders).hasSize(1); + assertThat(responseHeaders.size()).isOne(); assertThat(responseHeaders.getFirst("FOO")).isEqualTo("bar"); } diff --git a/spring-web/src/test/java/org/springframework/web/cors/reactive/DefaultCorsProcessorTests.java b/spring-web/src/test/java/org/springframework/web/cors/reactive/DefaultCorsProcessorTests.java index df86b40e8b..cdafe515ae 100644 --- a/spring-web/src/test/java/org/springframework/web/cors/reactive/DefaultCorsProcessorTests.java +++ b/spring-web/src/test/java/org/springframework/web/cors/reactive/DefaultCorsProcessorTests.java @@ -65,7 +65,7 @@ class DefaultCorsProcessorTests { this.processor.process(this.conf, exchange); ServerHttpResponse response = exchange.getResponse(); - assertThat(response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)).isFalse(); + assertThat(response.getHeaders().containsHeader(ACCESS_CONTROL_ALLOW_ORIGIN)).isFalse(); assertThat(response.getHeaders().get(VARY)).contains(ORIGIN, ACCESS_CONTROL_REQUEST_METHOD, ACCESS_CONTROL_REQUEST_HEADERS); assertThat(response.getStatusCode()).isNull(); @@ -81,7 +81,7 @@ class DefaultCorsProcessorTests { this.processor.process(this.conf, exchange); ServerHttpResponse response = exchange.getResponse(); - assertThat(response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)).isFalse(); + assertThat(response.getHeaders().containsHeader(ACCESS_CONTROL_ALLOW_ORIGIN)).isFalse(); assertThat(response.getHeaders().get(VARY)).contains(ORIGIN, ACCESS_CONTROL_REQUEST_METHOD, ACCESS_CONTROL_REQUEST_HEADERS); assertThat(response.getStatusCode()).isNull(); @@ -93,7 +93,7 @@ class DefaultCorsProcessorTests { this.processor.process(this.conf, exchange); ServerHttpResponse response = exchange.getResponse(); - assertThat(response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)).isFalse(); + assertThat(response.getHeaders().containsHeader(ACCESS_CONTROL_ALLOW_ORIGIN)).isFalse(); assertThat(response.getHeaders().get(VARY)).contains(ORIGIN, ACCESS_CONTROL_REQUEST_METHOD, ACCESS_CONTROL_REQUEST_HEADERS); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); @@ -105,7 +105,7 @@ class DefaultCorsProcessorTests { this.processor.process(null, exchange); ServerHttpResponse response = exchange.getResponse(); - assertThat(response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)).isFalse(); + assertThat(response.getHeaders().containsHeader(ACCESS_CONTROL_ALLOW_ORIGIN)).isFalse(); assertThat(response.getStatusCode()).isNull(); } @@ -116,10 +116,10 @@ class DefaultCorsProcessorTests { this.processor.process(this.conf, exchange); ServerHttpResponse response = exchange.getResponse(); - assertThat(response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); + assertThat(response.getHeaders().containsHeader(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); assertThat(response.getHeaders().getFirst(ACCESS_CONTROL_ALLOW_ORIGIN)).isEqualTo("*"); - assertThat(response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_MAX_AGE)).isFalse(); - assertThat(response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS)).isFalse(); + assertThat(response.getHeaders().containsHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE)).isFalse(); + assertThat(response.getHeaders().containsHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS)).isFalse(); assertThat(response.getHeaders().get(VARY)).contains(ORIGIN, ACCESS_CONTROL_REQUEST_METHOD, ACCESS_CONTROL_REQUEST_HEADERS); assertThat(response.getStatusCode()).isNull(); @@ -135,9 +135,9 @@ class DefaultCorsProcessorTests { this.processor.process(this.conf, exchange); ServerHttpResponse response = exchange.getResponse(); - assertThat(response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); + assertThat(response.getHeaders().containsHeader(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); assertThat(response.getHeaders().getFirst(ACCESS_CONTROL_ALLOW_ORIGIN)).isEqualTo("https://domain2.com"); - assertThat(response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)).isTrue(); + assertThat(response.getHeaders().containsHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)).isTrue(); assertThat(response.getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)).isEqualTo("true"); assertThat(response.getHeaders().get(VARY)).contains(ORIGIN, ACCESS_CONTROL_REQUEST_METHOD, ACCESS_CONTROL_REQUEST_HEADERS); @@ -156,9 +156,9 @@ class DefaultCorsProcessorTests { this.processor.process(this.conf, exchange); ServerHttpResponse response = exchange.getResponse(); - assertThat(response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); + assertThat(response.getHeaders().containsHeader(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); assertThat(response.getHeaders().getFirst(ACCESS_CONTROL_ALLOW_ORIGIN)).isEqualTo("https://domain2.com"); - assertThat(response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)).isTrue(); + assertThat(response.getHeaders().containsHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)).isTrue(); assertThat(response.getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)).isEqualTo("true"); assertThat(response.getHeaders().get(VARY)).contains(ORIGIN, ACCESS_CONTROL_REQUEST_METHOD, ACCESS_CONTROL_REQUEST_HEADERS); @@ -173,7 +173,7 @@ class DefaultCorsProcessorTests { ServerHttpResponse response = exchange.getResponse(); assertThat(response.getStatusCode()).isNull(); - assertThat(response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); + assertThat(response.getHeaders().containsHeader(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); } @Test // gh-26892 @@ -187,7 +187,7 @@ class DefaultCorsProcessorTests { ServerHttpResponse response = exchange.getResponse(); assertThat(response.getStatusCode()).isNull(); - assertThat(response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); + assertThat(response.getHeaders().containsHeader(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); } @Test // gh-33682 @@ -202,7 +202,7 @@ class DefaultCorsProcessorTests { assertThat(result).isFalse(); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); - assertThat(response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)).isFalse(); + assertThat(response.getHeaders().containsHeader(ACCESS_CONTROL_ALLOW_ORIGIN)).isFalse(); } @Test @@ -214,9 +214,9 @@ class DefaultCorsProcessorTests { this.processor.process(this.conf, exchange); ServerHttpResponse response = exchange.getResponse(); - assertThat(response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); + assertThat(response.getHeaders().containsHeader(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); assertThat(response.getHeaders().getFirst(ACCESS_CONTROL_ALLOW_ORIGIN)).isEqualTo("https://domain2.com"); - assertThat(response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS)).isTrue(); + assertThat(response.getHeaders().containsHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS)).isTrue(); assertThat(response.getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS)).contains("header1"); assertThat(response.getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS)).contains("header2"); assertThat(response.getHeaders().get(VARY)).contains(ORIGIN, @@ -271,7 +271,7 @@ class DefaultCorsProcessorTests { this.processor.process(this.conf, exchange); ServerHttpResponse response = exchange.getResponse(); - assertThat(response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)).isFalse(); + assertThat(response.getHeaders().containsHeader(ACCESS_CONTROL_ALLOW_ORIGIN)).isFalse(); assertThat(response.getHeaders().get(VARY)).contains(ORIGIN, ACCESS_CONTROL_REQUEST_METHOD, ACCESS_CONTROL_REQUEST_HEADERS); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); @@ -284,7 +284,7 @@ class DefaultCorsProcessorTests { this.processor.process(this.conf, exchange); ServerHttpResponse response = exchange.getResponse(); - assertThat(response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)).isFalse(); + assertThat(response.getHeaders().containsHeader(ACCESS_CONTROL_ALLOW_ORIGIN)).isFalse(); assertThat(response.getHeaders().get(VARY)).contains(ORIGIN, ACCESS_CONTROL_REQUEST_METHOD, ACCESS_CONTROL_REQUEST_HEADERS); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); @@ -299,7 +299,7 @@ class DefaultCorsProcessorTests { this.processor.process(this.conf, exchange); ServerHttpResponse response = exchange.getResponse(); - assertThat(response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)).isFalse(); + assertThat(response.getHeaders().containsHeader(ACCESS_CONTROL_ALLOW_ORIGIN)).isFalse(); assertThat(response.getHeaders().get(VARY)).contains(ORIGIN, ACCESS_CONTROL_REQUEST_METHOD, ACCESS_CONTROL_REQUEST_HEADERS); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); @@ -320,11 +320,11 @@ class DefaultCorsProcessorTests { this.processor.process(this.conf, exchange); ServerHttpResponse response = exchange.getResponse(); - assertThat(response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); + assertThat(response.getHeaders().containsHeader(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); assertThat(response.getHeaders().getFirst(ACCESS_CONTROL_ALLOW_ORIGIN)).isEqualTo("*"); - assertThat(response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS)).isTrue(); + assertThat(response.getHeaders().containsHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS)).isTrue(); assertThat(response.getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS)).isEqualTo("GET,PUT"); - assertThat(response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_MAX_AGE)).isFalse(); + assertThat(response.getHeaders().containsHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE)).isFalse(); assertThat(response.getHeaders().get(VARY)).contains(ORIGIN, ACCESS_CONTROL_REQUEST_METHOD, ACCESS_CONTROL_REQUEST_HEADERS); assertThat(response.getStatusCode()).isNull(); @@ -345,9 +345,9 @@ class DefaultCorsProcessorTests { this.processor.process(this.conf, exchange); ServerHttpResponse response = exchange.getResponse(); - assertThat(response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); + assertThat(response.getHeaders().containsHeader(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); assertThat(response.getHeaders().getFirst(ACCESS_CONTROL_ALLOW_ORIGIN)).isEqualTo("https://domain2.com"); - assertThat(response.getHeaders().containsKey(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)).isTrue(); + assertThat(response.getHeaders().containsHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)).isTrue(); assertThat(response.getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)).isEqualTo("true"); assertThat(response.getHeaders().get(VARY)).contains(ORIGIN, ACCESS_CONTROL_REQUEST_METHOD, ACCESS_CONTROL_REQUEST_HEADERS); @@ -372,7 +372,7 @@ class DefaultCorsProcessorTests { this.processor.process(this.conf, exchange); ServerHttpResponse response = exchange.getResponse(); - assertThat(response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); + assertThat(response.getHeaders().containsHeader(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); assertThat(response.getHeaders().getFirst(ACCESS_CONTROL_ALLOW_ORIGIN)).isEqualTo("https://domain2.com"); assertThat(response.getHeaders().get(VARY)).contains(ORIGIN, ACCESS_CONTROL_REQUEST_METHOD, ACCESS_CONTROL_REQUEST_HEADERS); @@ -398,8 +398,8 @@ class DefaultCorsProcessorTests { this.processor.process(this.conf, exchange); ServerHttpResponse response = exchange.getResponse(); - assertThat(response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); - assertThat(response.getHeaders().containsKey(DefaultCorsProcessor.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK)).isTrue(); + assertThat(response.getHeaders().containsHeader(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); + assertThat(response.getHeaders().containsHeader(DefaultCorsProcessor.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK)).isTrue(); assertThat(response.getHeaders().getFirst(ACCESS_CONTROL_ALLOW_ORIGIN)).isEqualTo("https://domain2.com"); assertThat(response.getHeaders().get(VARY)).contains(ORIGIN, ACCESS_CONTROL_REQUEST_METHOD, ACCESS_CONTROL_REQUEST_HEADERS); @@ -420,8 +420,8 @@ class DefaultCorsProcessorTests { this.processor.process(this.conf, exchange); ServerHttpResponse response = exchange.getResponse(); - assertThat(response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); - assertThat(response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_HEADERS)).isTrue(); + assertThat(response.getHeaders().containsHeader(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); + assertThat(response.getHeaders().containsHeader(ACCESS_CONTROL_ALLOW_HEADERS)).isTrue(); assertThat(response.getHeaders().getFirst(ACCESS_CONTROL_ALLOW_HEADERS)).contains("Header1"); assertThat(response.getHeaders().getFirst(ACCESS_CONTROL_ALLOW_HEADERS)).contains("Header2"); assertThat(response.getHeaders().getFirst(ACCESS_CONTROL_ALLOW_HEADERS)).doesNotContain("Header3"); @@ -442,8 +442,8 @@ class DefaultCorsProcessorTests { this.processor.process(this.conf, exchange); ServerHttpResponse response = exchange.getResponse(); - assertThat(response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); - assertThat(response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_HEADERS)).isTrue(); + assertThat(response.getHeaders().containsHeader(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); + assertThat(response.getHeaders().containsHeader(ACCESS_CONTROL_ALLOW_HEADERS)).isTrue(); assertThat(response.getHeaders().getFirst(ACCESS_CONTROL_ALLOW_HEADERS)).contains("Header1"); assertThat(response.getHeaders().getFirst(ACCESS_CONTROL_ALLOW_HEADERS)).contains("Header2"); assertThat(response.getHeaders().getFirst(ACCESS_CONTROL_ALLOW_HEADERS)).doesNotContain("*"); @@ -464,8 +464,8 @@ class DefaultCorsProcessorTests { this.processor.process(this.conf, exchange); ServerHttpResponse response = exchange.getResponse(); - assertThat(response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); - assertThat(response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_HEADERS)).isFalse(); + assertThat(response.getHeaders().containsHeader(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); + assertThat(response.getHeaders().containsHeader(ACCESS_CONTROL_ALLOW_HEADERS)).isFalse(); assertThat(response.getHeaders().get(VARY)).contains(ORIGIN, ACCESS_CONTROL_REQUEST_METHOD, ACCESS_CONTROL_REQUEST_HEADERS); assertThat(response.getStatusCode()).isNull(); @@ -479,7 +479,7 @@ class DefaultCorsProcessorTests { this.processor.process(null, exchange); ServerHttpResponse response = exchange.getResponse(); - assertThat(response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)).isFalse(); + assertThat(response.getHeaders().containsHeader(ACCESS_CONTROL_ALLOW_ORIGIN)).isFalse(); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); } @@ -513,8 +513,8 @@ class DefaultCorsProcessorTests { this.processor.process(this.conf, exchange); ServerHttpResponse response = exchange.getResponse(); - assertThat(response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); - assertThat(response.getHeaders().containsKey(DefaultCorsProcessor.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK)).isFalse(); + assertThat(response.getHeaders().containsHeader(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); + assertThat(response.getHeaders().containsHeader(DefaultCorsProcessor.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK)).isFalse(); assertThat(response.getStatusCode()).isNull(); } @@ -530,8 +530,8 @@ class DefaultCorsProcessorTests { this.processor.process(this.conf, exchange); ServerHttpResponse response = exchange.getResponse(); - assertThat(response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); - assertThat(response.getHeaders().containsKey(DefaultCorsProcessor.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK)).isFalse(); + assertThat(response.getHeaders().containsHeader(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); + assertThat(response.getHeaders().containsHeader(DefaultCorsProcessor.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK)).isFalse(); assertThat(response.getStatusCode()).isNull(); } @@ -548,8 +548,8 @@ class DefaultCorsProcessorTests { this.processor.process(this.conf, exchange); ServerHttpResponse response = exchange.getResponse(); - assertThat(response.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); - assertThat(response.getHeaders().containsKey(DefaultCorsProcessor.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK)).isTrue(); + assertThat(response.getHeaders().containsHeader(ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue(); + assertThat(response.getHeaders().containsHeader(DefaultCorsProcessor.ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK)).isTrue(); assertThat(response.getStatusCode()).isNull(); } diff --git a/spring-web/src/test/java/org/springframework/web/server/adapter/ForwardedHeaderTransformerTests.java b/spring-web/src/test/java/org/springframework/web/server/adapter/ForwardedHeaderTransformerTests.java index c1244707a7..82a0324613 100644 --- a/spring-web/src/test/java/org/springframework/web/server/adapter/ForwardedHeaderTransformerTests.java +++ b/spring-web/src/test/java/org/springframework/web/server/adapter/ForwardedHeaderTransformerTests.java @@ -240,7 +240,7 @@ class ForwardedHeaderTransformerTests { private void assertForwardedHeadersRemoved(ServerHttpRequest request) { ForwardedHeaderTransformer.FORWARDED_HEADER_NAMES - .forEach(name -> assertThat(request.getHeaders().containsKey(name)).isFalse()); + .forEach(name -> assertThat(request.getHeaders().containsHeader(name)).isFalse()); } } diff --git a/spring-web/src/test/kotlin/org/springframework/http/converter/cbor/KotlinSerializationCborHttpMessageConverterTests.kt b/spring-web/src/test/kotlin/org/springframework/http/converter/cbor/KotlinSerializationCborHttpMessageConverterTests.kt index 52e55ab22d..91ac9ced07 100644 --- a/spring-web/src/test/kotlin/org/springframework/http/converter/cbor/KotlinSerializationCborHttpMessageConverterTests.kt +++ b/spring-web/src/test/kotlin/org/springframework/http/converter/cbor/KotlinSerializationCborHttpMessageConverterTests.kt @@ -170,7 +170,8 @@ class KotlinSerializationCborHttpMessageConverterTests { this.converter.write(serializableBean, null, outputMessage) - assertThat(outputMessage.headers).containsEntry("Content-Type", listOf("application/cbor")) + @Suppress("DEPRECATION") + assertThat(outputMessage.headers.asMultiValueMap()).containsEntry("Content-Type", listOf("application/cbor")) assertThat(outputMessage.bodyAsBytes.isNotEmpty()).isTrue() } @@ -181,7 +182,8 @@ class KotlinSerializationCborHttpMessageConverterTests { this.converter.write(serializableBean, null, outputMessage) - assertThat(outputMessage.headers).containsEntry("Content-Type", listOf("application/cbor")) + @Suppress("DEPRECATION") + assertThat(outputMessage.headers.asMultiValueMap()).containsEntry("Content-Type", listOf("application/cbor")) assertThat(outputMessage.bodyAsBytes.isNotEmpty()).isTrue() } @@ -191,7 +193,8 @@ class KotlinSerializationCborHttpMessageConverterTests { this.converter.write(serializableBeanArray, null, outputMessage) - assertThat(outputMessage.headers).containsEntry("Content-Type", listOf("application/cbor")) + @Suppress("DEPRECATION") + assertThat(outputMessage.headers.asMultiValueMap()).containsEntry("Content-Type", listOf("application/cbor")) assertThat(outputMessage.bodyAsBytes.isNotEmpty()).isTrue() } @@ -202,7 +205,8 @@ class KotlinSerializationCborHttpMessageConverterTests { this.converter.write(listOf(serializableBean), ResolvableType.forType(typeOf>().javaType), null, outputMessage, null) - assertThat(outputMessage.headers).containsEntry("Content-Type", listOf("application/cbor")) + @Suppress("DEPRECATION") + assertThat(outputMessage.headers.asMultiValueMap()).containsEntry("Content-Type", listOf("application/cbor")) assertThat(outputMessage.bodyAsBytes.isNotEmpty()).isTrue() } diff --git a/spring-web/src/test/kotlin/org/springframework/http/converter/json/KotlinSerializationJsonHttpMessageConverterTests.kt b/spring-web/src/test/kotlin/org/springframework/http/converter/json/KotlinSerializationJsonHttpMessageConverterTests.kt index e90047201f..4c3b9b5dd0 100644 --- a/spring-web/src/test/kotlin/org/springframework/http/converter/json/KotlinSerializationJsonHttpMessageConverterTests.kt +++ b/spring-web/src/test/kotlin/org/springframework/http/converter/json/KotlinSerializationJsonHttpMessageConverterTests.kt @@ -258,7 +258,8 @@ class KotlinSerializationJsonHttpMessageConverterTests { val result = outputMessage.getBodyAsString(StandardCharsets.UTF_8) - assertThat(outputMessage.headers).containsEntry("Content-Type", listOf("application/json")) + @Suppress("DEPRECATION") + assertThat(outputMessage.headers.asMultiValueMap()).containsEntry("Content-Type", listOf("application/json")) assertThat(result) .contains("\"bytes\":[1,2]") .contains("\"array\":[\"Foo\",\"Bar\"]") @@ -277,7 +278,8 @@ class KotlinSerializationJsonHttpMessageConverterTests { val result = outputMessage.getBodyAsString(StandardCharsets.UTF_8) - assertThat(outputMessage.headers).containsEntry("Content-Type", listOf("application/json")) + @Suppress("DEPRECATION") + assertThat(outputMessage.headers.asMultiValueMap()).containsEntry("Content-Type", listOf("application/json")) assertThat(result) .contains("\"bytes\":[1,2]") .contains("\"array\":[\"Foo\",\"Bar\"]") @@ -299,7 +301,8 @@ class KotlinSerializationJsonHttpMessageConverterTests { val result = outputMessage.getBodyAsString(StandardCharsets.UTF_8) - assertThat(outputMessage.headers).containsEntry("Content-Type", listOf("application/json")) + @Suppress("DEPRECATION") + assertThat(outputMessage.headers.asMultiValueMap()).containsEntry("Content-Type", listOf("application/json")) assertThat(result).isEqualTo(expectedJson) } @@ -317,7 +320,8 @@ class KotlinSerializationJsonHttpMessageConverterTests { val result = outputMessage.getBodyAsString(StandardCharsets.UTF_8) - assertThat(outputMessage.headers).containsEntry("Content-Type", listOf("application/json")) + @Suppress("DEPRECATION") + assertThat(outputMessage.headers.asMultiValueMap()).containsEntry("Content-Type", listOf("application/json")) assertThat(result).isEqualTo(expectedJson) } @@ -331,7 +335,8 @@ class KotlinSerializationJsonHttpMessageConverterTests { val result = outputMessage.getBodyAsString(StandardCharsets.UTF_16BE) - assertThat(outputMessage.headers).containsEntry("Content-Type", listOf(contentType.toString())) + @Suppress("DEPRECATION") + assertThat(outputMessage.headers.asMultiValueMap()).containsEntry("Content-Type", listOf(contentType.toString())) assertThat(result).isEqualTo("\"H\u00e9llo W\u00f6rld\"") } @@ -367,7 +372,8 @@ class KotlinSerializationJsonHttpMessageConverterTests { val result = outputMessage.getBodyAsString(StandardCharsets.UTF_8) - assertThat(outputMessage.headers).containsEntry("Content-Type", listOf("application/json")) + @Suppress("DEPRECATION") + assertThat(outputMessage.headers.asMultiValueMap()).containsEntry("Content-Type", listOf("application/json")) assertThat(result).isEqualTo("1.0") } @@ -384,7 +390,8 @@ class KotlinSerializationJsonHttpMessageConverterTests { val result = outputMessage.getBodyAsString(StandardCharsets.UTF_8) - assertThat(outputMessage.headers).containsEntry("Content-Type", listOf("application/json")) + @Suppress("DEPRECATION") + assertThat(outputMessage.headers.asMultiValueMap()).containsEntry("Content-Type", listOf("application/json")) assertThat(result).isEqualTo(expectedJson) } diff --git a/spring-web/src/test/kotlin/org/springframework/http/converter/protobuf/KotlinSerializationProtobufHttpMessageConverterTests.kt b/spring-web/src/test/kotlin/org/springframework/http/converter/protobuf/KotlinSerializationProtobufHttpMessageConverterTests.kt index b79efc9929..8449cec11d 100644 --- a/spring-web/src/test/kotlin/org/springframework/http/converter/protobuf/KotlinSerializationProtobufHttpMessageConverterTests.kt +++ b/spring-web/src/test/kotlin/org/springframework/http/converter/protobuf/KotlinSerializationProtobufHttpMessageConverterTests.kt @@ -186,7 +186,8 @@ class KotlinSerializationProtobufHttpMessageConverterTests { this.converter.write(serializableBean, null, outputMessage) - assertThat(outputMessage.headers).containsEntry("Content-Type", listOf("application/x-protobuf")) + @Suppress("DEPRECATION") + assertThat(outputMessage.headers.asMultiValueMap()).containsEntry("Content-Type", listOf("application/x-protobuf")) assertThat(outputMessage.bodyAsBytes.isNotEmpty()).isTrue() } @@ -197,7 +198,8 @@ class KotlinSerializationProtobufHttpMessageConverterTests { this.converter.write(serializableBean, null, outputMessage) - assertThat(outputMessage.headers).containsEntry("Content-Type", listOf("application/x-protobuf")) + @Suppress("DEPRECATION") + assertThat(outputMessage.headers.asMultiValueMap()).containsEntry("Content-Type", listOf("application/x-protobuf")) assertThat(outputMessage.bodyAsBytes.isNotEmpty()).isTrue() } @@ -207,7 +209,8 @@ class KotlinSerializationProtobufHttpMessageConverterTests { this.converter.write(serializableBeanArray, null, outputMessage) - assertThat(outputMessage.headers).containsEntry("Content-Type", listOf("application/x-protobuf")) + @Suppress("DEPRECATION") + assertThat(outputMessage.headers.asMultiValueMap()).containsEntry("Content-Type", listOf("application/x-protobuf")) assertThat(outputMessage.bodyAsBytes.isNotEmpty()).isTrue() } @@ -218,7 +221,8 @@ class KotlinSerializationProtobufHttpMessageConverterTests { this.converter.write(listOf(serializableBean), ResolvableType.forType(typeOf>().javaType), null, outputMessage, null) - assertThat(outputMessage.headers).containsEntry("Content-Type", listOf("application/x-protobuf")) + @Suppress("DEPRECATION") + assertThat(outputMessage.headers.asMultiValueMap()).containsEntry("Content-Type", listOf("application/x-protobuf")) assertThat(outputMessage.bodyAsBytes.isNotEmpty()).isTrue() } diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/server/reactive/MockServerHttpRequest.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/server/reactive/MockServerHttpRequest.java index 4bd546ace4..491ea6b6b7 100644 --- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/server/reactive/MockServerHttpRequest.java +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/server/reactive/MockServerHttpRequest.java @@ -299,9 +299,18 @@ public final class MockServerHttpRequest extends AbstractServerHttpRequest { /** * Add the given header values. * @param headers the header values + * @deprecated Use {@link #headers(HttpHeaders)} */ + @Deprecated B headers(MultiValueMap headers); + /** + * Add the given header values. + * @param headers the header values + * @since 7.0 + */ + B headers(HttpHeaders headers); + /** * Set the list of acceptable {@linkplain MediaType media types}, as * specified by the {@code Accept} header. @@ -491,11 +500,18 @@ public final class MockServerHttpRequest extends AbstractServerHttpRequest { } @Override + @Deprecated public BodyBuilder headers(MultiValueMap headers) { this.headers.putAll(headers); return this; } + @Override + public BodyBuilder headers(HttpHeaders headers) { + this.headers.putAll(headers); + return this; + } + @Override public BodyBuilder accept(MediaType... acceptableMediaTypes) { this.headers.setAccept(Arrays.asList(acceptableMediaTypes)); diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockPart.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockPart.java index 99715488dc..36ae5fcb5f 100644 --- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockPart.java +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockPart.java @@ -129,7 +129,7 @@ public class MockPart implements Part { @Override public Collection getHeaderNames() { - return this.headers.keySet(); + return this.headers.headerNames(); } /** diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java index 08109bf3ae..86174a9cc4 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java @@ -495,10 +495,10 @@ final class DefaultWebClient implements WebClient { } private void initHeaders(HttpHeaders out) { - if (!CollectionUtils.isEmpty(defaultHeaders)) { + if (defaultHeaders != null && !defaultHeaders.isEmpty()) { out.putAll(defaultHeaders); } - if (!CollectionUtils.isEmpty(this.headers)) { + if (this.headers != null && !this.headers.isEmpty()) { out.putAll(this.headers); } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClientRequestException.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClientRequestException.java index b8e7256037..0265a3dcfc 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClientRequestException.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClientRequestException.java @@ -58,7 +58,7 @@ public class WebClientRequestException extends WebClientException { */ private static HttpHeaders copy(HttpHeaders headers) { HttpHeaders result = new HttpHeaders(); - for (Map.Entry> entry : headers.entrySet()) { + for (Map.Entry> entry : headers.headerSet()) { for (String value : entry.getValue()) { result.add(entry.getKey(), value); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClientResponseException.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClientResponseException.java index 9e08f919e7..258159c55b 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClientResponseException.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClientResponseException.java @@ -150,7 +150,7 @@ public class WebClientResponseException extends WebClientException { } else { HttpHeaders result = new HttpHeaders(); - for (Map.Entry> entry : headers.entrySet()) { + for (Map.Entry> entry : headers.headerSet()) { for (String value : entry.getValue()) { result.add(entry.getKey(), value); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerResponseBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerResponseBuilder.java index d2736b05ff..e732ec0e57 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerResponseBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerResponseBuilder.java @@ -357,6 +357,12 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder { dst.putAll(src); } } + + private static void copy(HttpHeaders src, HttpHeaders dst) { + if (!src.isEmpty()) { + dst.putAll(src); + } + } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ExtendedWebExchangeDataBinder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ExtendedWebExchangeDataBinder.java index a018372462..9e7bf0537d 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ExtendedWebExchangeDataBinder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ExtendedWebExchangeDataBinder.java @@ -85,7 +85,7 @@ public class ExtendedWebExchangeDataBinder extends WebExchangeDataBinder { vars.forEach((key, value) -> addValueIfNotPresent(map, "URI variable", key, value)); } HttpHeaders headers = exchange.getRequest().getHeaders(); - for (Map.Entry> entry : headers.entrySet()) { + for (Map.Entry> entry : headers.headerSet()) { String name = entry.getKey(); if (!this.headerPredicate.test(entry.getKey())) { continue; diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMapMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMapMethodArgumentResolver.java index f4da262a4e..fd2d4c3265 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMapMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMapMethodArgumentResolver.java @@ -55,17 +55,22 @@ public class RequestHeaderMapMethodArgumentResolver extends HandlerMethodArgumen } private boolean allParams(RequestHeader annotation, Class type) { - return Map.class.isAssignableFrom(type); + return Map.class.isAssignableFrom(type) || HttpHeaders.class.isAssignableFrom(type); } + @SuppressWarnings("deprecation") @Override public Object resolveArgumentValue( MethodParameter methodParameter, BindingContext context, ServerWebExchange exchange) { boolean isMultiValueMap = MultiValueMap.class.isAssignableFrom(methodParameter.getParameterType()); HttpHeaders headers = exchange.getRequest().getHeaders(); - return (isMultiValueMap ? headers : headers.toSingleValueMap()); + if (isMultiValueMap) { + return headers.asMultiValueMap(); + } + boolean isHttpHeaders = HttpHeaders.class.isAssignableFrom(methodParameter.getParameterType()); + return (isHttpHeaders ? headers : headers.toSingleValueMap()); } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMethodArgumentResolver.java index c4b3205368..e183a9908c 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMethodArgumentResolver.java @@ -25,6 +25,7 @@ import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.core.MethodParameter; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.convert.ConversionService; +import org.springframework.http.HttpHeaders; import org.springframework.util.Assert; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.server.MissingRequestValueException; @@ -68,7 +69,7 @@ public class RequestHeaderMethodArgumentResolver extends AbstractNamedValueSyncA } private boolean singleParam(RequestHeader annotation, Class type) { - return !Map.class.isAssignableFrom(type); + return !Map.class.isAssignableFrom(type) && !HttpHeaders.class.isAssignableFrom(type); } @Override diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java index a574679df6..afad15a841 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java @@ -473,7 +473,7 @@ public class ViewResolutionResultHandler extends HandlerResultHandlerSupport imp @Override public HttpHeaders getHeaders() { - if (!super.getHeaders().containsKey(HttpHeaders.CONTENT_TYPE)) { + if (!super.getHeaders().containsHeader(HttpHeaders.CONTENT_TYPE)) { return super.getHeaders(); } // Content-type is set, ignore further updates diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/JettyWebSocketClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/JettyWebSocketClient.java index d0b6f352ff..3ba4d10ca6 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/JettyWebSocketClient.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/JettyWebSocketClient.java @@ -92,7 +92,7 @@ public class JettyWebSocketClient implements WebSocketClient, Lifecycle { ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest(); upgradeRequest.setSubProtocols(handler.getSubProtocols()); if (headers != null) { - headers.keySet().forEach(header -> upgradeRequest.setHeader(header, headers.getValuesAsList(header))); + headers.headerNames().forEach(header -> upgradeRequest.setHeader(header, headers.getValuesAsList(header))); } final AtomicReference handshakeInfo = new AtomicReference<>(); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/StandardWebSocketClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/StandardWebSocketClient.java index 8158eda5f9..e07724e9d6 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/StandardWebSocketClient.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/StandardWebSocketClient.java @@ -179,12 +179,12 @@ public class StandardWebSocketClient implements WebSocketClient { @Override public void beforeRequest(Map> requestHeaders) { - requestHeaders.putAll(this.requestHeaders); + this.requestHeaders.forEach(requestHeaders::put); } @Override public void afterResponse(HandshakeResponse response) { - this.responseHeaders.putAll(response.getHeaders()); + response.getHeaders().forEach(this.responseHeaders::put); } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/UndertowWebSocketClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/UndertowWebSocketClient.java index 8b68b19c8b..b211c6c0cf 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/UndertowWebSocketClient.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/client/UndertowWebSocketClient.java @@ -242,7 +242,7 @@ public class UndertowWebSocketClient implements WebSocketClient { @Override public void beforeRequest(Map> headers) { - headers.putAll(this.requestHeaders); + this.requestHeaders.forEach(headers::put); if (this.delegate != null) { this.delegate.beforeRequest(headers); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientRequestBuilderTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientRequestBuilderTests.java index 90fb4c3b66..b3de5dedc7 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientRequestBuilderTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientRequestBuilderTests.java @@ -70,7 +70,7 @@ class DefaultClientRequestBuilderTests { assertThat(result.url()).isEqualTo(DEFAULT_URL); assertThat(result.method()).isEqualTo(GET); - assertThat(result.headers()).hasSize(1); + assertThat(result.headers().size()).isOne(); assertThat(result.headers().getFirst("foo")).isEqualTo("baar"); assertThat(result.cookies()).hasSize(1); assertThat(result.cookies().getFirst("baz")).isEqualTo("quux"); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientResponseBuilderTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientResponseBuilderTests.java index 63d193b8ce..c9212f86af 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientResponseBuilderTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientResponseBuilderTests.java @@ -88,7 +88,7 @@ class DefaultClientResponseBuilderTests { assertThat(result.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST); - assertThat(result.headers().asHttpHeaders()).hasSize(3); + assertThat(result.headers().asHttpHeaders().size()).isEqualTo(3); assertThat(result.headers().asHttpHeaders().getFirst("foo")).isEqualTo("baar"); assertThat(result.headers().asHttpHeaders().getFirst("bar")).isEqualTo("baz"); assertThat(result.cookies()).hasSize(1); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientResponseTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientResponseTests.java index d6f9a24dad..0c0a4c8695 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientResponseTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultClientResponseTests.java @@ -338,7 +338,7 @@ class DefaultClientResponseTests { WebClientResponseException exception = resultMono.block(); assertThat(exception.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); assertThat(exception.getMessage()).isEqualTo("404 Not Found from UNKNOWN https://example.org:9999/app/path"); - assertThat(exception.getHeaders()).containsExactly(entry("Content-Type", List.of("text/plain"))); + assertThat(exception.getHeaders().asMultiValueMap()).containsExactly(entry("Content-Type", List.of("text/plain"))); assertThat(exception.getResponseBodyAsByteArray()).isEqualTo(bytes); } @@ -397,7 +397,7 @@ class DefaultClientResponseTests { WebClientResponseException exception = (WebClientResponseException) t; assertThat(exception.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); assertThat(exception.getMessage()).isEqualTo("404 Not Found"); - assertThat(exception.getHeaders()).containsExactly(entry("Content-Type",List.of("text/plain"))); + assertThat(exception.getHeaders().asMultiValueMap()).containsExactly(entry("Content-Type",List.of("text/plain"))); assertThat(exception.getResponseBodyAsByteArray()).isEqualTo(bytes); }) .verify(); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultWebClientTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultWebClientTests.java index 16c433429f..3c7fa0b21d 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultWebClientTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultWebClientTests.java @@ -295,17 +295,17 @@ public class DefaultWebClientTests { WebClient.Builder builder1 = client1.mutate(); builder1.filters(filters -> assertThat(filters).hasSize(1)); - builder1.defaultHeaders(headers -> assertThat(headers).hasSize(1)); + builder1.defaultHeaders(headers -> assertThat(headers.size()).isOne()); builder1.defaultCookies(cookies -> assertThat(cookies).hasSize(1)); WebClient.Builder builder2 = client2.mutate(); builder2.filters(filters -> assertThat(filters).hasSize(2)); - builder2.defaultHeaders(headers -> assertThat(headers).hasSize(2)); + builder2.defaultHeaders(headers -> assertThat(headers.size()).isEqualTo(2)); builder2.defaultCookies(cookies -> assertThat(cookies).hasSize(2)); WebClient.Builder builder1a = client1a.mutate(); builder1a.filters(filters -> assertThat(filters).hasSize(2)); - builder1a.defaultHeaders(headers -> assertThat(headers).hasSize(2)); + builder1a.defaultHeaders(headers -> assertThat(headers.size()).isEqualTo(2)); builder1a.defaultCookies(cookies -> assertThat(cookies).hasSize(2)); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/ExchangeFilterFunctionsTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/ExchangeFilterFunctionsTests.java index 8a7c0ab0ef..e9ff0e264a 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/ExchangeFilterFunctionsTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/ExchangeFilterFunctionsTests.java @@ -103,13 +103,13 @@ class ExchangeFilterFunctionsTests { ClientResponse response = mock(); ExchangeFunction exchange = r -> { - assertThat(r.headers().containsKey(HttpHeaders.AUTHORIZATION)).isTrue(); + assertThat(r.headers().containsHeader(HttpHeaders.AUTHORIZATION)).isTrue(); assertThat(r.headers().getFirst(HttpHeaders.AUTHORIZATION)).startsWith("Basic "); return Mono.just(response); }; ExchangeFilterFunction auth = ExchangeFilterFunctions.basicAuthentication("foo", "bar"); - assertThat(request.headers().containsKey(HttpHeaders.AUTHORIZATION)).isFalse(); + assertThat(request.headers().containsHeader(HttpHeaders.AUTHORIZATION)).isFalse(); ClientResponse result = auth.filter(request, exchange).block(); assertThat(result).isEqualTo(response); } @@ -133,13 +133,13 @@ class ExchangeFilterFunctionsTests { ClientResponse response = mock(); ExchangeFunction exchange = r -> { - assertThat(r.headers().containsKey(HttpHeaders.AUTHORIZATION)).isTrue(); + assertThat(r.headers().containsHeader(HttpHeaders.AUTHORIZATION)).isTrue(); assertThat(r.headers().getFirst(HttpHeaders.AUTHORIZATION)).startsWith("Basic "); return Mono.just(response); }; ExchangeFilterFunction auth = ExchangeFilterFunctions.basicAuthentication(); - assertThat(request.headers().containsKey(HttpHeaders.AUTHORIZATION)).isFalse(); + assertThat(request.headers().containsHeader(HttpHeaders.AUTHORIZATION)).isFalse(); ClientResponse result = auth.filter(request, exchange).block(); assertThat(result).isEqualTo(response); } @@ -151,12 +151,12 @@ class ExchangeFilterFunctionsTests { ClientResponse response = mock(); ExchangeFunction exchange = r -> { - assertThat(r.headers().containsKey(HttpHeaders.AUTHORIZATION)).isFalse(); + assertThat(r.headers().containsHeader(HttpHeaders.AUTHORIZATION)).isFalse(); return Mono.just(response); }; ExchangeFilterFunction auth = ExchangeFilterFunctions.basicAuthentication(); - assertThat(request.headers().containsKey(HttpHeaders.AUTHORIZATION)).isFalse(); + assertThat(request.headers().containsHeader(HttpHeaders.AUTHORIZATION)).isFalse(); ClientResponse result = auth.filter(request, exchange).block(); assertThat(result).isEqualTo(response); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientDataBufferAllocatingTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientDataBufferAllocatingTests.java index 8b67d28902..f335c732d7 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientDataBufferAllocatingTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientDataBufferAllocatingTests.java @@ -204,7 +204,7 @@ class WebClientDataBufferAllocatingTests extends AbstractDataBufferAllocatingTes StepVerifier.create(result) .assertNext(entity -> { assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.CREATED); - assertThat(entity.getHeaders()).containsEntry("Foo", Collections.singletonList("bar")); + assertThat(entity.getHeaders().hasHeaderValues("Foo", Collections.singletonList("bar"))).isTrue(); assertThat(entity.getBody()).isNull(); }) .expectComplete() diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientObservationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientObservationTests.java index 1e5699e367..5f403d812a 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientObservationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientObservationTests.java @@ -91,7 +91,7 @@ class WebClientObservationTests { assertThatHttpObservation().hasLowCardinalityKeyValue("outcome", "SUCCESS") .hasLowCardinalityKeyValue("uri", "/base/resource/{id}"); - assertThat(clientRequest.headers()).containsEntry("foo", Collections.singletonList("bar")); + assertThat(clientRequest.headers().hasHeaderValues("foo", Collections.singletonList("bar"))).isTrue(); } @Test diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultServerRequestBuilderTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultServerRequestBuilderTests.java index ff21883457..ee82d209e6 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultServerRequestBuilderTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DefaultServerRequestBuilderTests.java @@ -73,7 +73,7 @@ class DefaultServerRequestBuilderTests { assertThat(result.uri()).isEqualTo(uri); assertThat(result.requestPath().pathWithinApplication().value()).isEqualTo("/bar"); assertThat(result.requestPath().contextPath().value()).isEqualTo("/foo"); - assertThat(result.headers().asHttpHeaders()).hasSize(1); + assertThat(result.headers().asHttpHeaders().size()).isOne(); assertThat(result.headers().asHttpHeaders().getFirst("foo")).isEqualTo("baar"); assertThat(result.cookies()).hasSize(1); assertThat(result.cookies().getFirst("baz").getValue()).isEqualTo("quux"); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/resource/ResourceWebHandlerTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/resource/ResourceWebHandlerTests.java index c6574131ee..00aefd8f8d 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/resource/ResourceWebHandlerTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/resource/ResourceWebHandlerTests.java @@ -417,7 +417,7 @@ class ResourceWebHandlerTests { this.handler.handle(exchange).block(TIMEOUT); HttpHeaders headers = exchange.getResponse().getHeaders(); - assertThat(headers.containsKey("Last-Modified")).isTrue(); + assertThat(headers.containsHeader("Last-Modified")).isTrue(); assertThat(resourceLastModifiedDate("test/foo.css") / 1000).isEqualTo(headers.getLastModified() / 1000); } @@ -448,7 +448,7 @@ class ResourceWebHandlerTests { MockServerHttpResponse response = exchange.getResponse(); assertThat(response.getHeaders().getCacheControl()).isEqualTo("no-store"); - assertThat(response.getHeaders().containsKey("Last-Modified")).isTrue(); + assertThat(response.getHeaders().containsHeader("Last-Modified")).isTrue(); assertThat(resourceLastModifiedDate("test/foo.css") / 1000).isEqualTo(response.getHeaders().getLastModified() / 1000); } @@ -561,7 +561,7 @@ class ResourceWebHandlerTests { HttpHeaders headers = exchange.getResponse().getHeaders(); assertThat(headers.getContentType()).isEqualTo(MediaType.parseMediaType("text/css")); assertThat(headers.getContentLength()).isEqualTo(17); - assertThat(headers.containsKey("Last-Modified")).isFalse(); + assertThat(headers.containsHeader("Last-Modified")).isFalse(); assertResponseBody(exchange, "h1 { color:red; }"); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/AbstractRequestMappingIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/AbstractRequestMappingIntegrationTests.java index 5c2b0559e4..a126f2b380 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/AbstractRequestMappingIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/AbstractRequestMappingIntegrationTests.java @@ -123,7 +123,7 @@ public abstract class AbstractRequestMappingIntegrationTests extends AbstractHtt } private void addHeaders(RequestEntity.HeadersBuilder builder, HttpHeaders headers) { - for (Map.Entry> entry : headers.entrySet()) { + for (Map.Entry> entry : headers.headerSet()) { for (String value : entry.getValue()) { builder.header(entry.getKey(), value); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java index de0452b586..28009efd3c 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ResponseEntityResultHandlerTests.java @@ -157,7 +157,7 @@ class ResponseEntityResultHandlerTests { this.resultHandler.handleResult(exchange, result).block(Duration.ofSeconds(5)); assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT); - assertThat(exchange.getResponse().getHeaders()).isEmpty(); + assertThat(exchange.getResponse().getHeaders().isEmpty()).isTrue(); assertResponseBodyIsEmpty(exchange); } @@ -171,7 +171,7 @@ class ResponseEntityResultHandlerTests { this.resultHandler.handleResult(exchange, result).block(Duration.ofSeconds(5)); assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(exchange.getResponse().getHeaders()).hasSize(1); + assertThat(exchange.getResponse().getHeaders().size()).isOne(); assertThat(exchange.getResponse().getHeaders().getFirst("Allow")).isEqualTo("GET,POST,OPTIONS"); assertResponseBodyIsEmpty(exchange); } @@ -186,7 +186,7 @@ class ResponseEntityResultHandlerTests { this.resultHandler.handleResult(exchange, result).block(Duration.ofSeconds(5)); assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.CREATED); - assertThat(exchange.getResponse().getHeaders()).hasSize(1); + assertThat(exchange.getResponse().getHeaders().size()).isOne(); assertThat(exchange.getResponse().getHeaders().getLocation()).isEqualTo(location); assertResponseBodyIsEmpty(exchange); } @@ -236,7 +236,7 @@ class ResponseEntityResultHandlerTests { this.resultHandler.handleResult(exchange, result).block(Duration.ofSeconds(5)); assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); - assertThat(exchange.getResponse().getHeaders()).hasSize(3); + assertThat(exchange.getResponse().getHeaders().size()).isEqualTo(3); assertThat(exchange.getResponse().getHeaders().get("foo")).containsExactly("bar"); assertThat(exchange.getResponse().getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_PROBLEM_JSON); assertResponseBody(exchange, @@ -256,7 +256,7 @@ class ResponseEntityResultHandlerTests { this.resultHandler.handleResult(exchange, result).block(Duration.ofSeconds(5)); assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); - assertThat(exchange.getResponse().getHeaders()).hasSize(2); + assertThat(exchange.getResponse().getHeaders().size()).isEqualTo(2); assertThat(exchange.getResponse().getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_PROBLEM_JSON); assertResponseBody(exchange, "{\"type\":\"about:blank\"," + @@ -385,7 +385,7 @@ class ResponseEntityResultHandlerTests { this.resultHandler.handleResult(exchange, result).block(Duration.ofSeconds(5)); assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(exchange.getResponse().getHeaders()).hasSize(1); + assertThat(exchange.getResponse().getHeaders().size()).isOne(); assertThat(exchange.getResponse().getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON); assertResponseBodyIsEmpty(exchange); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/DefaultRenderingBuilderTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/DefaultRenderingBuilderTests.java index 287a0377fa..d8e44b4b89 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/DefaultRenderingBuilderTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/DefaultRenderingBuilderTests.java @@ -41,7 +41,7 @@ class DefaultRenderingBuilderTests { assertThat(rendering.view()).isEqualTo("abc"); assertThat(rendering.modelAttributes()).isEqualTo(Collections.emptyMap()); assertThat(rendering.status()).isNull(); - assertThat(rendering.headers()).isEmpty(); + assertThat(rendering.headers().isEmpty()).isTrue(); } @Test @@ -97,7 +97,7 @@ class DefaultRenderingBuilderTests { void header() { Rendering rendering = Rendering.view("foo").header("foo", "bar").build(); - assertThat(rendering.headers()).hasSize(1); + assertThat(rendering.headers().size()).isOne(); assertThat(rendering.headers().get("foo")).isEqualTo(Collections.singletonList("bar")); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequest.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequest.java index 2e168c72c4..45881cd478 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequest.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequest.java @@ -606,7 +606,7 @@ class DefaultServerRequest implements ServerRequest { @Override public boolean containsHeader(String name) { - return this.headers.containsKey(name); + return this.headers.containsHeader(name); } @Override @@ -647,7 +647,7 @@ class DefaultServerRequest implements ServerRequest { @Override public Collection getHeaderNames() { - return this.headers.keySet(); + return this.headers.headerNames(); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java index 5fcf91ddb1..32084d33e5 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java @@ -489,7 +489,7 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe */ private void addContentDispositionHeader(ServletServerHttpRequest request, ServletServerHttpResponse response) { HttpHeaders headers = response.getHeaders(); - if (headers.containsKey(HttpHeaders.CONTENT_DISPOSITION)) { + if (headers.containsHeader(HttpHeaders.CONTENT_DISPOSITION)) { return; } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java index 97a97e1a31..1dcb6f94ed 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java @@ -224,7 +224,7 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro HttpHeaders entityHeaders = httpEntity.getHeaders(); if (!entityHeaders.isEmpty()) { entityHeaders.forEach((key, value) -> { - if (HttpHeaders.VARY.equals(key) && outputHeaders.containsKey(HttpHeaders.VARY)) { + if (HttpHeaders.VARY.equals(key) && outputHeaders.containsHeader(HttpHeaders.VARY)) { List values = getVaryRequestHeadersToAdd(outputHeaders, entityHeaders); if (!values.isEmpty()) { outputHeaders.setVary(values); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java index 3b47c09bc7..8d6b079be1 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java @@ -301,7 +301,7 @@ class ResponseEntityExceptionHandlerTests { ResponseEntity responseEntity = testException(new NoHandlerFoundException("GET", "/resource", requestHeaders)); - assertThat(responseEntity.getHeaders()).isEmpty(); + assertThat(responseEntity.getHeaders().isEmpty()).isTrue(); } @Test diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java index a8435ec981..e302d28d24 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java @@ -3576,7 +3576,21 @@ class ServletAnnotationControllerHandlerMethodTests extends AbstractServletHandl @RequestMapping("/httpHeaders") public void httpHeaders(@RequestHeader HttpHeaders headers, Writer writer) throws IOException { assertThat(headers.getContentType()).as("Invalid Content-Type").isEqualTo(new MediaType("text", "html")); - multiValueMap(headers, writer); + for (Iterator>> it1 = headers.headerSet().iterator(); it1.hasNext();) { + Map.Entry> entry = it1.next(); + writer.write(entry.getKey() + "=["); + for (Iterator it2 = entry.getValue().iterator(); it2.hasNext();) { + String value = it2.next(); + writer.write(value); + if (it2.hasNext()) { + writer.write(','); + } + } + writer.write(']'); + if (it1.hasNext()) { + writer.write(','); + } + } } } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketHttpHeaders.java b/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketHttpHeaders.java index 167714ccd8..06ebf19469 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketHttpHeaders.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketHttpHeaders.java @@ -17,7 +17,6 @@ package org.springframework.web.socket; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; @@ -244,30 +243,30 @@ public class WebSocketHttpHeaders extends HttpHeaders { } @Override - public boolean containsKey(Object key) { - return this.headers.containsKey(key); + public boolean containsHeader(String key) { + return this.headers.containsHeader(key); } @Override - public boolean containsValue(Object value) { - return this.headers.containsValue(value); + public @Nullable List get(String headerName) { + return this.headers.get(headerName); } @Override - public @Nullable List get(Object key) { - return this.headers.get(key); - } - - @Override - public List put(String key, List value) { + public @Nullable List put(String key, List value) { return this.headers.put(key, value); } @Override - public List remove(Object key) { + public @Nullable List remove(String key) { return this.headers.remove(key); } + @Override + public void putAll(HttpHeaders headers) { + this.headers.putAll(headers); + } + @Override public void putAll(Map> m) { this.headers.putAll(m); @@ -279,18 +278,13 @@ public class WebSocketHttpHeaders extends HttpHeaders { } @Override - public Set keySet() { - return this.headers.keySet(); + public Set headerNames() { + return this.headers.headerNames(); } @Override - public Collection> values() { - return this.headers.values(); - } - - @Override - public Set>> entrySet() { - return this.headers.entrySet(); + public Set>> headerSet() { + return this.headers.headerSet(); } @Override @@ -299,8 +293,8 @@ public class WebSocketHttpHeaders extends HttpHeaders { } @Override - public List putIfAbsent(String key, List value) { - return this.headers.putIfAbsent(key, value); + public @Nullable List putIfAbsent(String headerName, List headerValues) { + return this.headers.putIfAbsent(headerName, headerValues); } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/client/standard/StandardWebSocketClient.java b/spring-websocket/src/main/java/org/springframework/web/socket/client/standard/StandardWebSocketClient.java index 6d778c55b5..aa9557c977 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/client/standard/StandardWebSocketClient.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/client/standard/StandardWebSocketClient.java @@ -216,7 +216,7 @@ public class StandardWebSocketClient extends AbstractWebSocketClient { @Override public void beforeRequest(Map> requestHeaders) { - requestHeaders.putAll(this.headers); + this.headers.forEach(requestHeaders::put); if (logger.isTraceEnabled()) { logger.trace("Handshake request headers: " + requestHeaders); } diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/client/standard/StandardWebSocketClientTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/client/standard/StandardWebSocketClientTests.java index eef50c7d85..bc9314775c 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/client/standard/StandardWebSocketClientTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/client/standard/StandardWebSocketClientTests.java @@ -100,7 +100,7 @@ class StandardWebSocketClientTests { WebSocketSession session = this.wsClient.execute(this.wsHandler, this.headers, uri).get(); - assertThat(session.getHandshakeHeaders()).hasSize(1); + assertThat(session.getHandshakeHeaders().size()).isOne(); assertThat(session.getHandshakeHeaders().getFirst("foo")).isEqualTo("bar"); } diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/client/SockJsClientTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/client/SockJsClientTests.java index 88b3321c8e..6aeb5e0a72 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/client/SockJsClientTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/client/SockJsClientTests.java @@ -108,12 +108,12 @@ class SockJsClientTests { this.sockJsClient.execute(handler, headers, URI.create(URL)).whenComplete(this.connectCallback); HttpHeaders httpHeaders = headersCaptor.getValue(); - assertThat(httpHeaders).hasSize(2); + assertThat(httpHeaders.size()).isEqualTo(2); assertThat(httpHeaders.getFirst("foo")).isEqualTo("bar"); assertThat(httpHeaders.getFirst("auth")).isEqualTo("123"); httpHeaders = this.xhrTransport.getRequest().getHttpRequestHeaders(); - assertThat(httpHeaders).hasSize(2); + assertThat(httpHeaders.size()).isEqualTo(2); assertThat(httpHeaders.getFirst("foo")).isEqualTo("bar"); assertThat(httpHeaders.getFirst("auth")).isEqualTo("123"); } @@ -129,9 +129,9 @@ class SockJsClientTests { this.sockJsClient.setHttpHeaderNames("auth"); this.sockJsClient.execute(handler, headers, URI.create(URL)).whenComplete(this.connectCallback); - assertThat(headersCaptor.getValue()).hasSize(1); + assertThat(headersCaptor.getValue().size()).isEqualTo(1); assertThat(headersCaptor.getValue().getFirst("auth")).isEqualTo("123"); - assertThat(this.xhrTransport.getRequest().getHttpRequestHeaders()).hasSize(1); + assertThat(this.xhrTransport.getRequest().getHttpRequestHeaders().size()).isEqualTo(1); assertThat(this.xhrTransport.getRequest().getHttpRequestHeaders().getFirst("auth")).isEqualTo("123"); } diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/client/XhrTransportTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/client/XhrTransportTests.java index 51ba2749b0..fc7be911b6 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/client/XhrTransportTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/client/XhrTransportTests.java @@ -69,7 +69,7 @@ class XhrTransportTests { transport.sendMessageResponseToReturn = new ResponseEntity<>(HttpStatus.NO_CONTENT); URI url = URI.create("https://example.com"); transport.executeSendRequest(url, requestHeaders, new TextMessage("payload")); - assertThat(transport.actualSendRequestHeaders).hasSize(2); + assertThat(transport.actualSendRequestHeaders.size()).isEqualTo(2); assertThat(transport.actualSendRequestHeaders.getFirst("foo")).isEqualTo("bar"); assertThat(transport.actualSendRequestHeaders.getContentType()).isEqualTo(MediaType.APPLICATION_JSON); } @@ -105,7 +105,7 @@ class XhrTransportTests { verify(request).getHttpRequestHeaders(); verifyNoMoreInteractions(request); - assertThat(transport.actualHandshakeHeaders).hasSize(1); + assertThat(transport.actualHandshakeHeaders.size()).isOne(); assertThat(transport.actualHandshakeHeaders.getOrigin()).isEqualTo("foo"); assertThat(transport.actualSession.isDisconnected()).isFalse();